Skip to main content

f00_compat/
lib.rs

1//! GNU / POSIX `ls` compatibility helpers for **f00**.
2
3use f00_core::{IconsWhen, ListOptions, OutputMode, SortBy};
4
5/// Compatibility profile applied when `--gnu` is set.
6#[derive(Debug, Clone, Default)]
7pub struct GnuProfile {
8    /// Prefer one-per-line when not a TTY (GNU ls default).
9    pub force_one_per_line_when_not_tty: bool,
10    /// Sort directories mixed with files (GNU default); disable dirs_first.
11    pub disable_dirs_first: bool,
12    /// Use strict name sort without stripping dots for collate key.
13    pub strict_name_sort: bool,
14    /// Disable icons and git decorations.
15    pub disable_decorations: bool,
16}
17
18impl GnuProfile {
19    pub fn enabled() -> Self {
20        Self {
21            force_one_per_line_when_not_tty: true,
22            disable_dirs_first: true,
23            strict_name_sort: true,
24            disable_decorations: true,
25        }
26    }
27}
28
29/// Apply GNU-mode tweaks onto listing options.
30pub fn apply_gnu_list_options(opts: &mut ListOptions, gnu: bool) {
31    if !gnu {
32        return;
33    }
34    opts.gnu_mode = true;
35    // GNU ls does not default to directories-first.
36    opts.dirs_first = false;
37}
38
39/// Adjust output mode for GNU-ish behavior.
40pub fn apply_gnu_output(mode: OutputMode, is_tty: bool, gnu: bool) -> OutputMode {
41    if !gnu {
42        return mode;
43    }
44    match mode {
45        OutputMode::Default if !is_tty => OutputMode::OnePerLine,
46        other => other,
47    }
48}
49
50/// Whether a flag combination is "strict GNU" enough.
51pub fn gnu_mode_active(gnu_flag: bool) -> bool {
52    gnu_flag
53}
54
55/// Soft defaults when the binary is invoked as `ls` / `ls.exe`.
56///
57/// Turns off icons and dirs-first unless the corresponding CLI flags were set.
58/// Config may re-enable them after this runs. Does **not** enable full `--gnu`
59/// strict mode — that remains opt-in via `--gnu` / `F00_GNU`.
60pub fn prefer_ls_defaults(
61    icons: &mut IconsWhen,
62    dirs_first: &mut bool,
63    icons_from_cli: bool,
64    dirs_first_from_cli: bool,
65) {
66    if !icons_from_cli {
67        *icons = IconsWhen::Never;
68    }
69    if !dirs_first_from_cli {
70        *dirs_first = false;
71    }
72}
73
74/// Parse GNU `--sort=WORD`.
75pub fn parse_sort_word(word: &str) -> Option<SortBy> {
76    match word.to_ascii_lowercase().as_str() {
77        "name" => Some(SortBy::Name),
78        "size" => Some(SortBy::Size),
79        "time" => Some(SortBy::Time),
80        "extension" | "ext" => Some(SortBy::Extension),
81        "version" | "v" => Some(SortBy::Version),
82        "none" => Some(SortBy::None),
83        _ => None,
84    }
85}
86
87/// Parse GNU `--format=WORD`.
88pub fn parse_format_word(word: &str) -> Option<OutputMode> {
89    match word.to_ascii_lowercase().as_str() {
90        "across" | "horizontal" | "x" => Some(OutputMode::Across),
91        "commas" | "m" => Some(OutputMode::Commas),
92        "long" | "verbose" | "l" => Some(OutputMode::Long),
93        "single-column" | "single" | "1" => Some(OutputMode::OnePerLine),
94        "vertical" | "c" => Some(OutputMode::Columns),
95        _ => None,
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn apply_gnu_disables_dirs_first() {
105        let mut opts = ListOptions {
106            dirs_first: true,
107            ..Default::default()
108        };
109        apply_gnu_list_options(&mut opts, true);
110        assert!(!opts.dirs_first);
111        assert!(opts.gnu_mode);
112    }
113
114    #[test]
115    fn without_flag_leaves_options() {
116        let mut opts = ListOptions {
117            dirs_first: true,
118            ..Default::default()
119        };
120        apply_gnu_list_options(&mut opts, false);
121        assert!(opts.dirs_first);
122        assert!(!opts.gnu_mode);
123    }
124
125    #[test]
126    fn prefer_ls_clears_presentation_defaults() {
127        let mut icons = IconsWhen::Auto;
128        let mut dirs_first = true;
129        prefer_ls_defaults(&mut icons, &mut dirs_first, false, false);
130        assert_eq!(icons, IconsWhen::Never);
131        assert!(!dirs_first);
132    }
133
134    #[test]
135    fn prefer_ls_keeps_cli_overrides() {
136        let mut icons = IconsWhen::Always;
137        let mut dirs_first = true;
138        prefer_ls_defaults(&mut icons, &mut dirs_first, true, true);
139        assert_eq!(icons, IconsWhen::Always);
140        assert!(dirs_first);
141    }
142
143    #[test]
144    fn parse_sort_words() {
145        assert_eq!(parse_sort_word("size"), Some(SortBy::Size));
146        assert_eq!(parse_sort_word("none"), Some(SortBy::None));
147        assert_eq!(parse_sort_word("version"), Some(SortBy::Version));
148        assert_eq!(parse_sort_word("bogus"), None);
149    }
150
151    #[test]
152    fn parse_format_words() {
153        assert_eq!(parse_format_word("long"), Some(OutputMode::Long));
154        assert_eq!(parse_format_word("commas"), Some(OutputMode::Commas));
155        assert_eq!(parse_format_word("across"), Some(OutputMode::Across));
156    }
157}