ADR 0001 — Configuration file formats¶
- Status: Accepted
- Date: 2026-09-26
- Decides: D4 in
roadmap.md
Context¶
Configuration is rainbow-fmt's main user interface: a project's config file is the written record of its style decisions. The format must:
- represent typed option values unambiguously, in particular enum strings
such as
"preserve","off","never", which some formats coerce to booleans; - allow comments, so a team can record why a choice was made;
- fit the conventions of the ecosystems rainbow-fmt targets — Python
(
pyproject.toml,[tool.<name>]) and JavaScript/Svelte (package.json, JSON/YAML dotfiles); - report the file, line, and column of every value, for
rainbow-fmt explainprovenance and for validation error messages; - support comment-preserving writes, for
rainbow-fmt inferupdating an existing config.
Decision¶
-
The option schema is format-independent. Configuration is defined as a typed data model. Each file format is a thin source that parses a file into that model and attaches a source position to every value. Validation, cascading, and provenance operate on the model only.
-
TOML is the primary, documented format. All documentation examples, generated option references, presets shipped with rainbow-fmt, and the output of
rainbow-fmt inferuse TOML. -
YAML is supported from the first release, with the same schema and the same keys, as a core dependency (no extra install).
-
package.json("rainbow"key) is supported as a JSON source for JavaScript projects. -
Recognized configuration files, searched from the formatted file's directory upwards:
| File | Format |
|---|---|
rainbow.toml, .rainbow.toml |
TOML |
rainbow.yaml, rainbow.yml, .rainbow.yaml, .rainbow.yml |
YAML |
pyproject.toml — [tool.rainbow] table |
TOML |
package.json — "rainbow" key |
JSON |
A directory containing more than one of these (counting pyproject.toml
and package.json only when they contain a rainbow section) is a
configuration error that names every file found. There is no silent
precedence between formats.
-
YAML is parsed with the YAML 1.2 core schema (
ruamel.yaml), where onlytrue/falseare booleans. Additionally, the schema validator rejects a boolean where an enum is expected, with a targeted message:line_breaks: expected one of "preserve", "off", ...; got boolean false — if you meant the string "off", quote it. This catches YAML 1.1 habits and files authored for other tools. -
Libraries (to confirm in the config-loader task):
- TOML:
tomlkit(reading with positions, comment-preserving writes);tomllib(stdlib) is acceptable for read-only paths iftomlkitposition reporting proves insufficient and positions are tracked separately. - YAML:
ruamel.yamlround-trip mode (YAML 1.2, line/column on every node, comment-preserving writes). - JSON: stdlib
jsonplus a small position-tracking pass forpackage.json.
Consequences¶
- Python users can keep configuration in
pyproject.toml; JS/Svelte users can usepackage.jsonor a YAML dotfile. - Every schema test must run against all three sources. The config test suite is parameterized by format: the same logical config, expressed in TOML, YAML, and JSON, must produce identical resolved options and equivalent provenance.
- Two runtime dependencies (
tomlkit,ruamel.yaml) are added to the core. - Documentation shows TOML only; a single page documents the TOML → YAML → JSON mapping, which is mechanical because the schema is shared.
rainbow-fmt infer --format yamlwrites YAML on request; the default output is TOML.rainbow-fmt config convert(low priority) can translate between formats, since all formats share one model.
Alternatives considered¶
- TOML only. Simplest, but pushes JS/Svelte users, who predominantly use JSON/YAML tooling configs, to a format foreign to their ecosystem. Rejected at the maintainer's request to support YAML from the start.
- YAML primary. Familiar in JS and CI contexts, but implicit typing
(YAML 1.1
off/no→false,1.10→1.1) collides directly with rainbow's enum values, and it has no role in the Python packaging ecosystem. - JSON / JSONC primary. No comments (JSON) or no standard library support (JSONC); poor for a document meant to explain decisions.
- Python or JavaScript config files (
rainbow.config.py/.js). Maximally flexible, but executing code to read configuration is a security and tooling (editor, LSP, CI) liability, and it makesinfer/convertimpossible to implement reliably.
Amendment 1 — 2026-09-27 (library check, TASKS.md T6)¶
Decision 7 asked the config-loader task to confirm the libraries.
- TOML:
tomlkithas no source positions. Its items (0.15.1) carry whitespace and comment trivia for round-tripping, but no line, column or offset. As decision 7 allows, TOML files are read withtomllib(stdlib; the authority for values and syntax errors), and a small scanner inrainbow_fmt.configrecords the position of every key and value. A test checks that every leaf valuetomllibreturns has a position.tomlkitstays a dependency for comment-preserving writes (rainbow-fmt infer). - YAML:
ruamel.yamlconfirmed. Round-trip mode (0.19) uses the YAML 1.2 core schema by default (unquotedoffis a string), reports line and column for every key, value and sequence item, and rejects duplicate keys. - JSON: own position-tracking parser. The stdlib
jsonmodule reports no positions and silently keeps the last of duplicate keys; a small parser inrainbow_fmt.configreadspackage.jsonwith positions and rejects duplicate keys.