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    /// Size predicate [+|-]N[k|m|g]: +N larger than, -N smaller than, N at least N.
51    #[arg(long)]
52    pub size: Option<String>,
53
54    /// Include dot-entries (names starting with '.'); default skips them.
55    #[arg(long)]
56    pub hidden: bool,
57
58    /// Follow symlinks while traversing.
59    #[arg(long)]
60    pub follow: bool,
61
62    /// Walk gitignored / .ignore files too (the .git directory is always skipped); by default the walk skips what git would.
63    #[arg(long)]
64    pub no_ignore: bool,
65
66    /// Stop after N matches.
67    #[arg(long)]
68    pub limit: Option<usize>,
69
70    /// Abort with exit 2 if the search exceeds SECS seconds (fractional allowed).
71    #[arg(long, value_name = "SECS")]
72    pub timeout: Option<f64>,
73
74    #[command(flatten)]
75    pub heartbeat: HeartbeatOpts,
76
77    /// Question this search answers, framing it as a test; printed as a "== ... ==" banner unless --quiet.
78    #[arg(long)]
79    pub question: Option<String>,
80
81    /// 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.
82    #[arg(long)]
83    pub expect: Option<String>,
84
85    /// Template written to stdout after the search. Tokens: {RESULT} {QUESTION} {COUNT} {LINES} {BASE} {MATCHES}.
86    #[arg(long, alias = "emit-stdout")]
87    pub emit: Option<String>,
88
89    /// Template written to stderr after the search (same tokens as --emit).
90    #[arg(long)]
91    pub emit_stderr: Option<String>,
92
93    /// Output mode: print one matching path per line (default).
94    #[arg(long)]
95    pub list: bool,
96
97    /// Output mode: print counts only.
98    #[arg(long)]
99    pub summary: bool,
100
101    /// Output mode: print matches plus, for --grep, each hit as path:line:text.
102    #[arg(long)]
103    pub detail: bool,
104
105    /// Output mode: print nothing; report via exit status only.
106    #[arg(long)]
107    pub quiet: bool,
108
109    /// Emit a structured JSON result instead of text (overrides the output mode and --emit).
110    #[arg(long)]
111    pub json: bool,
112
113    /// Like `--json`, but pretty-printed (indented).
114    #[arg(long)]
115    pub json_pretty: bool,
116
117    /// Print agent usage docs (md or json) and exit.
118    #[arg(long, value_enum, num_args = 0..=1, default_missing_value = "md")]
119    pub explain: Option<Format>,
120}