Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased

### Fixed

- A dotted key (`b.c = 1`) followed by a table header for the same name (`[a.b.e]`) no longer drops the parent prefix from the header, which changed the document
- Dotted keys at the top level or inside a table (`top.key = 1`) are no longer rendered after a following table header, which moved them into that table

### Changed

- Dotted keys (`a.b = 1`) are now sorted together with the other keys of their table instead of being placed after them

## 0.24.4

### Changed
Expand Down
20 changes: 20 additions & 0 deletions tests/examples/dotted-key-header.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# A dotted key and a table header with the same name are distinct items.
# Merging them would render the header without its parent prefix, turning
# [q.b.e] into [b.e].

a.c = 1

[a.e]
x = 1

[q]
b.c = 1

[q.b.e]
x = 1

[r]
b.c = 1

[[r.b.e]]
x = 1
22 changes: 22 additions & 0 deletions tests/examples/dotted-key-order.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Dotted keys are key/value lines. They must stay ahead of any table header
# in the same table, or the header would capture them.

z = 0
top.key = 1

[t]
z = 0
b.y = 1
a = 2
b.x = 3

[t.c]
k = 1

[[arr]]
a.b.c = 1
a.b.d = 2

[[arr]]
a.b.d = 4
a.b.c = 3
20 changes: 20 additions & 0 deletions tests/examples/sorted/dotted-key-header.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# A dotted key and a table header with the same name are distinct items.
# Merging them would render the header without its parent prefix, turning
# [q.b.e] into [b.e].

a.c = 1

[a.e]
x = 1

[q]
b.c = 1

[q.b.e]
x = 1

[r]
b.c = 1

[[r.b.e]]
x = 1
22 changes: 22 additions & 0 deletions tests/examples/sorted/dotted-key-order-no-sort-keys.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Dotted keys are key/value lines. They must stay ahead of any table header
# in the same table, or the header would capture them.

z = 0
top.key = 1

[[arr]]
a.b.c = 1
a.b.d = 2

[[arr]]
a.b.d = 4
a.b.c = 3

[t]
z = 0
a = 2
b.y = 1
b.x = 3

[t.c]
k = 1
22 changes: 22 additions & 0 deletions tests/examples/sorted/dotted-key-order.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Dotted keys are key/value lines. They must stay ahead of any table header
# in the same table, or the header would capture them.

top.key = 1
z = 0

[[arr]]
a.b.c = 1
a.b.d = 2

[[arr]]
a.b.c = 3
a.b.d = 4

[t]
a = 2
b.x = 3
b.y = 1
z = 0

[t.c]
k = 1
15 changes: 15 additions & 0 deletions tests/test_toml_sort.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,21 @@ def test_sort_toml_is_str() -> None:
),
},
),
(
"dotted-key-header",
"dotted-key-header",
{},
),
(
"dotted-key-order",
"dotted-key-order",
{"sort_config": SortConfiguration(table_keys=True)},
),
(
"dotted-key-order",
"dotted-key-order-no-sort-keys",
{"sort_config": SortConfiguration(table_keys=False)},
),
(
"inline",
"inline",
Expand Down
31 changes: 23 additions & 8 deletions toml_sort/tomlsort.py
Original file line number Diff line number Diff line change
Expand Up @@ -518,18 +518,33 @@ def array_sort_func(self, value: Tuple[_ArrayItemGroup, Any]) -> str:
def sorted_children_table(
self, parent_keys: Optional[TomlSortKeys], parent: List[TomlSortItem]
) -> Iterable[TomlSortItem]:
"""Get the sorted children of a table."""
"""Get the sorted children of a table.

tomlkit models a dotted key (``a.b = 1``) as a table, but it is a
key/value line: it must be rendered before any ``[header]`` of the
same table, or the header captures it. So dotted keys are grouped
and sorted with the keys, never with the headers. Sibling dotted
keys (``a.b`` and ``a.c``) arrive as separate entries and are
coalesced like tables.
"""
sort_config = self.sort_config(parent_keys)
tables = coalesce_tables(
item for item in parent if isinstance(item.value, (Table, AoT))
)

def is_header(item: TomlSortItem) -> bool:
return (
isinstance(item.value, (Table, AoT)) and not item.keys.base.is_dotted()
)

def is_dotted_key(item: TomlSortItem) -> bool:
return isinstance(item.value, Table) and item.keys.base.is_dotted()

tables = coalesce_tables(item for item in parent if is_header(item))
dotted_keys = coalesce_tables(item for item in parent if is_dotted_key(item))
non_tables = self.sort_items(
[item for item in parent if not isinstance(item.value, (Table, AoT))]
[item for item in parent if not is_header(item) and not is_dotted_key(item)]
)
keys = list(itertools.chain(non_tables, dotted_keys))
non_tables_final = (
self.sort_keys(non_tables, sort_config)
if sort_config.table_keys
else non_tables
self.sort_keys(keys, sort_config) if sort_config.table_keys else keys
)
tables_final = (
self.sort_keys(tables, sort_config)
Expand Down