hlctl/src/hlwm/attribute.rs

349 lines
11 KiB
Rust

use std::str::FromStr;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::hlwm::{command::HlwmCommand, hex::ParseHex};
use super::{
color::{Color, X11Color},
command::CommandError,
hex::HexError,
hlwmbool,
octal::{OctalError, ParseOctal},
parser::{FromStringsHint, ParseError, ToOption},
};
#[derive(Debug, Clone, Error)]
pub enum AttributeError {
#[error("error parsing value: {0}")]
ParseError(#[from] 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),
}
impl From<AttributeError> for ParseError {
fn from(value: AttributeError) -> Self {
match value {
AttributeError::UnknownType(t) => ParseError::InvalidCommand(t),
_ => ParseError::PrimitiveError(value.to_string()),
}
}
}
#[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 FromStringsHint<&str> for AttributeOption {
fn from_strings_hint<I: Iterator<Item = String>>(s: I, hint: &str) -> Result<Self, ParseError> {
let s = s.collect::<Vec<_>>().to_option().map(|t| t.join(" "));
Ok(Self::new(AttributeType::from_str(hint)?, s.as_ref())?)
}
}
impl Default for AttributeOption {
fn default() -> Self {
Self::Int(None)
}
}
impl AttributeOption {
pub fn new(
attr_type: AttributeType,
value_string: Option<&String>,
) -> Result<Self, AttributeError> {
if let Some(val) = value_string {
return Ok(Attribute::new(attr_type, val)?.into());
}
match attr_type {
AttributeType::Int => Ok(Self::Int(None)),
AttributeType::Bool => Ok(Self::Bool(None)),
AttributeType::Uint => Ok(Self::Uint(None)),
AttributeType::Color => Ok(Self::Color(None)),
AttributeType::WindowID => Ok(Self::WindowID(None)),
AttributeType::Rectangle => Ok(Self::Rectangle(None)),
AttributeType::String => Ok(Self::String(None)),
}
}
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 FromStringsHint<&str> for Attribute {
fn from_strings_hint<I: Iterator<Item = String>>(s: I, hint: &str) -> Result<Self, ParseError> {
let value = s.collect::<Vec<_>>().join(" ");
Ok(Self::new(
AttributeType::get_type_or_guess(hint, &value),
&value,
)?)
}
}
impl Attribute {
pub fn new(attr_type: AttributeType, value_string: &str) -> Result<Self, AttributeError> {
match attr_type {
AttributeType::Bool => Ok(Self::Bool(hlwmbool::from_hlwm_string(value_string)?)),
AttributeType::Color => Ok(Attribute::Color(Color::from_str(value_string)?)),
AttributeType::Int => Ok(Attribute::Int(
value_string.parse().map_err(|err| ParseError::from(err))?,
)),
AttributeType::String => Ok(Attribute::String(value_string.to_string())),
AttributeType::Uint => Ok(Attribute::Uint(
value_string.parse().map_err(|err| ParseError::from(err))?,
)),
AttributeType::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()
.map_err(|err| ParseError::from(err))?,
y: parts
.get(1)
.unwrap()
.parse()
.map_err(|err| ParseError::from(err))?,
})
}
AttributeType::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().map_err(|err| ParseError::from(err))?,
))
}
}
}
}
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 = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"bool" => Ok(Self::Bool),
"color" => Ok(Self::Color),
"int" => Ok(Self::Int),
"string" | "names" | "regex" | "font" => Ok(Self::String),
"uint" => Ok(Self::Uint),
"rectangle" => Ok(Self::Rectangle),
"windowid" => Ok(Self::WindowID),
_ => Err(ParseError::InvalidCommand(s.to_string())),
}
}
}
impl AttributeType {
/// Gets the type for the given `path` from herbstluftwm
pub fn get_type(path: &str) -> Result<AttributeType, CommandError> {
Ok(Self::from_str(
&String::from_utf8(HlwmCommand::AttrType(path.to_string()).execute()?.stdout)?
.split('\n')
.map(|l| l.trim())
.filter(|l| !l.is_empty())
.next()
.ok_or(CommandError::Empty)?,
)?)
}
/// Tries to get the type for the given `path`, but defaults to [AttributeType::String]
/// if that fails in any way
pub fn get_type_or_string(path: &str) -> AttributeType {
Self::get_type(path).unwrap_or(AttributeType::String)
}
/// Tries to get the type for the given `path`, and if that fails
/// tries to guess the type using [Self::guess]
pub fn get_type_or_guess(path: &str, value: &str) -> Self {
Self::get_type(path).unwrap_or(Self::guess(value))
}
pub fn guess(value: &str) -> Self {
if value.is_empty() {
return Self::String;
}
match value {
"on" | "true" | "off" | "false" => Self::Bool,
_ => {
// Match for all colors first
if X11Color::iter().into_iter().any(|c| c.to_string() == value) {
return Self::Color;
}
// Is it a valid color string?
if Color::from_hex(value).is_ok() {
return Self::Color;
}
// Match for primitive types or color string
let mut chars = value.chars();
match chars.next().unwrap() {
'+' => Self::guess(&chars.collect::<String>()),
'0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '-' => Self::Int,
_ => Self::String,
}
}
}
}
}