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