use std::fmt::{self, Write};
#[inline]
pub(crate) fn is_token(s: &str) -> bool {
!s.is_empty() && s.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'-')
}
pub(crate) fn write_escaped_text(
w: &mut impl Write,
s: &str,
escape_semicolon: bool,
) -> fmt::Result {
let bytes = s.as_bytes();
let mut start = 0;
let mut i = 0;
while i < bytes.len() {
let b = bytes[i];
let replacement = match b {
b'\\' => "\\\\",
b',' => "\\,",
b';' if escape_semicolon => "\\;",
b'\r' | b'\n' => "\\n",
_ => {
i += 1;
continue;
},
};
w.write_str(&s[start..i])?;
w.write_str(replacement)?;
if b == b'\r' && bytes.get(i + 1) == Some(&b'\n') {
i += 1;
}
i += 1;
start = i;
}
w.write_str(&s[start..])
}
pub(crate) fn unescape_text(s: &str) -> String {
let mut out = String::with_capacity(s.len());
let mut chars = s.chars();
while let Some(c) = chars.next() {
if c != '\\' {
out.push(c);
continue;
}
match chars.next() {
Some('\\') => out.push('\\'),
Some(',') => out.push(','),
Some(';') => out.push(';'),
Some('n') | Some('N') => out.push('\n'),
Some(other) => {
out.push('\\');
out.push(other);
},
None => out.push('\\'),
}
}
out
}
pub(crate) fn split_unescaped(s: &str, separator: u8) -> Vec<&str> {
debug_assert!(separator.is_ascii() && separator != b'\\');
let bytes = s.as_bytes();
let mut parts = Vec::new();
let mut start = 0;
let mut escaped = false;
for (i, b) in bytes.iter().enumerate() {
if escaped {
escaped = false;
} else if *b == b'\\' {
escaped = true;
} else if *b == separator {
parts.push(&s[start..i]);
start = i + 1;
}
}
parts.push(&s[start..]);
parts
}
pub(crate) fn write_caret_encoded(w: &mut impl Write, s: &str) -> fmt::Result {
let bytes = s.as_bytes();
let mut start = 0;
let mut i = 0;
while i < bytes.len() {
let b = bytes[i];
let replacement = match b {
b'^' => "^^",
b'"' => "^'",
b'\r' | b'\n' => "^n",
_ => {
i += 1;
continue;
},
};
w.write_str(&s[start..i])?;
w.write_str(replacement)?;
if b == b'\r' && bytes.get(i + 1) == Some(&b'\n') {
i += 1;
}
i += 1;
start = i;
}
w.write_str(&s[start..])
}
pub(crate) fn caret_decode(s: &str) -> String {
let mut out = String::with_capacity(s.len());
let mut chars = s.chars();
while let Some(c) = chars.next() {
if c != '^' {
out.push(c);
continue;
}
match chars.next() {
Some('^') => out.push('^'),
Some('\'') => out.push('"'),
Some('n') | Some('N') => out.push('\n'),
Some(other) => {
out.push('^');
out.push(other);
},
None => out.push('^'),
}
}
out
}
#[inline]
pub(crate) fn param_value_needs_quoting(s: &str) -> bool {
s.bytes().any(|b| matches!(b, b',' | b';' | b':'))
}
pub(crate) fn write_param_value(w: &mut impl Write, s: &str) -> fmt::Result {
if param_value_needs_quoting(s) {
w.write_char('"')?;
write_caret_encoded(w, s)?;
w.write_char('"')
} else {
write_caret_encoded(w, s)
}
}