use crate::error::Result;
use crate::traits::{Repair, RepairStrategy, Validator};
use regex::Regex;
use std::sync::OnceLock;
#[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"))
}
pub struct TomlRepairer {
inner: crate::repairer_base::GenericRepairer,
}
impl TomlRepairer {
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;
}
let mut score: f64 = 0.0;
if content.contains('[') && content.contains(']') {
score += 0.3;
}
if content.contains('=') {
score += 0.3;
}
if content.contains('[') && content.contains(',') {
score += 0.2;
}
if content.contains('"') || content.contains("'") {
score += 0.1;
}
if content.chars().any(|c| c.is_ascii_digit()) {
score += 0.1;
}
score.min(1.0)
}
}
pub struct TomlValidator;
impl Validator for TomlValidator {
fn is_valid(&self, content: &str) -> bool {
if content.trim().is_empty() {
return false;
}
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;
}
if trimmed.contains('=') {
let parts: Vec<&str> = trimmed.splitn(2, '=').collect();
if parts.len() == 2 {
let value = parts[1].trim();
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;
}
}
}
}
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;
}
match toml::from_str::<toml::Value>(content) {
Ok(_) => {} Err(e) => {
errors.push(format!("TOML parsing error: {e}"));
}
}
errors
}
}
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: ®ex::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"
}
}
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: ®ex::Captures| {
let content = &caps[1];
format!("[{content}]")
});
Ok(result.to_string())
}
fn priority(&self) -> u8 {
5
}
fn name(&self) -> &str {
"FixMalformedArraysStrategy"
}
}
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: ®ex::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"
}
}
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: ®ex::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"
}
}
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: ®ex::Captures| {
let indent = &caps[1];
let key = &caps[2];
let number = &caps[3];
let fixed_number = number.replace("..", ".");
format!("{}{} = {}", indent, key, fixed_number)
});
Ok(result.to_string())
}
fn priority(&self) -> u8 {
2
}
fn name(&self) -> &str {
"FixMalformedNumbersStrategy"
}
}
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: ®ex::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"
}
}
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();
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"
}
}