» Make grep CLI App in Rust » 2. Development » 2.8 Support Multiple File Paths

Support Multiple File Paths

If you want to support multiple file paths like this, you need to tune clap parsing logic again.

grustep pattern file1.txt file2.txt file3.txt

# Or
grustep pattern *.txt

Note:
Using *.txt as an argument in the command line directly may not work as expected because the shell (e.g., Bash) is responsible for expanding wildcard patterns like *.txt. The clap crate in Rust won't automatically perform this expansion.
However, you can use the glob crate to manually expand the wildcard pattern.

Add multiple paths version grep in src/lib.rs:

pub fn grep_multi(
    pattern: &str,
    file_paths: &Vec<&str>,
    options: &GrepOptions,
) -> Result<MatchResult, io::Error> {
    if file_paths.is_empty() {
        // For pipes like "cat a.txt | ..."
        return grep(pattern, Path::new(""), options);
    }
    let mut results = MatchResult::new();
    for &file_path in file_paths {
        if let Ok(result) = grep(pattern, Path::new(file_path), options) {
            results.extend(result);
        }
    }
    Ok(results)
}

pub fn grep_recursive_multi(
    pattern: &str,
    dir_paths: &Vec<&str>,
    options: &GrepOptions,
) -> Result<MatchResult, io::Error> {
    let mut results = MatchResult::new();
    for &dir_path in dir_paths {
        if let Ok(result) = grep_recursive(pattern, Path::new(dir_path), options) {
            results.extend(result);
        }
    }
    Ok(results)
}

Modify the clap part and call multiple path version functions in main.rs:

@@ -1,5 +1,6 @@
 use clap::{App, Arg};
-use lr_grustep::{grep, grep_count, grep_recursive, GrepOptions, MatchResult};
+
+use lr_grustep::{grep_count, grep_multi, grep_recursive_multi, GrepOptions, MatchResult};
 
 fn main() {
     // Define command-line arguments using clap
@@ -8,7 +9,7 @@ fn main() {
         .author("literank")
         .about("A grep-like utility in Rust")
         .arg(Arg::with_name("pattern").required(true).index(1).help("The pattern to search for"))
-        .arg(Arg::with_name("file_path").required(false).index(2).help("The file to search in"))
+        .arg(Arg::with_name("file_paths").required(false).multiple(true).help("The files to search in"))
         .arg(Arg::with_name("count").short("c").long("count").help("Only a count of selected lines is written to standard output"))
         .arg(Arg::with_name("ignore-case").short("i").long("ignore-case").help("Perform case-insensitive matching"))
         .arg(Arg::with_name("line-number").short("n").long("line-number").help("Each output line is preceded by its relative line number in the file, starting at line 1"))
@@ -18,16 +19,19 @@ fn main() {
 
     // Extract command-line arguments
     let pattern = matches.value_of("pattern").unwrap();
-    let file_path = matches.value_of("file_path").unwrap_or("");
+    let file_paths: Vec<&str> = matches
+        .values_of("file_paths")
+        .unwrap_or_default()
+        .collect();
     let options = GrepOptions {
         ignore_case: matches.is_present("ignore-case"),
         invert_match: matches.is_present("invert-match"),
     };
 
-    let result = if matches.is_present("recursive") && !file_path.is_empty() {
-        grep_recursive(pattern, file_path.as_ref(), &options)
+    let result = if matches.is_present("recursive") && !file_paths.is_empty() {
+        grep_recursive_multi(pattern, &file_paths, &options)
     } else {
-        grep(pattern, file_path.as_ref(), &options)
+        grep_multi(pattern, &file_paths, &options)
     };

Re-install your project:

cargo install --path .

# Or
make install

Then you can do this:

grustep -n result src/*rs

# Or
grustep -n result src/lib.rs src/main.rs

Result:

src/lib.rs:
42: let mut result = MatchResult::new();
43: result.insert(file_path.to_string_lossy().to_string(), matching_lines);
44: Ok(result)
56: let mut results = MatchResult::new();
58: if let Ok(result) = grep(pattern, Path::new(file_path), options) {
59: results.extend(result);
62: Ok(results)
65: pub fn grep_count(result: &MatchResult) -> usize {
66: result.values().map(|v| v.len()).sum()
74: let mut results = MatchResult::new();
79: let result = grep(pattern, file_path, options)?;
80: results.extend(result);
83: Ok(results)
91: let mut results = MatchResult::new();
93: if let Ok(result) = grep_recursive(pattern, Path::new(dir_path), options) {
94: results.extend(result);
97: Ok(results)

src/main.rs:
31: let result = if matches.is_present("recursive") && !file_paths.is_empty() {
37: match result {
38: Ok(result) => {
40: println!("{}", grep_count(&result));
42: print_result(&result, matches.is_present("line-number"))
51: fn print_result(result: &MatchResult, show_line_number: bool) {
53: let file_count = result.len();
55: for (file_path, items) in result {

Congratulations🎉! You've made a great CLI app in Rust now.

Complete code: https://github.com/Literank/lr_grustep

Keep Going! Keep Learning!

PrevNext