]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/theme.rs
Rollup merge of #62019 - jeremystucki:refactoring, r=estebank
[rust.git] / src / librustdoc / theme.rs
1 use rustc_data_structures::fx::FxHashSet;
2 use std::fs;
3 use std::hash::{Hash, Hasher};
4 use std::path::Path;
5
6 use errors::Handler;
7
8 macro_rules! try_something {
9     ($e:expr, $diag:expr, $out:expr) => ({
10         match $e {
11             Ok(c) => c,
12             Err(e) => {
13                 $diag.struct_err(&e.to_string()).emit();
14                 return $out;
15             }
16         }
17     })
18 }
19
20 #[derive(Debug, Clone, Eq)]
21 pub struct CssPath {
22     pub name: String,
23     pub children: FxHashSet<CssPath>,
24 }
25
26 // This PartialEq implementation IS NOT COMMUTATIVE!!!
27 //
28 // The order is very important: the second object must have all first's rules.
29 // However, the first doesn't require to have all second's rules.
30 impl PartialEq for CssPath {
31     fn eq(&self, other: &CssPath) -> bool {
32         if self.name != other.name {
33             false
34         } else {
35             for child in &self.children {
36                 if !other.children.iter().any(|c| child == c) {
37                     return false;
38                 }
39             }
40             true
41         }
42     }
43 }
44
45 impl Hash for CssPath {
46     fn hash<H: Hasher>(&self, state: &mut H) {
47         self.name.hash(state);
48         for x in &self.children {
49             x.hash(state);
50         }
51     }
52 }
53
54 impl CssPath {
55     fn new(name: String) -> CssPath {
56         CssPath {
57             name,
58             children: FxHashSet::default(),
59         }
60     }
61 }
62
63 /// All variants contain the position they occur.
64 #[derive(Debug, Clone, Copy)]
65 enum Events {
66     StartLineComment(usize),
67     StartComment(usize),
68     EndComment(usize),
69     InBlock(usize),
70     OutBlock(usize),
71 }
72
73 impl Events {
74     fn get_pos(&self) -> usize {
75         match *self {
76             Events::StartLineComment(p) |
77             Events::StartComment(p) |
78             Events::EndComment(p) |
79             Events::InBlock(p) |
80             Events::OutBlock(p) => p,
81         }
82     }
83
84     fn is_comment(&self) -> bool {
85         match *self {
86             Events::StartLineComment(_) |
87             Events::StartComment(_) |
88             Events::EndComment(_) => true,
89             _ => false,
90         }
91     }
92 }
93
94 fn previous_is_line_comment(events: &[Events]) -> bool {
95     if let Some(&Events::StartLineComment(_)) = events.last() {
96         true
97     } else {
98         false
99     }
100 }
101
102 fn is_line_comment(pos: usize, v: &[u8], events: &[Events]) -> bool {
103     if let Some(&Events::StartComment(_)) = events.last() {
104         return false;
105     }
106     v[pos + 1] == b'/'
107 }
108
109 fn load_css_events(v: &[u8]) -> Vec<Events> {
110     let mut pos = 0;
111     let mut events = Vec::with_capacity(100);
112
113     while pos + 1 < v.len() {
114         match v[pos] {
115             b'/' if v[pos + 1] == b'*' => {
116                 events.push(Events::StartComment(pos));
117                 pos += 1;
118             }
119             b'/' if is_line_comment(pos, v, &events) => {
120                 events.push(Events::StartLineComment(pos));
121                 pos += 1;
122             }
123             b'\n' if previous_is_line_comment(&events) => {
124                 events.push(Events::EndComment(pos));
125             }
126             b'*' if v[pos + 1] == b'/' => {
127                 events.push(Events::EndComment(pos + 2));
128                 pos += 1;
129             }
130             b'{' if !previous_is_line_comment(&events) => {
131                 if let Some(&Events::StartComment(_)) = events.last() {
132                     pos += 1;
133                     continue
134                 }
135                 events.push(Events::InBlock(pos + 1));
136             }
137             b'}' if !previous_is_line_comment(&events) => {
138                 if let Some(&Events::StartComment(_)) = events.last() {
139                     pos += 1;
140                     continue
141                 }
142                 events.push(Events::OutBlock(pos + 1));
143             }
144             _ => {}
145         }
146         pos += 1;
147     }
148     events
149 }
150
151 fn get_useful_next(events: &[Events], pos: &mut usize) -> Option<Events> {
152     while *pos < events.len() {
153         if !events[*pos].is_comment() {
154             return Some(events[*pos]);
155         }
156         *pos += 1;
157     }
158     None
159 }
160
161 fn get_previous_positions(events: &[Events], mut pos: usize) -> Vec<usize> {
162     let mut ret = Vec::with_capacity(3);
163
164     ret.push(events[pos].get_pos());
165     if pos > 0 {
166         pos -= 1;
167     }
168     loop {
169         if pos < 1 || !events[pos].is_comment() {
170             let x = events[pos].get_pos();
171             if *ret.last().unwrap() != x {
172                 ret.push(x);
173             } else {
174                 ret.push(0);
175             }
176             break
177         }
178         ret.push(events[pos].get_pos());
179         pos -= 1;
180     }
181     if ret.len() & 1 != 0 && events[pos].is_comment() {
182         ret.push(0);
183     }
184     ret.iter().rev().cloned().collect()
185 }
186
187 fn build_rule(v: &[u8], positions: &[usize]) -> String {
188     positions.chunks(2)
189              .map(|x| ::std::str::from_utf8(&v[x[0]..x[1]]).unwrap_or(""))
190              .collect::<String>()
191              .trim()
192              .replace("\n", " ")
193              .replace("/", "")
194              .replace("\t", " ")
195              .replace("{", "")
196              .replace("}", "")
197              .split(' ')
198              .filter(|s| s.len() > 0)
199              .collect::<Vec<&str>>()
200              .join(" ")
201 }
202
203 fn inner(v: &[u8], events: &[Events], pos: &mut usize) -> FxHashSet<CssPath> {
204     let mut paths = Vec::with_capacity(50);
205
206     while *pos < events.len() {
207         if let Some(Events::OutBlock(_)) = get_useful_next(events, pos) {
208             *pos += 1;
209             break
210         }
211         if let Some(Events::InBlock(_)) = get_useful_next(events, pos) {
212             paths.push(CssPath::new(build_rule(v, &get_previous_positions(events, *pos))));
213             *pos += 1;
214         }
215         while let Some(Events::InBlock(_)) = get_useful_next(events, pos) {
216             if let Some(ref mut path) = paths.last_mut() {
217                 for entry in inner(v, events, pos).iter() {
218                     path.children.insert(entry.clone());
219                 }
220             }
221         }
222         if let Some(Events::OutBlock(_)) = get_useful_next(events, pos) {
223             *pos += 1;
224         }
225     }
226     paths.iter().cloned().collect()
227 }
228
229 pub fn load_css_paths(v: &[u8]) -> CssPath {
230     let events = load_css_events(v);
231     let mut pos = 0;
232
233     let mut parent = CssPath::new("parent".to_owned());
234     parent.children = inner(v, &events, &mut pos);
235     parent
236 }
237
238 pub fn get_differences(against: &CssPath, other: &CssPath, v: &mut Vec<String>) {
239     if against.name != other.name {
240         return
241     } else {
242         for child in &against.children {
243             let mut found = false;
244             let mut found_working = false;
245             let mut tmp = Vec::new();
246
247             for other_child in &other.children {
248                 if child.name == other_child.name {
249                     if child != other_child {
250                         get_differences(child, other_child, &mut tmp);
251                     } else {
252                         found_working = true;
253                     }
254                     found = true;
255                     break
256                 }
257             }
258             if found == false {
259                 v.push(format!("  Missing \"{}\" rule", child.name));
260             } else if found_working == false {
261                 v.extend(tmp.iter().cloned());
262             }
263         }
264     }
265 }
266
267 pub fn test_theme_against<P: AsRef<Path>>(
268     f: &P,
269     against: &CssPath,
270     diag: &Handler,
271 ) -> (bool, Vec<String>) {
272     let data = try_something!(fs::read(f), diag, (false, vec![]));
273     let paths = load_css_paths(&data);
274     let mut ret = vec![];
275     get_differences(against, &paths, &mut ret);
276     (true, ret)
277 }
278
279 #[cfg(test)]
280 mod test {
281     use super::*;
282
283     #[test]
284     fn test_comments_in_rules() {
285         let text = r#"
286 rule a {}
287
288 rule b, c
289 // a line comment
290 {}
291
292 rule d
293 // another line comment
294 e {}
295
296 rule f/* a multine
297
298 comment*/{}
299
300 rule g/* another multine
301
302 comment*/h
303
304 i {}
305
306 rule j/*commeeeeent
307
308 you like things like "{}" in there? :)
309 */
310 end {}"#;
311
312         let against = r#"
313 rule a {}
314
315 rule b, c {}
316
317 rule d e {}
318
319 rule f {}
320
321 rule gh i {}
322
323 rule j end {}
324 "#;
325
326         let mut ret = Vec::new();
327         get_differences(&load_css_paths(against.as_bytes()),
328                         &load_css_paths(text.as_bytes()),
329                         &mut ret);
330         assert!(ret.is_empty());
331     }
332
333     #[test]
334     fn test_text() {
335         let text = r#"
336 a
337 /* sdfs
338 */ b
339 c // sdf
340 d {}
341 "#;
342         let paths = load_css_paths(text.as_bytes());
343         assert!(paths.children.contains(&CssPath::new("a b c d".to_owned())));
344     }
345
346     #[test]
347     fn test_comparison() {
348         let x = r#"
349 a {
350     b {
351         c {}
352     }
353 }
354 "#;
355
356         let y = r#"
357 a {
358     b {}
359 }
360 "#;
361
362         let against = load_css_paths(y.as_bytes());
363         let other = load_css_paths(x.as_bytes());
364
365         let mut ret = Vec::new();
366         get_differences(&against, &other, &mut ret);
367         assert!(ret.is_empty());
368         get_differences(&other, &against, &mut ret);
369         assert_eq!(ret, vec!["  Missing \"c\" rule".to_owned()]);
370     }
371
372     #[test]
373     fn check_empty_css() {
374         let events = load_css_events(&[]);
375         assert_eq!(events.len(), 0);
376     }
377
378     #[test]
379     fn check_invalid_css() {
380         let events = load_css_events(b"*");
381         assert_eq!(events.len(), 0);
382     }
383 }