Skip to main content

Crate shcl

Crate shcl 

Source
Expand description

SHCL reference implementation: parser, accessor, writer/formatter. Single file on purpose - the drop-in story is “copy this file into your tree”. The language spec lives in project/spec.md; the conformance corpus in project/conformance/ pins every behavior here. Every other binding mirrors this file’s structure on purpose (parity over idiom - see style-guide.md), so restructuring here means restructuring all.

Structs§

Diagnostic
One parser or validator finding, tied to a source line.
Document
A parsed SHCL document: the tree, its diagnostics, and its strictness level.
LoadError
A failed strict load: the diagnostics that failed it, plus the recovered tree.
Read
Full-tier read result: value plus status plus the original raw text (when the path resolved), so a caller can always recover what was actually in the file. Array reads also carry one status per slot (element, or wildcard instance) in slots; status is then the worst slot. Scalar reads leave slots empty. line is the 1-based source line of the resolved binding (0 when the path did not resolve to one node, or the node was writer-built), so a consumer check the schema cannot express can still cite the line. quoted is true when the read’s single scalar element was quoted in the source - the escape hatch that lets a downstream language reserve @null while "@null" stays a plain string. Arrays, raw blocks, and empties leave it false.
ShclDateTime
Local (floating) date/time unless a zone suffix was present. Fields mirror what was written: a date-only value has no time, and vice versa.

Enums§

FileStatus
What load_file found: the four cases a consumer’s own load path otherwise confuses. Clean and HadErrors both carry a usable document; NotFound and Unreadable come back with an empty one.
SaveError
Why a save did not happen. The two cases need different handling, so they are separate values rather than two spellings of one message: Refused is the lost-content gate, which the caller can reverse with save_file_lossy, and Io is the disk’s answer, which they cannot.
Severity
Only Error fails a strict load; Hint flags legal-but-lookalike input.
Status
Read status sentinels. Empty is informational - the empty value is still returned. Ordered by severity so a worst-of aggregate is just max.
Strictness
Per-document forgiveness knob. Set once at load; composes with per-call onBad.
WriteReason
Why a write would fail (write_reason()): the distinctions behind a setter’s bare false. Writable = the path passes the writer’s validation; the rest name the five ways it cannot.
ZoneSpec
A datetime’s zone suffix as written.

Constants§

MAX_DEPTH
Maximum nesting depth (levels below the document root), enforced at load and by the Writer. Deeper lines are skipped with an E016 error. The cap is what keeps the recursive tree walks (emit, merge, clone) safely inside every binding’s stack, so a hostile or machine-generated document can make a load fail but never crash the consumer.

Functions§

format_f64
Render a float the way the writer and the CLI do: shortest round-trip decimal, never scientific notation, inf/-inf/NaN spelled out. Rust’s own Display already does exactly that, which is why this is a wrapper - the other three bindings have to implement it, and a consumer building canonical text by hand should not have to know which of the four they are reading.
generate
Emit a commented, typed starter config from a schema (shcl init --schema). Paths that must exist (required, or a repeat lower bound of 1+) are live (their default, or an empty value); optional paths are commented out so the file is valid and minimal as-is. A must-exist wildcard path whose parent gets materialized by another live line is generated too, in dotted form - otherwise the file would fail the very schema that produced it - and remaining wildcard or [#N] paths (which cannot be materialized) are listed in a trailing comment block. The output always loads clean and validates clean against its schema, except a repeat lower bound of 2+ (identical generated lines would merge, so the shortfall is reported). A footer naming the format and pointing at the spec is written last unless no_banner; the flag is negative so leaving it alone writes the footer. Err = schema faults (V09x), same as validate/check --schema.
parse_datetime
Whole-value date/time parse per the whitelist. None = BadType.
quote_segment
Quote one path segment so it can be spliced into a lookup path: a bare name passes through, anything else comes back quoted and escaped in the form the path scanner accepts. Splicing user-typed text into a path without this is path injection - a dotted name silently reads as nesting. Same spelling paths() and the canonical emitter produce.
suppress_declared_reopens
Drop the H002 hints a schema disavows: a section whose entry declares reopen: true is MEANT to be written in parts, so the merge hint is structurally a false positive there. Matching is by leaf name, same as the H001 suppressor, and it errs toward quiet, for a hint. Used by check --schema and load_and_validate; call it wherever doc diagnostics and a schema meet.
suppress_declared_repeats
Drop the H001 hints a schema disavows: a field whose declared repeat upper bound is above 1 repeats BY DESIGN (repetition is its instance mechanism), so the repeated-bare-leaf hint is structurally a false positive there and trains users to ignore hints. Matching is by leaf name - the filter consumers were hand-rolling - which errs toward quiet, for a hint. Used by check --schema and load_and_validate; call it wherever doc diagnostics and a schema meet.
write_file_atomic
The file tier’s write mechanism (also what the CLI’s --write uses): a temp file in the same dir, then a rename over the target, so an interrupted write can never truncate the config it rewrites. The data is synced before the rename so a crash cannot publish an empty file.