349 lines
11 KiB
Rust
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,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|