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