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.
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.
11 use std::collections::HashSet;
13 use std::hash::{Hash, Hasher};
19 macro_rules! try_something {
20 ($e:expr, $diag:expr, $out:expr) => ({
24 $diag.struct_err(&e.to_string()).emit();
31 #[derive(Debug, Clone, Eq)]
34 pub children: HashSet<CssPath>,
37 // This PartialEq implementation IS NOT COMMUTATIVE!!!
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 {
46 for child in &self.children {
47 if !other.children.iter().any(|c| child == c) {
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 {
66 fn new(name: String) -> CssPath {
69 children: HashSet::new(),
74 /// All variants contain the position they occur.
75 #[derive(Debug, Clone, Copy)]
77 StartLineComment(usize),
85 fn get_pos(&self) -> usize {
87 Events::StartLineComment(p) |
88 Events::StartComment(p) |
89 Events::EndComment(p) |
91 Events::OutBlock(p) => p,
95 fn is_comment(&self) -> bool {
97 Events::StartLineComment(_) |
98 Events::StartComment(_) |
99 Events::EndComment(_) => true,
105 fn previous_is_line_comment(events: &[Events]) -> bool {
106 if let Some(&Events::StartLineComment(_)) = events.last() {
113 fn is_line_comment(pos: usize, v: &[u8], events: &[Events]) -> bool {
114 if let Some(&Events::StartComment(_)) = events.last() {
117 pos + 1 < v.len() && v[pos + 1] == b'/'
120 fn load_css_events(v: &[u8]) -> Vec<Events> {
122 let mut events = Vec::with_capacity(100);
124 while pos < v.len() - 1 {
126 b'/' if pos + 1 < v.len() && v[pos + 1] == b'*' => {
127 events.push(Events::StartComment(pos));
130 b'/' if is_line_comment(pos, v, &events) => {
131 events.push(Events::StartLineComment(pos));
134 b'\n' if previous_is_line_comment(&events) => {
135 events.push(Events::EndComment(pos));
137 b'*' if pos + 1 < v.len() && v[pos + 1] == b'/' => {
138 events.push(Events::EndComment(pos + 2));
141 b'{' if !previous_is_line_comment(&events) => {
142 if let Some(&Events::StartComment(_)) = events.last() {
146 events.push(Events::InBlock(pos + 1));
148 b'}' if !previous_is_line_comment(&events) => {
149 if let Some(&Events::StartComment(_)) = events.last() {
153 events.push(Events::OutBlock(pos + 1));
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]);
172 fn get_previous_positions(events: &[Events], mut pos: usize) -> Vec<usize> {
173 let mut ret = Vec::with_capacity(3);
175 ret.push(events[pos].get_pos());
180 if pos < 1 || !events[pos].is_comment() {
181 let x = events[pos].get_pos();
182 if *ret.last().unwrap() != x {
189 ret.push(events[pos].get_pos());
192 if ret.len() & 1 != 0 && events[pos].is_comment() {
195 ret.iter().rev().cloned().collect()
198 fn build_rule(v: &[u8], positions: &[usize]) -> String {
200 .map(|x| ::std::str::from_utf8(&v[x[0]..x[1]]).unwrap_or(""))
209 .filter(|s| s.len() > 0)
210 .collect::<Vec<&str>>()
214 fn inner(v: &[u8], events: &[Events], pos: &mut usize) -> HashSet<CssPath> {
215 let mut paths = Vec::with_capacity(50);
217 while *pos < events.len() {
218 if let Some(Events::OutBlock(_)) = get_useful_next(events, pos) {
222 if let Some(Events::InBlock(_)) = get_useful_next(events, pos) {
223 paths.push(CssPath::new(build_rule(v, &get_previous_positions(events, *pos))));
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());
233 if let Some(Events::OutBlock(_)) = get_useful_next(events, pos) {
237 paths.iter().cloned().collect()
240 pub fn load_css_paths(v: &[u8]) -> CssPath {
241 let events = load_css_events(v);
244 let mut parent = CssPath::new("parent".to_owned());
245 parent.children = inner(v, &events, &mut pos);
249 pub fn get_differences(against: &CssPath, other: &CssPath, v: &mut Vec<String>) {
250 if against.name != other.name {
253 for child in &against.children {
254 let mut found = false;
255 let mut found_working = false;
256 let mut tmp = Vec::new();
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);
263 found_working = true;
270 v.push(format!(" Missing \"{}\" rule", child.name));
271 } else if found_working == false {
272 v.extend(tmp.iter().cloned());
278 pub fn test_theme_against<P: AsRef<Path>>(f: &P, against: &CssPath, diag: &Handler)
279 -> (bool, Vec<String>)
281 let mut file = try_something!(File::open(f), diag, (false, Vec::new()));
282 let mut data = Vec::with_capacity(1000);
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);
296 fn test_comments_in_rules() {
305 // another line comment
312 rule g/* another multine
320 you like things like "{}" in there? :)
338 let mut ret = Vec::new();
339 get_differences(&load_css_paths(against.as_bytes()),
340 &load_css_paths(text.as_bytes()),
342 assert!(ret.is_empty());
354 let paths = load_css_paths(text.as_bytes());
355 assert!(paths.children.contains(&CssPath::new("a b c d".to_owned())));
359 fn test_comparison() {
374 let against = load_css_paths(y.as_bytes());
375 let other = load_css_paths(x.as_bytes());
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()]);