#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EscapeContext {
Python,
JavaScript,
Ruby,
Php,
Rust,
}
#[must_use]
pub fn escape_quotes(s: &str, context: EscapeContext) -> String {
match context {
EscapeContext::Python => {
s.replace('\\', "\\\\").replace('\'', "\\'")
}
EscapeContext::JavaScript => {
s.replace('\\', "\\\\").replace('\'', "\\'")
}
EscapeContext::Ruby => {
s.replace('\\', "\\\\").replace('\'', "\\'")
}
EscapeContext::Php => {
s.replace('\\', "\\\\").replace('\'', "\\'")
}
EscapeContext::Rust => {
s.replace('\\', "\\\\").replace('\'', "\\'")
}
}
}
#[must_use]
pub fn escape_double_quotes(s: &str, context: EscapeContext) -> String {
match context {
EscapeContext::Python | EscapeContext::Rust => {
s.replace('\\', "\\\\").replace('"', "\\\"")
}
EscapeContext::JavaScript => {
s.replace('\\', "\\\\").replace('"', "\\\"")
}
EscapeContext::Ruby => {
s.replace('\\', "\\\\").replace('"', "\\\"")
}
EscapeContext::Php => {
s.replace('\\', "\\\\").replace('"', "\\\"")
}
}
}
#[must_use]
pub fn escape_template_literal(s: &str, context: EscapeContext) -> String {
match context {
EscapeContext::JavaScript => {
s.replace('\\', "\\\\").replace('`', "\\`").replace('$', "\\$")
}
EscapeContext::Python | EscapeContext::Ruby | EscapeContext::Php | EscapeContext::Rust => {
s.replace('\\', "\\\\").replace('"', "\\\"")
}
}
}
#[must_use]
pub fn escape_for_docstring(s: &str, context: EscapeContext) -> String {
match context {
EscapeContext::Python => {
s.replace("\"\"\"", "\" \" \"")
}
EscapeContext::JavaScript => {
s.replace("*/", "*\\/").replace('\\', "\\\\").replace('"', "\\\"")
}
EscapeContext::Ruby => {
s.replace('\\', "\\\\").replace('"', "\\\"")
}
EscapeContext::Php => {
s.replace("*/", "*\\/").replace('\\', "\\\\").replace('"', "\\\"")
}
EscapeContext::Rust => {
s.replace('\\', "\\\\").replace('"', "\\\"")
}
}
}
#[must_use]
pub fn escape_graphql_sdl_description(s: &str, _context: EscapeContext) -> String {
s.replace("\"\"\"", "\\\"\\\"\\\"")
}
#[must_use]
pub fn escape_graphql_string(s: &str, _context: EscapeContext) -> String {
s.replace('\\', "\\\\").replace('"', "\\\"")
}
#[must_use]
pub fn escape_json_string(s: &str, _context: EscapeContext) -> String {
let mut result = String::new();
for ch in s.chars() {
match ch {
'"' => result.push_str("\\\""),
'\\' => result.push_str("\\\\"),
'\n' => result.push_str("\\n"),
'\r' => result.push_str("\\r"),
'\t' => result.push_str("\\t"),
'\x08' => result.push_str("\\b"), '\x0C' => result.push_str("\\f"), c if c.is_control() => {
result.push_str(&format!("\\u{:04x}", c as u32));
}
c => result.push(c),
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
mod python {
use super::*;
#[test]
fn test_escape_quotes_simple() {
assert_eq!(escape_quotes("hello", EscapeContext::Python), "hello");
}
#[test]
fn test_escape_quotes_single_quote() {
assert_eq!(escape_quotes("it's a string", EscapeContext::Python), "it\\'s a string");
}
#[test]
fn test_escape_quotes_backslash() {
assert_eq!(
escape_quotes("path\\to\\file", EscapeContext::Python),
"path\\\\to\\\\file"
);
}
#[test]
fn test_escape_double_quotes() {
assert_eq!(
escape_double_quotes("say \"hello\"", EscapeContext::Python),
"say \\\"hello\\\""
);
}
#[test]
fn test_escape_for_docstring_triple_quotes() {
let result = escape_for_docstring("Description with \"\"\" in it", EscapeContext::Python);
assert!(!result.contains("\"\"\""));
assert_eq!(result, "Description with \" \" \" in it");
}
#[test]
fn test_escape_graphql_sdl_description() {
let result = escape_graphql_sdl_description("Has \"\"\" in description", EscapeContext::Python);
assert!(result.contains("\\\"\\\"\\\""));
assert_eq!(result, "Has \\\"\\\"\\\" in description");
}
#[test]
fn test_escape_docstring_multiple_triple_quotes() {
let result = escape_for_docstring("First \"\"\" and second \"\"\"", EscapeContext::Python);
assert!(!result.contains("\"\"\""));
}
}
mod php {
use super::*;
#[test]
fn test_escape_quotes_simple() {
assert_eq!(escape_quotes("hello", EscapeContext::Php), "hello");
}
#[test]
fn test_escape_quotes_single_quote() {
assert_eq!(escape_quotes("it's a string", EscapeContext::Php), "it\\'s a string");
}
#[test]
fn test_escape_quotes_backslash() {
assert_eq!(
escape_quotes("path\\to\\file", EscapeContext::Php),
"path\\\\to\\\\file"
);
}
#[test]
fn test_escape_double_quotes() {
assert_eq!(
escape_double_quotes("say \"hello\"", EscapeContext::Php),
"say \\\"hello\\\""
);
}
#[test]
fn test_escape_quotes_combined() {
assert_eq!(escape_quotes("path\\it's", EscapeContext::Php), "path\\\\it\\'s");
}
}
mod javascript {
use super::*;
#[test]
fn test_escape_quotes_simple() {
assert_eq!(escape_quotes("hello", EscapeContext::JavaScript), "hello");
}
#[test]
fn test_escape_quotes_single_quote() {
assert_eq!(
escape_quotes("it's a string", EscapeContext::JavaScript),
"it\\'s a string"
);
}
#[test]
fn test_escape_template_literal_backtick() {
assert_eq!(
escape_template_literal("say `hi`", EscapeContext::JavaScript),
"say \\`hi\\`"
);
}
#[test]
fn test_escape_template_literal_dollar_sign() {
assert_eq!(
escape_template_literal("hello $name", EscapeContext::JavaScript),
"hello \\$name"
);
}
#[test]
fn test_escape_template_literal_combined() {
assert_eq!(
escape_template_literal("say `$name`", EscapeContext::JavaScript),
"say \\`\\$name\\`"
);
}
#[test]
fn test_escape_for_docstring_jsdoc() {
let result = escape_for_docstring("Some text */", EscapeContext::JavaScript);
assert!(!result.contains("*/"));
}
}
mod ruby {
use super::*;
#[test]
fn test_escape_quotes_simple() {
assert_eq!(escape_quotes("hello", EscapeContext::Ruby), "hello");
}
#[test]
fn test_escape_quotes_single_quote() {
assert_eq!(escape_quotes("it's a string", EscapeContext::Ruby), "it\\'s a string");
}
#[test]
fn test_escape_quotes_backslash() {
assert_eq!(
escape_quotes("path\\to\\file", EscapeContext::Ruby),
"path\\\\to\\\\file"
);
}
#[test]
fn test_escape_double_quotes() {
assert_eq!(
escape_double_quotes("say \"hello\"", EscapeContext::Ruby),
"say \\\"hello\\\""
);
}
}
mod json {
use super::*;
#[test]
fn test_escape_json_simple() {
assert_eq!(escape_json_string("hello", EscapeContext::Rust), "hello");
}
#[test]
fn test_escape_json_double_quote() {
assert_eq!(
escape_json_string("say \"hello\"", EscapeContext::Rust),
"say \\\"hello\\\""
);
}
#[test]
fn test_escape_json_newline() {
assert_eq!(escape_json_string("line1\nline2", EscapeContext::Rust), "line1\\nline2");
}
#[test]
fn test_escape_json_tab() {
assert_eq!(escape_json_string("col1\tcol2", EscapeContext::Rust), "col1\\tcol2");
}
#[test]
fn test_escape_json_combined() {
let result = escape_json_string("path\\to\\file with \"quotes\"\nand\ttabs", EscapeContext::Rust);
assert!(result.contains("\\\\"));
assert!(result.contains("\\\""));
assert!(result.contains("\\n"));
assert!(result.contains("\\t"));
}
}
mod graphql {
use super::*;
#[test]
fn test_escape_graphql_string() {
assert_eq!(
escape_graphql_string("hello \"world\"", EscapeContext::Rust),
"hello \\\"world\\\""
);
}
#[test]
fn test_escape_graphql_backslash() {
assert_eq!(
escape_graphql_string("path\\to\\file", EscapeContext::Rust),
"path\\\\to\\\\file"
);
}
}
mod consistency {
use super::*;
#[test]
fn test_all_contexts_handle_empty_string() {
for context in &[
EscapeContext::Python,
EscapeContext::JavaScript,
EscapeContext::Ruby,
EscapeContext::Php,
EscapeContext::Rust,
] {
assert_eq!(escape_quotes("", *context), "");
assert_eq!(escape_double_quotes("", *context), "");
}
}
#[test]
fn test_all_contexts_escape_backslash() {
for context in &[
EscapeContext::Python,
EscapeContext::JavaScript,
EscapeContext::Ruby,
EscapeContext::Php,
EscapeContext::Rust,
] {
assert!(escape_quotes("\\", *context).contains("\\\\"));
}
}
#[test]
fn test_docstring_all_contexts() {
for context in &[
EscapeContext::Python,
EscapeContext::JavaScript,
EscapeContext::Ruby,
EscapeContext::Php,
EscapeContext::Rust,
] {
let result = escape_for_docstring("test string", *context);
assert!(!result.is_empty());
}
}
}
}