diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
commit | 698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch) | |
tree | 173a775858bd501c378080a10dca74132f05bc50 /src/librustdoc/theme.rs | |
parent | Initial commit. (diff) | |
download | rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip |
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/librustdoc/theme.rs')
-rw-r--r-- | src/librustdoc/theme.rs | 271 |
1 files changed, 271 insertions, 0 deletions
diff --git a/src/librustdoc/theme.rs b/src/librustdoc/theme.rs new file mode 100644 index 000000000..0118d7dd2 --- /dev/null +++ b/src/librustdoc/theme.rs @@ -0,0 +1,271 @@ +use rustc_data_structures::fx::FxHashSet; +use std::fs; +use std::hash::{Hash, Hasher}; +use std::path::Path; + +use rustc_errors::Handler; + +#[cfg(test)] +mod tests; + +#[derive(Debug, Clone, Eq)] +pub(crate) struct CssPath { + pub(crate) name: String, + pub(crate) children: FxHashSet<CssPath>, +} + +// This PartialEq implementation IS NOT COMMUTATIVE!!! +// +// The order is very important: the second object must have all first's rules. +// However, the first is not required to have all of the 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: FxHashSet::default() } + } +} + +/// 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 { + matches!( + self, + Events::StartLineComment(_) | Events::StartComment(_) | Events::EndComment(_) + ) + } +} + +fn previous_is_line_comment(events: &[Events]) -> bool { + matches!(events.last(), Some(&Events::StartLineComment(_))) +} + +fn is_line_comment(pos: usize, v: &[u8], events: &[Events]) -> bool { + if let Some(&Events::StartComment(_)) = events.last() { + return false; + } + 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 + 1 < v.len() { + match v[pos] { + b'/' if 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 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()); + if pos > 0 { + pos -= 1; + } + loop { + if pos < 1 || !events[pos].is_comment() { + let x = events[pos].get_pos(); + if *ret.last().unwrap() != x { + ret.push(x); + } else { + ret.push(0); + } + break; + } + ret.push(events[pos].get_pos()); + pos -= 1; + } + if ret.len() & 1 != 0 && events[pos].is_comment() { + ret.push(0); + } + ret.iter().rev().cloned().collect() +} + +fn build_rule(v: &[u8], positions: &[usize]) -> String { + minifier::css::minify( + &positions + .chunks(2) + .map(|x| ::std::str::from_utf8(&v[x[0]..x[1]]).unwrap_or("")) + .collect::<String>() + .trim() + .chars() + .filter_map(|c| match c { + '\n' | '\t' => Some(' '), + '/' | '{' | '}' => None, + c => Some(c), + }) + .collect::<String>() + .split(' ') + .filter(|s| !s.is_empty()) + .intersperse(" ") + .collect::<String>(), + ) + .map(|css| css.to_string()) + .unwrap_or_else(|_| String::new()) +} + +fn inner(v: &[u8], events: &[Events], pos: &mut usize) -> FxHashSet<CssPath> { + let mut paths = 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) { + paths.push(CssPath::new(build_rule(v, &get_previous_positions(events, *pos)))); + *pos += 1; + } + while let Some(Events::InBlock(_)) = get_useful_next(events, pos) { + if let Some(ref mut path) = paths.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; + } + } + paths.iter().cloned().collect() +} + +pub(crate) fn load_css_paths(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(crate) fn get_differences(against: &CssPath, other: &CssPath, v: &mut Vec<String>) { + if against.name == other.name { + 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 { + v.push(format!(" Missing \"{}\" rule", child.name)); + } else if !found_working { + v.extend(tmp.iter().cloned()); + } + } + } +} + +pub(crate) fn test_theme_against<P: AsRef<Path>>( + f: &P, + against: &CssPath, + diag: &Handler, +) -> (bool, Vec<String>) { + let data = match fs::read(f) { + Ok(c) => c, + Err(e) => { + diag.struct_err(&e.to_string()).emit(); + return (false, vec![]); + } + }; + + let paths = load_css_paths(&data); + let mut ret = vec![]; + get_differences(against, &paths, &mut ret); + (true, ret) +} |