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 /// Walk gitignored / .ignore files too (the .git directory is always skipped); by default the walk skips what git would.
48 #[arg(long)]
49 pub no_ignore: bool,
50
51 /// Keep entries whose name matches (substring->glob->regex promoted, anchored to the whole declaration name).
52 #[arg(long = "match")]
53 pub pattern: Option<String>,
54
55 /// Pin how --match/--name patterns are interpreted (promotion off): literal, glob, or regex.
56 #[arg(long, value_enum)]
57 pub mode: Option<pattern::Mode>,
58
59 /// Keep entries of these kinds (comma-separated), e.g. --kind fn,struct. Kinds are per-language keywords.
60 #[arg(long, value_delimiter = ',')]
61 pub kind: Vec<String>,
62
63 /// Keep entries nested at most N levels deep (1 = top-level only).
64 #[arg(long)]
65 pub depth: Option<usize>,
66
67 /// Output one grep-friendly row per matched entry: path:start:end:kind:name.
68 #[arg(long)]
69 pub flat: bool,
70
71 /// Question this outline answers, framing it as a test; printed as a "== ... ==" banner unless --quiet.
72 #[arg(long)]
73 pub question: Option<String>,
74
75 /// Verdict expectation over the matched-entry count: any|none|N|=N|+N|-N (default: any).
76 #[arg(long)]
77 pub expect: Option<String>,
78
79 /// Template written to stdout after the outline. Tokens: {RESULT} {QUESTION} {COUNT} {BASE} {MATCHES}.
80 #[arg(long, alias = "emit-stdout")]
81 pub emit: Option<String>,
82
83 /// Template written to stderr after the outline (same tokens as --emit).
84 #[arg(long)]
85 pub emit_stderr: Option<String>,
86
87 /// Print nothing; report via exit status (and --emit, which still fires).
88 #[arg(long)]
89 pub quiet: bool,
90
91 /// Emit a structured JSON result instead of text (overrides the text modes and --emit).
92 #[arg(long)]
93 pub json: bool,
94
95 /// Abort with exit 2 if the run exceeds SECS seconds (fractional allowed).
96 #[arg(long, value_name = "SECS")]
97 pub timeout: Option<f64>,
98
99 #[command(flatten)]
100 pub heartbeat: HeartbeatOpts,
101
102 /// Print agent usage docs (md or json) and exit.
103 #[arg(long, value_enum, num_args = 0..=1, default_missing_value = "md")]
104 pub explain: Option<Format>,
105}