diff --git a/README.md b/README.md index a645440..54dbe36 100644 --- a/README.md +++ b/README.md @@ -34,11 +34,11 @@ This project can be used as either a command line utility or a Python library. R ```console $ toml-sort --help usage: toml-sort [-h] [--version] [-o OUTPUT] [-i] [-I] [-a] [--no-sort-tables] [--sort-table-keys] - [--sort-inline-tables] [--sort-inline-arrays] [--no-header] [--no-comments] [--no-header-comments] - [--no-footer-comments] [--no-inline-comments] [--no-block-comments] + [--sort-inline-tables] [--sort-inline-arrays] [--sort-first KEYS] [--no-header] [--no-comments] + [--no-header-comments] [--no-footer-comments] [--no-inline-comments] [--no-block-comments] [--spaces-before-inline-comment {1,2,3,4}] [--spaces-indent-inline-array {2,4,6,8}] [--trailing-comma-inline-array] [--check] - [F [F ...]] + [F ...] Toml sort: a sorting utility for toml files. @@ -64,6 +64,8 @@ sort: --sort-table-keys Sort the keys in tables and arrays of tables (excluding inline tables and arrays). --sort-inline-tables Sort inline tables. --sort-inline-arrays Sort inline arrays. + --sort-first KEYS Table keys that will be sorted first in the output. Multiple keys can be given separated by a + comma. comments: exclude comments from output @@ -120,6 +122,7 @@ no_footer_comments = true no_inline_comments = true no_block_comments = true no_sort_tables = true +sort_first = ["key1", "key2"] sort_table_keys = true sort_inline_tables = true sort_inline_arrays = true @@ -137,6 +140,7 @@ Only the following options can be included in an override: ```toml [tool.tomlsort.overrides."path.to.key"] +first = ["key1", "key2"] table_keys = true inline_tables = true inline_arrays = true diff --git a/tests/examples/sorted/from-toml-lang-first.toml b/tests/examples/sorted/from-toml-lang-first.toml new file mode 100644 index 0000000..94d9394 --- /dev/null +++ b/tests/examples/sorted/from-toml-lang-first.toml @@ -0,0 +1,44 @@ +# This is a TOML document. Boom. + +title = "TOML Example" + +[servers] + +# You can indent as you please. Tabs or spaces. TOML don't care. +[servers.alpha] +dc = "eqdc10" +ip = "10.0.0.1" + +[servers.beta] +country = "中国" # This should be parsed as UTF-8 +dc = "eqdc10" +ip = "10.0.0.2" + +[[products]] +name = "Hammer" +sku = 738594937 + +[[products]] +color = "gray" +name = "Nail" +sku = 284758393 + +[clients] +data = [["delta", "gamma"], [1, 2]] # just an update to make sure parsers support it +# Line breaks are OK when inside arrays +hosts = [ + "alpha", + "omega" +] + +[database] +ports = [8001, 8001, 8002] +connection_max = 5000 +enabled = true # Comment after a boolean +server = "192.168.1.1" + +[owner] +name = "Tom Preston-Werner" +dob = 1979-05-27T07:32:00Z # First class dates? Why not? +bio = "GitHub Cofounder & CEO\nLikes tater tots and beer." +organization = "GitHub" diff --git a/tests/test_cli.py b/tests/test_cli.py index 43a9786..9a42cec 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -279,6 +279,7 @@ def test_load_config_file_read(): "[tool.tomlsort]\nspaces_before_inline_comment=4", {"spaces_before_inline_comment": 4}, ), + ("[tool.tomlsort]\nsort_first=['x', 'y']", {"sort_first": "x,y"}), ], ) def test_load_config_file(toml, expected): @@ -339,6 +340,17 @@ def test_load_config_file_invalid(toml): "test.789": SortOverrideConfiguration(inline_arrays=False), }, ), + ( + """ + [tool.tomlsort.overrides] + "test.123".first = ["one", "two", "three"] + """, + { + "test.123": SortOverrideConfiguration( + first=["one", "two", "three"] + ), + }, + ), ], ) def test_load_config_overrides(toml, expected): diff --git a/tests/test_toml_sort.py b/tests/test_toml_sort.py index 6b0b50a..db8c74f 100644 --- a/tests/test_toml_sort.py +++ b/tests/test_toml_sort.py @@ -103,6 +103,24 @@ def test_sort_toml_is_str() -> None: }, }, ), + ( + "from-toml-lang", + "from-toml-lang-first", + { + "sort_config": SortConfiguration( + inline_arrays=True, + inline_tables=True, + first=["servers", "products"], + ), + "format_config": FormattingConfiguration( + spaces_before_inline_comment=1 + ), + "sort_config_overrides": { + "database": SortOverrideConfiguration(first=["ports"]), + "owner": SortOverrideConfiguration(first=["name", "dob"]), + }, + }, + ), ( "pyproject-weird-order", "pyproject-weird-order", diff --git a/toml_sort/cli.py b/toml_sort/cli.py index ac057dc..6573e88 100644 --- a/toml_sort/cli.py +++ b/toml_sort/cli.py @@ -124,6 +124,9 @@ def parse_config(tomlsort_section: TOMLDocument) -> Dict[str, Any]: validate_and_copy( config, clean_config, "trailing_comma_inline_array", bool ) + validate_and_copy(config, clean_config, "sort_first", list) + if "sort_first" in clean_config: + clean_config["sort_first"] = ",".join(clean_config["sort_first"]) if config: printerr(f"Unexpected configuration values: {config}") @@ -242,6 +245,16 @@ def get_parser(defaults: Dict[str, Any]) -> ArgumentParser: help=("Sort inline arrays."), action="store_true", ) + sort.add_argument( + "--sort-first", + help=( + "Table keys that will be sorted first in the output. Multiple " + "keys can be given separated by a comma." + ), + metavar="KEYS", + type=str, + default="", + ) comments = parser.add_argument_group( "comments", "exclude comments from output" ) @@ -386,6 +399,7 @@ def cli( # pylint: disable=too-many-branches table_keys=bool(args.sort_table_keys or args.all), inline_tables=bool(args.sort_inline_tables or args.all), inline_arrays=bool(args.sort_inline_arrays or args.all), + first=args.sort_first.split(","), ), format_config=FormattingConfiguration( spaces_before_inline_comment=args.spaces_before_inline_comment, diff --git a/toml_sort/tomlsort.py b/toml_sort/tomlsort.py index 6a3618f..f4020f6 100644 --- a/toml_sort/tomlsort.py +++ b/toml_sort/tomlsort.py @@ -234,6 +234,7 @@ class SortConfiguration: inline_tables: bool = False inline_arrays: bool = False ignore_case: bool = False + first: List[str] = field(default_factory=list) @dataclass @@ -252,6 +253,7 @@ class SortOverrideConfiguration: table_keys: Optional[bool] = None inline_tables: Optional[bool] = None inline_arrays: Optional[bool] = None + first: Optional[List[str]] = None class TomlSort: @@ -440,6 +442,24 @@ def sort_item( return item + def sort_keys( + self, items: Iterable[TomlSortItem], sort_config: SortConfiguration + ) -> List[TomlSortItem]: + """Sorts Iterable of Tomlsort item based on keys. + + The sort respects the sort_config.first setting which allows + overriding the sorted order of keys. + """ + + def sort_first(item): + if item.keys.base in sort_config.first: + return sort_config.first.index(item.keys.base) + return len(sort_config.first) + + items = sorted(items, key=self.key_sort_func) + items = sorted(items, key=sort_first) + return items + def sort_inline_table( self, keys: TomlSortKeys, item: Item, indent_depth: int = 0 ) -> InlineTable: @@ -452,8 +472,9 @@ def sort_inline_table( for k, v in item.value.body if not isinstance(v, Whitespace) and k is not None ] - if self.sort_config(keys).inline_tables: - tomlsort_items = sorted(tomlsort_items, key=self.key_sort_func) + sort_config = self.sort_config(keys) + if sort_config.inline_tables: + tomlsort_items = self.sort_keys(tomlsort_items, sort_config) new_table = InlineTable( Container(parsed=True), trivia=item.trivia, new=True ) @@ -514,6 +535,7 @@ def sorted_children_table( self, parent_keys: Optional[TomlSortKeys], parent: List[TomlSortItem] ) -> Iterable[TomlSortItem]: """Get the sorted children of a table.""" + sort_config = self.sort_config(parent_keys) tables = coalesce_tables( item for item in parent if isinstance(item.value, (Table, AoT)) ) @@ -525,12 +547,12 @@ def sorted_children_table( ] ) non_tables_final = ( - sorted(non_tables, key=self.key_sort_func) - if self.sort_config(parent_keys).table_keys + self.sort_keys(non_tables, sort_config) + if sort_config.table_keys else non_tables ) tables_final = ( - sorted(tables, key=self.key_sort_func) + self.sort_keys(tables, sort_config) if self.sort_config(parent_keys).tables else tables )