Skip to main content

coding_tools/cli/
ct_outline.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Jonathan Shook
3
4//! The `ct-outline` command grammar (see [`crate::cli`]); the `ct-outline` bin
5//! is a 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-outline",
18    version,
19    about = "Report the declarations in a file or tree: kind, name, start:end span, and nesting.",
20    long_about = "ct-outline detects declarations heuristically per language (Rust, Python, Markdown) \
21                  and reports each with its kind, name, and 1-based start:end line span (also \
22                  reachable as `ct outline`) — locate a symbol, then read exactly that region with \
23                  ct-view --range. Start lines are exact; an underivable end renders as start:?. \
24                  See `ct-outline --explain` for agent-oriented documentation."
25)]
26pub struct Cli {
27    /// Root to outline; a file outlines just that file, a directory is descended.
28    #[arg(long, default_value = ".")]
29    pub base: PathBuf,
30
31    /// Limit to files whose name matches; '|'-separated alternatives, each substring->glob->regex promoted and anchored.
32    #[arg(long)]
33    pub name: Option<String>,
34
35    /// Restrict to these extensions (comma-separated, no dots), e.g. --ext rs,py. Combined with --name as alternatives.
36    #[arg(long, value_delimiter = ',')]
37    pub ext: Vec<String>,
38
39    /// Include dot-entries (names starting with '.'); default skips them.
40    #[arg(long)]
41    pub hidden: bool,
42
43    /// Follow symlinks while traversing.
44    #[arg(long)]
45    pub follow: bool,
46
47    /// Keep entries whose name matches (substring->glob->regex promoted, anchored to the whole declaration name).
48    #[arg(long = "match")]
49    pub pattern: Option<String>,
50
51    /// Pin how --match/--name patterns are interpreted (promotion off): literal, glob, or regex.
52    #[arg(long, value_enum)]
53    pub mode: Option<pattern::Mode>,
54
55    /// Keep entries of these kinds (comma-separated), e.g. --kind fn,struct. Kinds are per-language keywords.
56    #[arg(long, value_delimiter = ',')]
57    pub kind: Vec<String>,
58
59    /// Keep entries nested at most N levels deep (1 = top-level only).
60    #[arg(long)]
61    pub depth: Option<usize>,
62
63    /// Output one grep-friendly row per matched entry: path:start:end:kind:name.
64    #[arg(long)]
65    pub flat: bool,
66
67    /// Question this outline answers, framing it as a test; printed as a "== ... ==" banner unless --quiet.
68    #[arg(long)]
69    pub question: Option<String>,
70
71    /// Verdict expectation over the matched-entry count: any|none|N|=N|+N|-N (default: any).
72    #[arg(long)]
73    pub expect: Option<String>,
74
75    /// Template written to stdout after the outline. Tokens: {RESULT} {QUESTION} {COUNT} {BASE} {MATCHES}.
76    #[arg(long, alias = "emit-stdout")]
77    pub emit: Option<String>,
78
79    /// Template written to stderr after the outline (same tokens as --emit).
80    #[arg(long)]
81    pub emit_stderr: Option<String>,
82
83    /// Print nothing; report via exit status (and --emit, which still fires).
84    #[arg(long)]
85    pub quiet: bool,
86
87    /// Emit a structured JSON result instead of text (overrides the text modes and --emit).
88    #[arg(long)]
89    pub json: bool,
90
91    /// Abort with exit 2 if the run exceeds SECS seconds (fractional allowed).
92    #[arg(long, value_name = "SECS")]
93    pub timeout: Option<f64>,
94
95    #[command(flatten)]
96    pub heartbeat: HeartbeatOpts,
97
98    /// Print agent usage docs (md or json) and exit.
99    #[arg(long, value_enum, num_args = 0..=1, default_missing_value = "md")]
100    pub explain: Option<Format>,
101}