opengrep 1.1.0

Advanced AST-aware code search tool with tree-sitter parsing and AI integration capabilities
Documentation
//! Filesystem traversal and file discovery.
//!
//! This module is responsible for walking the directory tree, applying ignore
//! rules, and collecting files to be searched. It uses the `ignore` crate
//! to respect `.gitignore`, `.ignore`, and other similar files.

use anyhow::Result;
use ignore::{WalkBuilder, WalkState};
use log::{debug, warn};
use std::path::PathBuf;
use std::sync::mpsc::Sender;

/// A file entry to be processed.
#[derive(Debug)]
pub struct FileEntry {
    /// Path to the file.
    pub path: PathBuf,
}

/// A file walker that traverses directories and sends files to a channel.
pub struct FileWalker {
    paths: Vec<PathBuf>,
    threads: usize,
    sender: Sender<FileEntry>,
}

impl FileWalker {
    /// Creates a new `FileWalker`.
    ///
    /// # Arguments
    ///
    /// * `paths` - A list of file or directory paths to start walking from.
    /// * `threads` - The number of threads to use for walking.
    /// * `sender` - The channel sender to send `FileEntry` objects to.
    pub fn new(paths: Vec<PathBuf>, threads: usize, sender: Sender<FileEntry>) -> Self {
        Self {
            paths,
            threads,
            sender,
        }
    }

    /// Starts walking the filesystem.
    ///
    /// This method builds a parallel file walker and sends each valid file
    /// entry to the processing channel.
    pub fn walk(&self) -> Result<()> {
        let mut walker_builder = WalkBuilder::new(&self.paths[0]);
        if self.paths.len() > 1 {
            for path in &self.paths[1..] {
                walker_builder.add(path);
            }
        }

        walker_builder
            .threads(self.threads)
            .standard_filters(true)
            .hidden(false);

        let sender = self.sender.clone();
        walker_builder.build_parallel().run(move || {
            let tx = sender.clone();
            Box::new(move |result| {
                match result {
                    Ok(entry) => {
                        if entry.file_type().map_or(false, |ft| ft.is_file()) {
                            let path = entry.into_path();
                            debug!("Found file: {:?}", path);
                            if tx.send(FileEntry { path }).is_err() {
                                warn!("Receiver dropped, stopping file walk.");
                                return WalkState::Quit;
                            }
                        }
                    }
                    Err(err) => {
                        warn!("Error walking directory: {}", err);
                    }
                }
                WalkState::Continue
            })
        });

        Ok(())
    }
}

/// A simple, single-threaded file walker for cases where parallelism is not needed.
pub fn walk_simple(path: PathBuf) -> Result<Vec<PathBuf>> {
    let mut files = Vec::new();
    let walker = WalkBuilder::new(path).build();
    for result in walker {
        match result {
            Ok(entry) => {
                if entry.file_type().map_or(false, |ft| ft.is_file()) {
                    files.push(entry.into_path());
                }
            }
            Err(err) => {
                warn!("Error walking directory: {}", err);
            }
        }
    }
    Ok(files)
}