pub fn unescape(s: &str) -> String {
let mut result = String::with_capacity(s.len());
let mut chars = s.chars().peekable();
while let Some(ch) = chars.next() {
if ch == '\\' {
match chars.next() {
Some('n') => result.push('\n'),
Some('t') => result.push('\t'),
Some('r') => result.push('\r'),
Some('\\') => result.push('\\'),
Some('"') => result.push('"'),
Some('\'') => result.push('\''),
Some('0') => result.push('\0'),
Some(other) => {
result.push('\\');
result.push(other);
}
None => result.push('\\'),
}
} else {
result.push(ch);
}
}
result
}
pub fn process_triple_quoted_string(s: &str) -> String {
let lines: Vec<&str> = s.lines().collect();
let first_non_empty = lines.iter().position(|line| !line.trim().is_empty());
let last_non_empty = lines.iter().rposition(|line| !line.trim().is_empty());
let (start, end) = match (first_non_empty, last_non_empty) {
(Some(s), Some(e)) => (s, e),
_ => return String::new(), };
let content_lines = &lines[start..=end];
let min_indent = content_lines
.iter()
.filter(|line| !line.trim().is_empty())
.map(|line| line.len() - line.trim_start().len())
.min()
.unwrap_or(0);
let dedented: Vec<String> = content_lines
.iter()
.map(|line| {
if line.len() >= min_indent {
line[min_indent..].to_string()
} else {
line.trim_start().to_string()
}
})
.collect();
let joined = dedented.join("\n");
unescape(&joined)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_escapes() {
assert_eq!(unescape(r"Hello\nWorld"), "Hello\nWorld");
assert_eq!(unescape(r"Hello\tWorld"), "Hello\tWorld");
assert_eq!(unescape(r"Hello\rWorld"), "Hello\rWorld");
}
#[test]
fn test_quote_escapes() {
assert_eq!(unescape(r#"Say \"Hello\""#), "Say \"Hello\"");
assert_eq!(unescape(r"It\'s"), "It's");
}
#[test]
fn test_backslash_escape() {
assert_eq!(unescape(r"Path\\to\\file"), "Path\\to\\file");
}
#[test]
fn test_null_escape() {
assert_eq!(unescape(r"Null\0char"), "Null\0char");
}
#[test]
fn test_no_escapes() {
assert_eq!(unescape("Plain text"), "Plain text");
}
#[test]
fn test_unknown_escape() {
assert_eq!(unescape(r"\x"), "\\x");
}
#[test]
fn test_trailing_backslash() {
assert_eq!(unescape(r"ends with \"), "ends with \\");
}
#[test]
fn test_multiple_escapes() {
assert_eq!(unescape(r"Line1\nLine2\nLine3"), "Line1\nLine2\nLine3");
assert_eq!(unescape(r"\t\t\tThree tabs"), "\t\t\tThree tabs");
}
#[test]
fn test_mixed_escapes() {
assert_eq!(
unescape(r#"Name:\t\"Alice\"\nAge:\t25"#),
"Name:\t\"Alice\"\nAge:\t25"
);
}
}