]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/theme.rs
Rollup merge of #66514 - GuillaumeGomez:fix-search-filter-save, r=kinnison
[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 #[cfg(test)]
9 mod tests;
10
11 macro_rules! try_something {
12     ($e:expr, $diag:expr, $out:expr) => ({
13         match $e {
14             Ok(c) => c,
15             Err(e) => {
16                 $diag.struct_err(&e.to_string()).emit();
17                 return $out;
18             }
19         }
20     })
21 }
22
23 #[derive(Debug, Clone, Eq)]
24 pub struct CssPath {
25     pub name: String,
26     pub children: FxHashSet<CssPath>,
27 }
28
29 // This PartialEq implementation IS NOT COMMUTATIVE!!!
30 //
31 // The order is very important: the second object must have all first's rules.
32 // However, the first doesn't require to have all second's rules.
33 impl PartialEq for CssPath {
34     fn eq(&self, other: &CssPath) -> bool {
35         if self.name != other.name {
36             false
37         } else {
38             for child in &self.children {
39                 if !other.children.iter().any(|c| child == c) {
40                     return false;
41                 }
42             }
43             true
44         }
45     }
46 }
47
48 impl Hash for CssPath {
49     fn hash<H: Hasher>(&self, state: &mut H) {
50         self.name.hash(state);
51         for x in &self.children {
52             x.hash(state);
53         }
54     }
55 }
56
57 impl CssPath {
58     fn new(name: String) -> CssPath {
59         CssPath {
60             name,
61             children: FxHashSet::default(),
62         }
63     }
64 }
65
66 /// All variants contain the position they occur.
67 #[derive(Debug, Clone, Copy)]
68 enum Events {
69     StartLineComment(usize),
70     StartComment(usize),
71     EndComment(usize),
72     InBlock(usize),
73     OutBlock(usize),
74 }
75
76 impl Events {
77     fn get_pos(&self) -> usize {
78         match *self {
79             Events::StartLineComment(p) |
80             Events::StartComment(p) |
81             Events::EndComment(p) |
82             Events::InBlock(p) |
83             Events::OutBlock(p) => p,
84         }
85     }
86
87     fn is_comment(&self) -> bool {
88         match *self {
89             Events::StartLineComment(_) |
90             Events::StartComment(_) |
91             Events::EndComment(_) => true,
92             _ => false,
93         }
94     }
95 }
96
97 fn previous_is_line_comment(events: &[Events]) -> bool {
98     if let Some(&Events::StartLineComment(_)) = events.last() {
99         true
100     } else {
101         false
102     }
103 }
104
105 fn is_line_comment(pos: usize, v: &[u8], events: &[Events]) -> bool {
106     if let Some(&Events::StartComment(_)) = events.last() {
107         return false;
108     }
109     v[pos + 1] == b'/'
110 }
111
112 fn load_css_events(v: &[u8]) -> Vec<Events> {
113     let mut pos = 0;
114     let mut events = Vec::with_capacity(100);
115
116     while pos + 1 < v.len() {
117         match v[pos] {
118             b'/' if v[pos + 1] == b'*' => {
119                 events.push(Events::StartComment(pos));
120                 pos += 1;
121             }
122             b'/' if is_line_comment(pos, v, &events) => {
123                 events.push(Events::StartLineComment(pos));
124                 pos += 1;
125             }
126             b'\n' if previous_is_line_comment(&events) => {
127                 events.push(Events::EndComment(pos));
128             }
129             b'*' if v[pos + 1] == b'/' => {
130                 events.push(Events::EndComment(pos + 2));
131                 pos += 1;
132             }
133             b'{' if !previous_is_line_comment(&events) => {
134                 if let Some(&Events::StartComment(_)) = events.last() {
135                     pos += 1;
136                     continue
137                 }
138                 events.push(Events::InBlock(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::OutBlock(pos + 1));
146             }
147             _ => {}
148         }
149         pos += 1;
150     }
151     events
152 }
153
154 fn get_useful_next(events: &[Events], pos: &mut usize) -> Option<Events> {
155     while *pos < events.len() {
156         if !events[*pos].is_comment() {
157             return Some(events[*pos]);
158         }
159         *pos += 1;
160     }
161     None
162 }
163
164 fn get_previous_positions(events: &[Events], mut pos: usize) -> Vec<usize> {
165     let mut ret = Vec::with_capacity(3);
166
167     ret.push(events[pos].get_pos());
168     if pos > 0 {
169         pos -= 1;
170     }
171     loop {
172         if pos < 1 || !events[pos].is_comment() {
173             let x = events[pos].get_pos();
174             if *ret.last().unwrap() != x {
175                 ret.push(x);
176             } else {
177                 ret.push(0);
178             }
179             break
180         }
181         ret.push(events[pos].get_pos());
182         pos -= 1;
183     }
184     if ret.len() & 1 != 0 && events[pos].is_comment() {
185         ret.push(0);
186     }
187     ret.iter().rev().cloned().collect()
188 }
189
190 fn build_rule(v: &[u8], positions: &[usize]) -> String {
191     positions.chunks(2)
192              .map(|x| ::std::str::from_utf8(&v[x[0]..x[1]]).unwrap_or(""))
193              .collect::<String>()
194              .trim()
195              .replace("\n", " ")
196              .replace("/", "")
197              .replace("\t", " ")
198              .replace("{", "")
199              .replace("}", "")
200              .split(' ')
201              .filter(|s| s.len() > 0)
202              .collect::<Vec<&str>>()
203              .join(" ")
204 }
205
206 fn inner(v: &[u8], events: &[Events], pos: &mut usize) -> FxHashSet<CssPath> {
207     let mut paths = Vec::with_capacity(50);
208
209     while *pos < events.len() {
210         if let Some(Events::OutBlock(_)) = get_useful_next(events, pos) {
211             *pos += 1;
212             break
213         }
214         if let Some(Events::InBlock(_)) = get_useful_next(events, pos) {
215             paths.push(CssPath::new(build_rule(v, &get_previous_positions(events, *pos))));
216             *pos += 1;
217         }
218         while let Some(Events::InBlock(_)) = get_useful_next(events, pos) {
219             if let Some(ref mut path) = paths.last_mut() {
220                 for entry in inner(v, events, pos).iter() {
221                     path.children.insert(entry.clone());
222                 }
223             }
224         }
225         if let Some(Events::OutBlock(_)) = get_useful_next(events, pos) {
226             *pos += 1;
227         }
228     }
229     paths.iter().cloned().collect()
230 }
231
232 pub fn load_css_paths(v: &[u8]) -> CssPath {
233     let events = load_css_events(v);
234     let mut pos = 0;
235
236     let mut parent = CssPath::new("parent".to_owned());
237     parent.children = inner(v, &events, &mut pos);
238     parent
239 }
240
241 pub fn get_differences(against: &CssPath, other: &CssPath, v: &mut Vec<String>) {
242     if against.name != other.name {
243         return
244     } else {
245         for child in &against.children {
246             let mut found = false;
247             let mut found_working = false;
248             let mut tmp = Vec::new();
249
250             for other_child in &other.children {
251                 if child.name == other_child.name {
252                     if child != other_child {
253                         get_differences(child, other_child, &mut tmp);
254                     } else {
255                         found_working = true;
256                     }
257                     found = true;
258                     break
259                 }
260             }
261             if found == false {
262                 v.push(format!("  Missing \"{}\" rule", child.name));
263             } else if found_working == false {
264                 v.extend(tmp.iter().cloned());
265             }
266         }
267     }
268 }
269
270 pub fn test_theme_against<P: AsRef<Path>>(
271     f: &P,
272     against: &CssPath,
273     diag: &Handler,
274 ) -> (bool, Vec<String>) {
275     let data = try_something!(fs::read(f), diag, (false, vec![]));
276
277     let paths = load_css_paths(&data);
278     let mut ret = vec![];
279     get_differences(against, &paths, &mut ret);
280     (true, ret)
281 }