use std::borrow::Cow;
#[allow(unused_imports, deprecated)]
use std::ascii::AsciiExt;
use error::CoreError;
use spec::{
QuotingClassifier,
QuotingClass,
WithoutQuotingValidator,
PartialCodePoint,
GeneralQSSpec
};
#[inline]
pub fn quote<Spec: GeneralQSSpec>(
input: &str
) -> Result<String, CoreError>
{
let mut out = String::with_capacity(input.len()+2);
out.push('"');
quote_inner::<Spec>(input, &mut out)?;
out.push('"');
Ok(out)
}
fn quote_inner<Spec: GeneralQSSpec>(
input: &str,
out: &mut String,
) -> Result<(), CoreError>
{
use self::QuotingClass::*;
for ch in input.chars() {
match Spec::Quoting::classify_for_quoting(PartialCodePoint::from_code_point(ch as u32)) {
QText => out.push(ch),
NeedsQuoting => { out.push('\\'); out.push(ch); }
Invalid => return Err(CoreError::InvalidChar)
}
}
Ok(())
}
pub fn quote_if_needed<'a, Spec, WQImpl>(
input: &'a str,
validator: &mut WQImpl
) -> Result<Cow<'a, str>, CoreError>
where Spec: GeneralQSSpec,
WQImpl: WithoutQuotingValidator
{
let mut needs_quoting_from = None;
for (idx, ch) in input.char_indices() {
let pcp = PartialCodePoint::from_code_point(ch as u32);
if !validator.next(pcp) {
needs_quoting_from = Some(idx);
break;
} else {
#[cfg(debug_assertions)]
{
use self::QuotingClass::*;
match Spec::Quoting::classify_for_quoting(pcp) {
QText => {},
Invalid => panic!(concat!("[BUG] representable without quoted string,",
"but invalid in quoted string: {}"), ch),
NeedsQuoting => panic!(concat!("[BUG] representable without quoted string,",
"but not without escape in quoted string: {}"), ch)
}
}
}
}
let start_quoting_from =
if input.len() == 0 {
0
} else if let Some(offset) = needs_quoting_from {
offset
} else {
return if validator.end() {
Ok(Cow::Borrowed(input))
} else {
let mut out = String::with_capacity(input.len() + 2);
out.push('"');
out.push_str(input);
out.push('"');
Ok(Cow::Owned(out))
};
};
let mut out = String::with_capacity(input.len() + 3);
out.push('"');
out.push_str(&input[0..start_quoting_from]);
quote_inner::<Spec>(&input[start_quoting_from..], &mut out)?;
out.push('"');
Ok(Cow::Owned(out))
}
#[cfg(test)]
mod test {
#[allow(warnings)]
use std::ascii::AsciiExt;
use test_utils::*;
use error::CoreError;
use super::*;
#[test]
fn quote_simple() {
let data = &[
("this is simple", "\"this is simple\""),
("with quotes\" ", "\"with quotes\\\" \""),
("with slash\\ ", "\"with slash\\\\ \"")
];
for &(unquoted, quoted) in data.iter() {
let got_quoted = quote::<TestSpec>(unquoted).unwrap();
assert_eq!(got_quoted, quoted);
}
}
#[test]
fn quote_unquotable() {
let res = quote::<TestSpec>("→");
assert_eq!(res.unwrap_err(), CoreError::InvalidChar);
}
#[test]
fn quote_if_needed_unneeded() {
let mut without_quoting = TestUnquotedValidator::new();
let out= quote_if_needed::<TestSpec, _>("abcdef", &mut without_quoting).unwrap();
assert_eq!(out, Cow::Borrowed("abcdef"));
}
#[test]
fn quote_if_needed_state() {
let mut without_quoting = TestUnquotedValidator::new();
let out = quote_if_needed::<TestSpec, _>("abcd.e", &mut without_quoting).unwrap();
assert_eq!(out, Cow::Borrowed("abcd.e"));
assert_eq!(without_quoting.count, 6);
assert_eq!(without_quoting.last_was_dot, false)
}
#[test]
fn quote_if_needed_needed_because_char() {
let mut without_quoting = TestUnquotedValidator::new();
let out = quote_if_needed::<TestSpec, _>("ab def", &mut without_quoting).unwrap();
let expected: Cow<'static, str> = Cow::Owned("\"ab def\"".into());
assert_eq!(out, expected);
assert!(without_quoting.count >= 2);
}
#[test]
fn quote_if_needed_needed_because_state() {
let mut without_quoting = TestUnquotedValidator::new();
let out = quote_if_needed::<TestSpec, _>("abc..f", &mut without_quoting).unwrap();
let expected: Cow<'static, str> = Cow::Owned("\"abc..f\"".into());
assert_eq!(out, expected);
assert!(without_quoting.count >= 4);
}
#[test]
fn quote_if_needed_needed_because_end() {
let mut without_quoting = TestUnquotedValidator::new();
let out = quote_if_needed::<TestSpec, _>("a", &mut without_quoting).unwrap();
let expected: Cow<'static, str> = Cow::Owned("\"a\"".into());
assert_eq!(out, expected);
assert!(without_quoting.count >= 1);
}
#[test]
fn quote_if_needed_empty_value() {
let mut without_quoting = TestUnquotedValidator::new();
let out = quote_if_needed::<TestSpec, _>("", &mut without_quoting).unwrap();
let expected: Cow<'static, str> = Cow::Owned("\"\"".into());
assert_eq!(out, expected);
assert_eq!(without_quoting.count, 0);
}
}