The Cargo workspace contains five crates:
toml-doc/provides the mutable, format-preserving TOML document model.common/provides formatting passes shared by both formatters.tox-rules/shares tox formatting betweentox.tomland[tool.tox].pyproject-fmt/contains the PyO3 implementation forpyproject-fmt.tox-toml-fmt/contains the PyO3 implementation fortox-toml-fmt.
Three Python packages provide the command-line layer:
toml-fmt-common/handles argument parsing, file selection, and diff output.pyproject-fmt/exposes thepyproject-fmtcommand and Python API.tox-toml-fmt/exposes thetox-toml-fmtcommand and Python API.
The formatter crates keep Rust integration tests in rust/tests/. Python tests live in each package's tests/
directory. tasks/ contains repository maintenance scripts.
Use Cargo from the repository root for Rust changes:
cargo fmt --all
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace --no-default-featuresThe last command disables PyO3's extension-module feature so Rust test binaries can link to Python. Test the compiled
extension through tox after changing a formatter crate:
cd pyproject-fmt
tox run -e 3.14
tox run -e type
cd ../tox-toml-fmt
tox run -e 3.14
tox run -e typeChoose an installed Python version if 3.14 is unavailable. Tox skips missing interpreters.
Run the shared Python package in the same way:
cd toml-fmt-common
tox run -e 3.14
tox run -e typeThe root formatting environment runs the configured repository checks:
tox run -e fixtoml-doc parses source text into a mutable document while borrowing unchanged text. Formatting passes mutate that
document, and its Display implementation writes the result.
flowchart LR
source[TOML source] --> parser[toml_parser events]
parser --> document[toml-doc Document]
document --> passes[common and tool passes]
passes --> layout[layout and spacing]
layout --> output[formatted TOML]
Document stores root entries, sections, and trailing trivia. A section owns its header and entries, which lets table
ordering move the complete unit. A child table after an array-of-tables entry belongs to that array element, so use
common::sections::reorder_within instead of sorting document.sections directly.
Comments and blank lines lead the item below them. A Member stores padding around its value, while its container
writes commas. This split lets array sorting move comments with their values and retain a trailing comma.
Use Key::segments when quoted dots matter. Key::path joins segments with ., so it cannot distinguish "a.b" from
a.b.
common::sections finds tables regardless of whether the input used headers, dotted keys, or inline tables.
use common::sections;
sections::for_table_at(document, &["tool".to_owned(), "demo".to_owned()], |table| {
sections::reorder_inline_table(table, &["name", "version"]);
});Use the public traversal functions instead of indexing sections by hand. The traversal code handles repeated headers, arrays of tables, and equivalent TOML spellings.
common::strings::update decodes a string, applies a transformation, and chooses a valid representation for the new
text.
use common::strings;
strings::update(value, str::to_lowercase);Use update_wrapped when the result may need line continuations.
use common::{arrays, sections};
sections::reorder_keys(&mut section.entries, &["", "name", "version"]);
arrays::sort_strings(array, &str::to_lowercase, &str::cmp);An empty string in a key order reserves a slot for unknown keys. A named key also claims its dotted descendants.
For typed inline tables, provide a discriminator and key order:
use common::sections::{reorder_inline_tables, InlineSchema};
let path = ["tool", "tox"].map(str::to_owned);
reorder_inline_tables(
document,
&path,
&[InlineSchema {
discriminator: "replace",
key_order: &["replace", "default", "extend"],
}],
);The table path limits the schema to its owning tool.
common::build creates unspaced entries. The layout pass supplies whitespace later.
use common::build;
section.entries.push(build::string_entry("name", "example"));
section
.entries
.push(build::entry("tags", build::array([build::string("one")])));use common::nesting;
nesting::collapse(document, "tool.x");
nesting::expand(document, "tool.x");collapse_where leaves selected child tables expanded. The expand_tables and collapse_tables settings use this
path.
Write behavior tests through public APIs. Keep one assertion target per test, parameterize repeated cases, and use fixtures for shared setup. Mock network, clock, filesystem, or subprocess boundaries; run formatter logic directly.
Formatter output tests use inline insta snapshots:
#[test]
fn a_dependency_list_is_normalized() {
insta::assert_snapshot!(format("dependencies = ['Demo>=1.0.0']"), @"");
}Populate and review snapshots with:
cargo insta test --accept
cargo insta reviewAssertions should cover the complete result. A substring assertion can pass when the formatter leaves the input unchanged or damages adjacent structure.
Each package enforces 100% coverage. Clear stale instrumentation before measuring Rust coverage:
cargo llvm-cov clean --workspace
cargo llvm-cov --workspace --no-default-features --summary-onlyPython tox environments include their package and tests in the coverage report.
Edit files under each package's docs/ directory. Generate the published README from those sources:
tox run -e readme -c pyproject-fmt/tox.toml
tox run -e readme -c tox-toml-fmt/tox.tomlBuild and check links with the package documentation environment:
tox run -e docs -c pyproject-fmt/tox.toml
tox run -e docs -c tox-toml-fmt/tox.tomlRun the checks for each changed layer. A shared Rust change needs the workspace tests and both formatter tox suites. A documentation change needs both README generation and the affected docs build. Keep commits limited to one concern.