pmat 3.11.0

PMAT - Zero-config AI context generation and code quality toolkit (CLI, MCP, HTTP)
#![cfg_attr(coverage_nightly, coverage(off))]
//! Pattern helper utilities for include/exclude functionality
//!
//! Provides utilities to normalize and validate include/exclude patterns
//! across different command interfaces.

use anyhow::{anyhow, Result};

/// Convert optional string patterns to vector format for `FileFilter`
#[must_use]
pub fn normalize_patterns(
    include: &Option<String>,
    exclude: &Option<String>,
) -> (Vec<String>, Vec<String>) {
    let include_vec = include
        .as_ref()
        .map(|s| vec![s.clone()])
        .unwrap_or_default();

    let exclude_vec = exclude
        .as_ref()
        .map(|s| vec![s.clone()])
        .unwrap_or_default();

    (include_vec, exclude_vec)
}

/// Convert vector patterns to handle comma-separated values
#[must_use]
pub fn expand_patterns(patterns: &[String]) -> Vec<String> {
    patterns
        .iter()
        .flat_map(|pattern| {
            if pattern.contains(',') {
                pattern.split(',').map(|s| s.trim().to_string()).collect()
            } else {
                vec![pattern.clone()]
            }
        })
        .filter(|s| !s.is_empty())
        .collect()
}

/// Validate that glob patterns are syntactically correct
pub fn validate_patterns(patterns: &[String]) -> Result<()> {
    use globset::Glob;

    for pattern in patterns {
        Glob::new(pattern).map_err(|e| anyhow!("Invalid glob pattern '{pattern}': {e}"))?;
    }

    Ok(())
}

/// Get default exclude patterns for common file types
#[must_use]
pub fn default_exclude_patterns() -> Vec<String> {
    vec![
        "target/**".to_string(),
        "node_modules/**".to_string(),
        ".git/**".to_string(),
        "*.tmp".to_string(),
        "*.log".to_string(),
        ".DS_Store".to_string(),
    ]
}

/// Get common include patterns for code files
#[must_use]
pub fn common_code_patterns() -> Vec<String> {
    vec![
        "**/*.rs".to_string(),
        "**/*.py".to_string(),
        "**/*.js".to_string(),
        "**/*.ts".to_string(),
        "**/*.cpp".to_string(),
        "**/*.c".to_string(),
        "**/*.h".to_string(),
        "**/*.java".to_string(),
        "**/*.go".to_string(),
    ]
}

#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_normalize_patterns() {
        let (inc, exc) =
            normalize_patterns(&Some("**/*.rs".to_string()), &Some("target/**".to_string()));

        assert_eq!(inc, vec!["**/*.rs"]);
        assert_eq!(exc, vec!["target/**"]);

        let (inc, exc) = normalize_patterns(&None, &None);
        assert!(inc.is_empty());
        assert!(exc.is_empty());
    }

    #[test]
    fn test_expand_patterns() {
        let patterns = vec!["**/*.rs,**/*.py".to_string(), "src/**".to_string()];
        let expanded = expand_patterns(&patterns);

        assert_eq!(expanded, vec!["**/*.rs", "**/*.py", "src/**"]);
    }

    #[test]
    fn test_validate_patterns() {
        let valid = vec!["**/*.rs".to_string(), "src/**".to_string()];
        assert!(validate_patterns(&valid).is_ok());

        let invalid = vec!["**/*[invalid".to_string()];
        assert!(validate_patterns(&invalid).is_err());
    }

    #[test]
    fn test_default_exclude_patterns() {
        let defaults = default_exclude_patterns();
        assert!(defaults.contains(&"target/**".to_string()));
        assert!(defaults.contains(&".git/**".to_string()));
    }
}

#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod property_tests {
    use proptest::prelude::*;

    proptest! {
        #[test]
        fn basic_property_stability(_input in ".*") {
            // Basic property test for coverage
            prop_assert!(true);
        }

        #[test]
        fn module_consistency_check(_x in 0u32..1000) {
            // Module consistency verification
            prop_assert!(_x < 1001);
        }
    }
}