Skip to main content

coding_tools/cli/
ct_each.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Jonathan Shook
3
4//! The `ct-each` command grammar (see [`crate::cli`]); the `ct-each` bin is a
5//! thin parse-and-dispatch wrapper over this `Cli`.
6
7use std::path::PathBuf;
8
9use clap::Parser;
10
11use crate::explain::Format;
12use crate::pattern;
13use crate::pulse::HeartbeatOpts;
14
15#[derive(Parser, Debug)]
16#[command(
17    name = "ct-each",
18    version,
19    about = "Run a command template once per item (no shell), with per-item verdicts and an aggregate --expect.",
20    long_about = "ct-each dispatches one command over a set of distinct items: {ITEM} and {INDEX} \
21                  expand inside the argv elements after `--`, each expansion is launched directly \
22                  (never through a shell), each run is classified by exit status, and the SUCCESS \
23                  count is judged against --expect (also reachable as `ct each`). See \
24                  `ct-each --explain` for agent-oriented documentation."
25)]
26pub struct Cli {
27    /// Items to dispatch over, in order (repeatable; one run per item). file:PATH expands to the file's non-empty lines; text:VALUE is one literal item.
28    #[arg(long, num_args = 1.., value_name = "ITEM")]
29    pub items: Vec<String>,
30
31    /// Pin how --name/--ext walker patterns are interpreted (promotion off): literal, glob, or regex.
32    #[arg(long, value_enum)]
33    pub mode: Option<pattern::Mode>,
34
35    /// Also read items from standard input, one per line (blank lines skipped), after any walker items.
36    #[arg(long)]
37    pub stdin: bool,
38
39    /// Walker item source: files under this root become items (paths). A file yields itself; a directory is descended.
40    #[arg(long)]
41    pub base: Option<PathBuf>,
42
43    /// Walker item source: limit to files whose name matches; '|'-separated alternatives, each substring->glob->regex promoted and anchored. Implies --base . when --base is absent.
44    #[arg(long)]
45    pub name: Option<String>,
46
47    /// Walker item source: restrict to these extensions (comma-separated, no dots). Combined with --name as alternatives. Implies --base . when --base is absent.
48    #[arg(long, value_delimiter = ',')]
49    pub ext: Vec<String>,
50
51    /// Include dot-entries while walking; default skips them.
52    #[arg(long)]
53    pub hidden: bool,
54
55    /// Follow symlinks while walking.
56    #[arg(long)]
57    pub follow: bool,
58
59    /// Question this sweep answers; printed as a "== ... ==" banner.
60    #[arg(long)]
61    pub question: Option<String>,
62
63    /// Expectation over the per-item SUCCESS count: all|any|none|N|=N|+N|-N (default: all).
64    #[arg(long)]
65    pub expect: Option<String>,
66
67    /// Stop after the first per-item ERROR; remaining items are reported as skipped.
68    #[arg(long)]
69    pub fail_fast: bool,
70
71    /// Permit the suite's mutating tools (ct-edit, ct-patch) as the command.
72    #[arg(long)]
73    pub mutating: bool,
74
75    /// Print each expanded command without running anything.
76    #[arg(long)]
77    pub dry_run: bool,
78
79    /// Per item: kill the run and classify that item ERROR after SECS seconds (fractional allowed); its {CODE} becomes "timeout".
80    #[arg(long, value_name = "SECS")]
81    pub timeout: Option<f64>,
82
83    #[command(flatten)]
84    pub heartbeat: HeartbeatOpts,
85
86    /// Per-item template written to stdout. Tokens: {RESULT} {ITEM} {INDEX} {CODE} {CMD} {STDOUT} {STDERR}. Default (unless --quiet): "{RESULT} {ITEM}".
87    #[arg(long, value_name = "TEMPLATE")]
88    pub emit_each: Option<String>,
89
90    /// Summary template written to stdout after the sweep. Tokens: {RESULT} {OK} {ERRORS} {SKIPPED} {TOTAL} {QUESTION} {EXPECT} {REASON}.
91    #[arg(long, alias = "emit-stdout", value_name = "TEMPLATE")]
92    pub emit: Option<String>,
93
94    /// Summary template written to stderr (same tokens as --emit).
95    #[arg(long, value_name = "TEMPLATE")]
96    pub emit_stderr: Option<String>,
97
98    /// Also pass each child's stdout/stderr through verbatim.
99    #[arg(long)]
100    pub show_output: bool,
101
102    /// Suppress the question banner, the default per-item lines, and the default summary.
103    #[arg(long)]
104    pub quiet: bool,
105
106    /// Emit a structured JSON result instead of text (overrides the emit templates).
107    #[arg(long)]
108    pub json: bool,
109
110    /// Print agent usage docs (md or json) and exit.
111    #[arg(long, value_enum, num_args = 0..=1, default_missing_value = "md")]
112    pub explain: Option<Format>,
113
114    /// Command and arguments run per item (after `--`); {ITEM} and {INDEX} expand in every element.
115    #[arg(last = true, value_name = "CMD [ARGS...]")]
116    pub command: Vec<String>,
117}