skeletor 0.3.9

A blazing-fast Rust scaffolding tool with YAML-driven snapshots.
Documentation
use crate::errors::SkeletorError;
use crate::output::{DefaultReporter, Reporter};
use crate::utils::read_file_to_string;
use ignore::gitignore::{Gitignore, GitignoreBuilder};
use std::path::{Path, PathBuf};

#[derive(Debug)]
pub struct IgnoreSpec {
    pub matcher: Option<Gitignore>,
    pub patterns: Vec<String>,
}

fn add_ignore_line(
    builder: &mut GitignoreBuilder,
    source: Option<PathBuf>,
    line: &str,
    reporter: &DefaultReporter,
    patterns: &mut Vec<String>,
) -> Result<(), SkeletorError> {
    let trimmed = line.trim();
    if trimmed.is_empty() || trimmed.starts_with('#') {
        return Ok(());
    }

    if let Err(e) = builder.add_line(source.clone(), trimmed) {
        if source.is_none() {
            return Err(SkeletorError::InvalidIgnorePattern {
                pattern: format!("{} ({})", trimmed, e),
            });
        }

        reporter.warning(&format!(
            "Skipping invalid ignore pattern '{}'{}: {}",
            trimmed,
            source
                .as_ref()
                .map(|p| format!(" from {}", p.display()))
                .unwrap_or_default(),
            e
        ));
        reporter.tip("Check ignore pattern syntax or escape special characters");
        return Ok(());
    }

    patterns.push(trimmed.to_string());
    Ok(())
}

fn add_ignore_file(
    builder: &mut GitignoreBuilder,
    path: &Path,
    reporter: &DefaultReporter,
    patterns: &mut Vec<String>,
) -> Result<(), SkeletorError> {
    if !path.exists() || !path.is_file() {
        return Err(SkeletorError::FileNotFound {
            path: path.to_path_buf(),
        });
    }

    let content = read_file_to_string(path)?;
    for line in content.lines() {
        add_ignore_line(
            builder,
            Some(path.to_path_buf()),
            line,
            reporter,
            patterns,
        )?;
    }

    Ok(())
}

pub fn collect_ignore_spec(
    root: &Path,
    ignore_values: Option<impl Iterator<Item = String>>,
    ignore_files: Option<impl Iterator<Item = String>>,
    reporter: &DefaultReporter,
) -> Result<IgnoreSpec, SkeletorError> {
    let mut builder = GitignoreBuilder::new(root);
    let mut patterns = Vec::new();

    if let Some(vals) = ignore_values {
        for val in vals {
            let candidate = Path::new(&val);
            if candidate.exists() && candidate.is_file() {
                add_ignore_file(&mut builder, candidate, reporter, &mut patterns)?;
            } else {
                add_ignore_line(&mut builder, None, &val, reporter, &mut patterns)?;
            }
        }
    }

    if let Some(files) = ignore_files {
        for file in files {
            let path = Path::new(&file);
            add_ignore_file(&mut builder, path, reporter, &mut patterns)?;
        }
    }

    if patterns.is_empty() {
        return Ok(IgnoreSpec {
            matcher: None,
            patterns,
        });
    }

    let matcher = builder
        .build()
        .map_err(|e| SkeletorError::InvalidIgnorePattern {
            pattern: format!("Failed to compile ignore patterns: {}", e),
        })?;

    Ok(IgnoreSpec {
        matcher: Some(matcher),
        patterns,
    })
}