diff options
author | Manos Pitsidianakis <el13635@mail.ntua.gr> | 2020-01-24 02:29:41 +0200 |
---|---|---|
committer | Manos Pitsidianakis <el13635@mail.ntua.gr> | 2020-01-24 02:29:41 +0200 |
commit | 72e1d5d52db4a24a1a6fee0a41a415798d1cfe38 (patch) | |
tree | efa73aca91320dfb6e4715209b0848f6c7c5aee2 /ui/src | |
parent | 2a4ecc83147f892cf23ffece6eb5183f490f4318 (diff) |
ui/themes: add link cycle detection in theme validation
Diffstat (limited to 'ui/src')
-rw-r--r-- | ui/src/conf/themes.rs | 116 |
1 files changed, 115 insertions, 1 deletions
diff --git a/ui/src/conf/themes.rs b/ui/src/conf/themes.rs index 83d02d71..94c9b00a 100644 --- a/ui/src/conf/themes.rs +++ b/ui/src/conf/themes.rs @@ -21,8 +21,9 @@ use crate::terminal::{Attr, Color}; use crate::Context; -use melib::Result; +use melib::{MeliError, Result}; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; +use smallvec::SmallVec; use std::borrow::Cow; use std::collections::{HashMap, HashSet}; @@ -250,6 +251,18 @@ pub struct Theme { impl Theme { pub fn validate(&self) -> Result<()> { + if let Err(err) = is_cyclic(&self.light) { + return Err(MeliError::new(format!( + "light theme contains a cycle: {}", + err + ))); + } + if let Err(err) = is_cyclic(&self.dark) { + return Err(MeliError::new(format!( + "dark theme contains a cycle: {}", + err + ))); + } let hash_set: HashSet<&'static str> = DEFAULT_KEYS.into_iter().map(|k| *k).collect(); let keys: Vec<&'_ str> = self .light @@ -536,3 +549,104 @@ impl Serialize for Theme { s.end() } } + +fn is_cyclic( + theme: &HashMap<Cow<'static, str>, ThemeAttributeInner>, +) -> std::result::Result<(), String> { + enum Course { + Fg, + Bg, + Attrs, + } + fn is_cyclic_util<'a>( + course: &Course, + k: &'a Cow<'static, str>, + visited: &mut HashMap<&'a Cow<'static, str>, bool>, + stack: &mut HashMap<&'a Cow<'static, str>, bool>, + path: &mut SmallVec<[&'a Cow<'static, str>; 16]>, + theme: &'a HashMap<Cow<'static, str>, ThemeAttributeInner>, + ) -> bool { + if !visited[k] { + visited.entry(k).and_modify(|e| *e = true); + stack.entry(k).and_modify(|e| *e = true); + + match course { + Course::Fg => match theme[k].fg { + ThemeValue::Link(ref l) => { + path.push(l); + if !visited[l] && is_cyclic_util(course, l, visited, stack, path, theme) { + return true; + } else if stack[l] { + return true; + } + path.pop(); + } + _ => {} + }, + Course::Bg => match theme[k].bg { + ThemeValue::Link(ref l) => { + path.push(l); + if !visited[l] && is_cyclic_util(course, l, visited, stack, path, theme) { + return true; + } else if stack[l] { + return true; + } + path.pop(); + } + _ => {} + }, + Course::Attrs => match theme[k].attrs { + ThemeValue::Link(ref l) => { + path.push(l); + if !visited[l] && is_cyclic_util(course, l, visited, stack, path, theme) { + return true; + } else if stack[l] { + return true; + } + path.pop(); + } + _ => {} + }, + } + } + stack.entry(k).and_modify(|e| *e = false); + return false; + } + + let mut path = SmallVec::new(); + let mut visited = theme + .keys() + .map(|k| (k, false)) + .collect::<HashMap<&Cow<'static, str>, bool>>(); + + let mut stack = theme + .keys() + .map(|k| (k, false)) + .collect::<HashMap<&Cow<'static, str>, bool>>(); + for k in theme.keys() { + for course in [Course::Fg, Course::Bg, Course::Attrs].into_iter() { + path.push(k); + if is_cyclic_util(course, k, &mut visited, &mut stack, &mut path, &theme) { + let path = path + .into_iter() + .map(|k| k.to_string()) + .collect::<Vec<String>>(); + return Err(format!( + "{} {}", + match course { + Course::Fg => "fg: ", + Course::Bg => "bg: ", + Course::Attrs => "attrs: ", + }, + path.join(" -> ") + )); + } + for v in visited.values_mut() { + *v = false; + } + path.pop(); + } + } + + return Ok(()); +} |