Skip to content

docs: a project site at lejmr.github.io/zig-hocon - #1

Open
lejmr wants to merge 9 commits into
mainfrom
docs-site
Open

lejmr wants to merge 9 commits into
mainfrom
docs-site

Conversation

@lejmr

@lejmr lejmr commented Sep 13, 2026 •

Copy link
Copy Markdown
Owner

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/ with common.conf
and defaults.conf under prod/, and uat/ built on top of prod/. Then one
section 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 after port = 8080 is the whole of environment
override.

Every claim on that page was run through tools/oracle/hocon-java before being
written down — including the one worth the page on its own: overriding
service.name in the last layer moves data-dir with it, because the
substitution 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 chromastyles for light and dark). Palette from the
badge already in docs/brand/. CI needs no Sass, no Node, no
submodules: recursive.

Deploy is a separate workflow, path-filtered to docs/**, so a documentation
change 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.zig and
src/Value.zig; they are deliberately not part of this branch.

lejmr and others added 3 commits September 13, 2026 17:32
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
@lejmr lejmr changed the title docs: a project site at hocon.miloskozak.cz docs: a project site at lejmr.github.io/zig-hocon Sep 13, 2026
lejmr and others added 6 commits September 13, 2026 17:51
…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant