pub fn quote(value: &str) -> String {
if value.is_empty() {
return "''".to_string();
}
if value.starts_with('\'') && value.ends_with('\'') && value.len() >= 2 {
let inner = &value[1..value.len() - 1];
if !inner.contains('\'') {
return value.to_string();
}
}
if value.starts_with('"') && value.ends_with('"') && value.len() > 2 {
return format!("'{}'", value);
}
let safe_pattern = regex::Regex::new(r"^[a-zA-Z0-9_\-./=,+@:]+$").unwrap();
if safe_pattern.is_match(value) {
return value.to_string();
}
format!("'{}'", value.replace('\'', "'\\''"))
}
pub fn quote_all(values: &[&str]) -> String {
values
.iter()
.map(|v| quote(v))
.collect::<Vec<_>>()
.join(" ")
}
pub fn needs_quoting(value: &str) -> bool {
if value.is_empty() {
return true;
}
let safe_pattern = regex::Regex::new(r"^[a-zA-Z0-9_\-./=,+@:]+$").unwrap();
!safe_pattern.is_match(value)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_quote_empty() {
assert_eq!(quote(""), "''");
}
#[test]
fn test_quote_safe_chars() {
assert_eq!(quote("hello"), "hello");
assert_eq!(quote("/path/to/file"), "/path/to/file");
assert_eq!(quote("file.txt"), "file.txt");
assert_eq!(quote("key=value"), "key=value");
assert_eq!(quote("user@host"), "user@host");
}
#[test]
fn test_quote_special_chars() {
assert_eq!(quote("hello world"), "'hello world'");
assert_eq!(quote("it's"), "'it'\\''s'");
assert_eq!(quote("$var"), "'$var'");
assert_eq!(quote("test*"), "'test*'");
}
#[test]
fn test_quote_already_quoted() {
assert_eq!(quote("'already quoted'"), "'already quoted'");
assert_eq!(quote("\"double quoted\""), "'\"double quoted\"'");
}
#[test]
fn test_quote_all() {
let args = vec!["echo", "hello world", "test"];
assert_eq!(quote_all(&args), "echo 'hello world' test");
}
#[test]
fn test_needs_quoting() {
assert!(!needs_quoting("hello"));
assert!(!needs_quoting("/path/to/file"));
assert!(needs_quoting("hello world"));
assert!(needs_quoting("$PATH"));
assert!(needs_quoting(""));
assert!(needs_quoting("test*"));
}
#[test]
fn test_quote_with_newlines() {
assert_eq!(quote("line1\nline2"), "'line1\nline2'");
}
#[test]
fn test_quote_with_tabs() {
assert_eq!(quote("col1\tcol2"), "'col1\tcol2'");
}
}