Skip to content

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 explain provenance and for validation error messages;
  • support comment-preserving writes, for rainbow-fmt infer updating an existing config.

Decision

  1. 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.

  2. TOML is the primary, documented format. All documentation examples, generated option references, presets shipped with rainbow-fmt, and the output of rainbow-fmt infer use TOML.

  3. YAML is supported from the first release, with the same schema and the same keys, as a core dependency (no extra install).

  4. package.json ("rainbow" key) is supported as a JSON source for JavaScript projects.

  5. 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.

  1. YAML is parsed with the YAML 1.2 core schema (ruamel.yaml), where only true/false are 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.

  2. Libraries (to confirm in the config-loader task):

  3. TOML: tomlkit (reading with positions, comment-preserving writes); tomllib (stdlib) is acceptable for read-only paths if tomlkit position reporting proves insufficient and positions are tracked separately.
  4. YAML: ruamel.yaml round-trip mode (YAML 1.2, line/column on every node, comment-preserving writes).
  5. JSON: stdlib json plus a small position-tracking pass for package.json.

Consequences

  • Python users can keep configuration in pyproject.toml; JS/Svelte users can use package.json or 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 yaml writes 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 makes infer/convert impossible 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: tomlkit has 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 with tomllib (stdlib; the authority for values and syntax errors), and a small scanner in rainbow_fmt.config records the position of every key and value. A test checks that every leaf value tomllib returns has a position. tomlkit stays a dependency for comment-preserving writes (rainbow-fmt infer).
  • YAML: ruamel.yaml confirmed. Round-trip mode (0.19) uses the YAML 1.2 core schema by default (unquoted off is a string), reports line and column for every key, value and sequence item, and rejects duplicate keys.
  • JSON: own position-tracking parser. The stdlib json module reports no positions and silently keeps the last of duplicate keys; a small parser in rainbow_fmt.config reads package.json with positions and rejects duplicate keys.