themes: Add support for Color/Attribute aliases
Add aliases to avoid repetition of raw values when defining new themes. Aliases are strings starting with "$" and must be defined in the `color_aliases` and `attr_aliases` fields of a theme.memfd
parent
eca8a30c3f
commit
de03b106f3
|
@ -122,6 +122,21 @@ fn unlink_fg<'k, 't: 'k>(
|
|||
key = new_key;
|
||||
field = new_field
|
||||
}
|
||||
ThemeValue::Alias(ref alias_ident) => {
|
||||
let mut alias_ident = alias_ident;
|
||||
'self_alias_loop: loop {
|
||||
match &theme.color_aliases[alias_ident.as_ref()] {
|
||||
ThemeValue::Link(ref new_key, ref new_field) => {
|
||||
key = new_key;
|
||||
field = new_field;
|
||||
break 'self_alias_loop;
|
||||
}
|
||||
ThemeValue::Alias(ref new_alias_ident) => alias_ident = new_alias_ident,
|
||||
ThemeValue::Value(val) => return *val,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ThemeValue::Value(val) => return *val,
|
||||
},
|
||||
ColorField::Bg => match &theme[key].bg {
|
||||
|
@ -129,6 +144,20 @@ fn unlink_fg<'k, 't: 'k>(
|
|||
key = new_key;
|
||||
field = new_field
|
||||
}
|
||||
ThemeValue::Alias(ref alias_ident) => {
|
||||
let mut alias_ident = alias_ident;
|
||||
'other_alias_loop: loop {
|
||||
match &theme.color_aliases[alias_ident.as_ref()] {
|
||||
ThemeValue::Link(ref new_key, ref new_field) => {
|
||||
key = new_key;
|
||||
field = new_field;
|
||||
break 'other_alias_loop;
|
||||
}
|
||||
ThemeValue::Alias(ref new_alias_ident) => alias_ident = new_alias_ident,
|
||||
ThemeValue::Value(val) => return *val,
|
||||
}
|
||||
}
|
||||
}
|
||||
ThemeValue::Value(val) => return *val,
|
||||
},
|
||||
}
|
||||
|
@ -148,6 +177,20 @@ fn unlink_bg<'k, 't: 'k>(
|
|||
key = new_key;
|
||||
field = new_field
|
||||
}
|
||||
ThemeValue::Alias(ref alias_ident) => {
|
||||
let mut alias_ident = alias_ident;
|
||||
'self_alias_loop: loop {
|
||||
match &theme.color_aliases[alias_ident.as_ref()] {
|
||||
ThemeValue::Link(ref new_key, ref new_field) => {
|
||||
key = new_key;
|
||||
field = new_field;
|
||||
break 'self_alias_loop;
|
||||
}
|
||||
ThemeValue::Alias(ref new_alias_ident) => alias_ident = new_alias_ident,
|
||||
ThemeValue::Value(val) => return *val,
|
||||
}
|
||||
}
|
||||
}
|
||||
ThemeValue::Value(val) => return *val,
|
||||
},
|
||||
ColorField::Fg => match &theme[key].fg {
|
||||
|
@ -155,6 +198,20 @@ fn unlink_bg<'k, 't: 'k>(
|
|||
key = new_key;
|
||||
field = new_field
|
||||
}
|
||||
ThemeValue::Alias(ref alias_ident) => {
|
||||
let mut alias_ident = alias_ident;
|
||||
'other_alias_loop: loop {
|
||||
match &theme.color_aliases[alias_ident.as_ref()] {
|
||||
ThemeValue::Link(ref new_key, ref new_field) => {
|
||||
key = new_key;
|
||||
field = new_field;
|
||||
break 'other_alias_loop;
|
||||
}
|
||||
ThemeValue::Alias(ref new_alias_ident) => alias_ident = new_alias_ident,
|
||||
ThemeValue::Value(val) => return *val,
|
||||
}
|
||||
}
|
||||
}
|
||||
ThemeValue::Value(val) => return *val,
|
||||
},
|
||||
}
|
||||
|
@ -166,6 +223,19 @@ fn unlink_attrs<'k, 't: 'k>(theme: &'t Theme, mut key: &'k Cow<'static, str>) ->
|
|||
loop {
|
||||
match &theme[key].attrs {
|
||||
ThemeValue::Link(ref new_key, ()) => key = new_key,
|
||||
ThemeValue::Alias(ref alias_ident) => {
|
||||
let mut alias_ident = alias_ident;
|
||||
'alias_loop: loop {
|
||||
match &theme.attr_aliases[alias_ident.as_ref()] {
|
||||
ThemeValue::Link(ref new_key, ()) => {
|
||||
key = new_key;
|
||||
break 'alias_loop;
|
||||
}
|
||||
ThemeValue::Alias(ref new_alias_ident) => alias_ident = new_alias_ident,
|
||||
ThemeValue::Value(val) => return *val,
|
||||
}
|
||||
}
|
||||
}
|
||||
ThemeValue::Value(val) => return *val,
|
||||
}
|
||||
}
|
||||
|
@ -288,6 +358,7 @@ impl ThemeLink for Attr {
|
|||
/// Holds either an actual value or refers to the key name of the attribute that holds the value.
|
||||
enum ThemeValue<T: ThemeLink> {
|
||||
Value(T),
|
||||
Alias(Cow<'static, str>),
|
||||
Link(Cow<'static, str>, T::LinkType),
|
||||
}
|
||||
|
||||
|
@ -333,7 +404,9 @@ impl<'de> Deserialize<'de> for ThemeValue<Attr> {
|
|||
D: Deserializer<'de>,
|
||||
{
|
||||
if let Ok(s) = <String>::deserialize(deserializer) {
|
||||
if let Ok(c) = Attr::from_string_de::<'de, D, String>(s.clone()) {
|
||||
if s.starts_with("$") {
|
||||
Ok(ThemeValue::Alias(s[1..].to_string().into()))
|
||||
} else if let Ok(c) = Attr::from_string_de::<'de, D, String>(s.clone()) {
|
||||
Ok(ThemeValue::Value(c))
|
||||
} else {
|
||||
Ok(ThemeValue::Link(s.into(), ()))
|
||||
|
@ -351,6 +424,7 @@ impl Serialize for ThemeValue<Attr> {
|
|||
{
|
||||
match self {
|
||||
ThemeValue::Value(s) => s.serialize(serializer),
|
||||
ThemeValue::Alias(s) => format!("${}", s).serialize(serializer),
|
||||
ThemeValue::Link(s, ()) => serializer.serialize_str(s.as_ref()),
|
||||
}
|
||||
}
|
||||
|
@ -363,6 +437,7 @@ impl Serialize for ThemeValue<Color> {
|
|||
{
|
||||
match self {
|
||||
ThemeValue::Value(s) => s.serialize(serializer),
|
||||
ThemeValue::Alias(s) => format!("${}", s).serialize(serializer),
|
||||
ThemeValue::Link(s, ColorField::LikeSelf) => serializer.serialize_str(s.as_ref()),
|
||||
ThemeValue::Link(s, ColorField::Fg) => {
|
||||
serializer.serialize_str(format!("{}.fg", s).as_ref())
|
||||
|
@ -380,7 +455,9 @@ impl<'de> Deserialize<'de> for ThemeValue<Color> {
|
|||
D: Deserializer<'de>,
|
||||
{
|
||||
if let Ok(s) = <String>::deserialize(deserializer) {
|
||||
if let Ok(c) = Color::from_string_de::<'de, D>(s.clone()) {
|
||||
if s.starts_with("$") {
|
||||
Ok(ThemeValue::Alias(s[1..].to_string().into()))
|
||||
} else if let Ok(c) = Color::from_string_de::<'de, D>(s.clone()) {
|
||||
Ok(ThemeValue::Value(c))
|
||||
} else if s.ends_with(".fg") {
|
||||
Ok(ThemeValue::Link(
|
||||
|
@ -410,6 +487,8 @@ pub struct Themes {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Theme {
|
||||
color_aliases: HashMap<Cow<'static, str>, ThemeValue<Color>>,
|
||||
attr_aliases: HashMap<Cow<'static, str>, ThemeValue<Attr>>,
|
||||
pub keys: HashMap<Cow<'static, str>, ThemeAttributeInner>,
|
||||
}
|
||||
|
||||
|
@ -443,6 +522,10 @@ impl<'de> Deserialize<'de> for Themes {
|
|||
}
|
||||
#[derive(Deserialize, Default)]
|
||||
struct ThemeOptions {
|
||||
#[serde(default)]
|
||||
color_aliases: HashMap<Cow<'static, str>, ThemeValue<Color>>,
|
||||
#[serde(default)]
|
||||
attr_aliases: HashMap<Cow<'static, str>, ThemeValue<Attr>>,
|
||||
#[serde(flatten, default)]
|
||||
keys: HashMap<Cow<'static, str>, ThemeAttributeInnerOptions>,
|
||||
}
|
||||
|
@ -487,6 +570,8 @@ impl<'de> Deserialize<'de> for Themes {
|
|||
.join(", ")
|
||||
)));
|
||||
}
|
||||
ret.light.color_aliases = s.light.color_aliases;
|
||||
ret.light.attr_aliases = s.light.attr_aliases;
|
||||
for (k, v) in ret.dark.iter_mut() {
|
||||
if let Some(mut att) = s.dark.keys.remove(k) {
|
||||
if let Some(att) = att.fg.take() {
|
||||
|
@ -512,13 +597,12 @@ impl<'de> Deserialize<'de> for Themes {
|
|||
.join(", ")
|
||||
)));
|
||||
}
|
||||
ret.dark.color_aliases = s.dark.color_aliases;
|
||||
ret.dark.attr_aliases = s.dark.attr_aliases;
|
||||
for (tk, t) in ret.other_themes.iter_mut() {
|
||||
let mut theme = s.other_themes.remove(tk).unwrap();
|
||||
for (k, v) in t.iter_mut() {
|
||||
if let Some(mut att) = s
|
||||
.other_themes
|
||||
.get_mut(tk)
|
||||
.and_then(|theme| theme.keys.remove(k))
|
||||
{
|
||||
if let Some(mut att) = theme.keys.remove(k) {
|
||||
if let Some(att) = att.fg.take() {
|
||||
v.fg = att;
|
||||
}
|
||||
|
@ -530,11 +614,11 @@ impl<'de> Deserialize<'de> for Themes {
|
|||
}
|
||||
}
|
||||
}
|
||||
if !s.other_themes[tk].keys.is_empty() {
|
||||
if !theme.keys.is_empty() {
|
||||
return Err(de::Error::custom(format!(
|
||||
"{} theme contains unrecognized theme keywords: {}",
|
||||
tk,
|
||||
s.other_themes[tk]
|
||||
theme
|
||||
.keys
|
||||
.keys()
|
||||
.into_iter()
|
||||
|
@ -543,6 +627,8 @@ impl<'de> Deserialize<'de> for Themes {
|
|||
.join(", ")
|
||||
)));
|
||||
}
|
||||
t.color_aliases = theme.color_aliases;
|
||||
t.attr_aliases = theme.attr_aliases;
|
||||
}
|
||||
Ok(ret)
|
||||
}
|
||||
|
@ -554,15 +640,64 @@ impl Themes {
|
|||
.keys()
|
||||
.filter_map(|k| {
|
||||
if !hash_set.contains(&k.as_ref()) {
|
||||
Some((None, "key", k.as_ref()))
|
||||
Some((None, "key", "invalid key", k.as_ref()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.chain(theme.color_aliases.iter().filter_map(|(key, a)| match a {
|
||||
ThemeValue::Link(ref r, ref field) => {
|
||||
if !hash_set.contains(&r.as_ref()) {
|
||||
Some((
|
||||
Some(key),
|
||||
match field {
|
||||
ColorField::LikeSelf => "Color alias link",
|
||||
ColorField::Fg => "Color alias fg link",
|
||||
ColorField::Bg => "Color alias bg link",
|
||||
},
|
||||
"invalid key",
|
||||
r.as_ref(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
ThemeValue::Alias(ref ident) => {
|
||||
if !theme.color_aliases.contains_key(ident.as_ref()) {
|
||||
Some((Some(key), "alias", "nonexistant color alias", ident))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}))
|
||||
.chain(theme.attr_aliases.iter().filter_map(|(key, a)| match a {
|
||||
ThemeValue::Link(ref r, ()) => {
|
||||
if !hash_set.contains(&r.as_ref()) {
|
||||
Some((Some(key), "Attr alias link", "invalid key", r.as_ref()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
ThemeValue::Alias(ref ident) => {
|
||||
if !theme.attr_aliases.contains_key(ident.as_ref()) {
|
||||
Some((Some(key), "alias", "nonexistant color alias", ident))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}))
|
||||
.chain(theme.iter().filter_map(|(key, a)| {
|
||||
if let ThemeValue::Link(ref r, _) = a.fg {
|
||||
if !hash_set.contains(&r.as_ref()) {
|
||||
Some((Some(key), "fg link", r.as_ref()))
|
||||
Some((Some(key), "fg link", "invalid key", r.as_ref()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if let ThemeValue::Alias(ref ident) = a.fg {
|
||||
if !theme.color_aliases.contains_key(ident.as_ref()) {
|
||||
Some((Some(key), "fg alias", "nonexistant color alias", ident))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -573,7 +708,13 @@ impl Themes {
|
|||
.chain(theme.iter().filter_map(|(key, a)| {
|
||||
if let ThemeValue::Link(ref r, _) = a.bg {
|
||||
if !hash_set.contains(&r.as_ref()) {
|
||||
Some((Some(key), "bg link", r.as_ref()))
|
||||
Some((Some(key), "bg link", "invalid key", r.as_ref()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if let ThemeValue::Alias(ref ident) = a.bg {
|
||||
if !theme.color_aliases.contains_key(ident.as_ref()) {
|
||||
Some((Some(key), "bg alias", "nonexistant color alias", ident))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -584,7 +725,18 @@ impl Themes {
|
|||
.chain(theme.iter().filter_map(|(key, a)| {
|
||||
if let ThemeValue::Link(ref r, _) = a.attrs {
|
||||
if !hash_set.contains(&r.as_ref()) {
|
||||
Some((Some(key), "attrs link", r.as_ref()))
|
||||
Some((Some(key), "attrs link", "invalid key", r.as_ref()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if let ThemeValue::Alias(ref ident) = a.attrs {
|
||||
if !theme.attr_aliases.contains_key(ident.as_ref()) {
|
||||
Some((
|
||||
Some(key),
|
||||
"attrs alias",
|
||||
"nonexistant text attribute alias",
|
||||
ident,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -592,17 +744,17 @@ impl Themes {
|
|||
None
|
||||
}
|
||||
}))
|
||||
.collect::<SmallVec<[(Option<_>, &'_ str, &'_ str); 128]>>();
|
||||
.collect::<SmallVec<[(Option<_>, &'_ str, &'_ str, &'_ str); 128]>>();
|
||||
|
||||
if !keys.is_empty() {
|
||||
return Err(format!(
|
||||
"{} theme contains unrecognized theme keywords: {}",
|
||||
"{} theme contains invalid data: {}",
|
||||
name,
|
||||
keys.into_iter()
|
||||
.map(|(key_opt, desc, link)| if let Some(key) = key_opt {
|
||||
format!("{} {}: \"{}\"", key, desc, link)
|
||||
.map(|(key_opt, desc, kind, link)| if let Some(key) = key_opt {
|
||||
format!("{} {}: {} \"{}\"", key, desc, kind, link)
|
||||
} else {
|
||||
format!("{}: \"{}\"", desc, link)
|
||||
format!("{}: {} \"{}\"", desc, kind, link)
|
||||
})
|
||||
.collect::<SmallVec<[String; 128]>>()
|
||||
.join(", ")
|
||||
|
@ -1036,8 +1188,16 @@ impl Default for Themes {
|
|||
add!("pager.highlight_search", light = { fg: Color::White, bg: Color::Byte(6) /* Teal */, attrs: Attr::BOLD }, dark = { fg: Color::White, bg: Color::Byte(6) /* Teal */, attrs: Attr::BOLD });
|
||||
add!("pager.highlight_search_current", light = { fg: Color::White, bg: Color::Byte(17) /* NavyBlue */, attrs: Attr::BOLD }, dark = { fg: Color::White, bg: Color::Byte(17) /* NavyBlue */, attrs: Attr::BOLD });
|
||||
Themes {
|
||||
light: Theme { keys: light },
|
||||
dark: Theme { keys: dark },
|
||||
light: Theme {
|
||||
keys: light,
|
||||
attr_aliases: Default::default(),
|
||||
color_aliases: Default::default(),
|
||||
},
|
||||
dark: Theme {
|
||||
keys: dark,
|
||||
attr_aliases: Default::default(),
|
||||
color_aliases: Default::default(),
|
||||
},
|
||||
other_themes,
|
||||
}
|
||||
}
|
||||
|
@ -1103,6 +1263,9 @@ fn is_cyclic(theme: &Theme) -> std::result::Result<(), String> {
|
|||
Fg,
|
||||
Bg,
|
||||
Attrs,
|
||||
ColorAliasFg,
|
||||
ColorAliasBg,
|
||||
AttrAlias,
|
||||
}
|
||||
fn is_cyclic_util<'a>(
|
||||
course: Course,
|
||||
|
@ -1141,6 +1304,24 @@ fn is_cyclic(theme: &Theme) -> std::result::Result<(), String> {
|
|||
}
|
||||
path.pop();
|
||||
}
|
||||
ThemeValue::Alias(ref ident) => {
|
||||
path.push((ident, Course::ColorAliasFg));
|
||||
if !visited[&(ident, Course::ColorAliasFg)]
|
||||
&& is_cyclic_util(
|
||||
Course::ColorAliasFg,
|
||||
ident,
|
||||
visited,
|
||||
stack,
|
||||
path,
|
||||
theme,
|
||||
)
|
||||
{
|
||||
return true;
|
||||
} else if stack[&(ident, Course::ColorAliasFg)] {
|
||||
return true;
|
||||
}
|
||||
path.pop();
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Course::Bg => match theme[k].bg {
|
||||
|
@ -1167,6 +1348,24 @@ fn is_cyclic(theme: &Theme) -> std::result::Result<(), String> {
|
|||
}
|
||||
path.pop();
|
||||
}
|
||||
ThemeValue::Alias(ref ident) => {
|
||||
path.push((ident, Course::ColorAliasBg));
|
||||
if !visited[&(ident, Course::ColorAliasBg)]
|
||||
&& is_cyclic_util(
|
||||
Course::ColorAliasBg,
|
||||
ident,
|
||||
visited,
|
||||
stack,
|
||||
path,
|
||||
theme,
|
||||
)
|
||||
{
|
||||
return true;
|
||||
} else if stack[&(ident, Course::ColorAliasBg)] {
|
||||
return true;
|
||||
}
|
||||
path.pop();
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Course::Attrs => match theme[k].attrs {
|
||||
|
@ -1181,6 +1380,76 @@ fn is_cyclic(theme: &Theme) -> std::result::Result<(), String> {
|
|||
}
|
||||
path.pop();
|
||||
}
|
||||
ThemeValue::Alias(ref ident) => {
|
||||
path.push((ident, Course::AttrAlias));
|
||||
if !visited[&(ident, Course::AttrAlias)]
|
||||
&& is_cyclic_util(Course::AttrAlias, ident, visited, stack, path, theme)
|
||||
{
|
||||
return true;
|
||||
} else if stack[&(ident, Course::AttrAlias)] {
|
||||
return true;
|
||||
}
|
||||
path.pop();
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Course::ColorAliasFg | Course::ColorAliasBg => match &theme.color_aliases[k] {
|
||||
ThemeValue::Link(ref l, ref field) => {
|
||||
let course = match (course, field) {
|
||||
(Course::ColorAliasFg, ColorField::LikeSelf) => Course::Fg,
|
||||
(Course::ColorAliasBg, ColorField::LikeSelf) => Course::Bg,
|
||||
(_, ColorField::LikeSelf) => unsafe {
|
||||
std::hint::unreachable_unchecked()
|
||||
},
|
||||
(_, ColorField::Fg) => Course::Fg,
|
||||
(_, ColorField::Bg) => Course::Bg,
|
||||
};
|
||||
path.push((l, course));
|
||||
if !visited[&(l, course)]
|
||||
&& is_cyclic_util(course, l, visited, stack, path, theme)
|
||||
{
|
||||
return true;
|
||||
} else if stack[&(l, course)] {
|
||||
return true;
|
||||
}
|
||||
path.pop();
|
||||
}
|
||||
ThemeValue::Alias(ref ident) => {
|
||||
path.push((ident, course));
|
||||
if !visited[&(ident, course)]
|
||||
&& is_cyclic_util(course, ident, visited, stack, path, theme)
|
||||
{
|
||||
return true;
|
||||
} else if stack[&(ident, course)] {
|
||||
return true;
|
||||
}
|
||||
path.pop();
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Course::AttrAlias => match &theme.attr_aliases[k] {
|
||||
ThemeValue::Link(ref l, ()) => {
|
||||
path.push((l, Course::Attrs));
|
||||
if !visited[&(l, Course::Attrs)]
|
||||
&& is_cyclic_util(Course::Attrs, l, visited, stack, path, theme)
|
||||
{
|
||||
return true;
|
||||
} else if stack[&(l, Course::Attrs)] {
|
||||
return true;
|
||||
}
|
||||
path.pop();
|
||||
}
|
||||
ThemeValue::Alias(ref ident) => {
|
||||
path.push((ident, course));
|
||||
if !visited[&(ident, course)]
|
||||
&& is_cyclic_util(course, ident, visited, stack, path, theme)
|
||||
{
|
||||
return true;
|
||||
} else if stack[&(ident, course)] {
|
||||
return true;
|
||||
}
|
||||
path.pop();
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
|
@ -1198,6 +1467,22 @@ fn is_cyclic(theme: &Theme) -> std::result::Result<(), String> {
|
|||
.chain(std::iter::once(((k, Course::Attrs), false)))
|
||||
})
|
||||
.flatten()
|
||||
.chain(
|
||||
theme
|
||||
.color_aliases
|
||||
.keys()
|
||||
.map(|k| {
|
||||
std::iter::once(((k, Course::ColorAliasFg), false))
|
||||
.chain(std::iter::once(((k, Course::ColorAliasBg), false)))
|
||||
})
|
||||
.flatten(),
|
||||
)
|
||||
.chain(
|
||||
theme
|
||||
.attr_aliases
|
||||
.keys()
|
||||
.map(|k| ((k, Course::AttrAlias), false)),
|
||||
)
|
||||
.collect::<HashMap<(&Cow<'static, str>, Course), bool>>();
|
||||
|
||||
let mut stack = visited.clone();
|
||||
|
@ -1207,16 +1492,13 @@ fn is_cyclic(theme: &Theme) -> std::result::Result<(), String> {
|
|||
if is_cyclic_util(course, k, &mut visited, &mut stack, &mut path, &theme) {
|
||||
let path = path
|
||||
.into_iter()
|
||||
.map(|(k, c)| {
|
||||
format!(
|
||||
"{}.{}",
|
||||
k,
|
||||
match c {
|
||||
Course::Fg => "fg",
|
||||
Course::Bg => "bg",
|
||||
Course::Attrs => "attrs",
|
||||
}
|
||||
)
|
||||
.map(|(k, c)| match c {
|
||||
Course::Fg => format!("{}.fg", k,),
|
||||
Course::Bg => format!("{}.fg", k,),
|
||||
Course::Attrs => format!("{}.attrs", k,),
|
||||
Course::ColorAliasFg => format!("(Color fg) ${}", k),
|
||||
Course::ColorAliasBg => format!("(Color bg) ${}", k),
|
||||
Course::AttrAlias => format!("(Attr) ${}", k),
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
return Err(format!(
|
||||
|
@ -1225,6 +1507,9 @@ fn is_cyclic(theme: &Theme) -> std::result::Result<(), String> {
|
|||
Course::Fg => "fg: ",
|
||||
Course::Bg => "bg: ",
|
||||
Course::Attrs => "attrs: ",
|
||||
Course::ColorAliasFg => "color alias fg: ",
|
||||
Course::ColorAliasBg => "color alias bg: ",
|
||||
Course::AttrAlias => "attribute alias: ",
|
||||
},
|
||||
path.join(" -> ")
|
||||
));
|
||||
|
@ -1241,8 +1526,10 @@ fn is_cyclic(theme: &Theme) -> std::result::Result<(), String> {
|
|||
|
||||
#[test]
|
||||
fn test_theme_parsing() {
|
||||
/* MUST SUCCEED: default themes should be valid */
|
||||
let def = Themes::default();
|
||||
assert!(def.validate().is_ok());
|
||||
/* MUST SUCCEED: new user theme `hunter2`, theme `dark` has user redefinitions */
|
||||
const TEST_STR: &'static str = r##"[dark]
|
||||
"mail.listing.tag_default" = { fg = "White", bg = "HotPink3" }
|
||||
"mail.listing.attachment_flag" = { fg = "mail.listing.tag_default.bg" }
|
||||
|
@ -1277,15 +1564,74 @@ fn test_theme_parsing() {
|
|||
Color::Byte(15), // White
|
||||
);
|
||||
assert!(parsed.validate().is_ok());
|
||||
/* MUST FAIL: theme `dark` contains a cycle */
|
||||
const HAS_CYCLE: &'static str = r##"[dark]
|
||||
"mail.listing.compact.even" = { fg = "mail.listing.compact.odd" }
|
||||
"mail.listing.compact.odd" = { fg = "mail.listing.compact.even" }
|
||||
"##;
|
||||
let parsed: Themes = toml::from_str(HAS_CYCLE).unwrap();
|
||||
assert!(parsed.validate().is_err());
|
||||
/* MUST FAIL: theme `dark` contains an invalid key */
|
||||
const HAS_INVALID_KEYS: &'static str = r##"[dark]
|
||||
"asdfsafsa" = { fg = "Black" }
|
||||
"##;
|
||||
let parsed: std::result::Result<Themes, _> = toml::from_str(HAS_INVALID_KEYS);
|
||||
assert!(parsed.is_err());
|
||||
/* MUST SUCCEED: alias $Jebediah resolves to a valid color */
|
||||
const TEST_ALIAS_STR: &'static str = r##"[dark]
|
||||
color_aliases= { "Jebediah" = "#b4da55" }
|
||||
"mail.listing.tag_default" = { fg = "$Jebediah" }
|
||||
"##;
|
||||
let parsed: Themes = toml::from_str(TEST_ALIAS_STR).unwrap();
|
||||
assert!(parsed.validate().is_ok());
|
||||
assert_eq!(
|
||||
unlink_fg(
|
||||
&parsed.dark,
|
||||
&ColorField::Fg,
|
||||
&Cow::from("mail.listing.tag_default")
|
||||
),
|
||||
Color::Rgb(180, 218, 85)
|
||||
);
|
||||
/* MUST FAIL: Mispell color alias $Jebediah as $Jebedia */
|
||||
const TEST_INVALID_ALIAS_STR: &'static str = r##"[dark]
|
||||
color_aliases= { "Jebediah" = "#b4da55" }
|
||||
"mail.listing.tag_default" = { fg = "$Jebedia" }
|
||||
"##;
|
||||
let parsed: Themes = toml::from_str(TEST_INVALID_ALIAS_STR).unwrap();
|
||||
assert!(parsed.validate().is_err());
|
||||
/* MUST FAIL: Color alias $Jebediah is defined as itself */
|
||||
const TEST_CYCLIC_ALIAS_STR: &'static str = r##"[dark]
|
||||
color_aliases= { "Jebediah" = "$Jebediah" }
|
||||
"mail.listing.tag_default" = { fg = "$Jebediah" }
|
||||
"##;
|
||||
let parsed: Themes = toml::from_str(TEST_CYCLIC_ALIAS_STR).unwrap();
|
||||
assert!(parsed.validate().is_err());
|
||||
/* MUST FAIL: Attr alias $Jebediah is defined as itself */
|
||||
const TEST_CYCLIC_ALIAS_ATTR_STR: &'static str = r##"[dark]
|
||||
attr_aliases= { "Jebediah" = "$Jebediah" }
|
||||
"mail.listing.tag_default" = { attrs = "$Jebediah" }
|
||||
"##;
|
||||
let parsed: Themes = toml::from_str(TEST_CYCLIC_ALIAS_ATTR_STR).unwrap();
|
||||
assert!(parsed.validate().is_err());
|
||||
/* MUST FAIL: alias $Jebediah resolves to a cycle */
|
||||
const TEST_CYCLIC_ALIAS_STR_2: &'static str = r##"[dark]
|
||||
color_aliases= { "Jebediah" = "$JebediahJr", "JebediahJr" = "mail.listing.tag_default" }
|
||||
"mail.listing.tag_default" = { fg = "$Jebediah" }
|
||||
"##;
|
||||
let parsed: Themes = toml::from_str(TEST_CYCLIC_ALIAS_STR_2).unwrap();
|
||||
assert!(parsed.validate().is_err());
|
||||
/* MUST SUCCEED: alias $Jebediah resolves to a key's field */
|
||||
const TEST_CYCLIC_ALIAS_STR_3: &'static str = r##"[dark]
|
||||
color_aliases= { "Jebediah" = "$JebediahJr", "JebediahJr" = "mail.listing.tag_default.bg" }
|
||||
"mail.listing.tag_default" = { fg = "$Jebediah", bg = "Black" }
|
||||
"##;
|
||||
let parsed: Themes = toml::from_str(TEST_CYCLIC_ALIAS_STR_3).unwrap();
|
||||
assert!(parsed.validate().is_ok());
|
||||
/* MUST FAIL: alias $Jebediah resolves to an invalid key */
|
||||
const TEST_INVALID_LINK_KEY_FIELD_STR: &'static str = r##"[dark]
|
||||
color_aliases= { "Jebediah" = "$JebediahJr", "JebediahJr" = "mail.listing.tag_default.attrs" }
|
||||
"mail.listing.tag_default" = { fg = "$Jebediah", bg = "Black" }
|
||||
"##;
|
||||
let parsed: Themes = toml::from_str(TEST_INVALID_LINK_KEY_FIELD_STR).unwrap();
|
||||
assert!(parsed.validate().is_err());
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue