]> git.lizzy.rs Git - rust.git/blob - src/config/license.rs
Refactor parsing code into struct
[rust.git] / src / config / license.rs
1 use regex;
2
3 // the template is parsed using a state machine
4 enum ParsingState {
5     Lit,
6     LitEsc,
7     // the u32 keeps track of brace nesting
8     Re(u32),
9     ReEsc(u32),
10     Abort(String),
11 }
12
13 use self::ParsingState::*;
14
15 pub struct TemplateParser {
16     parsed: String,
17     buffer: String,
18     state: ParsingState,
19     linum: u32,
20     open_brace_line: u32,
21 }
22
23 impl TemplateParser {
24     fn new() -> Self {
25         Self {
26             parsed: "^".to_owned(),
27             buffer: String::new(),
28             state: Lit,
29             linum: 1,
30             // keeps track of last line on which a regex placeholder was started
31             open_brace_line: 0,
32         }
33     }
34
35     /// Convert a license template into a string which can be turned into a regex.
36     ///
37     /// The license template could use regex syntax directly, but that would require a lot of manual
38     /// escaping, which is inconvenient. It is therefore literal by default, with optional regex
39     /// subparts delimited by `{` and `}`. Additionally:
40     ///
41     /// - to insert literal `{`, `}` or `\`, escape it with `\`
42     /// - an empty regex placeholder (`{}`) is shorthand for `{.*?}`
43     ///
44     /// This function parses this input format and builds a properly escaped *string* representation
45     /// of the equivalent regular expression. It **does not** however guarantee that the returned
46     /// string is a syntactically valid regular expression.
47     ///
48     /// # Examples
49     ///
50     /// ```
51     /// # use rustfmt_config::license::TemplateParser;
52     /// assert_eq!(
53     ///     TemplateParser::parse(
54     ///         r"
55     /// // Copyright {\d+} The \} Rust \\ Project \{ Developers. See the {([A-Z]+)}
56     /// // file at the top-level directory of this distribution and at
57     /// // {}.
58     /// //
59     /// // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
60     /// // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
61     /// // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
62     /// // option. This file may not be copied, modified, or distributed
63     /// // except according to those terms.
64     /// "
65     ///     ).unwrap(),
66     ///     r"^
67     /// // Copyright \d+ The \} Rust \\ Project \{ Developers\. See the ([A-Z]+)
68     /// // file at the top\-level directory of this distribution and at
69     /// // .*?\.
70     /// //
71     /// // Licensed under the Apache License, Version 2\.0 <LICENSE\-APACHE or
72     /// // http://www\.apache\.org/licenses/LICENSE\-2\.0> or the MIT license
73     /// // <LICENSE\-MIT or http://opensource\.org/licenses/MIT>, at your
74     /// // option\. This file may not be copied, modified, or distributed
75     /// // except according to those terms\.
76     /// "
77     /// );
78     /// ```
79     pub fn parse(template: &str) -> Result<String, String> {
80         let mut parser = Self::new();
81         for chr in template.chars() {
82             if chr == '\n' {
83                 parser.linum += 1;
84             }
85             parser.state = match parser.state {
86                 Lit => parser.trans_from_lit(chr),
87                 LitEsc => parser.trans_from_litesc(chr),
88                 Re(brace_nesting) => parser.trans_from_re(chr, brace_nesting),
89                 ReEsc(brace_nesting) => parser.trans_from_reesc(chr, brace_nesting),
90                 Abort(msg) => return Err(msg),
91             };
92         }
93         // check if we've ended parsing in a valid state
94         match parser.state {
95             Abort(msg) => return Err(msg),
96             Re(_) | ReEsc(_) => {
97                 return Err(format!(
98                     "escape or balance opening brace on l. {}",
99                     parser.open_brace_line
100                 ));
101             }
102             LitEsc => return Err(format!("incomplete escape sequence on l. {}", parser.linum)),
103             _ => (),
104         }
105         parser.parsed.push_str(&regex::escape(&parser.buffer));
106
107         Ok(parser.parsed)
108     }
109
110     fn trans_from_lit(&mut self, chr: char) -> ParsingState {
111         match chr {
112             '{' => {
113                 self.parsed.push_str(&regex::escape(&self.buffer));
114                 self.buffer.clear();
115                 self.open_brace_line = self.linum;
116                 Re(1)
117             }
118             '}' => Abort(format!(
119                 "escape or balance closing brace on l. {}",
120                 self.linum
121             )),
122             '\\' => LitEsc,
123             _ => {
124                 self.buffer.push(chr);
125                 Lit
126             }
127         }
128     }
129
130     fn trans_from_litesc(&mut self, chr: char) -> ParsingState {
131         self.buffer.push(chr);
132         Lit
133     }
134
135     fn trans_from_re(&mut self, chr: char, brace_nesting: u32) -> ParsingState {
136         match chr {
137             '{' => {
138                 self.buffer.push(chr);
139                 Re(brace_nesting + 1)
140             }
141             '}' => {
142                 match brace_nesting {
143                     1 => {
144                         // default regex for empty placeholder {}
145                         if self.buffer.is_empty() {
146                             self.parsed.push_str(".*?");
147                         } else {
148                             self.parsed.push_str(&self.buffer);
149                         }
150                         self.buffer.clear();
151                         Lit
152                     }
153                     _ => {
154                         self.buffer.push(chr);
155                         Re(brace_nesting - 1)
156                     }
157                 }
158             }
159             '\\' => {
160                 self.buffer.push(chr);
161                 ReEsc(brace_nesting)
162             }
163             _ => {
164                 self.buffer.push(chr);
165                 Re(brace_nesting)
166             }
167         }
168     }
169
170     fn trans_from_reesc(&mut self, chr: char, brace_nesting: u32) -> ParsingState {
171         self.buffer.push(chr);
172         Re(brace_nesting)
173     }
174 }
175
176 #[cfg(test)]
177 mod test {
178     use super::TemplateParser;
179
180     #[test]
181     fn test_parse_license_template() {
182         assert_eq!(
183             TemplateParser::parse("literal (.*)").unwrap(),
184             r"^literal \(\.\*\)"
185         );
186         assert_eq!(
187             TemplateParser::parse(r"escaping \}").unwrap(),
188             r"^escaping \}"
189         );
190         assert!(TemplateParser::parse("unbalanced } without escape").is_err());
191         assert_eq!(
192             TemplateParser::parse(r"{\d+} place{-?}holder{s?}").unwrap(),
193             r"^\d+ place-?holders?"
194         );
195         assert_eq!(TemplateParser::parse("default {}").unwrap(), "^default .*?");
196         assert_eq!(
197             TemplateParser::parse(r"unbalanced nested braces {\{{3}}").unwrap(),
198             r"^unbalanced nested braces \{{3}"
199         );
200         assert_eq!(
201             TemplateParser::parse("parsing error }").unwrap_err(),
202             "escape or balance closing brace on l. 1"
203         );
204         assert_eq!(
205             TemplateParser::parse("parsing error {\nsecond line").unwrap_err(),
206             "escape or balance opening brace on l. 1"
207         );
208         assert_eq!(
209             TemplateParser::parse(r"parsing error \").unwrap_err(),
210             "incomplete escape sequence on l. 1"
211         );
212     }
213 }