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