239 lines
5.8 KiB
Rust
239 lines
5.8 KiB
Rust
use std::{borrow::BorrowMut, num::ParseIntError};
|
|
|
|
use serde::{de::Expected, Deserialize, Serialize};
|
|
use strum::IntoEnumIterator;
|
|
use thiserror::Error;
|
|
|
|
use crate::split;
|
|
|
|
use super::{
|
|
attribute::Attribute,
|
|
color::{self, Color},
|
|
StringParseError,
|
|
};
|
|
|
|
#[derive(Debug, Error)]
|
|
pub enum ThemeAttrParseError {
|
|
#[error("path not found")]
|
|
PathNotFound,
|
|
#[error("integer value parse error: [{0}]")]
|
|
ParseIntError(#[from] ParseIntError),
|
|
#[error("color value parse error: [{0}]")]
|
|
ColorParseError(#[from] color::ParseError),
|
|
#[error("string value parse error: [{0}]")]
|
|
StringParseError(#[from] StringParseError),
|
|
}
|
|
|
|
macro_rules! theme_attr {
|
|
($($name:tt($value:tt),)*) => {
|
|
#[derive(Debug, Clone, strum::Display, strum::EnumIter)]
|
|
#[strum(serialize_all = "snake_case")]
|
|
pub enum ThemeAttr {
|
|
$(
|
|
$name($value),
|
|
)*
|
|
}
|
|
|
|
impl PartialEq for ThemeAttr {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
match (self, other) {
|
|
$(
|
|
(Self::$name(_), Self::$name(_)) => true,
|
|
)+
|
|
_ => false,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<ThemeAttr> for Attribute {
|
|
fn from(value: ThemeAttr) -> Self {
|
|
match value {
|
|
$(
|
|
ThemeAttr::$name(val) => theme_attr!(Attr $value)(val),
|
|
)*
|
|
}
|
|
}
|
|
}
|
|
impl ThemeAttr {
|
|
pub fn from_raw_parts(path: &str, value: &str) -> Result<Self, ThemeAttrParseError> {
|
|
match ThemeAttr::iter()
|
|
.find(|attr| attr.attr_path() == path)
|
|
.map(|attr| -> Result<Self, ThemeAttrParseError> {
|
|
let mut attr = attr.clone();
|
|
match attr.borrow_mut() {
|
|
$(
|
|
ThemeAttr::$name(val) => *val = theme_attr!(Parse value: $value),
|
|
)+
|
|
};
|
|
Ok(attr)
|
|
})
|
|
.map(|res| if let Err(err) = res {panic!("::: {err}")} else {res})
|
|
.ok_or(ThemeAttrParseError::PathNotFound)
|
|
{
|
|
Ok(res) => res,
|
|
Err(err) => return Err(err),
|
|
}
|
|
}
|
|
}
|
|
};
|
|
(Attr u32) => {
|
|
Attribute::Uint
|
|
};
|
|
(Attr String) => {
|
|
Attribute::String
|
|
};
|
|
(Attr bool) => {
|
|
Attribute::Bool
|
|
};
|
|
(Attr Color) => {
|
|
Attribute::Color
|
|
};
|
|
(Attr i32) => {
|
|
Attribute::Int
|
|
};
|
|
(Default u32) => {
|
|
0u32
|
|
};
|
|
(Default i32) => {
|
|
0i32
|
|
};
|
|
(Default Color) => {
|
|
Color::BLACK
|
|
};
|
|
(Default String) => {
|
|
String::new()
|
|
};
|
|
(Parse $v:tt: String) => {
|
|
$v.to_string()
|
|
};
|
|
(Parse $v:tt: $v_ty:tt) => {
|
|
$v.parse::<$v_ty>()?
|
|
}
|
|
}
|
|
|
|
theme_attr!(
|
|
TitleHeight(u32),
|
|
TitleWhen(String),
|
|
TitleFont(String),
|
|
TitleDepth(i32),
|
|
InnerWidth(u32),
|
|
InnerColor(Color),
|
|
BorderWidth(u32),
|
|
FloatingBorderWidth(u32),
|
|
FloatingOuterWidth(u32),
|
|
TilingOuterWidth(u32),
|
|
BackgroundColor(Color),
|
|
ActiveColor(Color),
|
|
ActiveInnerColor(Color),
|
|
ActiveOuterColor(Color),
|
|
NormalColor(Color),
|
|
NormalInnerColor(Color),
|
|
NormalOuterColor(Color),
|
|
NormalTitleColor(Color),
|
|
UrgentColor(Color),
|
|
UrgentInnerColor(Color),
|
|
UrgentOuterColor(Color),
|
|
TitleColor(Color),
|
|
);
|
|
|
|
impl ThemeAttr {
|
|
pub fn attr_path(&self) -> String {
|
|
macro_rules! match_section {
|
|
($self:ident => $($section:literal),+) => {
|
|
{
|
|
let attr_str = $self.to_string();
|
|
let mut attr_parts = attr_str.split('_');
|
|
let first = attr_parts.next().unwrap();
|
|
let remainder = attr_parts.collect::<Vec<_>>().join("_");
|
|
match first {
|
|
$(
|
|
$section => ["theme", $section, &remainder].join("."),
|
|
)+
|
|
_ => ["theme", &attr_str].join("."),
|
|
}
|
|
}
|
|
};
|
|
}
|
|
match_section!(self => "floating", "tiling", "active", "normal", "urgent")
|
|
}
|
|
}
|
|
|
|
impl Serialize for ThemeAttr {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: serde::Serializer,
|
|
{
|
|
serializer.serialize_str(&format!(
|
|
"{} = {}",
|
|
self.attr_path(),
|
|
Attribute::from(self.clone())
|
|
))
|
|
}
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for ThemeAttr {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: serde::Deserializer<'de>,
|
|
{
|
|
pub enum Expect {
|
|
NoEquals,
|
|
ThemeAttrParseError(ThemeAttrParseError),
|
|
}
|
|
impl Expected for Expect {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
match self {
|
|
Expect::NoEquals => write!(f, "value having an equals sign"),
|
|
Expect::ThemeAttrParseError(err) => write!(f, "parsing error: {err}"),
|
|
}
|
|
}
|
|
}
|
|
|
|
let str_val: String = Deserialize::deserialize(deserializer)?;
|
|
|
|
let ((first, second), _) =
|
|
split::on_first_match(&str_val, &['=']).ok_or(serde::de::Error::invalid_value(
|
|
serde::de::Unexpected::Str(&str_val),
|
|
&Expect::NoEquals,
|
|
))?;
|
|
|
|
Ok(
|
|
Self::from_raw_parts(&first.trim(), &second.trim()).map_err(|err| {
|
|
serde::de::Error::invalid_value(
|
|
serde::de::Unexpected::Str(&str_val),
|
|
&Expect::ThemeAttrParseError(err),
|
|
)
|
|
})?,
|
|
)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::hlwm::theme::ThemeAttr;
|
|
use pretty_assertions::assert_eq;
|
|
|
|
#[test]
|
|
fn test_partial_eq_theme_attr_string() {
|
|
assert!(ThemeAttr::TitleFont(String::from("hello"))
|
|
.eq(&ThemeAttr::TitleFont(String::from("world"))))
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub struct ThemeAttrWrapper {
|
|
attr: ThemeAttr,
|
|
}
|
|
|
|
#[test]
|
|
fn theme_serialize_deserialize() {
|
|
let theme = ThemeAttrWrapper {
|
|
attr: ThemeAttr::TitleHeight(15),
|
|
};
|
|
let theme_str = toml::to_string_pretty(&theme).expect("serialization");
|
|
let theme_parsed: ThemeAttrWrapper = toml::from_str(&theme_str).expect("deserialization");
|
|
assert_eq!(theme.attr, theme_parsed.attr);
|
|
}
|
|
}
|