Files
rust/src/librustdoc/theme.rs

347 lines
8.6 KiB
Rust
Raw Normal View History

2018-01-24 00:38:41 +01:00
// 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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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;
2018-01-25 21:58:10 +01:00
macro_rules! try_something {
($e:expr, $out:expr) => ({
2018-01-24 00:38:41 +01:00
match $e {
Ok(c) => c,
Err(e) => {
eprintln!("rustdoc: got an error: {}", e);
2018-01-25 21:58:10 +01:00
return $out;
2018-01-24 00:38:41 +01:00
}
}
})
}
#[derive(Debug, Clone, Eq)]
pub struct CssPath {
pub name: String,
pub children: HashSet<CssPath>,
}
// 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<H: Hasher>(&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<Events> {
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<Events> {
while *pos < events.len() {
if !events[*pos].is_comment() {
return Some(events[*pos]);
}
*pos += 1;
}
None
}
fn get_previous_positions(events: &[Events], mut pos: usize) -> Vec<usize> {
let mut ret = Vec::with_capacity(3);
ret.push(events[pos].get_pos() - 1);
if pos > 0 {
pos -= 1;
}
loop {
ret.push(events[pos].get_pos());
if pos < 1 || !events[pos].is_comment() {
break
}
pos -= 1;
}
if events[pos].is_comment() {
ret.push(0);
}
ret.iter().rev().cloned().collect()
}
fn build_rule(v: &[u8], positions: &[usize]) -> String {
positions.chunks(2)
.map(|x| ::std::str::from_utf8(&v[x[0]..x[1]]).unwrap_or("").to_owned())
.collect::<String>()
.trim()
.replace("\n", " ")
}
2018-01-24 00:38:41 +01:00
fn inner(v: &[u8], events: &[Events], pos: &mut usize) -> HashSet<CssPath> {
let mut pathes = Vec::with_capacity(50);
while *pos < events.len() {
if let Some(Events::OutBlock(_)) = get_useful_next(events, pos) {
*pos += 1;
break
}
if let Some(Events::InBlock(_)) = get_useful_next(events, pos) {
pathes.push(CssPath::new(build_rule(v, &get_previous_positions(events, *pos))));
2018-01-24 00:38:41 +01:00
*pos += 1;
}
while let Some(Events::InBlock(_)) = get_useful_next(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 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;
let mut parent = CssPath::new("parent".to_owned());
parent.children = inner(v, &events, &mut pos);
parent
}
pub fn get_differences(against: &CssPath, other: &CssPath, v: &mut Vec<String>) {
2018-01-25 21:58:10 +01:00
if against.name != other.name {
return
} else {
for child in &against.children {
let mut found = false;
let mut found_working = false;
let mut tmp = Vec::new();
for other_child in &other.children {
if child.name == other_child.name {
if child != other_child {
get_differences(child, other_child, &mut tmp);
} else {
found_working = true;
}
found = true;
break
}
}
if found == false {
v.push(format!(" Missing \"{}\" rule", child.name));
} else if found_working == false {
v.extend(tmp.iter().cloned());
}
}
}
}
pub fn test_theme_against<P: AsRef<Path>>(f: &P, against: &CssPath) -> Vec<String> {
let mut file = try_something!(File::open(f), Vec::new());
2018-01-24 00:38:41 +01:00
let mut data = Vec::with_capacity(1000);
2018-01-25 21:58:10 +01:00
try_something!(file.read_to_end(&mut data), Vec::new());
2018-01-24 00:38:41 +01:00
let pathes = load_css_pathes(&data);
2018-01-25 21:58:10 +01:00
let mut ret = Vec::new();
get_differences(against, &pathes, &mut ret);
ret
2018-01-24 00:38:41 +01:00
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_comments_in_rules() {
let text = r#"
2018-01-24 00:38:41 +01:00
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 {}"#;
let against = r#"
rule a {}
rule b, c {}
rule d e {}
rule f {}
rule gh i {}
rule j end {}
"#;
assert!(get_differences(&load_css_pathes(against.as_bytes()),
&load_css_pathes(text.as_bytes())).is_empty());
}
#[test]
fn test_comparison() {
let x = r#"
a {
b {
c {}
}
}
"#;
let y = r#"
a {
b {}
}
2018-01-24 00:38:41 +01:00
"#;
let against = load_css_pathes(y.as_bytes());
let other = load_css_pathes(x.as_bytes());
assert!(get_differences(&against, &other).is_empty());
assert_eq!(get_differences(&other, &against), vec![" Missing \"c\" rule".to_owned()])
}
}