windjammer 0.48.0

A simple language inspired by Go, Ruby, and Elixir that transpiles to Rust - 80% of Rust's power with 20% of the complexity
Documentation
// Directory traversal for wjfind

use std::fs
use std::path
use std::path::Path

use ./config::Config
use ./config
use ./gitignore::GitignoreCache

pub fn collect_files(paths: Vec<string>, config: &Config) -> Result<Vec<string>, string> {
    let mut all_files = vec![]
    let mut gitignore_cache = GitignoreCache::new()
    
    for path in paths {
        let files = walk_path(path, config, &mut gitignore_cache)?
        all_files.extend(files)
    }
    
    Ok(all_files)
}

fn walk_path(path: string, config: &Config, gitignore_cache: &mut GitignoreCache) -> Result<Vec<string>, string> {
    let mut files = vec![]
    
    // Check if path exists
    if !fs::exists(&path) {
        return Err(format!("Path does not exist: {}", path))
    }
    
    // If it's a file, return it directly
    if fs::is_file(&path) {
        if should_include_file(&path, config, gitignore_cache) {
            files.push(path)
        }
        return Ok(files)
    }
    
    // If it's a directory, walk it recursively
    if fs::is_dir(&path) {
        walk_dir(path, config, &mut files, gitignore_cache)?
    }
    
    Ok(files)
}

fn walk_dir(dir: string, config: &Config, files: &mut Vec<string>, gitignore_cache: &mut GitignoreCache) -> Result<(), string> {
    // Read directory entries
    let entries = fs::read_dir(&dir)?
    
    // Load .gitignore rules for this directory if respecting ignore
    let gitignore_rules = if config.respect_ignore {
        Some(gitignore_cache.get_rules(&dir))
    } else {
        None
    }
    
    for entry in entries {
        let path = entry.path()
        let file_name = entry.file_name()
        
        // Skip hidden files/dirs unless --hidden flag
        if !config.search_hidden && file_name.starts_with(".") {
            continue
        }
        
        // Skip if excluded
        if config::should_exclude(&path, &config.exclude_patterns) {
            continue
        }
        
        // Skip common ignore patterns
        if config.respect_ignore && is_ignored(&file_name) {
            continue
        }
        
        // Check .gitignore rules
        if config.respect_ignore && gitignore_rules.is_some() {
            if gitignore_rules.as_ref().unwrap().is_ignored(&path) {
                continue
            }
        }
        
        if entry.is_dir() {
            // Recurse into subdirectory
            walk_dir(path, config, files, gitignore_cache)?
        } else if entry.is_file() {
            // Add file if it matches criteria
            if should_include_file(&path, config, gitignore_cache) {
                files.push(path)
            }
        }
    }
    
    Ok(())
}

fn should_include_file(path: &string, config: &Config, gitignore_cache: &mut GitignoreCache) -> bool {
    // Check file type filter
    if !config::matches_file_type(path, &config.file_types) {
        return false
    }
    
    // Check .gitignore for this file if respecting ignore
    if config.respect_ignore {
        let dir = path::parent(Path::new(path))
            .and_then(|p| p.to_str())
            .unwrap_or(".")
            .to_string()
        let gitignore_rules = gitignore_cache.get_rules(&dir)
        if gitignore_rules.is_ignored(path) {
            return false
        }
    }
    
    // Check if binary file (skip binary files)
    if is_likely_binary(path) {
        return false
    }
    
    true
}

fn is_ignored(name: &string) -> bool {
    // Common directories to ignore
    let ignored_dirs = vec![
        "target",
        "node_modules",
        ".git",
        ".svn",
        ".hg",
        "dist",
        "build",
        "__pycache__",
        ".cache",
        ".venv",
        "venv",
    ]
    
    ignored_dirs.contains(&name.as_ref())
}

fn is_likely_binary(path: &string) -> bool {
    // Check by extension
    let binary_extensions = vec![
        "exe", "dll", "so", "dylib", "a", "o",
        "png", "jpg", "jpeg", "gif", "bmp", "ico",
        "pdf", "zip", "ta", "gz", "bz2", "xz",
        "mp3", "mp4", "avi", "mov", "mkv",
        "wasm", "class", "pyc",
    ]
    
    let ext = path::extension(path).unwrap_or("".to_string())
    binary_extensions.contains(&ext.as_ref())
}