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