use regex::Regex;
use std::sync::LazyLock;
static TOML_STRING_PATTERN: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r#""([^"\\]*(?:\\.[^"\\]*)*)""#).expect("Invalid regex"));
fn fix_toml_strings(toml_str: &str, needs_fix: impl Fn(&str) -> bool) -> String {
let mut result = String::with_capacity(toml_str.len());
let mut last_end = 0;
for cap in TOML_STRING_PATTERN.captures_iter(toml_str) {
let full_match = cap.get(0).expect("regex match guarantees group 0");
let content = cap.get(1).expect("regex pattern captures group 1").as_str();
result.push_str(&toml_str[last_end..full_match.start()]);
if needs_fix(content) {
if content.contains('\'') {
let escaped = content.replace('\\', "\\\\");
result.push('"');
result.push_str(&escaped);
result.push('"');
} else {
let unescaped = content.replace("\\\\", "\\").replace("\\\"", "\"");
result.push('\'');
result.push_str(&unescaped);
result.push('\'');
}
} else {
result.push_str(full_match.as_str());
}
last_end = full_match.end();
}
result.push_str(&toml_str[last_end..]);
result
}
pub fn quote_strings_containing_backslashes(toml_str: &str) -> String {
fix_toml_strings(toml_str, |content| content.contains('\\'))
}
pub fn fix_invalid_toml_escapes(toml_str: &str) -> String {
fix_toml_strings(toml_str, |content| {
content.contains("\\<") || content.contains("\\>")
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_no_change_when_no_backslash() {
let input = r#"key = "hello world""#;
let result = quote_strings_containing_backslashes(input);
assert_eq!(result, input);
}
#[test]
fn test_backslash_converted_to_single_quotes() {
let input = "command = \"ping \\<website\\>\"";
let result = quote_strings_containing_backslashes(input);
assert_eq!(result, "command = 'ping \\<website\\>'");
}
#[test]
fn test_backslash_and_single_quote_escaped() {
let input = "command = \"echo 'test\\\\value'\"";
let result = quote_strings_containing_backslashes(input);
assert_eq!(result, "command = \"echo 'test\\\\\\\\value'\"");
}
#[test]
fn test_multiple_strings() {
let input = "command = \"ping \\<website\\>\"\nname = \"test\"";
let result = quote_strings_containing_backslashes(input);
assert_eq!(result, "command = 'ping \\<website\\>'\nname = \"test\"");
}
#[test]
fn test_nested_quotes() {
let input = r#"command = "echo hello""#;
let result = quote_strings_containing_backslashes(input);
assert_eq!(result, input);
}
#[test]
fn test_empty_string() {
let input = r#"key = """#;
let result = quote_strings_containing_backslashes(input);
assert_eq!(result, input);
}
#[test]
fn test_escaped_backslash_in_double_quotes() {
let input = r#"path = "C:\\Users\\test""#;
let result = quote_strings_containing_backslashes(input);
assert_eq!(result, r#"path = 'C:\Users\test'"#);
}
#[test]
fn test_fix_invalid_escape_simple() {
let input = r#"command = "sudo iptables-restore \< /path""#;
let result = fix_invalid_toml_escapes(input);
assert_eq!(result, r#"command = 'sudo iptables-restore \< /path'"#);
}
#[test]
fn test_fix_invalid_escape_with_single_quotes() {
let input = r#"command = "echo 'test \<value'""#;
let result = fix_invalid_toml_escapes(input);
assert_eq!(result, r#"command = "echo 'test \\<value'""#);
}
#[test]
fn test_fix_invalid_escape_multiple() {
let input = r#"command = "echo \<foo\> and \<bar\>""#;
let result = fix_invalid_toml_escapes(input);
assert_eq!(result, r#"command = 'echo \<foo\> and \<bar\>'"#);
}
#[test]
fn test_fix_invalid_escape_no_change_needed() {
let input = r#"command = "echo hello""#;
let result = fix_invalid_toml_escapes(input);
assert_eq!(result, input);
}
#[test]
fn test_fix_invalid_escape_with_single_quote_and_invalid_escape() {
let input = r#"command = "it's a \<test\>""#;
let result = fix_invalid_toml_escapes(input);
assert_eq!(result, r#"command = "it's a \\<test\\>""#);
}
#[test]
fn test_fix_invalid_escape_with_escaped_quote() {
let input = "command = \"it's a \\\"test\\\" \\<value\\>\"";
let result = fix_invalid_toml_escapes(input);
assert!(result.contains("\\\\\"test\\\\\""));
assert!(result.contains("\\\\<value\\\\>"));
}
#[test]
fn test_fix_invalid_escape_in_single_quoted() {
let input = r#"command = 'echo \<test\>'"#;
let result = fix_invalid_toml_escapes(input);
assert_eq!(result, input);
}
#[test]
fn test_multiline_string_ignored() {
let input = "key = \"no escapes here\"";
let result = fix_invalid_toml_escapes(input);
assert_eq!(result, input);
let input2 = "command = \"echo hello\"";
let result2 = fix_invalid_toml_escapes(input2);
assert_eq!(result2, input2);
}
}