This guide is for people contributing to Tomlet. For library usage, see
README.md.
Install mise, then run:
mise install
mise exec -- just deps.mise.toml defines the project tool versions. Use mise only to install tools
and set up the shell environment. Use just
to run tasks and download Gleam package dependencies. With mise
shell activation,
run just directly; otherwise, use mise exec -- just <recipe>.
CI installs tools from the same config. The test matrices override Erlang and
Node versions with MISE_ERLANG_VERSION and MISE_NODE_VERSION. CI tests OTP
27 and 28; Gleam 1.18.1 emits Erlang syntax that OTP 26 cannot compile. The SBOM
job requires OTP 28 to run the pinned licence_audit release.
Run just to list available recipes.
just build # compile the package
just test # run unit tests and TOML corpus checks
just format # format all Gleam files, including examples
just format-check # check formatting without changing files
just lint # check formatting and run glinter
just ci # run the full validation workflowShort aliases are also available: just b, just t, just f, just l,
just c, and just pr.
CI uses just format-check. These formatting tasks cover the full tree,
including examples, to match the check that Trellis runs before publication.
just test runs gleam test and then the TOML corpus checks.
The corpus runner lives at scripts/run_corpus_tests.py. It clones the
upstream toml-test fixtures into .toml-test/, generates temporary Gleam test
modules, runs them on the supported Gleam targets, and removes the generated
files afterwards.
Useful targeted commands:
gleam test
gleam test --target javascript
python3 scripts/run_corpus_tests.py valid
python3 scripts/run_corpus_tests.py invalid
python3 scripts/run_corpus_tests.pyValid TOML 1.0 corpus fixtures are expected to round-trip byte-for-byte unless a
fixture is explicitly listed in ROUNDTRIP_UNSUPPORTED. Invalid fixtures should
be rejected either by tomlet.parse or, for invalid byte encodings, by
tomlet.parse_bytes.
The supported public API is the top-level tomlet module. The modules listed in
gleam.toml under internal_modules are implementation details:
tomlet/asttomlet/parsertomlet/pathtomlet/lexer
tomlet/parser is token-driven: tomlet/lexer wraps the william TOML lexer to
produce byte-offset-annotated tokens, and the parser assembles the AST from that
token stream while reusing the source-preserving value validators. TOML 1.1
features are gated by an internal parser.Version, threaded from the public
tomlet.TomlVersion.
Avoid documenting, depending on, or expanding internal modules as public API unless the release intentionally changes that boundary.
When reviewing API changes, preserve this contract:
- Keep all user-facing additions in the top-level
tomletmodule. - Keep implementation modules in
internal_modulesunless intentionally promoting them to public API. - Public variant types are stable and matchable. Adding, removing, or renaming variants is treated as a breaking change.
User-facing changes should include a trellis changelog fragment:
just change Fixed "Reject dates with a zero month"
# or: trellis changelog new --kind Fixed --body "..."Fragments are TOML files in .changes/unreleased/. Use one of the trellis
default kinds (Breaking, Removed, Added, Changed, Deprecated, Fixed,
Performance, Security, Dependencies). The kind sets the version bump. Write
entries for users of the package rather than for maintainers reading the diff.
Preview the next version with:
just changelog-previewCHANGELOG.md is generated from the version sections in .changes/tomlet/. Do
not edit it directly.
Releases are automated with trellis:
- On each push to
main,trellis release propens or updates arelease/pendingPR that bumps the version and updatesCHANGELOG.md. - When that PR merges,
trellis tag create --github-releasecreates thevX.Y.Ztag and GitHub Release. - The tag push runs
trellis publish --tag, which publishes to Hex, and attaches an SBOM to the Release.
Run:
just ciAlso check that user-facing documentation reflects any public API or behavior
changes. Keep development-only details in this file so README.md stays focused
on package users.