Skip to main content

join_ai/
walker.rs

1use crate::cli::JoinArgs;
2use ignore::{WalkBuilder, WalkState};
3use std::path::PathBuf;
4use std::sync::mpsc;
5
6/// This module is responsible for efficiently finding all files that match the
7/// user's criteria using the `ignore` crate, which is excellent at respecting
8/// rules like `.gitignore` and handling parallel directory traversal.
9///
10/// The walker runs in a separate thread pool and sends valid file paths back to the
11/// main thread through a multi-producer, single-consumer (mpsc) channel.
12///
13/// # Arguments
14/// * `args` - A reference to the parsed `JoinArgs` containing all CLI options.
15///
16/// # Returns
17/// A `Result` containing the receiver end of the channel, which will be used by
18/// the processor to receive file paths.
19pub fn find_files(args: &JoinArgs) -> anyhow::Result<mpsc::Receiver<PathBuf>> {
20    // Create a channel for communication between the walker threads and the main thread.
21    let (tx, rx) = mpsc::channel();
22    let input_folder = args.input_folder.clone();
23
24    // --- 1. Configure the base walker ---
25    let mut walker_builder = WalkBuilder::new(&input_folder);
26    walker_builder
27        .follow_links(!args.no_follow)
28        .max_depth(args.max_depth);
29
30    // --- 2. Build a set of override rules for inclusion and exclusion ---
31    // The `OverrideBuilder` allows us to programmatically add glob patterns that
32    // take precedence over any `.gitignore` or similar rules.
33    let mut override_builder = ignore::overrides::OverrideBuilder::new(&input_folder);
34
35    // Add inclusion patterns. If none are provided, default to including everything.
36    if let Some(patterns) = &args.patterns {
37        for pattern in patterns {
38            override_builder.add(pattern)?;
39        }
40    } else {
41        // A single "*" will match all files, which is a good default.
42        override_builder.add("*")?;
43    }
44
45    // Add all exclusion patterns. These are prefixed with "!" to negate the match.
46    if let Some(exclude_patterns) = &args.exclude {
47        for pattern in exclude_patterns {
48            let exclusion_pattern = format!("!{pattern}");
49            override_builder.add(&exclusion_pattern)?;
50        }
51    }
52
53    // If hidden files are not requested, add a global ignore pattern for them.
54    // This is necessary because the `*` override would otherwise include them.
55    if !args.hidden {
56        override_builder.add("!.*")?;
57    }
58
59    // Apply the built override rules to the walker.
60    let overrides = override_builder.build()?;
61    walker_builder.overrides(overrides);
62
63    // --- 3. Run the walker in parallel ---
64    let walker = walker_builder.build_parallel();
65    let output_file_path = args.output_file.clone();
66
67    // The `run` method spawns a thread pool to perform the walk.
68    // We provide a closure that builds a "move closure" for each thread.
69    walker.run(move || {
70        // Clone the transmitter and other necessary data for each thread.
71        let tx = tx.clone();
72        let output_file_path = output_file_path.clone();
73
74        // This inner closure is executed for each directory entry found.
75        Box::new(move |result| {
76            if let Ok(entry) = result {
77                let path = entry.path();
78                // Skip directories and the application's own output file.
79                if path.is_dir() || path == output_file_path {
80                    return WalkState::Continue;
81                }
82
83                // All filtering is now handled by the `overrides`, so we don't
84                // need to manually check extensions or folders here.
85
86                // If all checks pass, send the valid file path to the processor.
87                tx.send(path.to_path_buf()).expect("Failed to send path");
88            }
89            // Continue the walk regardless of the result.
90            WalkState::Continue
91        })
92    });
93
94    // Return the receiver end of the channel to the caller.
95    Ok(rx)
96}