Skip to content
Merged
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 @@ -7,12 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Added `FormatOptions` for configuring canonical formatting.

### Deprecated

- The `comments` argument to `format()`; use
`FormatOptions(normalize_comments=...)`.

### Fixed

- `promote_inline` and `promote_array` now preserve entry key and value
formatting
- `set_multiline()` now preserves closing-bracket indentation for nested
arrays and inline tables.
- `format()` now preserves closing-bracket indentation when called on a
nested array or inline table directly.

## [2.0.2] - 2026-07-10

Expand Down
11 changes: 11 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ semver-stable API:
| `loads`, `load` | function |
| `dumps`, `dump` | function |
| `Document`, `Table`, `Array`, `AoT` | class |
| `FormatOptions` | class |
| `TomlInput` | type alias |
| `TOMLError`, `TOMLParseError` | exception |

Expand All @@ -26,6 +27,16 @@ code.
::: tomlrt.dumps
::: tomlrt.dump

## Formatting

::: tomlrt.FormatOptions
options:
members:
- normalize_comments
- indent
- eol_comment_spaces
- multiline_trailing_comma

## Containers

::: tomlrt.Document
Expand Down
30 changes: 23 additions & 7 deletions docs/layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,19 @@ The operations above are format-preserving. When you instead want to
_opt in_ to canonical formatting — to drop the original spacing of a
single value, a section, or the whole document — call `format()`.

`Container.format(*, comments=True)` and `Array.format(*, comments=True)`
both mutate in place and return `None`, mirroring `list.sort()`.
`Container.format()` and `Array.format()` both mutate in place and return
`None`, mirroring `list.sort()`. Pass a `FormatOptions` object to configure
canonical formatting consistently at any scope.

| Option | Default | Effect |
|--------|---------|--------|
| `normalize_comments` | `True` | Normalize comment text. |
| `indent` | `2` | Spaces added per nested multiline inline value. |
| `eol_comment_spaces` | `1` | Spaces before supported EOL comments. |
| `multiline_trailing_comma` | `True` | Emit a final comma in multiline arrays and inline tables. |

For example, `doc.format(options=tomlrt.FormatOptions(indent=4))` uses a
four-space step at every nested multiline level.

```python
import tomlrt
Expand Down Expand Up @@ -163,11 +174,11 @@ x = { a = 1, b = 2 }
- Slots outside the receiver's subtree are not touched: calling
`format()` on a single section leaves sibling sections alone.

### The `comments` flag
### Comment normalization

When `comments=True` (the default), every comment reached by the walk
is rewritten so that there is exactly one space between `#` and the
body, and any trailing whitespace inside the comment is stripped:
By default, every comment reached by the walk is rewritten so that there is
exactly one space between `#` and the body, and any trailing whitespace inside
the comment is stripped:

| Before | After |
|--------|-------|
Expand All @@ -176,7 +187,12 @@ body, and any trailing whitespace inside the comment is stripped:
| <code style="white-space: pre"># foo </code> | <code style="white-space: pre"># foo</code> |
| <code style="white-space: pre">#</code> (empty) | <code style="white-space: pre">#</code> |

Pass `comments=False` to leave comment text untouched.
Pass `FormatOptions(normalize_comments=False)` to leave comment text untouched.
The former `comments=` keyword remains available for compatibility but is
deprecated; do not pass it together with `options=`.

`eol_comment_spaces` applies to key/value, section-header, array-item, and
inline-table-entry comments. Opening-bracket comment spacing remains authored.

### Detached views

Expand Down
2 changes: 2 additions & 0 deletions src/tomlrt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@

from tomlrt._container import AoT, Array, Document, Table, TomlInput
from tomlrt._errors import TOMLError, TOMLParseError
from tomlrt._format import FormatOptions
from tomlrt._public import dump, dumps, load, loads

__all__ = [
"AoT",
"Array",
"Document",
"FormatOptions",
"TOMLError",
"TOMLParseError",
"Table",
Expand Down
20 changes: 14 additions & 6 deletions src/tomlrt/_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
)
from tomlrt._errors import TOMLError
from tomlrt._format import (
_canon_inline_value,
_resolve_format_options,
format_inline_root,
set_comma_value_multiline,
)
from tomlrt._trivia import (
Expand All @@ -54,6 +55,7 @@
from tomlrt._comma_ops import (
CommaStyle,
)
from tomlrt._format import FormatOptions
from tomlrt._trivia import TriviaPiece
from tomlrt._values import (
Value,
Expand Down Expand Up @@ -221,18 +223,24 @@ def leading_comments(self) -> MutableMapping[int, tuple[str, ...]]:
"""Leading-comment view, indexed by item position."""
return ArrayLeadingView(self)

def format(self, *, comments: bool = True) -> None:
def format(
self,
*,
options: FormatOptions | None = None,
comments: bool | None = None,
) -> None:
"""Canonicalise this array's formatting in place.

Rewrites whitespace, indentation, separators, and newlines
while preserving shape (single-line stays single-line, multi-line
stays multi-line) and orphan comment text.

When ``comments`` is true (the default), comment text is also
normalised: ``#foo`` and ``# foo`` both become ``# foo``, and
trailing whitespace inside comments is stripped.
``comments=`` is deprecated; use
``FormatOptions(normalize_comments=...)`` instead. Supplying both
arguments raises ``ValueError``.
"""
_canon_inline_value(self._value, nl=self._doc_newline, comments=comments)
resolved = _resolve_format_options(options=options, comments=comments)
format_inline_root(self._value, nl=self._doc_newline, options=resolved)

def set_multiline(self, *, multiline: bool, indent: int = 4) -> Array:
"""Switch this array between flush single-line and multi-line form.
Expand Down
37 changes: 23 additions & 14 deletions src/tomlrt/_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,11 @@
from tomlrt._errors import TOMLError
from tomlrt._format import (
_canon_header_slot,
_canon_inline_value,
_canon_kv_slot,
_canon_leading,
_resolve_format_options,
format_document_trailing,
format_inline_root,
format_subtree,
)
from tomlrt._inline_comments import InlineEolView, InlineLeadingView
Expand Down Expand Up @@ -77,6 +78,7 @@
from _typeshed import SupportsKeysAndGetItem, SupportsRichComparison
from typing_extensions import Self

from tomlrt._format import FormatOptions
from tomlrt._slots import AoTEntry, Slot, SlotRef
from tomlrt._trivia import EolTrivia, TriviaPiece
from tomlrt._values import (
Expand Down Expand Up @@ -266,7 +268,12 @@ def _doc_newline(self) -> str:
lr = self._layout_root
return lr._newline if lr is not None else "\n" # noqa: SLF001

def format(self, *, comments: bool = True) -> None:
def format(
self,
*,
options: FormatOptions | None = None,
comments: bool | None = None,
) -> None:
"""Canonicalise this container's formatting in place.

Rewrites this subtree to the canonical layout:
Expand All @@ -280,19 +287,21 @@ def format(self, *, comments: bool = True) -> None:
multi-line stays multi-line).
* Newlines use the owning document's style.

With ``comments=True``, ``#foo`` and ``# foo`` both become
``# foo`` and trailing comment whitespace is stripped.
``comments=`` is deprecated; use
``FormatOptions(normalize_comments=...)`` instead. Supplying both
arguments raises ``ValueError``.

Detached factory-style containers (``Table.section()`` /
``Table.inline()`` not yet assigned anywhere) and inline dotted
navigators are unsupported and raise `TOMLError`.
"""
resolved = _resolve_format_options(options=options, comments=comments)
kind = self._kind
nl = self._doc_newline

if kind is _Kind.INLINE_ROOT:
assert self._value is not None
_canon_inline_value(self._value, nl=nl, comments=comments)
format_inline_root(self._value, nl=nl, options=resolved)
return

if kind in (_Kind.INLINE_FACTORY, _Kind.INLINE_DOTTED_INNER):
Expand All @@ -306,10 +315,10 @@ def format(self, *, comments: bool = True) -> None:
path=(),
owner=None,
nl=nl,
comments=comments,
options=resolved,
)
format_document_trailing(self._preamble, nl=nl, comments=comments)
format_document_trailing(self._trailing, nl=nl, comments=comments)
format_document_trailing(self._preamble, nl=nl, options=resolved)
format_document_trailing(self._trailing, nl=nl, options=resolved)
return

if kind is _Kind.SECTION:
Expand All @@ -319,7 +328,7 @@ def format(self, *, comments: bool = True) -> None:
path=self._path,
owner=self._owner_aot_entry,
nl=nl,
comments=comments,
options=resolved,
)
return

Expand All @@ -331,16 +340,16 @@ def format(self, *, comments: bool = True) -> None:
for ref in list(self._refs):
slot = ref.slot
if isinstance(slot, KVSlot):
_canon_kv_slot(slot, nl=nl, comments=comments)
_canon_kv_slot(slot, nl=nl, options=resolved)
elif isinstance(slot, StructuralHeaderSlot):
_canon_header_slot(slot, nl=nl, comments=comments)
_canon_leading(slot, nl=nl, target_blanks=None, comments=comments)
_canon_header_slot(slot, nl=nl, options=resolved)
_canon_leading(slot, nl=nl, target_blanks=None, options=resolved)
for value in self.values():
if isinstance(value, (Container, Array)):
value.format(comments=comments)
value.format(options=resolved)
elif isinstance(value, AoT):
for entry in value:
entry.format(comments=comments)
entry.format(options=resolved)

@property
def _attached(self) -> bool:
Expand Down
Loading