Command line

The same engine as a command: format Markdoc, validate it against a schema file, and render it, with exit codes a CI pipeline can act on.

The library reads no files and decides no policy; a host does. accent-proust is the host for a shell -- the command that makes a documentation repository a CI gate, and the first place {% partial %} works without writing a Rust program.

Install

The crate is a workspace member, built from the repository:

cargo install --path crates/accent-proust-cli
accent-proust --help

Commands

CommandDoesNeeds a schema file
fmtReprints Markdoc source in canonical formNo
validateReports what a schema says is wrongFor tags of your own
renderPrints HTMLFor tags of your own
transformPrints the renderable tree as JSONFor tags of your own
parsePrints the syntax tree as JSONNo

Every command reads the files named on its command line, or stdin when none is named. A file that cannot be read is reported and the run goes on, so one bad path does not hide the rest; the exit code says a path failed.

fmt

accent-proust fmt docs/*.md            # formatted source to stdout
accent-proust fmt --check docs/*.md    # a unified diff per file that would change
accent-proust fmt --write docs/*.md    # rewrite in place
cat page.md | accent-proust fmt        # stdin to stdout

Spacing inside a tag is normalised; your own spellings are left alone, so __bold__ stays __bold__. parse(format(ast)) returns the same tree, so formatting loses nothing, and fmt reformats its own output until it stops changing -- refusing, with exit 2, a document still changing after four passes -- so write-then-check is clean. --check prints a diff rather than a list, because a CI log reader wants to know what is wrong, not only where. Line endings are written as LF; a CRLF file is named by --check and rewritten by --write.

validate

accent-proust validate --config schema.yaml docs/*.md
accent-proust validate --config schema.yaml --format json docs/page.md

One line per error, path:line:column: level[id]: message, lines and columns counted from one and the column in characters, as an editor shows it. The error ids are upstream Markdoc's, so tooling written against its codes reads them unchanged.

--format json prints one object per input, one per line, carrying file and its errors in the shape the JavaScript bindings return, positions included -- character and offset in UTF-16 code units, byteOffset in bytes -- so a consumer written against either host reads the other.

Exit 1 means an error at level error or critical. A warning, info or debug is printed and does not fail the run: that is how a schema ships a rule it wants surfaced but not enforced yet.

render, transform and parse

render prints HTML, inputs concatenated in order. transform prints the renderable tree as JSON, one array per input, one per line, in the shape upstream's renderers expect -- a tag is {"$$mdtype": "Tag", "name", "attributes", "children"}. parse prints the syntax tree the same way, every node in the field order of upstream's Node class, so the output is what JSON.stringify(Markdoc.parse(source)) gives, with two additions upstream does not make: positions in the bindings' shape, and a file label on every location. One value per line rather than one array for the run, because that composes with jq.

The schema file

accent-proust render \
  --config schema.yaml \
  --partials docs/partials \
  --var version=3 --var channel=stable \
  docs/page.md

--config is a YAML or JSON file declaring tags, nodes and variables, in the vocabulary accent-proust-schema-config defines -- which keys a declaration may carry, what each means, and the refusal of an unknown one with the path to it -- and the JavaScript bindings' Config reads from an object. A schema declared for one host is accepted by the other; a JSON file is the one spelling both read as it stands, the shell from disk and the browser through JSON.parse:

tags:
  callout:
    render: aside
    attributes:
      type:
        type: String
        required: true
        matches: [note, warning]
variables:
  channel: stable

An unknown key is refused with the path to it -- config.tags.callout.validate, not "invalid schema". A hook cannot be written in a file: transform and validate are code, and the refusal says to keep the hook in a Rust host.

--partials DIR reads every UTF-8 text file under the directory, at any depth, and {% partial file="sections/intro.md" /%} finds it by that path. This is the thing the browser cannot do, and the reason a command-line host exists.

--var NAME=VALUE declares a variable and overrides one the file declared. VALUE is read as YAML by the same reader as the file, so the two can never disagree about what 3 is:

WrittenBecomes
--var count=3the number 3
--var debug=truethe boolean true
--var name=productionthe string production
--var missing=null, --var missing=null
--var 'version="3"'the string 3
--var 'tags=[a, b]'a list of two strings

Exit codes

CodeMeans
0Success; for fmt --check, nothing would change
1A document has a problem: a file that would change, an error at level error or critical
2A usage error, a file that could not be read or written, a schema file that does not declare, or a document the formatter does not settle on

1 and 2 are kept apart so that CI can tell "the docs are wrong" from "the tool is misconfigured". They are different alerts.

Reference

The crate's README is the full reference, and accent-proust <command> --help prints every flag.