1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
// SPDX-License-Identifier: Apache-2.0
// Copyright 2026 Jonathan Shook
//! The `ct-tree` command grammar (see [`crate::cli`]); the `ct-tree` bin is a
//! thin parse-and-dispatch wrapper over this `Cli`.
use std::path::PathBuf;
use clap::Parser;
use crate::explain::Format;
use crate::pattern;
use crate::pulse::HeartbeatOpts;
#[derive(Parser, Debug)]
#[command(
name = "ct-tree",
version,
about = "Report a file tree with per-file line/word/char counts, filtered, sorted, and summarised.",
long_about = "ct-tree walks a directory for chosen file types and reports the effective tree with \
per-file line, word, and character counts (also reachable as `ct tree`). Filter by \
metric predicates (--min-lines etc.) and per-folder counts, sort by any column, and \
choose a summarisation level (--tree, --flat, --summary). See `ct-tree --explain` \
for agent-oriented documentation."
)]
#[command(group = clap::ArgGroup::new("output_mode")
.args(["tree", "flat", "summary"])
.multiple(false))]
pub struct Cli {
/// Root to walk (relative or absolute), independent of the current directory.
#[arg(long, default_value = ".")]
pub base: PathBuf,
/// File-name pattern; '|'-separated alternatives, each substring->glob->regex promoted and anchored.
#[arg(long)]
pub name: Option<String>,
/// Pin how --name/--ext patterns are interpreted (promotion off): literal, glob, or regex.
#[arg(long, value_enum)]
pub mode: Option<pattern::Mode>,
/// Restrict to these extensions (comma-separated, no dots), e.g. --ext rs,toml. Combined with --name as alternatives.
#[arg(long, value_delimiter = ',')]
pub ext: Vec<String>,
/// Include dot-entries (names starting with '.'); default skips them.
#[arg(long)]
pub hidden: bool,
/// Follow symlinks while traversing.
#[arg(long)]
pub follow: bool,
/// Walk gitignored / .ignore files too (the .git directory is always skipped); by default the walk skips what git would.
#[arg(long)]
pub no_ignore: bool,
/// Only include files with at least N lines.
#[arg(long)]
pub min_lines: Option<u64>,
/// Only include files with at most N lines.
#[arg(long)]
pub max_lines: Option<u64>,
/// Only include files with at least N words.
#[arg(long)]
pub min_words: Option<u64>,
/// Only include files with at most N words.
#[arg(long)]
pub max_words: Option<u64>,
/// Only include files with at least N characters.
#[arg(long)]
pub min_chars: Option<u64>,
/// Only include files with at most N characters.
#[arg(long)]
pub max_chars: Option<u64>,
/// Only include files of at least N bytes.
#[arg(long)]
pub min_bytes: Option<u64>,
/// Only include files of at most N bytes.
#[arg(long)]
pub max_bytes: Option<u64>,
/// Only include folders that directly contain at least N matching files.
#[arg(long)]
pub min_files_per_folder: Option<usize>,
/// Only include folders that directly contain at most N matching files.
#[arg(long)]
pub max_files_per_folder: Option<usize>,
/// Sort key: path, name, lines, words, chars, bytes, ext, or okf-type.
#[arg(long, value_enum, default_value_t = SortKey::Path)]
pub sort: SortKey,
/// Sort descending instead of ascending.
#[arg(long)]
pub desc: bool,
/// Output mode: an indented file tree with per-file and per-folder counts (default).
#[arg(long)]
pub tree: bool,
/// Output mode: one matching file per line with its counts.
#[arg(long)]
pub flat: bool,
/// Output mode: aggregate counts only, grouped by --group.
#[arg(long)]
pub summary: bool,
/// Add a byte-size column to the report (also enabled by --sort bytes).
#[arg(long)]
pub bytes: bool,
/// Grouping for --summary: ext, dir, okf-type, or none (grand total only).
#[arg(long, value_enum, default_value_t = GroupBy::Ext)]
pub group: GroupBy,
/// Emit a structured JSON result instead of text.
#[arg(long)]
pub json: bool,
/// Like `--json`, but pretty-printed (indented).
#[arg(long)]
pub json_pretty: bool,
/// Abort with exit 2 if the report exceeds SECS seconds (fractional allowed).
#[arg(long, value_name = "SECS")]
pub timeout: Option<f64>,
#[command(flatten)]
pub heartbeat: HeartbeatOpts,
/// Print agent usage docs (md or json) and exit.
#[arg(long, value_enum, num_args = 0..=1, default_missing_value = "md")]
pub explain: Option<Format>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
pub enum SortKey {
Path,
Name,
Lines,
Words,
Chars,
Bytes,
Ext,
/// OKF frontmatter `type` (files without one sort first).
OkfType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
pub enum GroupBy {
Ext,
Dir,
None,
/// OKF frontmatter `type` (files without one group under "(none)").
OkfType,
}