Skip to content

Commit d6335ff

Browse files
authored
fix(builtins): support long options in tree builtin (#973)
## Summary - Fix `tree --noreport` being parsed as combined short flags causing `invalid option -- '-'` - Added long option parsing (`--noreport`, `--dirsfirst`, etc.) before the short-flag character loop ## Test plan - [x] New spec test: `tree_noreport` — verifies `--noreport` suppresses report line - [x] No regressions in existing spec tests - [x] clippy + fmt clean Closes #949
1 parent 8610b5d commit d6335ff

File tree

2 files changed

+71
-20
lines changed

2 files changed

+71
-20
lines changed

crates/bashkit/src/builtins/tree.rs

Lines changed: 60 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,22 @@ use crate::interpreter::ExecResult;
99

1010
/// The tree builtin command.
1111
///
12-
/// Usage: tree [-a] [-d] [-L level] [-I pattern] [PATH...]
12+
/// Usage: tree [-a] [-d] [-L level] [-I pattern] [--noreport] [PATH...]
1313
///
1414
/// Options:
15-
/// -a Show hidden files
16-
/// -d Directories only
17-
/// -L level Limit depth to level
18-
/// -I pattern Exclude files matching pattern
15+
/// -a Show hidden files
16+
/// -d Directories only
17+
/// -L level Limit depth to level
18+
/// -I pattern Exclude files matching pattern
19+
/// --noreport Suppress directory/file count report
1920
pub struct Tree;
2021

2122
struct TreeOptions {
2223
show_hidden: bool,
2324
dirs_only: bool,
2425
max_depth: Option<usize>,
2526
exclude_pattern: Option<String>,
27+
noreport: bool,
2628
}
2729

2830
struct TreeCounts {
@@ -38,6 +40,7 @@ impl Builtin for Tree {
3840
dirs_only: false,
3941
max_depth: None,
4042
exclude_pattern: None,
43+
noreport: false,
4144
};
4245

4346
let mut paths: Vec<&str> = Vec::new();
@@ -60,11 +63,25 @@ impl Builtin for Tree {
6063
} else if let Some(val) = p.flag_value_opt("-I") {
6164
opts.exclude_pattern = Some(val.to_string());
6265
} else if p.is_flag() {
63-
// Handle combined flags like -ad
6466
let Some(s) = p.current() else {
6567
p.advance();
6668
continue;
6769
};
70+
// Handle long options (--foo) before short-flag loop
71+
if s.starts_with("--") {
72+
match s {
73+
"--noreport" => opts.noreport = true,
74+
_ => {
75+
return Ok(ExecResult::err(
76+
format!("tree: unrecognized option '{}'\n", s),
77+
1,
78+
));
79+
}
80+
}
81+
p.advance();
82+
continue;
83+
}
84+
// Handle combined short flags like -ad
6885
for ch in s[1..].chars() {
6986
match ch {
7087
'a' => opts.show_hidden = true,
@@ -108,20 +125,22 @@ impl Builtin for Tree {
108125
let mut counts = TreeCounts { dirs: 0, files: 0 };
109126
build_tree(&ctx, &root, "", &opts, 0, &mut counts, &mut output).await;
110127

111-
if opts.dirs_only {
112-
output.push_str(&format!(
113-
"\n{} director{}\n",
114-
counts.dirs,
115-
if counts.dirs == 1 { "y" } else { "ies" }
116-
));
117-
} else {
118-
output.push_str(&format!(
119-
"\n{} director{}, {} file{}\n",
120-
counts.dirs,
121-
if counts.dirs == 1 { "y" } else { "ies" },
122-
counts.files,
123-
if counts.files == 1 { "" } else { "s" }
124-
));
128+
if !opts.noreport {
129+
if opts.dirs_only {
130+
output.push_str(&format!(
131+
"\n{} director{}\n",
132+
counts.dirs,
133+
if counts.dirs == 1 { "y" } else { "ies" }
134+
));
135+
} else {
136+
output.push_str(&format!(
137+
"\n{} director{}, {} file{}\n",
138+
counts.dirs,
139+
if counts.dirs == 1 { "y" } else { "ies" },
140+
counts.files,
141+
if counts.files == 1 { "" } else { "s" }
142+
));
143+
}
125144
}
126145
}
127146

@@ -381,4 +400,25 @@ mod tests {
381400
assert_eq!(result.exit_code, 1);
382401
assert!(result.stderr.contains("invalid option"));
383402
}
403+
404+
#[tokio::test]
405+
async fn test_tree_noreport() {
406+
let fs = setup_fs().await;
407+
let result = run_tree(&["--noreport", "/project"], fs).await;
408+
assert_eq!(result.exit_code, 0);
409+
assert!(result.stdout.contains("/project"));
410+
assert!(result.stdout.contains("src"));
411+
assert!(result.stdout.contains("Cargo.toml"));
412+
// --noreport should suppress the summary line
413+
assert!(!result.stdout.contains("director"));
414+
assert!(!result.stdout.contains("file"));
415+
}
416+
417+
#[tokio::test]
418+
async fn test_tree_unknown_long_option() {
419+
let fs = Arc::new(InMemoryFs::new()) as Arc<dyn FileSystem>;
420+
let result = run_tree(&["--bogus"], fs).await;
421+
assert_eq!(result.exit_code, 1);
422+
assert!(result.stderr.contains("unrecognized option"));
423+
}
384424
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
### tree_noreport
2+
### bash_diff: tree --noreport is bashkit builtin
3+
# tree --noreport should suppress the report line
4+
mkdir -p /tmp/tree_nr/a
5+
touch /tmp/tree_nr/a/f.txt
6+
tree --noreport /tmp/tree_nr
7+
### expect
8+
/tmp/tree_nr
9+
└── a
10+
└── f.txt
11+
### end

0 commit comments

Comments
 (0)