anyrepair 0.2.4

A comprehensive Rust crate for repairing malformed structured data including JSON, YAML, XML, TOML, CSV, INI, Markdown, and Diff with format auto-detection
Documentation
//! TOML repair module

use crate::error::Result;
use crate::traits::{Repair, RepairStrategy, Validator};
use regex::Regex;
use std::sync::OnceLock;

/// Cached regex patterns for TOML performance optimization
#[allow(dead_code)]
struct TomlRegexCache {
    missing_quotes: Regex,
    malformed_arrays: Regex,
    malformed_tables: Regex,
    malformed_strings: Regex,
    malformed_numbers: Regex,
    malformed_dates: Regex,
}

impl TomlRegexCache {
    fn new() -> Result<Self> {
        Ok(Self {
            missing_quotes: Regex::new(
                r#"^(\s*)([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*([^"'\s].*[^"'\s])\s*$"#,
            )?,
            malformed_arrays: Regex::new(r#"\[([^,\]]+),\]"#)?,
            malformed_tables: Regex::new(r#"^(\s*)\[([^]]+)\]\s*$"#)?,
            malformed_strings: Regex::new(r#"^(\s*)([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*'([^']*)'\s*$"#)?,
            malformed_numbers: Regex::new(
                r#"^(\s*)([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(\d+\.\d*\.\d+)"#,
            )?,
            malformed_dates: Regex::new(
                r#"^(\s*)([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})"#,
            )?,
        })
    }
}

static TOML_REGEX_CACHE: OnceLock<TomlRegexCache> = OnceLock::new();

fn get_toml_regex_cache() -> &'static TomlRegexCache {
    TOML_REGEX_CACHE
        .get_or_init(|| TomlRegexCache::new().expect("Failed to initialize TOML regex cache"))
}

/// TOML repairer that can fix common TOML issues
///
/// Uses trait-based composition with GenericRepairer for better modularity
pub struct TomlRepairer {
    inner: crate::repairer_base::GenericRepairer,
}

impl TomlRepairer {
    /// Create a new TOML repairer
    pub fn new() -> Self {
        let strategies: Vec<Box<dyn RepairStrategy>> = vec![
            Box::new(FixMissingQuotesStrategy),
            Box::new(FixMalformedArraysStrategy),
            Box::new(FixMalformedTablesStrategy),
            Box::new(FixMalformedStringsStrategy),
            Box::new(FixMalformedNumbersStrategy),
            Box::new(FixMalformedDatesStrategy),
            Box::new(AddTableHeadersStrategy),
        ];

        let validator: Box<dyn Validator> = Box::new(TomlValidator);
        let inner = crate::repairer_base::GenericRepairer::new(validator, strategies);

        Self { inner }
    }
}

impl Default for TomlRepairer {
    fn default() -> Self {
        Self::new()
    }
}

impl Repair for TomlRepairer {
    fn repair(&mut self, content: &str) -> Result<String> {
        self.inner.repair(content)
    }

    fn needs_repair(&self, content: &str) -> bool {
        self.inner.needs_repair(content)
    }

    fn confidence(&self, content: &str) -> f64 {
        if content.trim().is_empty() {
            return 0.0;
        }

        // Calculate confidence based on TOML-like patterns
        let mut score: f64 = 0.0;

        // Check for table headers
        if content.contains('[') && content.contains(']') {
            score += 0.3;
        }

        // Check for key-value pairs
        if content.contains('=') {
            score += 0.3;
        }

        // Check for arrays
        if content.contains('[') && content.contains(',') {
            score += 0.2;
        }

        // Check for strings
        if content.contains('"') || content.contains("'") {
            score += 0.1;
        }

        // Check for numbers
        if content.chars().any(|c| c.is_ascii_digit()) {
            score += 0.1;
        }

        score.min(1.0)
    }
}

/// TOML validator
pub struct TomlValidator;

impl Validator for TomlValidator {
    fn is_valid(&self, content: &str) -> bool {
        if content.trim().is_empty() {
            return false;
        }

        // Check for missing quotes around string values
        let lines: Vec<&str> = content.lines().collect();
        for line in lines {
            let trimmed = line.trim();
            if trimmed.is_empty() || trimmed.starts_with('#') || trimmed.starts_with('[') {
                continue;
            }

            // Check for key-value pairs where the value should be quoted
            if trimmed.contains('=') {
                let parts: Vec<&str> = trimmed.splitn(2, '=').collect();
                if parts.len() == 2 {
                    let value = parts[1].trim();
                    // If value looks like a string but is not quoted, it's invalid
                    if !value.starts_with('"')
                        && !value.starts_with('\'')
                        && !value.starts_with('[')
                        && !value.starts_with('{')
                        && value.parse::<i64>().is_err()
                        && value.parse::<f64>().is_err()
                        && value != "true"
                        && value != "false"
                    {
                        return false;
                    }
                }
            }
        }

        // Basic TOML validation using toml crate
        toml::from_str::<toml::Value>(content).is_ok()
    }

    fn validate(&self, content: &str) -> Vec<String> {
        let mut errors = Vec::new();

        if content.trim().is_empty() {
            errors.push("Empty TOML content".to_string());
            return errors;
        }

        // Try to parse with toml crate
        match toml::from_str::<toml::Value>(content) {
            Ok(_) => {} // Valid TOML
            Err(e) => {
                errors.push(format!("TOML parsing error: {e}"));
            }
        }

        errors
    }
}

/// Strategy to fix missing quotes around string values
struct FixMissingQuotesStrategy;

impl RepairStrategy for FixMissingQuotesStrategy {
    fn apply(&self, content: &str) -> Result<String> {
        let cache = get_toml_regex_cache();
        let result = cache
            .missing_quotes
            .replace_all(content, |caps: &regex::Captures| {
                let indent = &caps[1];
                let key = &caps[2];
                let value = &caps[3];
                format!("{}{} = \"{}\"", indent, key, value)
            });

        Ok(result.to_string())
    }

    fn priority(&self) -> u8 {
        6
    }

    fn name(&self) -> &str {
        "FixMissingQuotesStrategy"
    }
}

/// Strategy to fix malformed arrays
struct FixMalformedArraysStrategy;

impl RepairStrategy for FixMalformedArraysStrategy {
    fn apply(&self, content: &str) -> Result<String> {
        let cache = get_toml_regex_cache();
        let result = cache
            .malformed_arrays
            .replace_all(content, |caps: &regex::Captures| {
                let content = &caps[1];
                format!("[{content}]")
            });

        Ok(result.to_string())
    }

    fn priority(&self) -> u8 {
        5
    }

    fn name(&self) -> &str {
        "FixMalformedArraysStrategy"
    }
}

/// Strategy to fix malformed table headers
struct FixMalformedTablesStrategy;

impl RepairStrategy for FixMalformedTablesStrategy {
    fn apply(&self, content: &str) -> Result<String> {
        let cache = get_toml_regex_cache();
        let result = cache
            .malformed_tables
            .replace_all(content, |caps: &regex::Captures| {
                let indent = &caps[1];
                let table_name = &caps[2];
                format!("{}[{}]", indent, table_name)
            });

        Ok(result.to_string())
    }

    fn priority(&self) -> u8 {
        4
    }

    fn name(&self) -> &str {
        "FixMalformedTablesStrategy"
    }
}

/// Strategy to fix malformed strings
struct FixMalformedStringsStrategy;

impl RepairStrategy for FixMalformedStringsStrategy {
    fn apply(&self, content: &str) -> Result<String> {
        let cache = get_toml_regex_cache();
        let result = cache
            .malformed_strings
            .replace_all(content, |caps: &regex::Captures| {
                let indent = &caps[1];
                let key = &caps[2];
                let value = &caps[3];
                format!("{}{} = \"{}\"", indent, key, value)
            });

        Ok(result.to_string())
    }

    fn priority(&self) -> u8 {
        3
    }

    fn name(&self) -> &str {
        "FixMalformedStringsStrategy"
    }
}

/// Strategy to fix malformed numbers
struct FixMalformedNumbersStrategy;

impl RepairStrategy for FixMalformedNumbersStrategy {
    fn apply(&self, content: &str) -> Result<String> {
        let cache = get_toml_regex_cache();
        let result = cache
            .malformed_numbers
            .replace_all(content, |caps: &regex::Captures| {
                let indent = &caps[1];
                let key = &caps[2];
                let number = &caps[3];
                // Remove extra decimal points
                let fixed_number = number.replace("..", ".");
                format!("{}{} = {}", indent, key, fixed_number)
            });

        Ok(result.to_string())
    }

    fn priority(&self) -> u8 {
        2
    }

    fn name(&self) -> &str {
        "FixMalformedNumbersStrategy"
    }
}

/// Strategy to fix malformed dates
struct FixMalformedDatesStrategy;

impl RepairStrategy for FixMalformedDatesStrategy {
    fn apply(&self, content: &str) -> Result<String> {
        let cache = get_toml_regex_cache();
        let result = cache
            .malformed_dates
            .replace_all(content, |caps: &regex::Captures| {
                let indent = &caps[1];
                let key = &caps[2];
                let date = &caps[3];
                format!("{}{} = \"{}\"", indent, key, date)
            });

        Ok(result.to_string())
    }

    fn priority(&self) -> u8 {
        1
    }

    fn name(&self) -> &str {
        "FixMalformedDatesStrategy"
    }
}

/// Strategy to add table headers if missing
struct AddTableHeadersStrategy;

impl RepairStrategy for AddTableHeadersStrategy {
    fn apply(&self, content: &str) -> Result<String> {
        let lines: Vec<&str> = content.lines().collect();
        let mut result = Vec::new();
        let mut has_table_header = false;

        for line in lines {
            let trimmed = line.trim();

            // Check if this is a key-value pair without a table header
            if trimmed.contains('=') && !trimmed.starts_with('[') && !has_table_header {
                result.push("[root]".to_string());
                has_table_header = true;
            }

            result.push(line.to_string());
        }

        Ok(result.join("\n"))
    }

    fn priority(&self) -> u8 {
        0
    }

    fn name(&self) -> &str {
        "AddTableHeadersStrategy"
    }
}