Skip to main content

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}