Skip to main content

Crate ctl_core

Crate ctl_core 

Source
Expand description

A framework for command-line programs built on clap. A library, not a command.

A program declares its arguments with clap’s derive macros and writes a handler that returns a typed result. ctl-core parses the arguments, prints help, and writes the result as terminal text or as JSON. It also supplies the shared flags, colour policy, error output, and exit codes. One serializable result feeds the pretty, colourless, and JSON output.

§Quick start

The derive macros expand to paths in clap and serde, so a program depends on both itself, each with its derive feature:

cargo add ctl-core
cargo add clap --no-default-features --features std,derive
cargo add serde --features derive
use ctl_core::prelude::*;
use serde::Serialize;

#[derive(Parser)]
#[command(version, about = "example")]
struct Cli {
    #[command(flatten)]
    output: OutputArgs,
    #[command(subcommand)]
    command: Command,
}

#[derive(Subcommand)]
enum Command {
    /// Show status.
    Status,
}

#[derive(Serialize)]
struct Status {
    pending: usize,
}

impl Present for Status {
    fn present(&self) -> Document {
        Document::new().fields(Fields::new().row("pending", self.pending.to_string()))
    }
}

fn main() -> ExitCode {
    App::<Cli>::new("example")
        .view(|cli| cli.output.view())
        .run(|cli| match cli.command {
            Command::Status => Ok(Status { pending: 0 }),
        })
}

The default app feature covers this example.

§Where to start

§How output flows

Clap types ──→ App ──→ typed domain result ──┬──→ JSON
                                             └──→ Document ──→ pretty/colourless
  • Domain handlers return data. They do not print, read terminal state, or choose the output format.
  • Present maps a serializable result to a semantic Document.
  • View serializes the result for JSON and renders its document for pretty and colourless output.
  • JSON always goes to stdout and never contains ANSI. Human failures go to stderr.
  • A reader that closes stdout early, such as head -1, does not change the exit code. A command that succeeded exits 0, and a failure keeps its code. App::broken_pipe can make every closed pipe a failure.

prelude is the one ctl-core import a program’s main.rs needs.

§The rendering engine

A Document renders through three companion crates, which a CLI can also use directly through the re-exports text, layout, and term (term with view or help):

  • ctl-text: styled spans, lines, and text, with widths counted in terminal cells, wrapping, and truncation. Text from outside the program loses its control characters when a span is built.
  • ctl-layout: the widgets a document becomes, such as tables, records, and rules. They lay out plain lines of styled spans and never see an escape sequence.
  • ctl-term: whether to write colour and how wide to lay out, decided for the stream being written. Its Encoder is the one place an escape sequence is written.

A Theme maps each Role to a style on the way from the document to the widgets. document::Text holds roles and text::Text holds styles.

§Cargo features

Features are additive. Unused ones do not compile clap, schemars, or the rendering crates. document builds documents with no engine. render adds ctl-text, ctl-layout, and ctl-term’s encoder, and renders to a String without reading any terminal. help and view turn on ctl-term’s detect feature, which reads the stream’s width and decides colour for it.

Declared input takes one feature per format. input-yaml reads YAML and input-toml reads TOML; neither links the other’s parser. validate adds garde validation, and input is input-yaml with validate.

  • app (enabled by default) — Complete typed CLI lifecycle over the shared help and view layers.
  • cli (enabled by default) — Clap derive helpers (OutputArgs, DryRunArgs) and parser defaults. Turns on only the Clap features ctl-core calls: std, derive, env, and string. ctl-core declares -h/--help and the help subcommand itself and renders the help, so Clap’s help feature stays off. A plain Cli::parse() outside ctl-core’s entry point gets Clap’s minimal output and no help subcommand. Clap’s own error-context, suggestions, help, and color features stay the CLI’s choice.
  • document (enabled by default) — Semantic presentation documents without a terminal engine.
  • json (enabled by default) — Serde models and the JSON view.
  • render (enabled by default) — Rendering semantic documents to a String through ctl-text and ctl-layout, with a replaceable Theme, written by the ctl-term encoder. No terminal is asked: the width is explicit or the fallback, and nothing is detected.
  • unsafe-chars (enabled by default) — Which characters are unsafe to print as they are (unsafe_chars::is_unsafe), re-exported from ctl-text.
  • schema — JSON Schema for the envelope and enums (schemars).
  • help (enabled by default) — Styled -h/--help through the shared document renderer. Adds Clap’s usage feature for the usage line, and ctl-term’s detect feature, which decides colour and width for the stream help goes to.
  • view (enabled by default) — Pretty, colourless, and JSON output from serializable results. Adds ctl-term’s detect feature, which decides colour and width for the stream each result goes to.
  • surface-model — A CLI’s commands and flags as data (surface::Surface), without serialization or a template engine.
  • surface-serde — Surface with serialization, without a template engine.
  • surface-templates — Shared MiniJinja fragments for generated documentation files.
  • surface — The same as surface-templates.
  • served — Files a CLI generates into a repository from templates, each template declaring its own target path and mode (served::render, Served::write).
  • validate — Garde rules every declared schema shares (validate).
  • input — Declared input from YAML, validated once with garde: input-yaml and validate.
  • input-yaml — Declared input from YAML: parse with serde-saphyr and place every problem on its file and line through yamled. Adds the YAML-only Limits fields and the repository’s .ctl/input.yaml. Needs no garde without validate.
  • input-toml — Declared input from TOML: Input::open reads .toml files, and the toml crate’s span-carrying tree places each problem on its line and column. Needs no YAML crate, and no garde without validate.
  • fixture — Temporary file trees for integration tests: Suite, its scenarios, and one call per file.
  • shell — The shells a CLI writes scripts for (Shell): bash, zsh, and fish.
  • completion — The completion <SHELL> verb and the COMPLETE request it registers (CompletionCommand, App::completion), through clap_complete’s dynamic engine. Enables app, which App::completion lives on.
  • shell-init — The init <SHELL> verb: a script that runs one command of the CLI once, before the shell’s first prompt (InitCommand, Hook).
  • usage — Mise Usage spec from clap (--usage-spec and the #USAGE mount line). Adds Clap’s usage feature, which renders the spec’s usage line.

Re-exports§

pub use ctl_layout as layout;render
pub use ctl_term as term;help or view
pub use ctl_text as text;render

Modules§

appapp
The typed CLI lifecycle: App parses argv, runs a handler, and writes the result through a View.
color
The colour policy for pretty output: auto, always, or never.
completioncompletion
Shell completion: one completion <SHELL> verb for a CLI.
documentdocument
The semantic document a command’s human output is built from.
fixturefixture
Files for integration tests, written through one call instead of fs::create_dir_all, fs::write, and an expect per file.
flagscli
The flags every CLI built on ctl-core shares, as Clap structs to flatten into a parser.
format
The output representation: pretty for people, or json for machines.
helphelp
Styled help extracted from Clap and rendered as a semantic document.
inputinput-toml or input-yaml
Declared input: read a file, parse it, validate it once, and report every problem on its file and line.
model
The JSON envelope types. A view serializes a result as is. The envelope wraps a success or a failure for a reader that wants a status and a schema version: Envelope::Ok holds a result, and Envelope::Err holds an ErrorBody.
parsercli
The parser rules every CLI built on ctl-core keeps: -h / --help, the help subcommand, and -V / --version stay on, and a repeated flag takes its last value.
prelude
One import for a program’s main.rs.
renderrender
Render semantic documents through the ctl-layout widgets.
servedserved
Files a CLI generates into a repository from templates, each template declaring its own target path and mode.
shellshell
The shells a CLI writes scripts for, and the script a shell verb prints.
shell_initshell-init
Shell integration: one init <SHELL> verb that hooks a CLI into the user’s shell.
surfacesurface-model
A CLI’s commands and flags as plain data, taken from its Clap types.
unsafe_charsunsafe-chars
Which characters are unsafe to print to a terminal as they are, and the function that removes them.
usageusage
Mise Usage spec from a clap Command.
validatevalidate
Garde rules every declared schema shares. A repository’s config and a template’s own exports are held to the same rules and report the same messages.
viewview
Pretty, colourless, and JSON output from one typed model.

Structs§

Themerender
Maps each Role, and the label of each NoticeLevel, to an anstyle::Style.