quoted_string/
quote.rs

1use std::borrow::Cow;
2// this import will become unused in future rust versions
3// but won't be removed for now for supporting current
4// rust versions
5#[allow(unused_imports, deprecated)]
6use std::ascii::AsciiExt;
7
8use error::CoreError;
9use spec::{
10    QuotingClassifier,
11    QuotingClass,
12    WithoutQuotingValidator,
13    PartialCodePoint,
14    GeneralQSSpec
15};
16
17
18
19/// quotes the input string returning the quoted string
20///
21/// # Example
22///
23/// ```
24/// // use your own Spec instead
25/// use quoted_string::test_utils::TestSpec;
26/// use quoted_string::quote;
27///
28/// let qs = quote::<TestSpec>("some\"text").unwrap();
29/// assert_eq!(qs, "\"some\\\"text\"");
30/// ```
31///
32#[inline]
33pub fn quote<Spec: GeneralQSSpec>(
34    input: &str
35) -> Result<String, CoreError>
36{
37    let mut out = String::with_capacity(input.len()+2);
38    out.push('"');
39    quote_inner::<Spec>(input, &mut out)?;
40    out.push('"');
41    Ok(out)
42}
43
44/// quotes a input writing it into the output buffer, does not add surrounding '"'
45///
46/// if ascii_only is true and non ascii chars a found an error is returned.
47///
48/// If no error is returned a boolean indicating if the whole input was ascii is
49/// returned.
50fn quote_inner<Spec: GeneralQSSpec>(
51    input: &str,
52    out: &mut String,
53) -> Result<(), CoreError>
54{
55    use self::QuotingClass::*;
56    for ch in input.chars() {
57        match Spec::Quoting::classify_for_quoting(PartialCodePoint::from_code_point(ch as u32)) {
58            QText => out.push(ch),
59            NeedsQuoting => { out.push('\\'); out.push(ch); }
60            Invalid => return Err(CoreError::InvalidChar)
61        }
62    }
63    Ok(())
64}
65
66/// quotes the input string if needed
67///
68/// The `validator` decides if the value is valid without
69/// quoting it, the `Spec` type decides how quoting is done if
70/// needed. The `Spec` only specifies the format of quoting
71/// e.g. which values are allowed in a quoted-string but
72/// wether or not a string needs quoting can often depend
73/// on additional factor.
74///
75/// Note that this implementation expects the
76/// validator and spec to be in sync, i.e. what
77/// is valid without quoting does not need to
78/// be escaped when appearing in a quoted string.
79///
80/// # Example
81///
82/// ```
83/// # use std::borrow::Cow;
84/// // use your own Spec
85/// use quoted_string::test_utils::{TestSpec, TestUnquotedValidator};
86/// use quoted_string::quote_if_needed;
87///
88/// let mut without_quoting = TestUnquotedValidator::new();
89/// let quoted = quote_if_needed::<TestSpec, _>("simple", &mut without_quoting)
90///     .expect("only fails if input can not be represented as quoted string with used Spec");
91///
92/// // The used spec states a 6 character us-ascii word does not need to be represented as
93/// // quoted string
94/// assert_eq!(quoted, Cow::Borrowed("simple"));
95///
96/// let mut without_quoting = TestUnquotedValidator::new();
97/// let quoted2 = quote_if_needed::<TestSpec, _>("more complex", &mut without_quoting).unwrap();
98/// let expected: Cow<'static, str> = Cow::Owned("\"more complex\"".into());
99/// assert_eq!(quoted2, expected);
100/// ```
101///
102pub fn quote_if_needed<'a, Spec, WQImpl>(
103    input: &'a str,
104    validator: &mut WQImpl
105) -> Result<Cow<'a, str>, CoreError>
106    where Spec: GeneralQSSpec,
107          WQImpl: WithoutQuotingValidator
108{
109    let mut needs_quoting_from = None;
110    for (idx, ch) in input.char_indices() {
111        let pcp = PartialCodePoint::from_code_point(ch as u32);
112        if !validator.next(pcp) {
113            needs_quoting_from = Some(idx);
114            break;
115        } else {
116            //FIXME check if is this even enabled in the right context
117            #[cfg(debug_assertions)]
118            {
119                use self::QuotingClass::*;
120                match Spec::Quoting::classify_for_quoting(pcp) {
121                    QText => {},
122                    Invalid => panic!(concat!("[BUG] representable without quoted string,",
123                                            "but invalid in quoted string: {}"), ch),
124                    NeedsQuoting => panic!(concat!("[BUG] representable without quoted string,",
125                                            "but not without escape in quoted string: {}"), ch)
126                }
127            }
128        }
129    }
130
131    let start_quoting_from =
132        if input.len() == 0 {
133            0
134        } else if let Some(offset) = needs_quoting_from {
135            offset
136        } else {
137            return if validator.end() {
138                Ok(Cow::Borrowed(input))
139            } else {
140                let mut out = String::with_capacity(input.len() + 2);
141                out.push('"');
142                out.push_str(input);
143                out.push('"');
144                Ok(Cow::Owned(out))
145            };
146        };
147
148
149    let mut out = String::with_capacity(input.len() + 3);
150    out.push('"');
151    out.push_str(&input[0..start_quoting_from]);
152    quote_inner::<Spec>(&input[start_quoting_from..], &mut out)?;
153    out.push('"');
154    Ok(Cow::Owned(out))
155}
156
157
158#[cfg(test)]
159mod test {
160    // this import will become unused in future rust versions
161    // but won't be removed for now for supporting current
162    // rust versions
163    #[allow(warnings)]
164    use std::ascii::AsciiExt;
165    use test_utils::*;
166    use error::CoreError;
167    use super::*;
168
169    #[test]
170    fn quote_simple() {
171        let data = &[
172            ("this is simple", "\"this is simple\""),
173            ("with quotes\"  ", "\"with quotes\\\"  \""),
174            ("with slash\\  ", "\"with slash\\\\  \"")
175        ];
176        for &(unquoted, quoted) in data.iter() {
177            let got_quoted = quote::<TestSpec>(unquoted).unwrap();
178            assert_eq!(got_quoted, quoted);
179        }
180    }
181
182    #[test]
183    fn quote_unquotable() {
184        let res = quote::<TestSpec>("→");
185        assert_eq!(res.unwrap_err(), CoreError::InvalidChar);
186    }
187
188    #[test]
189    fn quote_if_needed_unneeded() {
190        let mut without_quoting = TestUnquotedValidator::new();
191        let out= quote_if_needed::<TestSpec, _>("abcdef", &mut without_quoting).unwrap();
192        assert_eq!(out, Cow::Borrowed("abcdef"));
193    }
194
195    #[test]
196    fn quote_if_needed_state() {
197        let mut without_quoting = TestUnquotedValidator::new();
198        let out = quote_if_needed::<TestSpec, _>("abcd.e", &mut without_quoting).unwrap();
199        assert_eq!(out, Cow::Borrowed("abcd.e"));
200        assert_eq!(without_quoting.count, 6);
201        assert_eq!(without_quoting.last_was_dot, false)
202    }
203
204    #[test]
205    fn quote_if_needed_needed_because_char() {
206        let mut without_quoting = TestUnquotedValidator::new();
207        let out = quote_if_needed::<TestSpec, _>("ab def", &mut without_quoting).unwrap();
208        let expected: Cow<'static, str> = Cow::Owned("\"ab def\"".into());
209        assert_eq!(out, expected);
210        assert!(without_quoting.count >= 2);
211    }
212
213    #[test]
214    fn quote_if_needed_needed_because_state() {
215        let mut without_quoting = TestUnquotedValidator::new();
216        let out = quote_if_needed::<TestSpec, _>("abc..f", &mut without_quoting).unwrap();
217        let expected: Cow<'static, str> = Cow::Owned("\"abc..f\"".into());
218        assert_eq!(out, expected);
219        assert!(without_quoting.count >= 4);
220    }
221
222    #[test]
223    fn quote_if_needed_needed_because_end() {
224        let mut without_quoting = TestUnquotedValidator::new();
225        let out = quote_if_needed::<TestSpec, _>("a", &mut without_quoting).unwrap();
226        let expected: Cow<'static, str> = Cow::Owned("\"a\"".into());
227        assert_eq!(out, expected);
228        assert!(without_quoting.count >= 1);
229    }
230
231    #[test]
232    fn quote_if_needed_empty_value() {
233        let mut without_quoting = TestUnquotedValidator::new();
234        let out = quote_if_needed::<TestSpec, _>("", &mut without_quoting).unwrap();
235        let expected: Cow<'static, str> = Cow::Owned("\"\"".into());
236        assert_eq!(out, expected);
237        assert_eq!(without_quoting.count, 0);
238    }
239}