linecheck 0.3.0

A fast, configurable tool that warns or errors when files exceed a set line count
Documentation
use glob::Pattern;
use std::path::{Path, PathBuf};
use walkdir::WalkDir;

pub fn collect_files(paths: &[PathBuf], exclude: &[String]) -> Vec<PathBuf> {
    let patterns: Vec<Pattern> = exclude.iter().filter_map(|p| Pattern::new(p).ok()).collect();
    let mut files = Vec::new();
    for path in paths {
        if path.is_file() {
            if !is_excluded(path, None, &patterns) { files.push(path.clone()); }
        } else if path.is_dir() {
            for entry in WalkDir::new(path).follow_links(false).into_iter()
                .filter_entry(|e| (e.depth() == 0 || !is_hidden(e.path())) && !is_excluded(e.path(), Some(path), &patterns))
                .filter_map(|e| e.ok())
                .filter(|e| e.file_type().is_file())
            {
                files.push(entry.into_path());
            }
        }
    }
    files
}

fn is_hidden(path: &Path) -> bool {
    path.file_name()
        .and_then(|n| n.to_str())
        .map_or(false, |s| s.starts_with('.') && s.len() > 1)
}

fn is_excluded(path: &Path, root: Option<&Path>, patterns: &[Pattern]) -> bool {
    // Match relative to root when available (the common case for CLI usage)
    if let Some(root) = root {
        if let Ok(rel) = path.strip_prefix(root) {
            let s = rel.to_string_lossy();
            if patterns.iter().any(|p| p.matches(&s)) { return true; }
        }
    }
    let s = path.to_string_lossy();
    let normalized = s.strip_prefix("./").unwrap_or(&s);
    patterns.iter().any(|p| p.matches(normalized) || p.matches(&s))
}