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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
use crate::cli::depth::RecurseDepth;
use crate::cli::file::FileSet;
use crate::cli::order::{OrderKind, OrderVec};
use crate::cli::recent::RecentKind;
use crate::git::flags::GitFlags;
use chrono::{DateTime, Utc};
use clap::{ArgAction, Parser};
use clap_builder::ColorChoice;
use clap_complete::Shell;
use indexmap::IndexSet;
#[derive(Default, Parser)]
#[command(name = "ex", version, about, author)]
pub struct Cli {
/// Find files in subdirectories
#[arg(long = "recurse", short = 's')]
pub recurse_all: bool,
/// Find files to maximum depth M-N
///
/// Use "-d4" or "-d-4" to find files up to depth 4
/// Use "-d2-4" to find files at depth 2, 3 or 4
/// Use "-d2-" to find files at depth 2 and beyond
#[arg(
long = "depth",
short = 'd',
value_name = "DEPTH",
value_parser = RecurseDepth::from_str,
verbatim_doc_comment,
)]
pub recurse_depth: Option<RecurseDepth>,
/// Indent files in subdirectories
#[arg(long = "indent", short = 'i')]
pub show_indent: bool,
/// Show .* and __*__ files (twice to recurse)
///
/// Use "-a" to show hidden files and directories
/// Use "-aa" to recurse into hidden directories
/// Include Unix hidden files like ".bashrc"
/// Include Python cache directories "__pycache__"
#[arg(
long = "all-files",
short = 'a',
action = ArgAction::Count,
verbatim_doc_comment,
)]
pub show_hidden: u8,
/// Expand zip files
///
/// Show contents of *.zip, *.7z, *.tar, *.tar.gz
#[arg(long = "zip", short = 'z')]
pub zip_expand: bool,
/// Use zip password
///
/// Use with caution; may be stored in shell command history
#[arg(long = "password", value_name = "PASSWORD")]
pub zip_password: Option<String>,
/// Force case sensitive match
///
/// Use this option to override default Windows filename matching
#[arg(long = "case", overrides_with = "case_insensitive")]
pub case_sensitive: bool,
/// Force case insensitive match
///
/// Use this option to override default Linux filename matching
#[arg(long = "no-case")]
pub case_insensitive: bool,
/// Sort files by order [dnest][+-]...
///
/// Use "-od" to sort files by directory
/// Use "-on" to sort files by filename
/// Use "-oe" to sort files by extension
/// Use "-os" to sort files by size (increasing)
/// Use "-os-" to sort files by size (decreasing)
/// Use "-ot" to sort files by time (increasing)
/// Use "-ot-" to sort files by time (decreasing)
/// Use "-oest" to sort files by extension then size then time
#[arg(
long = "order",
short = 'o',
value_name = "ORDER",
value_parser = OrderVec::from_str,
verbatim_doc_comment,
)]
pub sort_order: Option<OrderVec>,
/// Include recent files [ymwdHMS][N]...
///
/// Use "-ry10" to include files up to ten years old
/// Use "-rm6" to include files up to six months old
/// Use "-rw2" to include files up to two weeks old
/// Use "-rd" to include files up to one day old
/// Use "-rH" to include files up to one hour old
/// Use "-rM5" to include files up to five minutes old
/// Use "-rS10" to include files up to ten seconds old
#[arg(
long = "recent",
short = 'r',
value_name = "RECENT",
value_parser = RecentKind::from_str,
verbatim_doc_comment,
)]
pub filter_recent: Option<RecentKind>,
/// Include files by type [fedl]...
///
/// Use "-tf" to include files
/// Use "-te" to include executables
/// Use "-td" to include directories
/// Use "-tl" to include links
#[arg(
long = "type",
short = 't',
value_name = "TYPE",
value_parser = FileSet::from_str,
verbatim_doc_comment,
)]
pub filter_types: Option<FileSet>,
/// Include files by Git status [xcamrui]...
///
/// Use "-gx" to include all files (with untracked and ignored)
/// Use "-gc" to include cached files (not untracked or ignored)
/// Use "-ga" to include only added files
/// Use "-gm" to include only modified files
/// Use "-gr" to include only renamed files
/// Use "-gu" to include only untracked files
/// Use "-gi" to include only ignored files (according to .gitignore)
#[arg(
long = "git",
short = 'g',
value_name = "GIT",
value_parser = GitFlags::from_str,
verbatim_doc_comment,
)]
pub filter_git: Option<GitFlags>,
/// Show debug information
#[cfg(debug_assertions)]
#[arg(long = "debug")]
pub show_debug: bool,
/// Show precise file size and time
#[arg(long = "precise", short = 'p')]
pub show_precise: bool,
/// Show UTC file time
#[arg(long = "utc", short = 'u')]
pub show_utc: bool,
/// Show total file size and number of files and directories
#[arg(long = "total")]
pub show_total: bool,
/// Show file owner
///
/// Show user and group names on Linux
#[cfg(unix)]
#[arg(long = "owner")]
pub show_owner: bool,
/// Show cyclic redundancy check
///
/// The CRC can be used to quickly compare files
#[arg(long = "crc")]
pub show_crc: bool,
/// Show file signature
///
/// The first four bytes can be used to identify some file types
#[arg(long = "sig")]
pub show_sig: bool,
/// Show paths only (repeat to show all attributes)
///
/// Use "-x" to show paths only
/// Use "-xx" to show all attributes
/// By default show all attributes when writing to the console
/// By default show escaped paths when writing to a file
#[arg(
long = "only-path",
short = 'x',
action = ArgAction::Count,
verbatim_doc_comment,
)]
pub only_path: u8,
/// Show paths only (with null separator for xargs)
#[arg(long = "null-path")]
pub null_path: bool,
/// Show absolute paths
#[arg(long = "abs-path", short = 'q')]
pub abs_path: bool,
/// Show Windows paths
///
/// For example "C:\\Path\\file.txt" not "/c/Path/file.txt" in Git Bash
#[cfg(windows)]
#[arg(long = "win-path", short = 'w')]
pub win_path: bool,
/// Show Windows versions
///
/// Applies to *.exe and *.dll only
#[cfg(windows)]
#[arg(long = "win-ver", short = 'v')]
pub win_ver: bool,
/// Set current time for readme examples
///
/// For example "2024-01-01T00:00:00Z"
#[cfg(debug_assertions)]
#[arg(long = "now", value_name = "TIME")]
pub curr_time: Option<DateTime<Utc>>,
/// Force terminal output for readme examples
///
/// Disables piped output
#[cfg(debug_assertions)]
#[arg(long = "terminal")]
pub terminal: bool,
/// Show colored output (default "auto").
///
/// Use "--color auto" to show color for terminal output only
/// Use "--color always" to always show color
/// Use "--color never" to never show color
#[arg(
long = "color",
value_name = "COLOR",
hide_possible_values = true,
verbatim_doc_comment,
)]
pub color: Option<ColorChoice>,
/// Hide non-fatal (e.g. permission denied) errors
#[arg(long = "quiet")]
pub quiet: bool,
/// Create completion script
///
/// Use "--completion bash" to create script for Bash
/// Use "--completion elvish" to create script for Elvish
/// Use "--completion fish" to create script for Fish
/// Use "--completion powershell" to create script for PowerShell
/// Use "--completion zsh" to create script for Zsh
#[arg(
long = "completion",
value_name = "SHELL",
hide_possible_values = true,
verbatim_doc_comment,
)]
pub completion: Option<Shell>,
/// File matching patterns
#[arg(default_value = ".")]
pub patterns: Vec<String>,
}
impl Cli {
#[cfg(debug_assertions)]
pub fn curr_time<G: Fn() -> DateTime<Utc>>(&self, factory: G) -> DateTime<Utc> {
self.curr_time.unwrap_or_else(factory)
}
#[cfg(debug_assertions)]
pub fn terminal(&self) -> bool {
self.terminal
}
#[cfg(not(debug_assertions))]
pub fn curr_time<G: Fn() -> DateTime<Utc>>(&self, factory: G) -> DateTime<Utc> {
factory()
}
#[cfg(not(debug_assertions))]
pub fn terminal(&self) -> bool {
false
}
pub fn min_depth(&self) -> Option<usize> {
if self.recurse_all {
Some(1)
} else if let Some(depth) = &self.recurse_depth {
depth.min_depth
} else {
Some(1)
}
}
pub fn max_depth(&self) -> Option<usize> {
if self.recurse_all {
None
} else if let Some(depth) = &self.recurse_depth {
depth.max_depth
} else {
Some(1)
}
}
pub fn sort_order(&mut self) -> (Vec<OrderKind>, bool) {
let mut sort_order = IndexSet::new();
if self.show_indent {
sort_order.insert(OrderKind::Dir);
}
if let Some(order) = &self.sort_order {
for order in &order.inner {
sort_order.insert(*order);
}
}
let sort_name = sort_order.contains(&OrderKind::Name);
if sort_order.is_empty() {
if let Some(max_depth) = self.max_depth() {
if max_depth <= 1 {
sort_order.insert(OrderKind::Group);
}
}
}
sort_order.insert(OrderKind::Dir);
sort_order.insert(OrderKind::Name);
let sort_order = sort_order.into_iter().collect();
(sort_order, sort_name)
}
}