Skip to main content

f00_cli/
cli.rs

1use std::path::PathBuf;
2
3use clap::{ArgAction, Parser, ValueEnum};
4
5/// f00 — a modern, friendly directory lister
6#[derive(Debug, Clone, Parser)]
7#[command(
8    name = "f00",
9    version,
10    about = "List directory contents with a friendly default UI",
11    long_about = None,
12    // Free `-h` for human-readable (GNU ls style); keep `--help`.
13    disable_help_flag = true
14)]
15pub struct Args {
16    /// Paths to list (default: current directory)
17    pub paths: Vec<PathBuf>,
18
19    /// Print help
20    #[arg(long = "help", action = ArgAction::Help, help = "Print help")]
21    pub help: Option<bool>,
22
23    /// Do not ignore entries starting with `.`
24    #[arg(short = 'a', long = "all")]
25    pub all: bool,
26
27    /// Do not list implied `.` and `..` (show other hidden files)
28    #[arg(short = 'A', long = "almost-all")]
29    pub almost_all: bool,
30
31    /// Use a long listing format
32    #[arg(short = 'l')]
33    pub long: bool,
34
35    /// List one file per line
36    #[arg(short = '1')]
37    pub one_per_line: bool,
38
39    /// List entries by columns
40    #[arg(short = 'C')]
41    pub columns: bool,
42
43    /// Fill width with a comma separated list of entries
44    #[arg(short = 'm')]
45    pub commas: bool,
46
47    /// With -l and -s, print sizes like 1K 234M 2G etc.
48    #[arg(short = 'h', long = "human-readable")]
49    pub human_readable: bool,
50
51    /// Likewise, but use powers of 1000 not 1024
52    #[arg(long = "si")]
53    pub si: bool,
54
55    /// List subdirectories recursively
56    #[arg(short = 'R', long = "recursive")]
57    pub recursive: bool,
58
59    /// Reverse sort order
60    #[arg(short = 'r', long = "reverse")]
61    pub reverse: bool,
62
63    /// Sort by time, newest first
64    #[arg(short = 't')]
65    pub sort_time: bool,
66
67    /// Sort by file size, largest first
68    #[arg(short = 'S')]
69    pub sort_size: bool,
70
71    /// Sort alphabetically by entry extension
72    #[arg(short = 'X')]
73    pub sort_extension: bool,
74
75    /// Natural sort of (version) numbers within text (`strverscmp`)
76    #[arg(short = 'v')]
77    pub sort_version: bool,
78
79    /// Do not sort; list entries in directory order
80    #[arg(short = 'U')]
81    pub sort_none: bool,
82
83    /// Sort by WORD (name, size, time, extension, version, none)
84    #[arg(long = "sort", value_name = "WORD")]
85    pub sort: Option<String>,
86
87    /// Use time WORD for display/sort: mtime, atime, ctime, birth
88    #[arg(long = "time", value_name = "WORD")]
89    pub time: Option<String>,
90
91    /// Sort by, and show, access time
92    #[arg(short = 'u')]
93    pub access_time: bool,
94
95    /// Sort by, and show, ctime (status change) when possible
96    #[arg(short = 'c')]
97    pub change_time: bool,
98
99    /// Colorize output
100    #[arg(
101        long = "color",
102        value_name = "WHEN",
103        default_value = "auto",
104        num_args = 0..=1,
105        default_missing_value = "always",
106        require_equals = true
107    )]
108    pub color: ColorArg,
109
110    /// Emit structured JSON
111    #[arg(long = "json")]
112    pub json: bool,
113
114    /// Emit CSV
115    #[arg(long = "csv")]
116    pub csv: bool,
117
118    /// Emit TSV
119    #[arg(long = "tsv")]
120    pub tsv: bool,
121
122    /// Show entries as a tree
123    #[arg(long = "tree")]
124    pub tree: bool,
125
126    /// Stricter GNU ls-compatible behavior
127    #[arg(long = "gnu")]
128    pub gnu: bool,
129
130    /// Show file icons (auto/always/never; default: auto — TTY only, off under --gnu)
131    #[arg(
132        long = "icons",
133        value_name = "WHEN",
134        default_value = "auto",
135        num_args = 0..=1,
136        default_missing_value = "always",
137        require_equals = true
138    )]
139    pub icons: IconsArg,
140
141    /// Append indicator (one of */=@|) to entries
142    #[arg(short = 'F', long = "classify")]
143    pub classify: bool,
144
145    /// Append / indicator to directories
146    #[arg(short = 'p')]
147    pub indicator_slash: bool,
148
149    /// Like -F, except do not append '*'
150    #[arg(long = "file-type")]
151    pub file_type: bool,
152
153    /// Append indicator with style WORD: none, slash, file-type, classify
154    #[arg(long = "indicator-style", value_name = "WORD")]
155    pub indicator_style: Option<String>,
156
157    /// Group directories before files
158    #[arg(long = "group-directories-first", alias = "dirs-first")]
159    pub dirs_first: bool,
160
161    /// List directories themselves, not their contents
162    #[arg(short = 'd', long = "directory")]
163    pub directory: bool,
164
165    /// Do not list implied entries ending with ~
166    #[arg(short = 'B', long = "ignore-backups")]
167    pub ignore_backups: bool,
168
169    /// Do not list implied entries matching shell PATTERN (repeatable)
170    #[arg(short = 'I', long = "ignore", value_name = "PATTERN", action = ArgAction::Append)]
171    pub ignore: Vec<String>,
172
173    /// Do not list implied entries matching shell PATTERN (unless -a/-A)
174    #[arg(long = "hide", value_name = "PATTERN", action = ArgAction::Append)]
175    pub hide: Vec<String>,
176
177    /// When showing file information for a symbolic link, show info for the file it references
178    #[arg(short = 'L', long = "dereference")]
179    pub dereference: bool,
180
181    /// Follow symbolic links listed on the command line
182    #[arg(short = 'H', long = "dereference-command-line")]
183    pub dereference_command_line: bool,
184
185    /// Follow each command line symbolic link that points to a directory
186    #[arg(long = "dereference-command-line-symlink-to-dir")]
187    pub dereference_command_line_symlink_to_dir: bool,
188
189    /// Like -l, but do not list owner
190    #[arg(short = 'g')]
191    pub no_owner: bool,
192
193    /// Like -l, but do not list group information
194    #[arg(short = 'o')]
195    pub no_group_long: bool,
196
197    /// In a long listing, don't print group names
198    #[arg(short = 'G', long = "no-group")]
199    pub no_group: bool,
200
201    /// Like -l, but list numeric user and group IDs
202    #[arg(short = 'n', long = "numeric-uid-gid")]
203    pub numeric_uid_gid: bool,
204
205    /// Print the index number of each file
206    #[arg(short = 'i', long = "inode")]
207    pub inode: bool,
208
209    /// Print the allocated size of each file, in blocks
210    #[arg(short = 's', long = "size")]
211    pub size_blocks: bool,
212
213    /// Default to 1024-byte blocks for filesystem disk usage (`-s`)
214    #[arg(short = 'k', long = "kibibytes")]
215    pub kibibytes: bool,
216
217    /// Scale sizes by SIZE before printing (e.g. 1K, 1M, KB, MB)
218    #[arg(long = "block-size", value_name = "SIZE")]
219    pub block_size: Option<String>,
220
221    /// Like -l --time-style=full-iso
222    #[arg(long = "full-time")]
223    pub full_time: bool,
224
225    /// Time/date format with -l; also TIME_STYLE env
226    #[arg(long = "time-style", value_name = "TIME_STYLE")]
227    pub time_style: Option<String>,
228
229    /// Print ? instead of nongraphic characters
230    #[arg(short = 'q', long = "hide-control-chars")]
231    pub hide_control_chars: bool,
232
233    /// Show nongraphic characters as-is (the default unless -q)
234    #[arg(long = "show-control-chars")]
235    pub show_control_chars: bool,
236
237    /// Print C-style escapes for nongraphic characters
238    #[arg(short = 'b', long = "escape")]
239    pub escape: bool,
240
241    /// Enclose entry names in double quotes
242    #[arg(short = 'Q', long = "quote-name")]
243    pub quote_name: bool,
244
245    /// Print entry names without quoting
246    #[arg(short = 'N', long = "literal")]
247    pub literal: bool,
248
249    /// Use quoting style WORD for entry names
250    #[arg(long = "quoting-style", value_name = "WORD")]
251    pub quoting_style: Option<String>,
252
253    /// List entries by lines instead of by columns (row-major)
254    #[arg(short = 'x')]
255    pub across: bool,
256
257    /// Same as -a -U (and disable decorations in GNU mode)
258    #[arg(short = 'f')]
259    pub unsorted_all: bool,
260
261    /// Across/commas/long/single-column/vertical
262    #[arg(long = "format", value_name = "WORD")]
263    pub format: Option<String>,
264
265    /// Assume tab stops at each COLS instead of 8 (stored for layout)
266    #[arg(short = 'T', long = "tabsize", value_name = "COLS")]
267    pub tabsize: Option<usize>,
268
269    /// Set output width to COLS; 0 means no limit
270    #[arg(short = 'w', long = "width", value_name = "COLS")]
271    pub width: Option<usize>,
272
273    /// Print any security context of each file (SELinux)
274    #[arg(short = 'Z', long = "context")]
275    pub context: bool,
276
277    /// End each output line with NUL, not newline
278    #[arg(long = "zero")]
279    pub zero: bool,
280
281    /// Generate output designed for Emacs' dired mode
282    #[arg(short = 'D', long = "dired")]
283    pub dired: bool,
284
285    /// With -l, print the author of each file
286    #[arg(long = "author")]
287    pub author: bool,
288
289    /// Hyperlink file names (auto/always/never)
290    #[arg(
291        long = "hyperlink",
292        value_name = "WHEN",
293        num_args = 0..=1,
294        default_missing_value = "always",
295        require_equals = true
296    )]
297    pub hyperlink: Option<String>,
298
299    /// Maximum recursion depth (with -R / --tree)
300    #[arg(long = "max-depth", value_name = "N")]
301    pub max_depth: Option<usize>,
302
303    /// Annotate with git status (requires feature `git`)
304    #[arg(long = "git", default_value_t = true, action = clap::ArgAction::Set)]
305    pub git: bool,
306
307    /// Path to TOML config file (overrides default search path)
308    #[arg(long = "config", value_name = "PATH")]
309    pub config: Option<PathBuf>,
310
311    /// Interactive TUI directory browser (requires feature `tui`)
312    #[arg(long = "browse", visible_alias = "tui")]
313    pub browse: bool,
314
315    /// Honor `.gitignore` / `.f00ignore` when listing
316    #[arg(long = "ignore-files")]
317    pub ignore_files: bool,
318
319    /// Do not honor ignore files (overrides --ignore-files)
320    #[arg(long = "no-ignore")]
321    pub no_ignore: bool,
322
323    /// Auto-list zip/tar archives as directories (default: true; off under --gnu)
324    #[arg(long = "archive", default_value_t = true, action = clap::ArgAction::Set)]
325    pub archive: bool,
326
327    /// Parallel metadata threads: `0` = auto (rayon default), `1` = serial, `N>1` = fixed pool
328    #[arg(long = "threads", value_name = "N", default_value_t = 0)]
329    pub threads: usize,
330
331    /// Print phase timing breakdown to stderr (readdir/stat/sort/format/total)
332    #[arg(long = "profile")]
333    pub profile: bool,
334
335    /// Use io_uring batch metadata when built with `--features io-uring` (Linux)
336    #[arg(long = "io-uring", action = ArgAction::Set, default_value_t = true)]
337    pub io_uring: bool,
338
339    /// Generate shell completions to stdout and exit (`bash`, `zsh`, `fish`, `powershell`, `elvish`)
340    #[arg(long = "generate-completions", value_name = "SHELL", hide = true)]
341    pub generate_completions: Option<clap_complete::Shell>,
342
343    /// Generate a man page to stdout and exit
344    #[arg(long = "generate-man", hide = true, action = ArgAction::SetTrue)]
345    pub generate_man: bool,
346
347    /// Update f00 from the latest GitHub Release (checksum verified)
348    #[arg(long = "update", action = ArgAction::SetTrue)]
349    pub update: bool,
350
351    /// Check whether a newer release is available (no mutation); exit 1 if behind
352    #[arg(long = "check-update", action = ArgAction::SetTrue)]
353    pub check_update: bool,
354
355    /// List loaded plugins (requires feature `plugins`)
356    #[arg(long = "list-plugins", action = ArgAction::SetTrue)]
357    pub list_plugins: bool,
358}
359
360impl Args {
361    /// Minimal args for tests (all flags default/off).
362    pub fn test_default() -> Self {
363        Self {
364            paths: vec![],
365            help: None,
366            all: false,
367            almost_all: false,
368            long: false,
369            one_per_line: false,
370            columns: false,
371            commas: false,
372            human_readable: false,
373            si: false,
374            recursive: false,
375            reverse: false,
376            sort_time: false,
377            sort_size: false,
378            sort_extension: false,
379            sort_version: false,
380            sort_none: false,
381            sort: None,
382            time: None,
383            access_time: false,
384            change_time: false,
385            color: ColorArg::Auto,
386            json: false,
387            csv: false,
388            tsv: false,
389            tree: false,
390            gnu: false,
391            icons: IconsArg::Auto,
392            classify: false,
393            indicator_slash: false,
394            file_type: false,
395            indicator_style: None,
396            dirs_first: false,
397            directory: false,
398            ignore_backups: false,
399            ignore: vec![],
400            hide: vec![],
401            dereference: false,
402            dereference_command_line: false,
403            dereference_command_line_symlink_to_dir: false,
404            no_owner: false,
405            no_group_long: false,
406            no_group: false,
407            numeric_uid_gid: false,
408            inode: false,
409            size_blocks: false,
410            kibibytes: false,
411            block_size: None,
412            full_time: false,
413            time_style: None,
414            hide_control_chars: false,
415            show_control_chars: false,
416            escape: false,
417            quote_name: false,
418            literal: false,
419            quoting_style: None,
420            across: false,
421            unsorted_all: false,
422            format: None,
423            tabsize: None,
424            width: None,
425            context: false,
426            zero: false,
427            dired: false,
428            author: false,
429            hyperlink: None,
430            max_depth: None,
431            git: false,
432            config: None,
433            browse: false,
434            ignore_files: false,
435            no_ignore: false,
436            archive: true,
437            threads: 0,
438            profile: false,
439            io_uring: true,
440            generate_completions: None,
441            generate_man: false,
442            update: false,
443            check_update: false,
444            list_plugins: false,
445        }
446    }
447}
448
449#[derive(Debug, Clone, Copy, Default, ValueEnum)]
450pub enum ColorArg {
451    #[default]
452    Auto,
453    Always,
454    Never,
455}
456
457impl From<ColorArg> for f00_core::ColorWhen {
458    fn from(value: ColorArg) -> Self {
459        match value {
460            ColorArg::Auto => Self::Auto,
461            ColorArg::Always => Self::Always,
462            ColorArg::Never => Self::Never,
463        }
464    }
465}
466
467/// When to show file-type icons (mirrors [`f00_core::IconsWhen`]).
468#[derive(Debug, Clone, Copy, Default, ValueEnum, PartialEq, Eq)]
469pub enum IconsArg {
470    #[default]
471    Auto,
472    Always,
473    Never,
474}
475
476impl From<IconsArg> for f00_core::IconsWhen {
477    fn from(value: IconsArg) -> Self {
478        match value {
479            IconsArg::Auto => Self::Auto,
480            IconsArg::Always => Self::Always,
481            IconsArg::Never => Self::Never,
482        }
483    }
484}
485
486impl From<f00_core::IconsWhen> for IconsArg {
487    fn from(value: f00_core::IconsWhen) -> Self {
488        match value {
489            f00_core::IconsWhen::Auto => Self::Auto,
490            f00_core::IconsWhen::Always => Self::Always,
491            f00_core::IconsWhen::Never => Self::Never,
492        }
493    }
494}