hlctl/src/hlwm/attribute.rs

292 lines
9.3 KiB
Rust

use std::{num::ParseIntError, str::FromStr};
use serde::{Deserialize, Serialize};
use strum::IntoEnumIterator;
use thiserror::Error;
use crate::hlwm::hex::ParseHex;
use super::{
color::{self, Color, X11Color},
hex::HexError,
octal::{OctalError, ParseOctal},
StringParseError,
};
#[derive(Debug, Clone, Error)]
pub enum AttributeError {
#[error("error parsing integer value: {0:?}")]
ParseIntError(#[from] ParseIntError),
#[error("error parsing bool value: [{0}]")]
ParseBoolError(String),
#[error("error parsing color value: {0}")]
ParseColorError(#[from] color::ParseError),
#[error("unknown attribute type [{0}]")]
UnknownType(String),
#[error("not a valid rectangle: [{0}]")]
NotRectangle(String),
#[error("hex parsing error: [{0}]")]
HexError(#[from] HexError),
#[error("octal parsing error: [{0}]")]
OctalError(#[from] OctalError),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum AttributeOption {
Bool(Option<bool>),
Color(Option<Color>),
Int(Option<i32>),
String(Option<String>),
Uint(Option<u32>),
Rectangle(Option<(u32, u32)>),
WindowID(Option<u32>),
}
impl Default for AttributeOption {
fn default() -> Self {
Self::Int(None)
}
}
impl AttributeOption {
pub fn new(type_string: &str, value_string: &Option<String>) -> Result<Self, AttributeError> {
if let Some(val) = value_string {
return Ok(Attribute::new(type_string, val)?.into());
}
match type_string {
"int" => Ok(Self::Int(None)),
"bool" => Ok(Self::Bool(None)),
"uint" => Ok(Self::Uint(None)),
"color" => Ok(Self::Color(None)),
"windowid" => Ok(Self::WindowID(None)),
"rectangle" => Ok(Self::Rectangle(None)),
"string" | "names" | "regex" | "font" => Ok(Self::String(None)),
_ => Err(AttributeError::UnknownType(type_string.to_string())),
}
}
fn to_default_attr(&self) -> Attribute {
match self {
AttributeOption::Int(_) => Attribute::Int(Default::default()),
AttributeOption::Uint(_) => Attribute::Uint(Default::default()),
AttributeOption::Bool(_) => Attribute::Bool(Default::default()),
AttributeOption::Color(_) => Attribute::Color(Default::default()),
AttributeOption::String(_) => Attribute::String(Default::default()),
AttributeOption::WindowID(_) => Attribute::WindowID(Default::default()),
AttributeOption::Rectangle(_) => Attribute::Rectangle {
x: Default::default(),
y: Default::default(),
},
}
}
pub fn type_string(&self) -> &'static str {
self.to_default_attr().type_string()
}
pub fn value_string(&self) -> Option<String> {
match self {
AttributeOption::Int(val) => val.map(|val| val.to_string()),
AttributeOption::Bool(val) => val.map(|val| val.to_string()),
AttributeOption::Uint(val) => val.map(|val| val.to_string()),
AttributeOption::Color(val) => val.clone().map(|val| val.to_string()),
AttributeOption::String(val) => val.clone().map(|val| val.to_string()),
AttributeOption::WindowID(val) => val.map(|w| format!("{w:#x}")),
AttributeOption::Rectangle(val) => val.map(|(x, y)| format!("{x}x{y}")),
}
}
}
impl From<Attribute> for AttributeOption {
fn from(value: Attribute) -> Self {
match value {
Attribute::Bool(val) => Self::Bool(Some(val)),
Attribute::Color(val) => Self::Color(Some(val)),
Attribute::Int(val) => Self::Int(Some(val)),
Attribute::String(val) => Self::String(Some(val)),
Attribute::Uint(val) => Self::Uint(Some(val)),
Attribute::Rectangle { x, y } => Self::Rectangle(Some((x, y))),
Attribute::WindowID(win) => Self::WindowID(Some(win)),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, strum::EnumDiscriminants)]
#[strum_discriminants(
name(AttributeType),
derive(strum::Display, strum::EnumIter),
strum(serialize_all = "lowercase")
)]
pub enum Attribute {
Bool(bool),
Color(Color),
Int(i32),
String(String),
Uint(u32),
Rectangle { x: u32, y: u32 },
WindowID(u32),
}
impl From<i32> for Attribute {
fn from(value: i32) -> Self {
Self::Int(value)
}
}
impl From<u32> for Attribute {
fn from(value: u32) -> Self {
Self::Uint(value)
}
}
impl From<String> for Attribute {
fn from(value: String) -> Self {
Self::String(value)
}
}
impl From<&str> for Attribute {
fn from(value: &str) -> Self {
Self::String(value.to_string())
}
}
impl From<Color> for Attribute {
fn from(value: Color) -> Self {
Self::Color(value)
}
}
impl From<bool> for Attribute {
fn from(value: bool) -> Self {
Self::Bool(value)
}
}
impl From<(u32, u32)> for Attribute {
fn from((x, y): (u32, u32)) -> Self {
Self::Rectangle { x, y }
}
}
impl Attribute {
pub fn new(type_string: &str, value_string: &str) -> Result<Self, AttributeError> {
match type_string {
"bool" => match value_string {
"on" | "true" => Ok(Attribute::Bool(true)),
"off" | "false" => Ok(Attribute::Bool(false)),
_ => Err(AttributeError::ParseBoolError(type_string.to_string())),
},
"color" => Ok(Attribute::Color(Color::from_str(value_string)?)),
"int" => Ok(Attribute::Int(value_string.parse()?)),
"string" | "names" | "regex" | "font" => {
Ok(Attribute::String(value_string.to_string()))
}
"uint" => Ok(Attribute::Uint(value_string.parse()?)),
"rectangle" => {
let parts = value_string.split('x').collect::<Vec<_>>();
if parts.len() != 2 {
return Err(AttributeError::NotRectangle(value_string.to_string()));
}
Ok(Attribute::Rectangle {
x: parts.get(0).unwrap().parse()?,
y: parts.get(1).unwrap().parse()?,
})
}
"windowid" => {
if let Some(hex) = value_string.strip_prefix("0x") {
Ok(Attribute::WindowID(hex.parse_hex()?))
} else if let Some(octal) = value_string.strip_prefix("0") {
Ok(Attribute::WindowID(octal.parse_octal()?))
} else {
Ok(Attribute::WindowID(value_string.parse()?))
}
}
_ => Err(AttributeError::UnknownType(type_string.to_string())),
}
}
fn guess_from_value(value: &str) -> Option<Attribute> {
match value {
"on" | "true" => Some(Self::Bool(true)),
"off" | "false" => Some(Self::Bool(false)),
_ => {
// Match for all colors first
if let Some(color) = X11Color::iter()
.into_iter()
.find(|c| c.to_string() == value)
{
return Some(Attribute::Color(Color::X11(color)));
}
// Match for primitive types or color string
let mut chars = value.chars();
match chars.next().unwrap() {
'+' => Self::guess_from_value(&chars.collect::<String>()),
'0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '-' => {
i32::from_str(value).ok().map(|i| Attribute::Int(i))
}
'#' => Color::from_hex(value).ok().map(|c| Attribute::Color(c)),
_ => None,
}
}
}
}
pub fn guess_type(value: &str) -> Self {
if value.is_empty() {
Self::String(String::new())
} else {
Self::guess_from_value(value).unwrap_or(Attribute::String(value.to_string()))
}
}
pub fn type_string(&self) -> &'static str {
match self {
Attribute::Bool(_) => "bool",
Attribute::Color(_) => "color",
Attribute::Int(_) => "int",
Attribute::String(_) => "string",
Attribute::Uint(_) => "uint",
Attribute::Rectangle { x: _, y: _ } => "rectangle",
Attribute::WindowID(_) => "windowid",
}
}
}
impl std::fmt::Display for Attribute {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Attribute::Bool(b) => write!(f, "{b}"),
Attribute::Color(c) => write!(f, "{c}"),
Attribute::Int(i) => write!(f, "{i}"),
Attribute::String(s) => f.write_str(s),
Attribute::Uint(u) => write!(f, "{u}"),
Attribute::Rectangle { x, y } => write!(f, "{x}x{y}"),
Attribute::WindowID(win) => write!(f, "{win:#x}"),
}
}
}
impl Default for Attribute {
fn default() -> Self {
Self::Int(0)
}
}
impl Default for AttributeType {
fn default() -> Self {
Self::String
}
}
impl FromStr for AttributeType {
type Err = StringParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::iter()
.find(|t| t.to_string() == s)
.ok_or(StringParseError::UnknownValue)
}
}