Skip to content

Commit

Permalink
Top-level recipe keys are sorted for render_to_new_recipe_format()
Browse files Browse the repository at this point in the history
- This should mask any "odd" orderings caused by the patch operations required
  to switch to the new format.
- Ordering follows a "canonical" sorting. This is what most recipe builders are
  comfortable and used to seeing.
- Ordering was a group effort in our Conda Build Tools chatroom
  • Loading branch information
schuylermartin45 committed Feb 7, 2024
1 parent 53fe417 commit 5a8147a
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 10 deletions.
15 changes: 15 additions & 0 deletions percy/parser/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,21 @@
# Marker used to temporarily work around some Jinja-template parsing issues
PERCY_SUB_MARKER: Final[str] = "__PERCY_SUBSTITUTION_MARKER__"

# Ideal sort-order of the top-level YAML keys for human readability and traditionally how we organize our files. This
# should work on both old and new recipe formats.
TOP_LEVEL_KEY_SORT_ORDER: Final[dict[str, int]] = {
"schema_version": 0,
"context": 1,
"package": 2,
"source": 3,
"build": 4,
"requirements": 5,
"outputs": 6,
"test": 7,
"about": 8,
"extra": 9,
}

#### Private Classes (Not to be used external to the `parser` module) ####

# NOTE: The classes put in this file should be structures (NamedTuples) and very small support classes that don't make
Expand Down
45 changes: 36 additions & 9 deletions percy/parser/recipe_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import difflib
import json
import re
import sys
from typing import Callable, Final, Optional, TypeGuard, cast, no_type_check

import yaml
Expand All @@ -32,7 +33,14 @@
traverse_all,
traverse_with_index,
)
from percy.parser._types import PERCY_SUB_MARKER, ROOT_NODE_VALUE, ForceIndentDumper, Regex, StrStack
from percy.parser._types import (
PERCY_SUB_MARKER,
ROOT_NODE_VALUE,
TOP_LEVEL_KEY_SORT_ORDER,
ForceIndentDumper,
Regex,
StrStack,
)
from percy.parser._utils import (
dedupe_and_preserve_order,
normalize_multiline_strings,
Expand Down Expand Up @@ -371,6 +379,29 @@ def __init__(self, content: str):
# This table will have to be re-built or modified when the tree is modified with `patch()`.
self._rebuild_selectors()

def _sort_top_level_keys(self) -> None:
"""
Sorts the top-level keys to a "canonical" order (a human-centric order in which most recipes are currently
written). This should work on both old and new recipe formats.
TODO: Handle JINJA statements
The modification flag is not changed even though the underlying tree is. As far as YAML and our key-pathing
structure is concerned, order does not matter.
"""

def _comparison(n: Node) -> int:
# For now, put all comments at the top of the file. Arguably this is better than having them "randomly tag"
# to another top-level key.
if n.is_comment():
return -sys.maxsize
# Unidentified keys go to the bottom of the file.
if not isinstance(n.value, str) or n.value not in TOP_LEVEL_KEY_SORT_ORDER:
return sys.maxsize
return TOP_LEVEL_KEY_SORT_ORDER[n.value]

self._root.children.sort(key=_comparison)

@staticmethod
def _str_tree_recurse(node: Node, depth: int, lines: list[str]) -> None:
"""
Expand Down Expand Up @@ -657,15 +688,8 @@ def _patch_and_log(patch: JsonPatchType) -> None:
continue
_patch_and_log({"op": "add", "path": f"/context/{name}", "value": value})

# Hack: `add` has no concept of ordering and new fields are appended to the end. Logically, `context` should be
# at the top of the file, so we'll force it to the front of root's child list.
# TODO: make more robust and don't assume `context` will be at the end of the list
# TODO: manage some human-friendly ordering of all top-level sections
new_recipe._root.children.insert(0, new_recipe._root.children.pop(-1))

# Similarly, patch-in the new `schema_version` value to the top of the file
_patch_and_log({"op": "add", "path": "/schema_version", "value": CURRENT_RECIPE_SCHEMA_FORMAT})
new_recipe._root.children.insert(0, new_recipe._root.children.pop(-1))

# Swap all JINJA to use the new `${{ }}` format.
jinja_sub_locations: Final[list[str]] = new_recipe.search(Regex.JINJA_SUB)
Expand Down Expand Up @@ -748,11 +772,14 @@ def _patch_and_log(patch: JsonPatchType) -> None:
# TODO Complete: handle changes to the recipe structure and fields
# TODO Complete: move operations may result in empty fields we can eliminate. This may require changes
# to `contains_value()`
# TODO Complete: ensure some common "canonical" ordering to the top-level fields

# Hack: Wipe the existing table so the JINJA `set` statements don't render the final form
new_recipe._vars_tbl = {}

# Sort the top-level keys to a "canonical" ordering. This should make previous patch operations look more
# "sensible" to a human reader.
new_recipe._sort_top_level_keys()

return new_recipe.render(), msg_tbl

## YAML Access Functions ##
Expand Down
2 changes: 1 addition & 1 deletion percy/tests/test_aux_files/new_format_simple-recipe.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Comment above a top-level structure
schema_version: 1

context:
Expand All @@ -13,7 +14,6 @@ build:
skip: ${{ true if py<37 }}
is_true: true

# Comment above a top-level structure
requirements:
empty_field1:
host:
Expand Down

0 comments on commit 5a8147a

Please sign in to comment.