coding_tools/cli/ct_search.rs
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Jonathan Shook
3
4//! The `ct-search` command grammar (see [`crate::cli`]); the `ct-search` bin is
5//! 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;
14use crate::walk::EntryType;
15
16#[derive(Parser, Debug)]
17#[command(
18 name = "ct-search",
19 version,
20 about = "Recursively find files by name, type, size, and content from a chosen root.",
21 long_about = "ct-search combines the predicates you would otherwise assemble from find, xargs, \
22 and grep into one declarative command (also reachable as `ct search`). An entry \
23 matches only when every supplied predicate holds. See `ct-search --explain` for \
24 agent-oriented documentation."
25)]
26#[command(group = clap::ArgGroup::new("output_mode")
27 .args(["list", "summary", "detail", "quiet"])
28 .multiple(false))]
29pub struct Cli {
30 /// Search root (relative or absolute), independent of the current directory.
31 #[arg(long, default_value = ".")]
32 pub base: PathBuf,
33
34 /// File-name pattern; '|'-separated alternatives, each substring->glob->regex promoted and anchored to the whole name.
35 #[arg(long)]
36 pub name: Option<String>,
37
38 /// Restrict to entry kinds: f=file, d=dir, l=symlink (repeatable or comma-joined).
39 #[arg(long, value_enum, value_delimiter = ',')]
40 pub r#type: Vec<EntryType>,
41
42 /// Content pattern (substring->glob->regex promoted); searches file contents. Accepts file:PATH / text:VALUE; a multi-line pattern matches as a line-anchored literal block.
43 #[arg(long)]
44 pub grep: Option<String>,
45
46 /// Pin how patterns are interpreted (promotion off): literal, glob, or regex.
47 #[arg(long, value_enum)]
48 pub mode: Option<pattern::Mode>,
49
50 /// OKF: keep only Markdown concepts whose frontmatter `type` matches this pattern (substring->glob->regex promoted).
51 #[arg(long)]
52 pub okf_type: Option<String>,
53
54 /// OKF: keep only Markdown concepts whose frontmatter carries all these tags (comma-joined or repeated).
55 #[arg(long, value_delimiter = ',')]
56 pub okf_tag: Vec<String>,
57
58 /// Size predicate [+|-]N[k|m|g]: +N larger than, -N smaller than, N at least N.
59 #[arg(long)]
60 pub size: Option<String>,
61
62 /// Include dot-entries (names starting with '.'); default skips them.
63 #[arg(long)]
64 pub hidden: bool,
65
66 /// Follow symlinks while traversing.
67 #[arg(long)]
68 pub follow: bool,
69
70 /// Walk gitignored / .ignore files too (the .git directory is always skipped); by default the walk skips what git would.
71 #[arg(long)]
72 pub no_ignore: bool,
73
74 /// Stop after N matches.
75 #[arg(long)]
76 pub limit: Option<usize>,
77
78 /// Abort with exit 2 if the search exceeds SECS seconds (fractional allowed).
79 #[arg(long, value_name = "SECS")]
80 pub timeout: Option<f64>,
81
82 #[command(flatten)]
83 pub heartbeat: HeartbeatOpts,
84
85 /// Question this search answers, framing it as a test; printed as a "== ... ==" banner unless --quiet.
86 #[arg(long)]
87 pub question: Option<String>,
88
89 /// Verdict expectation over the match count: any|none|N|=N|+N|-N (default: any). Turns the search into a pass/fail test whose exit status follows the verdict.
90 #[arg(long)]
91 pub expect: Option<String>,
92
93 /// Template written to stdout after the search. Tokens: {RESULT} {QUESTION} {COUNT} {LINES} {BASE} {MATCHES}.
94 #[arg(long, alias = "emit-stdout")]
95 pub emit: Option<String>,
96
97 /// Template written to stderr after the search (same tokens as --emit).
98 #[arg(long)]
99 pub emit_stderr: Option<String>,
100
101 /// Output mode: print one matching path per line (default).
102 #[arg(long)]
103 pub list: bool,
104
105 /// Output mode: print counts only.
106 #[arg(long)]
107 pub summary: bool,
108
109 /// Output mode: print matches plus, for --grep, each hit as path:line:text.
110 #[arg(long)]
111 pub detail: bool,
112
113 /// Output mode: print nothing; report via exit status only.
114 #[arg(long)]
115 pub quiet: bool,
116
117 /// Emit a structured JSON result instead of text (overrides the output mode and --emit).
118 #[arg(long)]
119 pub json: bool,
120
121 /// Like `--json`, but pretty-printed (indented).
122 #[arg(long)]
123 pub json_pretty: bool,
124
125 /// Print agent usage docs (md or json) and exit.
126 #[arg(long, value_enum, num_args = 0..=1, default_missing_value = "md")]
127 pub explain: Option<Format>,
128}