use crate::shared::constants::{BACKSLASH, CARRIAGE_RETURN, DOUBLE_QUOTE, NEWLINE, TAB};
#[must_use]
pub fn escape_string(value: &str) -> String {
let mut out = String::with_capacity(value.len());
for ch in value.chars() {
match ch {
'\\' => {
out.push(BACKSLASH);
out.push(BACKSLASH);
}
'"' => {
out.push(BACKSLASH);
out.push(DOUBLE_QUOTE);
}
'\n' => {
out.push(BACKSLASH);
out.push('n');
}
'\r' => {
out.push(BACKSLASH);
out.push('r');
}
'\t' => {
out.push(BACKSLASH);
out.push('t');
}
_ => out.push(ch),
}
}
out
}
pub fn unescape_string(value: &str) -> Result<String, String> {
let mut out = String::with_capacity(value.len());
let mut chars = value.chars();
while let Some(ch) = chars.next() {
if ch == BACKSLASH {
let next = chars
.next()
.ok_or_else(|| "Invalid escape sequence: backslash at end of string".to_string())?;
match next {
'n' => out.push(NEWLINE),
't' => out.push(TAB),
'r' => out.push(CARRIAGE_RETURN),
'\\' => out.push(BACKSLASH),
'"' => out.push(DOUBLE_QUOTE),
other => {
return Err(format!("Invalid escape sequence: \\{other}"));
}
}
} else {
out.push(ch);
}
}
Ok(out)
}
#[must_use]
pub fn find_closing_quote(content: &str, start: usize) -> Option<usize> {
let bytes = content.as_bytes();
if start >= bytes.len() {
return None;
}
let mut i = start + 1;
while i < bytes.len() {
if bytes[i] == BACKSLASH as u8 && i + 1 < bytes.len() {
i += 2;
continue;
}
if bytes[i] == DOUBLE_QUOTE as u8 {
return Some(i);
}
i += 1;
}
None
}
#[must_use]
pub fn find_unquoted_char(content: &str, target: char, start: usize) -> Option<usize> {
if !target.is_ascii() {
return None;
}
let target_byte = target as u8;
let bs_byte = BACKSLASH as u8;
let dq_byte = DOUBLE_QUOTE as u8;
let bytes = content.as_bytes();
let mut i = start;
let mut in_quotes = false;
while i < bytes.len() {
let b = bytes[i];
if in_quotes && b == bs_byte && i + 1 < bytes.len() {
i += 2;
continue;
}
if b == dq_byte {
in_quotes = !in_quotes;
i += 1;
continue;
}
if b == target_byte && !in_quotes {
return Some(i);
}
i += 1;
}
None
}