Utility: fmt - format a script to the GodspeedOS standard
Status: BUILT and MERGED - the .gsh formatter, one canonical layout, non-negotiable,
applied in place. STREAMING (no file-size cap), semantics-preserving, and idempotent. Pinned by
osdev test files (207/0), osdev test fmt-demo (6/0 - jar before/after, a medium multi-flush file,
and a 10 MB file formatted with fmt check = Ok), and osdev test fmt-idem (format-twice diff
= IDENTICAL). Trails CLAUDE.md; does not amend it.
1. What it is
fmt <path> rewrites a .gsh script in place to the single GodspeedOS canonical layout. You run
it knowing exactly what happens - there is nothing to decide. It is the gofmt / terraform fmt /
rustfmt idea, for gsh: the point is not prettiness, it is uniformity. A canonical format kills
bikeshedding and makes diffs mean something - you can see what changed instead of squinting past ten
differently-cramped lines.
# before (valid gsh, but jarring - crammed onto lines with ; and inline blocks)
let n = 7 ; if $n > 5 { echo big } else { echo small }
for i in range 1 5{echo $i}
# after fmt /script.gsh
let n = 7
if $n > 5 {
echo big
} else {
echo small
}
for i in range 1 5 {
echo $i
}
(fmt fixes layout - the ; becomes newlines, inline blocks expand and indent, a space is inserted
before {, runs of whitespace between tokens collapse. It does not re-space inside a token, so a
$n>5 written without spaces would stay $n>5 - see the scope note in §2.)
Boring on purpose - zero style options
There is exactly one canonical layout and no knobs to change it. The second fmt grows a
style flag, you have re-introduced the argument it existed to end. fmt has subcommands (below), but
none of them change how the code is formatted - only what fmt does with the result.
2. The canonical standard (what "formatted" means)
- Indent 4 spaces per block depth. No tabs.
- Braces, K&R.
header {on one line, body indented,}at the block's own indent.} else {and} else if <cond> {stay on one line. - One statement per line. A
;statement separator becomes a newline (the biggest de-jarring win).cmd ; cmd ; cmdbecomes three lines. - One space between tokens. Runs of whitespace between tokens collapse to a single space; leading/trailing whitespace per line is stripped.
#comments keep one space after the#; a full-line comment sits at the current indent, a trailing comment is one space after the statement.- Blank lines collapse (2+ become 1); no leading or trailing blank lines; the file ends in exactly one newline.
- Strings are sacred. Content inside
'…'/"…"is never touched - not its spacing, not its case, nothing. - Idempotent.
fmtof already-formatted text is a no-op.fmt(fmt(x)) == fmt(x)is an acceptance test - met, proven from 8 KB to 10 MB byosdev test fmt-idem(format a chunk-spanning file twice and diff = IDENTICAL).
Scope note (honest).
fmtcanonicalizes layout - indentation, statement-per-line, brace placement, inter-token whitespace, blank lines. It does not re-space the insides of a token (blk-16,val-$x), because gsh uses-/+inside words as well as as operators, and guessing which is which would risk changing meaning. Micro-spacing of arithmetic is a later slice, gated on the tokenizer's exact rules. Layout is the jarring part; that is what ships first.
3. Surface
Subcommands are words and come before the path (fmt is a subcommand tool like drives, not
a file-first verb like write). There is no write subcommand - the default is the write.
fmt <path> format the script IN PLACE (the default; no subcommand)
e.g. fmt /script.gsh
fmt check <path> is it already canonical? Ok + silent if yes; Err + loud if no. Never writes.
e.g. fmt check /script.gsh
fmt <a>,<b>,... format (or check) several files - comma-separated, done one at a time
e.g. fmt /x.gsh,/y.gsh
fmt version print the version
fmt help print usage
fmt <path>- the default. Format in place, on the fly. Safe to run blind: it only moves whitespace and newlines, so it can never change what the script does (§2.7-2.8).fmt check <path>- the discipline / CI gate. ReturnsErr(via theResultmodel,32_result.md) and names the file if it is not canonical, so a script can gate on it (fmt check /s.gsh ; if result == Err { fail "run fmt" }). Changes nothing on disk.- Bare
fmtprints usage (a path is the actionable arg; without one there is nothing to do).
4. Guardrails & safety (loud, never silent - §26.7)
Because the default writes in place with no preview, fmt is all-or-nothing and never leaves a
damaged file. It streams (constant memory, no file-size cap), and the write is temp-then-rename, so
a failed write can never touch the original:
- Won't parse -> refuse, file untouched. A syntactically broken script (unbalanced braces, a bad
block header) gets a loud error and no write.
fmtnever lays a half-formatted result over a broken one; fix the syntax, then format. - One statement too long -> refuse, file untouched. The streamer holds one statement across a read boundary; a single statement bigger than the hold buffer is refused loudly. This is a per-statement bound, not a per-file one - the file itself can be any size (a 10 MB script formats fine).
Neither guardrail is an option - they are the safety that lets the zero-preview default be trusted.
5. Later (separate so it can grow)
fmt diff <path>- print the changesfmtwould make without writing (needs a bounded diff).fmt <dir>- format every.gshunder a directory in one call.- Host
osdev fmt+ VS Code format-on-save. The single definition of "GodspeedOS standard" is factored into ano_stdcore crate so the shell built-in and a hostosdev fmtshare it - one canonical format, never two that drift. This is where the gofmt/terraform muscle-memory lands.
6. Implementation shape & conformance
A shell built-in (0_conventions.md §2), using caps the shell already holds - no widened
authority. It is a fully streaming, token-level re-emitter: fmt_stream_pass reads the source in
chunks (fs_read_at) and fmt_walk_window formats each, emitting to a closure - holding only ONE
partial statement + the indent depth across a read boundary (no native recursion). Constant memory,
any file size. It never evaluates the script; it only re-lays-out its tokens, which is why it is
provably semantics-preserving.
The write is two streaming passes: pass 1 counts the output size (for OP_WRITE_NEW), pass 2
stream-writes 508-aligned chunks (OP_WRITE_AT) into a temp, which is then renamed over the
original - so a failed write leaves the original intact. fmt check formats to a temp and compares the
two files sequentially (never two concurrent reads of the same file). Pinned by osdev test fmt-idem
(the format-twice boundary diff) and osdev test fmt-demo (the 10 MB).
Has its own help_block and is listed under Console in the top-level help, and conforms to
0_conventions.md rules 1 and 5: fmt is in the shell's UTILS list, so fmt help and
fmt version are intercepted before cmd_fmt. (Found 2026-10-09 missing from UTILS, so fmt help
took help as a PATH to format; fixed in backlog/fixed/80 H8.)