Repository navigation
Conversation
Hugo under docs/, deployed to GitHub Pages by a workflow of its own so a typo fix does not wait on the test suite and a documentation change cannot break the library. Lives in this repository rather than on the blog because the interesting pages are the ones that have to track the code — spec coverage, the oracle divergences, and eventually a grammar generated from the tokenizer. Across two repositories those rot; here one commit moves both. The blog keeps the narrative posts and links across. Four pages: what it is with a real .conf beside the struct it lands in, getting started (marked plainly where it describes a plan rather than what runs), section-by-section coverage with the divergences named, and why the repository carries two other implementations. No theme and no submodule — about 150 lines of layout and CSS, taking the palette from the badge already in docs/brand. Builds with nothing but Hugo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KegbfxY6Ed3LhGMZJu9Yrt
Reworked after looking harder at ziggy-lang.io, whose homepage teaches rather than welcoming: a real artefact first, then one section per thing that makes the format worth using. So the landing page now opens on a config file doing four HOCON-specific things at once, then takes them one at a time — JSON beside the same document written as HOCON, composition (the part JSON, TOML and Ziggy have no answer to), the Zig struct it parses into, the eight lines that each cost a rule to get right, and the oracles. New Patterns page for the shape people actually build: config/ with common and defaults underneath prod, and uat built on prod. Every claim on it was run through the java oracle first — notably that overriding service.name in the last layer moves data-dir with it, because a substitution resolves against the finished document and not against the file it was written in. That one is the reason the format survives. Also shows the CLI the project wants: render to resolved JSON, get one path, diff two environments, check in CI. Reviewing layered config by reading the layers is how mistakes survive review. Dropped the custom domain: this goes to lejmr.github.io/zig-hocon, so no DNS to wait on. Links go through a shortcode that resolves pages, since absolute paths break under a subpath. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KegbfxY6Ed3LhGMZJu9Yrt
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KegbfxY6Ed3LhGMZJu9Yrt
…works The known-divergences list lumped bugs together with choices. Split into bugs (differs and should not), deliberate leniency (differs and stays that way), and cosmetic (same verdict, different error name), with the rule that keeps the middle one honest: leniency may accept more inputs, never produce a different value. Also states the position rather than implying it. The spec is the authority and typesafe/config is how the spec gets read, because it is what every .conf file in the world was written against. On every disagreement found so far the two agree and pyhocon is wrong, so "follows the spec" and "follows java" have not yet pulled apart — claiming one against the other would be a distinction without a difference. Validation gets a section of its own, with both honest answers. A test in the caller's own build is the one that scales, because the module graph is already right there; the CLI form writes a shim, compiles it with zig and runs it, which works for a standalone config.zig and stops where the struct pulls in the rest of a program. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KegbfxY6Ed3LhGMZJu9Yrt
The pitch was missing its best part. Checking a config against the struct needs no environment to run in, so prod, uat and dev are all checked in one CI run before any of them ship — instead of finding out that the UAT config no longer fits by deploying to UAT. Also names the failure HOCON cannot catch on its own: a deep override path is just a key, so `timeouts.raed = 5000` adds one rather than changing one, silently. A struct is the declared shape that turns that into a build failure. And the limit, in the same breath: a type is not a constraint. `u16` refuses 70000 and accepts 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KegbfxY6Ed3LhGMZJu9Yrt
From how this is actually done in production with pydantic and pytest: the configurations worth validating are the leaves. common.conf and defaults.conf are parts of a configuration, not configurations, and a part is legitimately missing what the leaf supplies. The inline for is this arrangement's parametrize, running at compile time, which is what lets @embedfile work and makes a missing file a build error rather than a test that quietly did not run. To discover the leaves rather than list them, build.zig is the place — it is an ordinary program and the only one that reads the filesystem before compilation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KegbfxY6Ed3LhGMZJu9Yrt
The earlier framing sent people to TOML for a single flat file, on the grounds that HOCON's composition is not worth paying for there. That was wrong for a reason worth stating: it only held while HOCON cost something to adopt, which is the thing this library removes. For one flat file the three are level. So the page raises the real decision to where it belongs. Layers — environments, shared defaults, overrides — is HOCON or Pkl, and everything else on the page makes you build layering yourself: merging dictionaries in application code, or YAML anchors that do not cross files, cannot be partially overridden, and whose merge key was never standard. The argument for HOCON in the simple case is then not that it wins there, but that it is the only one you do not have to leave when the config stops being one file. Pkl gets a fair hearing, including the two things HOCON has no answer to and the note that its constraints cannot reference other properties — and section 4 says outright that a schema shared across teams and languages is Pkl's problem, not this library's, and to use Pkl. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KegbfxY6Ed3LhGMZJu9Yrt
Section 3 had HOCON and Pkl sharing the layering answer, which reads as a coin flip and is not one. Layering is where HOCON earns its keep; section 3 now says so and stops hedging. Pkl moves into "so when is it not HOCON?" with the two cases where it is genuinely the better answer, and the case where it is not. The infra one is the sharp one and it was missing: in a YAML pipeline there is no type system anywhere between what you write and what runs, so Pkl supplies the one that is absent — and it can override a list element by predicate, which HOCON cannot do at all and which infrastructure config needs constantly. For an application in one language the argument reverses, and it is about artefacts rather than syntax. Pkl needs the schema, the generated types and your code to agree; a struct is all three. Removing an artefact beats shortening a syntax. Both honest costs stated: a validate() method is checked when the build runs, so config edited by someone who does not compile gets no feedback until CI, where Pkl's LSP would have flagged it while typing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KegbfxY6Ed3LhGMZJu9Yrt
Three present-tense statements that were not true, which a plan is still not allowed to make. The Patterns banner conceded includes and said everything else on the page was built and tested — but substitution resolution is every remaining item in that list, and it does not run. Worse than no banner: it bought credibility by admitting the small thing. Now says the page teaches the format rather than the library. Eighty tests were seventy-eight. "Every behavioural test carries an oracle note" was forty-four of seventy-seven groups. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KegbfxY6Ed3LhGMZJu9Yrt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A Hugo site under
docs/, published to GitHub Pages by a workflow of its own.Why here and not on the blog
The pages worth having are the ones that must track the code — spec coverage,
the oracle divergences, and eventually a grammar generated from the tokenizer.
Across two repositories those rot; in this one a single commit moves both. The
blog keeps the narrative posts and links across.
It also serves a different reader. The blog is how I built this; this is how
do I use it.
The pages
Landing — modelled on ziggy-lang.io, whose homepage teaches instead of
welcoming. Opens on a config file doing four HOCON-specific things at once,
then takes them one at a time: JSON beside the same document as HOCON;
composition, which is the part JSON, TOML and Ziggy have no answer to; the Zig
struct it parses into; the CLI; the eight lines that each cost a rule; the
oracles.
Patterns — the shape people actually build.
config/withcommon.confand
defaults.confunderprod/, anduat/built on top ofprod/. Then onesection per mechanism, with the reasoning: why objects merge instead of
replacing, why a substitution resolving against the finished document is the
most useful thing in the format, how a list extends itself, and why
port = ${?PORT}on the line afterport = 8080is the whole of environmentoverride.
Every claim on that page was run through
tools/oracle/hocon-javabefore beingwritten down — including the one worth the page on its own: overriding
service.namein the last layer movesdata-dirwith it, because thesubstitution was never bound to the file it was written in.
Getting started — marked plainly, twice, where it describes a plan rather
than what runs today.
Coverage — the spec section by section, divergences named.
Oracles — why the repository carries two other implementations.
Shape
No theme, no submodule: four layouts, one shortcode, two stylesheets (one of
them generated by
hugo gen chromastylesfor light and dark). Palette from thebadge already in
docs/brand/. CI needs no Sass, no Node, nosubmodules: recursive.Deploy is a separate workflow, path-filtered to
docs/**, so a documentationchange cannot break the library and a red suite cannot hold up a typo fix.
Published at lejmr.github.io/zig-hocon — no custom domain, so nothing to
wait on but the Pages setting.
Before merging
Repository settings → Pages → Source must be GitHub Actions. Nothing else.
Not included
My working tree still has uncommitted changes in
src/Key.zigandsrc/Value.zig; they are deliberately not part of this branch.