suture-cli 1.0.0

A patch-based version control system with semantic merge for structured files
use std::path::Path;

pub(crate) async fn cmd_ignore(args: &IgnoreArgs) -> Result<(), Box<dyn std::error::Error>> {
    match args {
        IgnoreArgs::List => cmd_ignore_list(),
        IgnoreArgs::Check { path } => cmd_ignore_check(path),
    }
}

fn cmd_ignore_list() -> Result<(), Box<dyn std::error::Error>> {
    let ignore_path = std::path::Path::new(".sutureignore");
    if !ignore_path.exists() {
        println!("No .sutureignore file found.");
        return Ok(());
    }
    let contents = std::fs::read_to_string(ignore_path)?;
    let lines: Vec<&str> = contents
        .lines()
        .filter(|l| !l.is_empty() && !l.starts_with('#'))
        .collect();
    if lines.is_empty() {
        println!("No ignore patterns defined.");
    } else {
        println!("Ignore patterns ({}):", lines.len());
        for line in &lines {
            println!("  {}", line);
        }
    }
    Ok(())
}

fn cmd_ignore_check(path: &str) -> Result<(), Box<dyn std::error::Error>> {
    let patterns = load_ignore_patterns(Path::new("."));
    let ignored = is_ignored(path, &patterns);
    if ignored {
        println!("{} is ignored", path);
    } else {
        println!("{} is NOT ignored", path);
    }
    Ok(())
}

fn load_ignore_patterns(root: &Path) -> Vec<String> {
    let ignore_file = root.join(".sutureignore");
    if !ignore_file.exists() {
        return Vec::new();
    }

    std::fs::read_to_string(&ignore_file)
        .unwrap_or_default()
        .lines()
        .map(|line| line.trim().to_string())
        .filter(|line| !line.is_empty() && !line.starts_with('#'))
        .collect()
}

fn is_ignored(rel_path: &str, patterns: &[String]) -> bool {
    for pattern in patterns {
        if let Some(suffix) = pattern.strip_prefix('*') {
            if rel_path.ends_with(suffix) {
                return true;
            }
        } else if pattern.ends_with('/') {
            if rel_path.starts_with(pattern) {
                return true;
            }
        } else {
            if rel_path == pattern || rel_path.starts_with(&format!("{}/", pattern)) {
                return true;
            }
        }
    }
    false
}

pub(crate) enum IgnoreArgs {
    List,
    Check { path: String },
}