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