Skip to content

Support pipe-separated exclude patterns (-I)#59

Merged
peteretelej merged 3 commits into
peteretelej:mainfrom
Simar-malhotra09:fix-exclude-patterns
Feb 25, 2026
Merged

Support pipe-separated exclude patterns (-I)#59
peteretelej merged 3 commits into
peteretelej:mainfrom
Simar-malhotra09:fix-exclude-patterns

Conversation

@Simar-malhotra09

@Simar-malhotra09 Simar-malhotra09 commented Feb 23, 2026

Copy link
Copy Markdown
Contributor

Split -I values on | in cli.rs and flatten them into individual glob patterns before matching.

Adds an integration test

All eintegration tests pass .

❯ cargo test --test integration_tests
warning: unused manifest key: target.cfg(windows).rustflags
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.06s
     Running tests/integration_tests.rs (target/debug/deps/integration_tests-e230cf2268c1a946)

running 24 tests
test test_basic_directory_listing ... ok
test test_directories_only ... ok
test test_classify ... ok
test test_ascii_mode ... ok
test test_error_handling ... ok
test test_file_sizes ... ok
test test_color_options ... ok
test test_depth_control ... ok
test test_fromfile_basic ... ok
test test_fromfile_prune_filters_hidden_dirs ... ok
test test_fromfile_prune_filters_excluded_dirs ... ok
test test_fromfile_prune_without_filter ... ok
test test_fromfile_prune_with_pattern ... ok
test test_fromfile_prune_with_exclude ... ok
test test_fromfile_windows_paths ... ok
test test_fromfile_prune_nested ... ok
test test_fromfile_with_flags ... ok
test test_full_path ... ok
test test_modification_date ... ok
test test_output_to_file ... ok
test test_hidden_files ... ok
test test_no_report ... ok
test test_pattern_matching ... ok
test test_sort_options ... ok

test result: ok. 24 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.28s

Summary by CodeRabbit

  • New Features

    • Support pipe-delimited exclude patterns in a single flag (e.g., -I "src|docs" to exclude multiple directories).
  • Tests

    • Added integration tests covering pipe-separated excludes and edge cases (leading/trailing pipes, consecutive pipes/empty segments) to ensure consistent exclusion behavior.

@coderabbitai

coderabbitai Bot commented Feb 23, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds CLI handling for pipe-delimited exclude patterns: each exclude string is split on '|', empty segments are filtered out, and each fragment is parsed into a separate Pattern. Integration tests cover multiple and edge-case pipe-separated exclude inputs.

Changes

Cohort / File(s) Summary
Pipe-delimited pattern parsing
src/rust_tree/cli.rs
Replaces single-map exclude parsing with a flat_map that splits exclude strings on `
Pattern exclusion tests
tests/integration_tests.rs
Adds integration tests for pipe-separated excludes and edge cases (leading/trailing pipes, consecutive pipes), asserting multiple directories are excluded when passed via a single `-I "src

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 I nibbled a pipe and split it in two,

Patterns hopped out and away they flew.
One flag, many paths, neat as a knot—
The rabbit applauds this tidy spot. 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Support pipe-separated exclude patterns (-I)' directly describes the main change: adding support for pipe-delimited exclude patterns in the -I flag.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov

codecov Bot commented Feb 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.37%. Comparing base (4ba61cf) to head (ac08bd6).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #59      +/-   ##
==========================================
+ Coverage   94.13%   94.37%   +0.23%     
==========================================
  Files           7        7              
  Lines        1348     1386      +38     
==========================================
+ Hits         1269     1308      +39     
+ Misses         79       78       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
tests/integration_tests.rs (2)

193-209: Edge cases for the filter(|s| !s.is_empty()) guard are untested

The implementation correctly strips empty segments from inputs like |src|docs| or src||docs, but none of these inputs are exercised. A few cheap extra assertions (or a separate test) would give confidence that the guard works end-to-end.

🧪 Suggested additions
// Leading/trailing pipes should still exclude both dirs
let output_pipe_edges = cmd()
    .args(["-I", "|src|docs|", temp_dir.path().to_str().unwrap()])
    .assert()
    .success()
    .get_output()
    .stdout
    .clone();
assert!(output_not_contains(&output_pipe_edges, "src"));
assert!(output_not_contains(&output_pipe_edges, "docs"));

// Consecutive pipes (empty segment between them) should be ignored
let output_double_pipe = cmd()
    .args(["-I", "src||docs", temp_dir.path().to_str().unwrap()])
    .assert()
    .success()
    .get_output()
    .stdout
    .clone();
assert!(output_not_contains(&output_double_pipe, "src"));
assert!(output_not_contains(&output_double_pipe, "docs"));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/integration_tests.rs` around lines 193 - 209, Add two assertions
exercising the filter(|s| !s.is_empty()) guard: invoke cmd() like the existing
tests but pass "-I" with a leading/trailing pipe string ("|src|docs|") and with
consecutive pipes ("src||docs"), capture stdout into new variables (e.g.,
output_pipe_edges and output_double_pipe) and assert that both "src" and "docs"
are excluded using output_not_contains, while other entries still exist; this
mirrors the existing output_pipe_exclude flow and verifies that empty segments
are ignored end-to-end.

204-205: output_not_contains uses an unrestricted substring match

output_not_contains(&output_pipe_exclude, "src") will spuriously fail if the temporary directory path printed as the tree root contains the substring "src". System TempDir paths rarely contain it, but anchoring the check to a tree-formatted line (e.g. prefixed by a connector or newline) makes the assertion more resilient and expressive.

🔒 Example of a more anchored assertion
-    assert!(output_not_contains(&output_pipe_exclude, "src"));
-    assert!(output_not_contains(&output_pipe_exclude, "docs"));
+    // Match the entry as it appears in tree output (preceded by a connector or newline)
+    let out_str = String::from_utf8_lossy(&output_pipe_exclude);
+    assert!(!out_str.contains("\nsrc\n") && !out_str.contains("── src"));
+    assert!(!out_str.contains("\ndocs\n") && !out_str.contains("── docs"));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/integration_tests.rs` around lines 204 - 205, The assertions use
output_not_contains(&output_pipe_exclude, "src") which can false-positive if the
temp dir path contains "src"; update the checks to anchor to tree-formatted
lines instead of raw substring matching: in the test that references
output_pipe_exclude replace the plain "src" and "docs" checks with assertions
that look for tree-line patterns (e.g. check the output does not contain "\n├──
src", "\n└── src" and similarly for "docs" or use a regex that matches
line-start plus tree connector and the folder name). Locate the calls to
output_not_contains and the variables output_pipe_exclude/output_pipe_include in
the failing test and change them to use the anchored tree-line patterns or
anchored regexes so only actual tree entries are matched.
src/rust_tree/cli.rs (2)

194-200: Add a unit test for the pipe-splitting path in cli_to_options

The existing test_cli_to_options_with_patterns only exercises a single non-pipe exclude string. The integration test covers end-to-end behaviour, but a focused unit test for the splitting logic in this module would catch regressions at a finer granularity.

🧪 Suggested unit test
#[test]
fn test_cli_to_options_pipe_separated_exclude() {
    let cli = Cli {
        path: ".".to_string(),
        exclude: vec!["src|docs".to_string()],
        // ... all other fields set to their defaults ...
        all_files: false, level: None, dir_only: false, no_indent: false,
        print_size: false, human_readable: false, pattern: None,
        full_path: false, color: false, no_color: false, ascii: false,
        sort_by_time: false, reverse: false, print_mod_date: false,
        output_file: None, file_limit: None, dirs_first: false,
        classify: false, no_report: false, print_permissions: false,
        fromfile: false, icons: false, prune: false, match_dirs: false,
    };

    let options = cli_to_options(&cli).unwrap();
    assert_eq!(options.exclude_patterns.len(), 2);
    assert!(options.exclude_patterns[0].matches("src"));
    assert!(options.exclude_patterns[1].matches("docs"));
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/rust_tree/cli.rs` around lines 194 - 200, Add a focused unit test that
constructs a Cli (using the Cli struct) with exclude containing a pipe-separated
string like "src|docs", calls cli_to_options(&cli) and asserts the returned
Options has two parsed patterns in exclude_patterns (using parse_glob_pattern
behavior): check length == 2 and that each Pattern matches the expected string
(e.g., options.exclude_patterns[0].matches("src") and [1].matches("docs")).
Place the test alongside existing tests for cli_to_options to exercise the
pipe-splitting path.

197-198: Whitespace around | is not trimmed — user-facing UX gap

Splitting on '|' without trimming means -I "src | docs" (spaces around the separator) produces glob patterns "src " and " docs", which won't match directory names src or docs. Consider adding a .map(|s| s.trim()) before the filter to absorb surrounding whitespace.

✂️ Proposed change
     .flat_map(|s| s.split('|'))
+    .map(|s| s.trim())
     .filter(|s| !s.is_empty())
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/rust_tree/cli.rs` around lines 197 - 198, The split/join iterator chain
currently does .flat_map(|s| s.split('|')).filter(|s| !s.is_empty()) which
leaves surrounding whitespace on each chunk (e.g., " src "); insert a .map(|s|
s.trim()) between the flat_map and filter so each segment is trimmed before
emptiness check (i.e., .flat_map(|s| s.split('|')).map(|s| s.trim()).filter(|s|
!s.is_empty())), ensuring user-provided patterns like `src | docs` match
correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/rust_tree/cli.rs`:
- Around line 187-193: Update the comment above the CLI glob-splitting logic to
remove the phrase "Not sure if this is okay to do." and replace the misleading
explanation: state that the code splits each CLI argument on '|' into individual
glob patterns and parses each separately, and that parse_glob_pattern is
retained because it converts glob::PatternError into String so the function can
use the `?` operator with a Result<_, String>; also fix the sentence starting
with "since" to include a clear subject. Reference: the comment near the
split-on-'|' logic and the parse_glob_pattern function.

---

Nitpick comments:
In `@src/rust_tree/cli.rs`:
- Around line 194-200: Add a focused unit test that constructs a Cli (using the
Cli struct) with exclude containing a pipe-separated string like "src|docs",
calls cli_to_options(&cli) and asserts the returned Options has two parsed
patterns in exclude_patterns (using parse_glob_pattern behavior): check length
== 2 and that each Pattern matches the expected string (e.g.,
options.exclude_patterns[0].matches("src") and [1].matches("docs")). Place the
test alongside existing tests for cli_to_options to exercise the pipe-splitting
path.
- Around line 197-198: The split/join iterator chain currently does
.flat_map(|s| s.split('|')).filter(|s| !s.is_empty()) which leaves surrounding
whitespace on each chunk (e.g., " src "); insert a .map(|s| s.trim()) between
the flat_map and filter so each segment is trimmed before emptiness check (i.e.,
.flat_map(|s| s.split('|')).map(|s| s.trim()).filter(|s| !s.is_empty())),
ensuring user-provided patterns like `src | docs` match correctly.

In `@tests/integration_tests.rs`:
- Around line 193-209: Add two assertions exercising the filter(|s|
!s.is_empty()) guard: invoke cmd() like the existing tests but pass "-I" with a
leading/trailing pipe string ("|src|docs|") and with consecutive pipes
("src||docs"), capture stdout into new variables (e.g., output_pipe_edges and
output_double_pipe) and assert that both "src" and "docs" are excluded using
output_not_contains, while other entries still exist; this mirrors the existing
output_pipe_exclude flow and verifies that empty segments are ignored
end-to-end.
- Around line 204-205: The assertions use
output_not_contains(&output_pipe_exclude, "src") which can false-positive if the
temp dir path contains "src"; update the checks to anchor to tree-formatted
lines instead of raw substring matching: in the test that references
output_pipe_exclude replace the plain "src" and "docs" checks with assertions
that look for tree-line patterns (e.g. check the output does not contain "\n├──
src", "\n└── src" and similarly for "docs" or use a regex that matches
line-start plus tree connector and the folder name). Locate the calls to
output_not_contains and the variables output_pipe_exclude/output_pipe_include in
the failing test and change them to use the anchored tree-line patterns or
anchored regexes so only actual tree entries are matched.

Comment thread src/rust_tree/cli.rs Outdated
Comment on lines +187 to +193
// since `glob` does not handle pipe-separated patterns (e.g. "x|y|z").
// Split each CLI argument on '|' into individual patterns,
// then parse each one separately.
// we keep parse_glob_pattern since otherwise,
// .filter/.flat_map yields
// &str while we need <Vec<T>, E>. Not sure if this is
// okay to do.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove draft uncertainty remarks and fix the inaccurate explanation

The note "Not sure if this is okay to do." should not be in production code. The explanation is also misleading: the reason parse_glob_pattern is kept is that it converts the error type from glob::PatternError to String to satisfy the ? operator's Result<_, String> expectation — not because of any &str vs <Vec<T>, E> concern (those types are unrelated to the reason). Additionally, the comment starts with since without a subject.

💬 Suggested replacement comment
-    // since `glob` does not handle pipe-separated patterns (e.g. "x|y|z").
-    // Split each CLI argument on '|' into individual patterns,
-    // then parse each one separately.
-    // we keep parse_glob_pattern since otherwise, 
-    // .filter/.flat_map yields 
-    // &str while we need <Vec<T>, E>. Not sure if this is 
-    // okay to do. 
+    // `glob::Pattern` does not support pipe-separated alternatives (e.g. "src|docs"),
+    // so split each `-I` argument on '|' and parse each fragment individually.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// since `glob` does not handle pipe-separated patterns (e.g. "x|y|z").
// Split each CLI argument on '|' into individual patterns,
// then parse each one separately.
// we keep parse_glob_pattern since otherwise,
// .filter/.flat_map yields
// &str while we need <Vec<T>, E>. Not sure if this is
// okay to do.
// `glob::Pattern` does not support pipe-separated alternatives (e.g. "src|docs"),
// so split each `-I` argument on '|' and parse each fragment individually.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/rust_tree/cli.rs` around lines 187 - 193, Update the comment above the
CLI glob-splitting logic to remove the phrase "Not sure if this is okay to do."
and replace the misleading explanation: state that the code splits each CLI
argument on '|' into individual glob patterns and parses each separately, and
that parse_glob_pattern is retained because it converts glob::PatternError into
String so the function can use the `?` operator with a Result<_, String>; also
fix the sentence starting with "since" to include a clear subject. Reference:
the comment near the split-on-'|' logic and the parse_glob_pattern function.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/rust_tree/cli.rs (1)

360-396: Consider testing invalid-pattern error propagation through a pipe-separated string.

The happy-path split of "src|docs" is covered, but the collect::<Result<Vec<_>, _>>()? short-circuit path — triggered when one fragment is an invalid glob (e.g. "src|[") — has no unit-test coverage. This exercises a different code path from test_cli_to_options_invalid_pattern, which only tests a bare invalid pattern, not one embedded inside a pipe-separated value.

✨ Suggested additional test
#[test]
fn test_cli_to_options_pipe_separated_exclude_invalid() {
    let cli = Cli {
        path: ".".to_string(),
        exclude: vec!["src|[".to_string()], // '[' is an invalid glob fragment
        all_files: false,
        level: None,
        dir_only: false,
        no_indent: false,
        print_size: false,
        human_readable: false,
        pattern: None,
        full_path: false,
        color: false,
        no_color: false,
        ascii: false,
        sort_by_time: false,
        reverse: false,
        print_mod_date: false,
        output_file: None,
        file_limit: None,
        dirs_first: false,
        classify: false,
        no_report: false,
        print_permissions: false,
        fromfile: false,
        icons: false,
        prune: false,
        match_dirs: false,
    };
    assert!(cli_to_options(&cli).is_err());
}

As a secondary note: the test body duplicates the non-default field values from test_cli_to_options_with_patterns (e.g. all_files: true, level: Some(2), etc.), none of which affect exclude_patterns. Since Cli has no Default derive, the verbosity is structurally forced — but deriving Default for Cli (all booleans/Options/Vecs already have zero-values; path would need a manual override or a helper factory) would allow future tests to use Cli { exclude: vec![...], ..Cli::default() } and express intent more clearly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/rust_tree/cli.rs` around lines 360 - 396, The test suite lacks coverage
for the error path when a pipe-separated exclude string contains an invalid glob
fragment; add a new unit test (e.g.
test_cli_to_options_pipe_separated_exclude_invalid) that constructs a Cli with
exclude: vec!["src|[".to_string()] and asserts that
cli_to_options(&cli).is_err(), to ensure the collect::<Result<Vec<_>, _>>()?
short-circuits on invalid fragments; optionally consider deriving Default for
Cli (or add a small helper) so future tests can set only the differing fields
(exclude) using ..Cli::default() to reduce boilerplate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/rust_tree/cli.rs`:
- Around line 360-396: The test suite lacks coverage for the error path when a
pipe-separated exclude string contains an invalid glob fragment; add a new unit
test (e.g. test_cli_to_options_pipe_separated_exclude_invalid) that constructs a
Cli with exclude: vec!["src|[".to_string()] and asserts that
cli_to_options(&cli).is_err(), to ensure the collect::<Result<Vec<_>, _>>()?
short-circuits on invalid fragments; optionally consider deriving Default for
Cli (or add a small helper) so future tests can set only the differing fields
(exclude) using ..Cli::default() to reduce boilerplate.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 486ec8e and ac08bd6.

📒 Files selected for processing (2)
  • src/rust_tree/cli.rs
  • tests/integration_tests.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/integration_tests.rs

@peteretelej peteretelej left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm. tyvm!

@peteretelej
peteretelej merged commit 5b937cd into peteretelej:main Feb 25, 2026
10 of 11 checks passed
@Simar-malhotra09
Simar-malhotra09 deleted the fix-exclude-patterns branch February 25, 2026 20:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants