diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index e39fe20310c..efdfcafb40a 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -91,6 +91,7 @@ pub mod plugins; pub mod visit_ast; pub mod visit_lib; pub mod test; +pub mod theme; use clean::AttributesExt; @@ -267,6 +268,11 @@ pub fn opts() -> Vec { "additional themes which will be added to the generated docs", "FILES") }), + unstable("theme-checker", |o| { + o.optmulti("", "theme-checker", + "check if given theme is valid", + "FILES") + }), ] } @@ -316,6 +322,27 @@ pub fn main_args(args: &[String]) -> isize { return 0; } + let to_check = matches.opt_strs("theme-checker"); + if !to_check.is_empty() { + let pathes = theme::load_css_pathes(include_bytes!("html/static/themes/main.css")); + let mut errors = 0; + + println!("rustdoc: [theme-checker] Starting tests!"); + for theme_file in to_check.iter() { + print!(" - Checking \"{}\"...", theme_file); + if !theme::test_theme_against(theme_file, &pathes) { + eprintln!(" FAILED"); + errors += 1; + } else { + println!(" OK"); + } + } + if errors != 0 { + return 1; + } + return 0; + } + if matches.free.is_empty() { print_error("missing file operand"); return 1; diff --git a/src/librustdoc/theme.rs b/src/librustdoc/theme.rs new file mode 100644 index 00000000000..ff1adc3e4c4 --- /dev/null +++ b/src/librustdoc/theme.rs @@ -0,0 +1,258 @@ +// Copyright 2012-2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::collections::HashSet; +use std::fs::File; +use std::hash::{Hash, Hasher}; +use std::io::Read; +use std::path::Path; + +macro_rules! try_false { + ($e:expr) => ({ + match $e { + Ok(c) => c, + Err(e) => { + eprintln!("rustdoc: got an error: {}", e); + return false; + } + } + }) +} + +#[derive(Debug, Clone, Eq)] +pub struct CssPath { + pub name: String, + pub children: HashSet, +} + +// This PartialEq implementation IS NOT COMMUTATIVE!!! +// +// The order is very important: the second object must have all first's rules. +// However, the first doesn't require to have all second's rules. +impl PartialEq for CssPath { + fn eq(&self, other: &CssPath) -> bool { + if self.name != other.name { + false + } else { + for child in &self.children { + if !other.children.iter().any(|c| child == c) { + return false; + } + } + true + } + } +} + +impl Hash for CssPath { + fn hash(&self, state: &mut H) { + self.name.hash(state); + for x in &self.children { + x.hash(state); + } + } +} + +impl CssPath { + fn new(name: String) -> CssPath { + CssPath { + name, + children: HashSet::new(), + } + } +} + +/// All variants contain the position they occur. +#[derive(Debug, Clone, Copy)] +enum Events { + StartLineComment(usize), + StartComment(usize), + EndComment(usize), + InBlock(usize), + OutBlock(usize), +} + +impl Events { + fn get_pos(&self) -> usize { + match *self { + Events::StartLineComment(p) | + Events::StartComment(p) | + Events::EndComment(p) | + Events::InBlock(p) | + Events::OutBlock(p) => p, + } + } + + fn is_comment(&self) -> bool { + match *self { + Events::StartLineComment(_) | + Events::StartComment(_) | + Events::EndComment(_) => true, + _ => false, + } + } +} + +fn previous_is_line_comment(events: &[Events]) -> bool { + if let Some(&Events::StartLineComment(_)) = events.last() { + true + } else { + false + } +} + +fn is_line_comment(pos: usize, v: &[u8], events: &[Events]) -> bool { + if let Some(&Events::StartComment(_)) = events.last() { + return false; + } + pos + 1 < v.len() && v[pos + 1] == b'/' +} + +fn load_css_events(v: &[u8]) -> Vec { + let mut pos = 0; + let mut events = Vec::with_capacity(100); + + while pos < v.len() - 1 { + match v[pos] { + b'/' if pos + 1 < v.len() && v[pos + 1] == b'*' => { + events.push(Events::StartComment(pos)); + pos += 1; + } + b'/' if is_line_comment(pos, v, &events) => { + events.push(Events::StartLineComment(pos)); + pos += 1; + } + b'\n' if previous_is_line_comment(&events) => { + events.push(Events::EndComment(pos)); + } + b'*' if pos + 1 < v.len() && v[pos + 1] == b'/' => { + events.push(Events::EndComment(pos + 2)); + pos += 1; + } + b'{' if !previous_is_line_comment(&events) => { + if let Some(&Events::StartComment(_)) = events.last() { + pos += 1; + continue + } + events.push(Events::InBlock(pos + 1)); + } + b'}' if !previous_is_line_comment(&events) => { + if let Some(&Events::StartComment(_)) = events.last() { + pos += 1; + continue + } + events.push(Events::OutBlock(pos + 1)); + } + _ => {} + } + pos += 1; + } + events +} + +fn get_useful_next(events: &[Events], pos: &mut usize) -> Option { + while *pos < events.len() { + if !events[*pos].is_comment() { + return Some(events[*pos]); + } + *pos += 1; + } + None +} + +fn inner(v: &[u8], events: &[Events], pos: &mut usize) -> HashSet { + let mut pathes = Vec::with_capacity(50); + + while *pos < events.len() { + if let Some(Events::OutBlock(_)) = get_useful_next(events, pos) { + println!("00 => {:?}", events[*pos]); + *pos += 1; + break + } + println!("a => {:?}", events[*pos]); + if let Some(Events::InBlock(start_pos)) = get_useful_next(events, pos) { + println!("aa => {:?}", events[*pos]); + pathes.push(CssPath::new(::std::str::from_utf8(if *pos > 0 { + &v[events[*pos - 1].get_pos()..start_pos - 1] + } else { + &v[..start_pos] + }).unwrap_or("").trim().to_owned())); + *pos += 1; + } + println!("b => {:?}", events[*pos]); + while let Some(Events::InBlock(_)) = get_useful_next(events, pos) { + println!("bb => {:?}", events[*pos]); + if let Some(ref mut path) = pathes.last_mut() { + for entry in inner(v, events, pos).iter() { + path.children.insert(entry.clone()); + } + } + } + if *pos < events.len() { + println!("c => {:?}", events[*pos]); + } + if let Some(Events::OutBlock(_)) = get_useful_next(events, pos) { + *pos += 1; + } + } + pathes.iter().cloned().collect() +} + +pub fn load_css_pathes(v: &[u8]) -> CssPath { + let events = load_css_events(v); + let mut pos = 0; + + println!("\n======> {:?}", events); + let mut parent = CssPath::new("parent".to_owned()); + parent.children = inner(v, &events, &mut pos); + parent +} + +pub fn test_theme_against>(f: &P, against: &CssPath) -> bool { + let mut file = try_false!(File::open(f)); + let mut data = Vec::with_capacity(1000); + + try_false!(file.read_to_end(&mut data)); + let pathes = load_css_pathes(&data); + println!("========= {:?}", pathes); + println!("========= {:?}", against); + pathes == *against +} + +#[test] +fn test_comments_in_rules() { + let text = r#" +rule a {} + +rule b, c +// a line comment +{} + +rule d +// another line comment +e {} + +rule f/* a multine + +comment*/{} + +rule g/* another multine + +comment*/h + +i {} + +rule j/*commeeeeent + +you like things like "{}" in there? :) +*/ +end {} +"#; +} \ No newline at end of file