nginx_config/
value.rs

1use std::mem;
2use std::str::FromStr;
3
4use combine::easy::Error;
5use combine::error::StreamError;
6
7use format::{Displayable, Formatter};
8use position::Pos;
9use tokenizer::Token;
10
11/// Generic string value
12///
13/// It may consist of strings and variable references
14///
15/// Some string parts might originally be escaped or quoted. We get rid of
16/// quotes when parsing
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct Value {
19    position: Pos,
20    pub(crate) data: Vec<Item>,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub(crate) enum Item {
25    Literal(String),
26    Variable(String),
27}
28
29
30impl Value {
31    pub(crate) fn parse<'a>(position: Pos, tok: Token<'a>)
32        -> Result<Value, Error<Token<'a>, Token<'a>>>
33    {
34        Value::parse_str(position, tok.value)
35    }
36    pub(crate) fn parse_str<'a>(position: Pos, token: &str)
37        -> Result<Value, Error<Token<'a>, Token<'a>>>
38    {
39        let data = if token.starts_with('"') {
40            Value::scan_quoted('"', token)?
41        } else if token.starts_with("'") {
42            Value::scan_quoted('\'', token)?
43        } else {
44            Value::scan_raw(token)?
45        };
46        Ok(Value { position, data })
47    }
48
49    fn scan_raw<'a>(value: &str)
50        -> Result<Vec<Item>, Error<Token<'a>, Token<'a>>>
51    {
52        use self::Item::*;
53        let mut buf = Vec::new();
54        let mut chiter = value.char_indices().peekable();
55        let mut prev_char = ' ';  // any having no special meaning
56        let mut cur_slice = 0;
57        // TODO(unquote) single and double quotes
58        while let Some((idx, cur_char)) = chiter.next() {
59            match cur_char {
60                _ if prev_char == '\\' => {
61                    prev_char = ' ';
62                    continue;
63                }
64                '$' => {
65                    let vstart = idx + 1;
66                    if idx != cur_slice {
67                        buf.push(Literal(value[cur_slice..idx].to_string()));
68                    }
69                    let fchar = chiter.next().map(|(_, c)| c)
70                        .ok_or_else(|| Error::unexpected_message(
71                            "bare $ in expression"))?;
72                    match fchar {
73                        '{' => {
74                            while let Some(&(_, c)) = chiter.peek() {
75                                match c {
76                                    'a'...'z' | 'A'...'Z' | '_' | '0'...'9'
77                                    => chiter.next(),
78                                    '}' => break,
79                                    _ => {
80                                        return Err(Error::expected("}".into()));
81                                    }
82                                };
83                            }
84                            let now = chiter.peek().map(|&(idx, _)| idx)
85                                .unwrap();
86                            buf.push(Variable(
87                                value[vstart+1..now].to_string()));
88                            cur_slice = now+1;
89                        }
90                        'a'...'z' | 'A'...'Z' | '_' | '0'...'9' => {
91                            while let Some(&(_, c)) = chiter.peek() {
92                                match c {
93                                    'a'...'z' | 'A'...'Z' | '_' | '0'...'9'
94                                    => chiter.next(),
95                                    _ => break,
96                                };
97                            }
98                            let now = chiter.peek().map(|&(idx, _)| idx)
99                                .unwrap_or(value.len());
100                            buf.push(Variable(
101                                value[vstart..now].to_string()));
102                            cur_slice = now;
103                        }
104                        _ => {
105                            return Err(Error::unexpected_message(
106                                format!("variable name starts with \
107                                    bad char {:?}", fchar)));
108                        }
109                    }
110                }
111                _ => {}
112            }
113            prev_char = cur_char;
114        }
115        if cur_slice != value.len() {
116            buf.push(Literal(value[cur_slice..].to_string()));
117        }
118        Ok(buf)
119    }
120
121    fn scan_quoted<'a>(quote: char, value: &str)
122        -> Result<Vec<Item>, Error<Token<'a>, Token<'a>>>
123    {
124        use self::Item::*;
125        let mut buf = Vec::new();
126        let mut chiter = value.char_indices().peekable();
127        chiter.next(); // skip quote
128        let mut prev_char = ' ';  // any having no special meaning
129        let mut cur_slice = String::new();
130        while let Some((idx, cur_char)) = chiter.next() {
131            match cur_char {
132                _ if prev_char == '\\' => {
133                    cur_slice.push(cur_char);
134                    continue;
135                }
136                '"' | '\'' if cur_char == quote => {
137                    if cur_slice.len() > 0 {
138                        buf.push(Literal(cur_slice));
139                    }
140                    if idx + 1 != value.len() {
141                        // TODO(tailhook) figure out maybe this is actually a
142                        // tokenizer error, or maybe make this cryptic message
143                        // better
144                        return Err(Error::unexpected_message(
145                            "quote closes prematurely"));
146                    }
147                    return Ok(buf);
148                }
149                '$' => {
150                    let vstart = idx + 1;
151                    if cur_slice.len() > 0 {
152                        buf.push(Literal(
153                            mem::replace(&mut cur_slice, String::new())));
154                    }
155                    let fchar = chiter.next().map(|(_, c)| c)
156                        .ok_or_else(|| Error::unexpected_message(
157                            "bare $ in expression"))?;
158                    match fchar {
159                        '{' => {
160                            unimplemented!();
161                        }
162                        'a'...'z' | 'A'...'Z' | '_' | '0'...'9' => {
163                            while let Some(&(_, c)) = chiter.peek() {
164                                match c {
165                                    'a'...'z' | 'A'...'Z' | '_' | '0'...'9'
166                                    => chiter.next(),
167                                    _ => break,
168                                };
169                            }
170                            let now = chiter.peek().map(|&(idx, _)| idx)
171                                .ok_or_else(|| {
172                                    Error::unexpected_message("unclosed quote")
173                                })?;
174                            buf.push(Variable(
175                                value[vstart..now].to_string()));
176                        }
177                        _ => {
178                            return Err(Error::unexpected_message(
179                                format!("variable name starts with \
180                                    bad char {:?}", fchar)));
181                        }
182                    }
183                }
184                _ => cur_slice.push(cur_char),
185            }
186            prev_char = cur_char;
187        }
188        return Err(Error::unexpected_message("unclosed quote"));
189    }
190}
191
192impl FromStr for Value {
193    type Err = String;
194    fn from_str(s: &str) -> Result<Value, String> {
195        Value::parse_str(Pos { line: 0, column: 0 }, s)
196        .map_err(|e| e.to_string())
197    }
198}
199
200impl Value {
201    fn has_specials(&self) -> bool {
202        use self::Item::*;
203        for item in &self.data {
204            match *item {
205                Literal(ref x) => {
206                    for c in x.chars() {
207                        match c {
208                            ' ' | ';' | '\r' | '\n' | '\t' | '{' | '}' => {
209                                return true;
210                            }
211                            _ => {}
212                        }
213                    }
214                }
215                Variable(_) => {}
216            }
217        }
218        return false;
219    }
220
221    /// Replace variable references in this string with literal values
222    pub fn replace_vars<'a, F, S>(&mut self, mut f: F)
223        where F: FnMut(&str) -> Option<S>,
224              S: AsRef<str> + Into<String> + 'a,
225    {
226        use self::Item::*;
227        // TODO(tailhook) join literal blocks
228        for item in &mut self.data {
229            let new_value = match *item {
230                Literal(..) => continue,
231                Variable(ref name) => match f(name) {
232                    Some(value) => value.into(),
233                    None => continue,
234                },
235            };
236            *item = Literal(new_value);
237        }
238    }
239}
240
241fn next_alphanum(data: &Vec<Item>, index: usize) -> bool {
242    use self::Item::*;
243    data.get(index+1).and_then(|item| {
244        match item {
245            Literal(s) => Some(s),
246            Variable(_) => None,
247        }
248    }).and_then(|s| {
249        s.chars().next().map(|c| c.is_alphanumeric())
250    }).unwrap_or(false)
251}
252
253impl Displayable for Value {
254    fn display(&self, f: &mut Formatter) {
255        use self::Item::*;
256        if self.data.is_empty() || self.has_specials() {
257            f.write("\"");
258            for (index, item) in self.data.iter().enumerate() {
259                match *item {
260                    // TODO(tailhook) escape special chars
261                    Literal(ref v) => f.write(v),
262                    Variable(ref v) if next_alphanum(&self.data, index) => {
263                        f.write("${");
264                        f.write(v);
265                        f.write("}");
266                    }
267                    Variable(ref v) => {
268                        f.write("$");
269                        f.write(v);
270                    }
271                }
272            }
273            f.write("\"");
274        } else {
275            for (index, item) in self.data.iter().enumerate() {
276                match *item {
277                    Literal(ref v) => f.write(v),
278                    Variable(ref v) if next_alphanum(&self.data, index) => {
279                        f.write("${");
280                        f.write(v);
281                        f.write("}");
282                    }
283                    Variable(ref v) => {
284                        f.write("$");
285                        f.write(v);
286                    }
287                }
288            }
289        }
290    }
291}