weavatrix-scan 0.4.2

Deterministic, safe repository scanner for code intelligence
Documentation
use super::ParallelWalkReport;
use crate::walker::{ErrorPolicy, WalkEntry, WalkError, WalkOptions, WalkSkipReason, Walker};
use std::collections::VecDeque;
use std::path::{Path, PathBuf};
use std::sync::Arc;

pub(super) fn collect_lane(
    tasks: Vec<DirectoryTask>,
    options: WalkOptions,
    parallel_root: &Arc<PathBuf>,
) -> LaneReport {
    let mut report = ParallelWalkReport {
        entries: Vec::new(),
        errors: Vec::new(),
    };
    let mut segments = VecDeque::with_capacity(tasks.len());
    for task in tasks {
        let entry_start = report.entries.len();
        let error_start = report.errors.len();
        let mut worker_options = options;
        worker_options.error_policy = ErrorPolicy::Continue;
        worker_options.max_open = 1;
        worker_options.min_depth = if options.same_file_system {
            options.min_depth.saturating_sub(task.depth)
        } else {
            options.min_depth
        };
        worker_options.max_depth = if options.same_file_system {
            options
                .max_depth
                .map(|depth| depth.saturating_sub(task.depth))
        } else {
            options.max_depth
        };
        if options.same_file_system {
            match Walker::with_options(&task.path, worker_options) {
                Ok(walker) => collect_rebased(walker, parallel_root, task.depth, &mut report),
                Err(mut error) => {
                    error.rebase_depth(task.depth);
                    report.errors.push(error);
                }
            }
        } else {
            let walker = Walker::from_known_directory(
                parallel_root,
                task.path,
                task.depth,
                worker_options,
                None,
            );
            for item in walker {
                match item {
                    Ok(entry) => report.entries.push(entry),
                    Err(error) => report.errors.push(error),
                }
            }
        }
        segments.push_back(TaskSegment {
            entries: report.entries.len() - entry_start,
            errors: report.errors.len() - error_start,
        });
    }
    LaneReport { report, segments }
}

fn collect_rebased(
    walker: Walker,
    root: &Arc<PathBuf>,
    depth: usize,
    report: &mut ParallelWalkReport,
) {
    for item in walker {
        match item {
            Ok(entry) if entry.depth() == 0 => {}
            Ok(mut entry) => {
                entry.rebase(root, depth);
                report.entries.push(entry);
            }
            Err(mut error) => {
                error.rebase_depth(depth);
                report.errors.push(error);
            }
        }
    }
}

pub(super) struct DirectoryTask {
    pub(super) path: PathBuf,
    pub(super) depth: usize,
}

pub(super) struct TaskSegment {
    pub(super) entries: usize,
    pub(super) errors: usize,
}

pub(super) struct LaneReport {
    pub(super) report: ParallelWalkReport,
    pub(super) segments: VecDeque<TaskSegment>,
}

pub(super) struct ShallowWalk {
    pub(super) root: Arc<PathBuf>,
    pub(super) entries: Vec<WalkEntry>,
    pub(super) errors: Vec<WalkError>,
    pub(super) tasks: VecDeque<DirectoryTask>,
}

pub(super) fn collect_shallow(root: &Path, options: WalkOptions) -> Result<ShallowWalk, WalkError> {
    let mut shallow_options = options;
    shallow_options.error_policy = ErrorPolicy::Continue;
    shallow_options.min_depth = 0;
    shallow_options.max_depth = Some(options.max_depth.unwrap_or(1).min(1));
    let shallow = Walker::with_options(root, shallow_options)?;
    let canonical_root = Arc::new(shallow.root().to_path_buf());
    let mut entries = Vec::new();
    let mut errors = Vec::new();
    let mut tasks = VecDeque::new();

    for item in shallow {
        match item {
            Ok(mut entry) => {
                let can_descend = entry.depth() == 1
                    && entry.is_dir()
                    && entry.skip_reason() == Some(WalkSkipReason::MaxDepth)
                    && options.max_depth.is_none_or(|max_depth| max_depth > 1);
                if can_descend {
                    entry.clear_depth_skip();
                    tasks.push_back(DirectoryTask {
                        path: entry.path().to_path_buf(),
                        depth: entry.depth(),
                    });
                }
                if entry.depth() >= options.min_depth {
                    entries.push(entry);
                }
            }
            Err(error) => errors.push(error),
        }
    }
    Ok(ShallowWalk {
        root: canonical_root,
        entries,
        errors,
        tasks,
    })
}

pub(super) fn expand_frontier(
    mut shallow: ShallowWalk,
    options: WalkOptions,
    target_tasks: usize,
) -> ShallowWalk {
    while !shallow.tasks.is_empty() && shallow.tasks.len() < target_tasks {
        let current = std::mem::take(&mut shallow.tasks);
        let root = Arc::clone(&shallow.root);
        for task in current {
            expand_task(&task, options, &root, &mut shallow);
        }
    }
    shallow
}

fn expand_task(
    task: &DirectoryTask,
    options: WalkOptions,
    root: &Arc<PathBuf>,
    shallow: &mut ShallowWalk,
) {
    let mut worker_options = options;
    worker_options.error_policy = ErrorPolicy::Continue;
    worker_options.min_depth = 0;
    worker_options.max_open = 1;
    worker_options.max_depth = Some(
        options
            .max_depth
            .unwrap_or(task.depth + 1)
            .min(task.depth + 1),
    );
    let walker =
        Walker::from_known_directory(root, task.path.clone(), task.depth, worker_options, None);
    for item in walker {
        match item {
            Ok(mut entry) => {
                let can_descend = entry.is_dir()
                    && entry.skip_reason() == Some(WalkSkipReason::MaxDepth)
                    && options
                        .max_depth
                        .is_none_or(|maximum| entry.depth() < maximum);
                if can_descend {
                    entry.clear_depth_skip();
                    shallow.tasks.push_back(DirectoryTask {
                        path: entry.path().to_path_buf(),
                        depth: entry.depth(),
                    });
                }
                if entry.depth() >= options.min_depth {
                    shallow.entries.push(entry);
                }
            }
            Err(error) => shallow.errors.push(error),
        }
    }
}

pub(super) fn collect_serial(
    root: &Path,
    options: WalkOptions,
) -> Result<ParallelWalkReport, WalkError> {
    let mut walker_options = options;
    walker_options.error_policy = ErrorPolicy::Continue;
    let walker = Walker::with_options(root, walker_options)?;
    let mut report = ParallelWalkReport {
        entries: Vec::new(),
        errors: Vec::new(),
    };
    for item in walker {
        match item {
            Ok(entry) => report.entries.push(entry),
            Err(error) => report.errors.push(error),
        }
    }
    if options.error_policy == ErrorPolicy::Abort && !report.errors.is_empty() {
        return Err(report.errors.remove(0));
    }
    Ok(report)
}