fig 2.1.0

Parse, edit, and convert config files while preserving comments. Supports JSON, YAML, TOML, and more.
//! `--help`/usage text for the `fig` CLI, one function per action plus the
//! top-level `general` summary. Pure output — no parsing or state.
const std = @import("std");
const Io = std.Io;

pub const title_string = "\n=========\n   FIG\n=========\n\n";

pub const Help = struct {
    pub fn general(term: *Io.Terminal, binary_name: []const u8) !void {
        try term.writer.print(
            \\Usage:
            \\  {s} <action> [action options] --[flags]
            \\Possible actions:
            \\  help: prints this text (default action)
            \\  version: prints version number
            \\  edit: edits part of file
            \\  set: upsert a value (create the key, embed block, or file, if absent)
            \\  insert: add a new key or list item to a file
            \\  delete: remove a key or list item from a file
            \\  get: print a file or a specific part of a file to stdout
            \\  comment: add or edit a comment on part of a file
            \\  check: validate that one or more files parse cleanly
            \\  fmt: reformat a file in place (house style; gofmt-style)
            \\  convert: convert a file (or a host document's embedded region)
            \\    from one format/archetype to another, in place
            \\
            \\For information on action options, pass --help or -h
            \\to the action you would like to learn about.
            \\
        , .{binary_name});
        try term.writer.flush();
    }

    pub fn edit(term: *Io.Terminal, binary_name: []const u8) !void {
        try term.writer.print(
            \\Usage: {s} edit [--key] <file> <path> <replacement>
            \\  --key: edit the object key at path instead of the value
            \\  path format: dot syntax for keys, bracket syntax for indices
            \\    example: school.class[0].student[3]
            \\  .md/.markdown files: edits the frontmatter/endmatter in place —
            \\    its archetype (YAML/JSON/fig frontmatter, YAML endmatter) is
            \\    sniffed from the file, defaulting to YAML when none is found
            \\
        , .{binary_name});
        try term.writer.flush();
    }

    pub fn set(term: *Io.Terminal, binary_name: []const u8) !void {
        try term.writer.print(
            \\Usage: {s} set [--embed <archetype>] <file> <path> <value>
            \\       {s} set [--embed <archetype>] --seq <file> <path> <item>...
            \\  Upsert: replace the value at <path>, or create it when absent —
            \\    one verb for `edit`+`insert`. Missing parent maps along <path>
            \\    are auto-created (`mkdir -p`); a segment that is an existing
            \\    non-map scalar is a type error and left untouched.
            \\  When <file> itself does not exist, it is CREATED and seeded with
            \\    <path>: <value>; `fig get <file>` then prints what was written.
            \\    The format comes from the extension, so a new file needs a known
            \\    one — .figl (.fig also accepted)/.json/.jsonc/.yaml/.yml/.toml (or a .md host, via
            \\    --embed). .zon/.json5 have no from-scratch seed and must already
            \\    exist.
            \\  --seq: reconcile the sequence at <path> to exactly <item>..., keeping
            \\    the comments on items that survive (only new items are inserted,
            \\    only dropped ones removed; result order matches the arguments).
            \\  --embed <archetype>: target an embedded region of a host file —
            \\    `frontmatter` (---/YAML), `frontmatter-json`
            \\    (;;;/JSON), `frontmatter-fig` (```fig fenced block), or
            \\    `endmatter` (trailing ```endmatter block). When the host has no
            \\    such block, it is CREATED (frontmatter at the top, endmatter at
            \\    the bottom) and seeded with <path>: <value>.
            \\  value: a literal in the target format (YAML/TOML/ZON verbatim; JSON
            \\    is quoted as a string, as with `edit`). A created key is rendered
            \\    in the target syntax too, so new keys work for strict JSON.
            \\  path format: dot syntax for keys, bracket syntax for indices
            \\    example: school.class[0].student[3]
            \\  .md/.markdown files: upserts the frontmatter/endmatter, creating
            \\    it (as YAML) if absent — the archetype is otherwise sniffed
            \\    from the file, not assumed from the extension.
            \\
        , .{ binary_name, binary_name });
        try term.writer.flush();
    }

    pub fn insert(term: *Io.Terminal, binary_name: []const u8) !void {
        try term.writer.print(
            \\Usage: {s} insert <file> <path> <value>
            \\  Adds a new entry. The last path segment names the slot to create:
            \\    a.b.newkey   -> insert key `newkey` into the mapping at a.b
            \\    a.list[0]    -> prepend <value> as the first item of a.list
            \\    a.list[-]    -> append <value> as the last item ([$] also works)
            \\  An empty parent targets the root container, so the document's own
            \\    root (mapping vs list) decides which form applies — not the format.
            \\  Mid-sequence insert (e.g. list[2]) is not yet supported.
            \\  value: a literal in the file's format (YAML/TOML/ZON verbatim); for
            \\    JSON it is quoted as a string, as with `edit`.
            \\  path format: dot syntax for keys, bracket syntax for indices.
            \\  .md/.markdown files: edits the frontmatter/endmatter in place —
            \\    its archetype is sniffed from the file (YAML by default).
            \\
        , .{binary_name});
        try term.writer.flush();
    }

    pub fn delete(term: *Io.Terminal, binary_name: []const u8) !void {
        try term.writer.print(
            \\Usage: {s} delete <file> <path>
            \\  Removes the entry the path points at. The last path segment decides:
            \\    a.b.key      -> delete that mapping entry (with its own comments)
            \\    a.list[2]    -> remove item 2 from the sequence a.list
            \\    a.list[-]    -> remove the last item of the sequence a.list
            \\  path format: dot syntax for keys, bracket syntax for indices
            \\    example: school.class[0].student[3]
            \\    [-] or [$] in place of an index means "the last item"
            \\  .md/.markdown files: edits the frontmatter/endmatter in place —
            \\    its archetype is sniffed from the file (YAML by default).
            \\
        , .{binary_name});
        try term.writer.flush();
    }

    pub fn comment(term: *Io.Terminal, binary_name: []const u8) !void {
        try term.writer.print(
            \\Usage: {s} comment [--inline] [--delete | --get] <file> <path> [<text>]
            \\  default: add an own-line comment ABOVE the node at <path>
            \\  --inline: target the same-line trailing comment on the value at
            \\    <path> instead (set replaces any existing one on that line)
            \\  --delete: remove the targeted comment instead of adding it; <text>
            \\    is then omitted (a no-op when there is no such comment)
            \\  --get: print the targeted comment to stdout (markers stripped) and
            \\    make no change; <text> is then omitted (prints a blank line when
            \\    there is no such comment)
            \\  the comment marker is added for you: # for YAML/TOML, // for
            \\    JSONC/JSON5/ZON. Strict JSON has no comments (rejected).
            \\  <text> may span multiple lines (leading only): one comment line each.
            \\  path format: dot syntax for keys, bracket syntax for indices
            \\    example: school.class[0].student[3]
            \\  .md/.markdown files: comments the frontmatter/endmatter in
            \\    place — its archetype is sniffed from the file (YAML by
            \\    default).
            \\
        , .{binary_name});
        try term.writer.flush();
    }

    pub fn get(term: *Io.Terminal, binary_name: []const u8) !void {
        try term.writer.print(
            \\Usage: {s} get [--input json|json5|yaml|toml|zon|xml|canonical|fig|gron] [--output json|json5|yaml|toml|zon|xml|canonical|fig|gron] <file> [path]
            \\  -i, --input: input format of file (defaults to the file extension,
            \\    then to sniffing the file's contents if the extension is unknown)
            \\  -o, --output:   output format (defaults to the input format)
            \\  canonical: the AST's 1:1 oracle text encoding; usable as input or
            \\    output, e.g. to inspect how any document parses. (Owns no file
            \\    extension — select it explicitly.)
            \\  fig: the human-facing authoring dialect (`.figl`; `.fig` still
            \\    accepted); lossy at the edges (non-string keys, YAML refs) —
            \\    use `canonical`/`--lossless` for those. `-o fig` prints in
            \\    house style; use `fig fmt` to
            \\    rewrite a file in place instead of printing to stdout.
            \\  xml: config-oriented, not a general XML tool — an element becomes
            \\    a mapping, `@name` attributes and `#text` mixed content fold
            \\    into it, repeated children become an array. `-o xml` requires
            \\    the document to have exactly one root key; every scalar
            \\    (numbers, booleans, ...) prints as plain text, since XML has no
            \\    other type. Compiled in only with `-Dxml=true` (opt-in, off by
            \\    default). No in-place editor yet (`edit`/`comment` reject it).
            \\  gron: a line-oriented `path = value;` projection (greppable, and
            \\    reversible with `-i gron`); must be selected explicitly, never
            \\    sniffed. Fidelity matches JSON (drops comments/anchors).
            \\  --gron-root NAME: root identifier for `-o gron` (default "json").
            \\  --gron-sep STR: key/value separator for `-o gron` (default " = ").
            \\    Print-only: ungron always splits on " = ", so a custom separator
            \\    is one-way unless it matches the default.
            \\  --gron-term STR: per-line terminator for `-o gron` (default ";");
            \\    pass "" to drop it. ungron strips an optional ";" regardless.
            \\  --compact: single-line output with minimal whitespace (JSON, JSON5, ZON).
            \\  --pretty: multi-line, indented output (the default).
            \\  --indent N: spaces per indent level for pretty JSON, and for TOML's
            \\    wrapped arrays (default 2).
            \\  --width N: TOML column budget (default 80); a mapping/array that fits
            \\    stays inline, a wider one expands to a [section] / wrapped array.
            \\  --strip-comments: drop comments instead of carrying them across formats.
            \\  --lossless: preserve values the target can't represent natively
            \\    (e.g. a null in TOML, a TOML datetime in JSON) via a $fig
            \\    envelope, and reconstruct any such envelope in the input.
            \\    --lossy (the default) emits clean, idiomatic output instead.
            \\  -q, --quiet: suppress warnings on stderr — lossy conversions, and
            \\    fig authoring lints (`Yes`-style strings, a likely missing comma
            \\    in a flow value, indent/marker-count disagreement, ...).
            \\  --strict: treat any warning as an error (exit non-zero).
            \\  --embed <archetype>: read an embedded region of a host file —
            \\    `frontmatter`, `frontmatter-json`, `frontmatter-fig`, or
            \\    `endmatter`. Without this flag, a `.md`/`.markdown` file has
            \\    its archetype sniffed from the content (falling back to
            \\    `frontmatter`/YAML when none is found).
            \\  --body: print the host prose OUTSIDE the fences (the body span) instead
            \\    of the embed content; the whole file when there is no such region.
            \\  path format: dot syntax for keys, bracket syntax for indices
            \\    example: school.class[0].student[3]
            \\  .md/.markdown files: reads the frontmatter/endmatter, whichever
            \\    archetype it turns out to be
            \\
        , .{binary_name});
        try term.writer.flush();
    }

    pub fn check(term: *Io.Terminal, binary_name: []const u8) !void {
        try term.writer.print(
            \\Usage: {s} check [--input <format>] [-q|--quiet] <file>...
            \\  Validate that each file parses cleanly as its format. Prints an
            \\  `ok` line per file and exits 0 when all parse; prints an error
            \\  line to stderr for each failing file and exits 1 if any fail.
            \\  -i, --input: parse every file as this format (json, jsonc, json5,
            \\    yaml, toml, zon, xml, canonical). Default: infer from each
            \\    file's extension, then by sniffing its contents.
            \\  -s, --spec: validate against a specific language version, where one
            \\    is selectable: TOML `1.0`/`1.1` (default 1.1), YAML `1.2.2`/`1.1`
            \\    (default 1.2.2).
            \\    JSON strictness is the format itself (json vs jsonc vs json5).
            \\  -q, --quiet: suppress the per-file `ok` lines and fig authoring
            \\    warnings; errors still print.
            \\  reads stdin when <file> is `-`.
            \\  .md/.markdown files: validates the frontmatter/endmatter,
            \\    whichever archetype it turns out to be (YAML by default).
            \\
        , .{binary_name});
        try term.writer.flush();
    }

    pub fn fmt(term: *Io.Terminal, binary_name: []const u8) !void {
        try term.writer.print(
            \\Usage: {s} fmt [--input <format>] [--dry-run | --diff] <file>
            \\  Reformat a file in place: parse then re-emit in the format's house
            \\  style — `.figl`'s printer applies the style DESIGN.md describes
            \\  (spaced marker runs, `[]`/`+` list sigils, ...); other formats get
            \\  their own printer's canonical layout. Unlike `get`, the output
            \\  format always matches the input — reformatting never converts.
            \\  A file already in house style is left byte-identical (no-op write).
            \\  --dry-run: print the reformatted result to stdout instead of
            \\    writing it back; exit 1 if reformatting would change the file,
            \\    0 if it's already clean — a check gate for pre-commit/CI.
            \\  --diff: like --dry-run, but print a unified diff of the change to
            \\    stdout instead of the whole reformatted file; nothing is printed
            \\    (and exit is 0) when the file is already clean.
            \\  -i, --input: input format (defaults to the file extension, then
            \\    to sniffing the file's contents if the extension is unknown).
            \\  --compact: single-line output with minimal whitespace (JSON, JSON5, ZON).
            \\  --pretty: multi-line, indented output (the default).
            \\  --indent N: spaces per indent level for pretty JSON, and for TOML's
            \\    wrapped arrays (default 2).
            \\  --width N: TOML column budget (default 80); a mapping/array that fits
            \\    stays inline, a wider one expands to a [section] / wrapped array.
            \\  --strip-comments: drop comments instead of re-emitting them.
            \\  -q, --quiet: suppress warnings on stderr.
            \\  --strict: treat any warning as an error (exit non-zero, no write).
            \\  --embed <archetype>: reformat an embedded region of a host file —
            \\    `frontmatter`, `frontmatter-json`, `frontmatter-fig`, or
            \\    `endmatter` — instead of the whole file. Without this flag, a
            \\    `.md`/`.markdown` file has its archetype sniffed from the
            \\    content (falling back to `frontmatter`/YAML when none is found).
            \\  reads stdin when <file> is `-`, but only with --dry-run/--diff:
            \\    there is nowhere to write an in-place result back to.
            \\  .md/.markdown files: reformats the frontmatter/endmatter in
            \\    place, whichever archetype it turns out to be.
            \\
        , .{binary_name});
        try term.writer.flush();
    }

    pub fn convert(term: *Io.Terminal, binary_name: []const u8) !void {
        try term.writer.print(
            \\Usage: {s} convert --output <format> [--input <format>] [--write | --diff] <file>
            \\       {s} convert --to-embed <archetype> [--embed <archetype>] [--write | --diff] <file>
            \\  Convert a file — `fmt`'s twin for when the target format differs
            \\  from the source. Exactly one of --output/--to-embed picks the
            \\  target; the other flag group is unused (rejected together).
            \\  Like `get`, it prints the converted result to stdout by default;
            \\  pass --write to write it back to <file> in place instead.
            \\
            \\  Whole-file mode (--output): parse the whole file as --input (else the
            \\    extension, else sniffed from its contents) and re-emit it as
            \\    --output, in the target format's house style. A host document
            \\    whose extension implies an embedded region (`.md`/`.markdown`) is
            \\    rejected here — use embed-archetype mode, or pass --input to force
            \\    whole-file conversion anyway.
            \\  -i, --input, -o, --output: json, json5, yaml, toml, zon, xml, canonical,
            \\    fig. `-o xml` requires the document to convert to have exactly one
            \\    root key (see `get --help`'s `xml:` entry); xml is compiled in only
            \\    with `-Dxml=true`.
            \\
            \\  Embed-archetype mode (--to-embed): rehouse a host document's
            \\    embedded region from one archetype's fence-and-content convention
            \\    to another's — e.g. turn YAML frontmatter into JSON frontmatter —
            \\    splicing the new fences and re-serialized content in place while
            \\    leaving the surrounding prose byte-identical. The source archetype
            \\    is --embed, else sniffed from the file's own fences (falling back
            \\    to frontmatter/YAML when none is found).
            \\  --embed, --to-embed <archetype>: frontmatter (---/YAML), frontmatter-json
            \\    (;;;/JSON), frontmatter-fig (```fig fenced block), or endmatter
            \\    (trailing ```endmatter block).
            \\
            \\  -w, --write: write the converted result back to <file> in place
            \\    (skipped if it's already byte-identical) instead of printing it.
            \\  --diff: print a unified diff of the change instead of the whole
            \\    converted file. Combine with --write to write AND see what changed.
            \\  --compact / --pretty: single-line vs multi-line output (default pretty).
            \\  --indent N / --width N: as in `get`/`fmt`.
            \\  --strip-comments: drop comments instead of carrying them across formats.
            \\  --lossless / --lossy: preserve values the target can't represent
            \\    natively via a $fig envelope (default --lossy).
            \\  --lax-tags: drop unknown/custom YAML tags instead of erroring, when
            \\    converting away from YAML.
            \\  -q, --quiet: suppress warnings on stderr.
            \\  --strict: treat any warning as an error (exit non-zero, no write).
            \\  reads stdin when <file> is `-`, but only without --write.
            \\
        , .{ binary_name, binary_name });
        try term.writer.flush();
    }
};