Compare commits

..

2 Commits

27 changed files with 1059 additions and 1339 deletions

8
Cargo.lock generated
View File

@ -1095,18 +1095,18 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]] [[package]]
name = "strum" name = "strum"
version = "0.26.1" version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "723b93e8addf9aa965ebe2d11da6d7540fa2283fcea14b3371ff055f7ba13f5f" checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
dependencies = [ dependencies = [
"strum_macros", "strum_macros",
] ]
[[package]] [[package]]
name = "strum_macros" name = "strum_macros"
version = "0.26.1" version = "0.25.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a3417fc93d76740d974a01654a09777cb500428cc874ca9f45edfe0c4d4cd18" checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",

View File

@ -11,7 +11,7 @@ clap = { version = "4.4.18", features = ["derive", "cargo"] }
paste = "1.0.14" paste = "1.0.14"
serde = { version = "1.0.195", features = ["derive"] } serde = { version = "1.0.195", features = ["derive"] }
serde_json = { version = "1.0.113" } serde_json = { version = "1.0.113" }
strum = { version = "0.26", features = ["derive"] } strum = { version = "0.25.0", features = ["derive"] }
thiserror = "1.0.57" thiserror = "1.0.57"
toml = "0.8.8" toml = "0.8.8"
which = "6.0.0" which = "6.0.0"

View File

@ -1,2 +0,0 @@
[toolchain]
channel = "stable"

View File

@ -4,8 +4,7 @@ use thiserror::Error;
use crate::hlwm::{ use crate::hlwm::{
command::{CommandParseError, HlwmCommand}, command::{CommandParseError, HlwmCommand},
key::Key, key::{Key, KeyParseError},
parser::{FromCommandArgs, ParseError},
}; };
#[derive(Subcommand, Debug, Clone)] #[derive(Subcommand, Debug, Clone)]
@ -22,8 +21,8 @@ pub enum Add {
pub enum Error { pub enum Error {
#[error("readline error: [{0}]")] #[error("readline error: [{0}]")]
ReadlineError(String), ReadlineError(String),
#[error("parse error: [{0}]")] #[error("key parse error: [{0}]")]
KeyParseError(#[from] ParseError), KeyParseError(#[from] KeyParseError),
#[error("could not parse command: [{0}]")] #[error("could not parse command: [{0}]")]
CommandParseError(#[from] CommandParseError), CommandParseError(#[from] CommandParseError),
} }
@ -68,7 +67,7 @@ fn keybind_interactive() -> Result<(), Error> {
.filter(|a| !a.is_empty()) .filter(|a| !a.is_empty())
.map(|a| a.to_string()); .map(|a| a.to_string());
match args.next() { match args.next() {
Some(cmd) => HlwmCommand::from_command_args(&cmd, args)?, Some(cmd) => HlwmCommand::from_raw_parts(&cmd, args.collect())?,
None => { None => {
println!("No command provided"); println!("No command provided");
std::process::exit(1); std::process::exit(1);

View File

@ -17,23 +17,22 @@ use strum::IntoEnumIterator;
use thiserror::Error; use thiserror::Error;
use crate::{ use crate::{
environ::{self, ActiveKeybinds}, environ::{self, ActiveKeybinds, EnvironError},
hlwm::{ hlwm::{
self, self,
attribute::{Attribute, AttributeType}, attribute::Attribute,
color::Color, color::Color,
command::{CommandError, HlwmCommand}, command::{CommandError, HlwmCommand},
hook::Hook, hook::Hook,
key::{Key, KeyUnbind, Keybind, MouseButton, Mousebind, MousebindAction}, key::{Key, KeyUnbind, Keybind, MouseButton, Mousebind, MousebindAction},
parser::{ArgParser, FromCommandArgs, FromStrings, ParseError},
rule::{Condition, Consequence, FloatPlacement, Rule, Unrule}, rule::{Condition, Consequence, FloatPlacement, Rule, Unrule},
setting::{FrameLayout, Setting, ShowFrameDecoration, SmartFrameSurroundings}, setting::{FrameLayout, Setting, ShowFrameDecoration, SmartFrameSurroundings},
theme::ThemeAttr, theme::ThemeAttr,
window::Window, window::Window,
Align, Client, Direction, Index, Operator, Separator, ToggleBool, Align, Client, Direction, Index, Operator, Separator, StringParseError, ToggleBool,
}, },
logerr::UnwrapLog, logerr::UnwrapLog,
rule, split, window_types, rule, window_types,
}; };
#[derive(Debug, Error)] #[derive(Debug, Error)]
@ -52,13 +51,14 @@ pub enum ConfigError {
CommandError(#[from] CommandError), CommandError(#[from] CommandError),
#[error("non-utf8 string error: {0}")] #[error("non-utf8 string error: {0}")]
Utf8StringError(#[from] FromUtf8Error), Utf8StringError(#[from] FromUtf8Error),
#[error("failed parsing value: {0}")] #[error("failed parsing value from string: {0}")]
ParseError(#[from] ParseError), StringParseError(#[from] StringParseError),
#[error("getting from environment error: [{0}]")]
EnvironError(#[from] EnvironError),
} }
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct Config { pub struct Config {
pub use_panel: bool,
pub font: String, pub font: String,
pub font_bold: String, pub font_bold: String,
/// Pango font for use where pango fonts are /// Pango font for use where pango fonts are
@ -98,36 +98,35 @@ impl Display for SetAttribute {
} }
} }
impl FromStrings for SetAttribute { impl FromStr for SetAttribute {
fn from_strings<I: Iterator<Item = String>>(s: I) -> Result<Self, ParseError> { type Err = StringParseError;
let s = s.collect::<Vec<_>>();
let original_str = s.join(" "); fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut parser = ArgParser::from_strings(s.into_iter()); let mut parts = s.split(')');
let x = parser.must_string("set_attribute(type/name)")?; let type_string = parts.next().map(|p| p.strip_prefix('(')).flatten().ok_or(
let (attr_ty, remainder) = split::parens(&x).ok_or(ParseError::InvalidValue { StringParseError::RequiredArgMissing("attribute type".into()),
value: original_str, )?;
expected: "does not have a valid type string (type)", let mut parts = parts
})?; .next()
if remainder.contains('=') { .ok_or(StringParseError::RequiredArgMissing(
let mut parser = ArgParser::from_strings(remainder.split('=').map(|c| c.to_string())); "attribute path/value".into(),
return Ok(Self { ))?
path: parser.must_string("set_attribute(path)")?, .split('=')
value: Attribute::new( .map(|v| v.trim());
AttributeType::from_str(&attr_ty)?, let path = parts
&parser.must_string("set_attribute(value)")?, .next()
)?, .ok_or(StringParseError::RequiredArgMissing(
}); "attribute path".into(),
))?
.to_string();
let value_string = parts.collect::<Vec<_>>().join("=");
if value_string.is_empty() {
return Err(StringParseError::RequiredArgMissing(
"attribute value".into(),
));
} }
if parser.must_string("set_attribute(equals)")? != "=" { let value = Attribute::new(type_string, &value_string)?;
return Err(ParseError::ValueMissing); Ok(Self { path, value })
}
Ok(Self {
path: remainder,
value: Attribute::new(
AttributeType::from_str(&attr_ty)?,
&parser.must_string("set_attribute(value)")?,
)?,
})
} }
} }
@ -152,16 +151,13 @@ impl<'de> Deserialize<'de> for SetAttribute {
} }
} }
let str_val: String = Deserialize::deserialize(deserializer)?; let str_val: String = Deserialize::deserialize(deserializer)?;
Ok( Ok(Self::from_str(&str_val).map_err(|_| {
Self::from_strings(split::tab_or_space(&str_val).into_iter()).map_err(|_| {
serde::de::Error::invalid_value(serde::de::Unexpected::Str(&str_val), &ExpectedKey) serde::de::Error::invalid_value(serde::de::Unexpected::Str(&str_val), &ExpectedKey)
})?, })?)
)
} }
} }
impl Config { impl Config {
const USE_PANEL: &'static str = "settings.my_hlctl_panel_enabled";
const FONT: &'static str = "theme.my_font"; const FONT: &'static str = "theme.my_font";
const FONT_BOLD: &'static str = "theme.my_font_bold"; const FONT_BOLD: &'static str = "theme.my_font_bold";
const FONT_PANGO: &'static str = "theme.my_font_pango"; const FONT_PANGO: &'static str = "theme.my_font_pango";
@ -220,7 +216,6 @@ impl Config {
fn attrs_set(&self) -> Result<Vec<HlwmCommand>, ConfigError> { fn attrs_set(&self) -> Result<Vec<HlwmCommand>, ConfigError> {
info!("loading attr settings command set"); info!("loading attr settings command set");
Ok([ Ok([
(Self::USE_PANEL, Some(self.use_panel.to_string())),
(Self::FONT, Some(self.font.clone())), (Self::FONT, Some(self.font.clone())),
(Self::FONT_BOLD, Some(self.font_bold.clone())), (Self::FONT_BOLD, Some(self.font_bold.clone())),
(Self::FONT_PANGO, Some(self.font_pango.clone())), (Self::FONT_PANGO, Some(self.font_pango.clone())),
@ -296,7 +291,8 @@ impl Config {
HlwmCommand::Spawn { HlwmCommand::Spawn {
executable: s.name, executable: s.name,
args: s.arguments, args: s.arguments,
}, }
.to_try(),
] ]
}) })
.flatten() .flatten()
@ -393,9 +389,6 @@ impl Config {
let default = Config::default(); let default = Config::default();
let mod_key = setting(Self::MOD_KEY, Key::from_str, default.mod_key); let mod_key = setting(Self::MOD_KEY, Key::from_str, default.mod_key);
Config { Config {
use_panel: client
.get_from_str_attr(Self::USE_PANEL.to_string())
.unwrap_or_log(default.use_panel),
font: client font: client
.get_str_attr(Self::FONT.to_string()) .get_str_attr(Self::FONT.to_string())
.unwrap_or_log(default.font), .unwrap_or_log(default.font),
@ -423,13 +416,12 @@ impl Config {
.into_iter() .into_iter()
.find(|f| (*f).eq(&attr)) .find(|f| (*f).eq(&attr))
.unwrap(); .unwrap();
ThemeAttr::from_command_args( ThemeAttr::from_raw_parts(
&attr_path, &attr_path,
[client &client
.get_attr(attr_path.clone()) .get_attr(attr_path.clone())
.map(|t| t.to_string()) .map(|t| t.to_string())
.unwrap_or_log(default.to_string())] .unwrap_or_log(default.to_string()),
.into_iter(),
) )
.unwrap_or_log(default.clone()) .unwrap_or_log(default.clone())
}) })
@ -440,13 +432,14 @@ impl Config {
.unwrap_or_log(default.keybinds), .unwrap_or_log(default.keybinds),
tags: Self::active_tags(mod_key).unwrap_or_log(default.tags), tags: Self::active_tags(mod_key).unwrap_or_log(default.tags),
rules: (|| -> Result<Vec<Rule>, ConfigError> { rules: (|| -> Result<Vec<Rule>, ConfigError> {
Ok(HlwmCommand::ListRules Ok(
.execute_str()? String::from_utf8(client.execute(HlwmCommand::ListRules)?.stdout)?
.split('\n') .split('\n')
.map(|l| l.trim()) .map(|l| l.trim())
.filter(|l| !l.is_empty()) .filter(|l| !l.is_empty())
.map(|line| Rule::from_strings(split::tab_or_space(line).into_iter())) .map(|line| Rule::from_str(line))
.collect::<Result<_, _>>()?) .collect::<Result<_, _>>()?,
)
})() })()
.unwrap_or_log(default.rules), .unwrap_or_log(default.rules),
settings: (|| -> Result<Vec<_>, CommandError> { settings: (|| -> Result<Vec<_>, CommandError> {
@ -589,7 +582,6 @@ impl Default for Config {
let text_color = Color::from_hex("#898989").expect("default text color"); let text_color = Color::from_hex("#898989").expect("default text color");
Self { Self {
use_panel: true,
mod_key, mod_key,
font: String::from("-*-fixed-medium-*-*-*-12-*-*-*-*-*-*-*"), font: String::from("-*-fixed-medium-*-*-*-12-*-*-*-*-*-*-*"),
font_bold: font_bold.clone(), font_bold: font_bold.clone(),
@ -727,10 +719,16 @@ impl Default for Config {
HlwmCommand::JumpTo(Window::Urgent), HlwmCommand::JumpTo(Window::Urgent),
), ),
], ],
services: vec![Service { services: vec![
Service {
name: String::from("fcitx5"), name: String::from("fcitx5"),
arguments: vec![], arguments: vec![],
}], },
Service {
name: String::from("hlctl"),
arguments: vec![String::from("panel")],
},
],
tags: [ tags: [
Tag::standard(".1", Key::Char('1')), Tag::standard(".1", Key::Char('1')),
Tag::standard(".2", Key::Char('2')), Tag::standard(".2", Key::Char('2')),

View File

@ -1,27 +1,43 @@
use std::{str::FromStr, string::FromUtf8Error};
use log::debug; use log::debug;
use thiserror::Error;
use crate::{ use crate::{
hlwm::{ hlwm::{
command::{CommandError, HlwmCommand}, command::{CommandError, HlwmCommand},
key::Keybind, key::{KeyParseError, Keybind},
parser::FromStrings, Client,
}, },
split, split,
}; };
#[derive(Clone, Debug, Error)]
pub enum EnvironError {
#[error("keybind parsing error: [{0}]")]
KeyParseError(#[from] KeyParseError),
#[error("command execution error: [{0}]")]
CommandError(#[from] CommandError),
}
impl From<FromUtf8Error> for EnvironError {
fn from(value: FromUtf8Error) -> Self {
CommandError::UtfError(value).into()
}
}
pub enum ActiveKeybinds { pub enum ActiveKeybinds {
All, All,
OmitNamedTagBinds, OmitNamedTagBinds,
OnlyNamedTagBinds, OnlyNamedTagBinds,
} }
pub fn active_keybinds(ty: ActiveKeybinds) -> Result<Vec<Keybind>, CommandError> { pub fn active_keybinds(ty: ActiveKeybinds) -> Result<Vec<Keybind>, EnvironError> {
let (use_tag, move_tag) = ( let (use_tag, move_tag) = (
HlwmCommand::UseTag(String::new()).to_string(), HlwmCommand::UseTag(String::new()).to_string(),
HlwmCommand::MoveTag(String::new()).to_string(), HlwmCommand::MoveTag(String::new()).to_string(),
); );
HlwmCommand::ListKeybinds String::from_utf8(Client::new().execute(HlwmCommand::ListKeybinds)?.stdout)?
.execute_str()?
.split("\n") .split("\n")
.map(|l| l.trim()) .map(|l| l.trim())
.filter(|l| !l.is_empty()) .filter(|l| !l.is_empty())
@ -42,12 +58,10 @@ pub fn active_keybinds(ty: ActiveKeybinds) -> Result<Vec<Keybind>, CommandError>
} }
}) })
.map(|row: &str| { .map(|row: &str| {
Ok( Ok(Keybind::from_str(row).map_err(|err| {
Keybind::from_strings(split::tab_or_space(row).into_iter()).map_err(|err| {
debug!("row: [{row}], error: [{err}]"); debug!("row: [{row}], error: [{err}]");
err err
})?, })?)
)
}) })
.collect::<Result<Vec<_>, CommandError>>() .collect::<Result<Vec<_>, EnvironError>>()
} }

View File

@ -1,117 +0,0 @@
use crate::hlwm::parser::ParseError;
use super::{
command::HlwmCommand,
parser::{ArgParser, FromCommandArgs, FromStringsHint},
Separator,
};
pub struct AndOrCommands(Vec<HlwmCommand>);
impl AndOrCommands {
pub fn commands(self) -> Vec<HlwmCommand> {
self.0
}
}
impl FromStringsHint<Separator> for AndOrCommands {
fn from_strings_hint<I: Iterator<Item = String>>(
s: I,
hint: Separator,
) -> Result<Self, ParseError> {
Ok(Self(
split_by_separator(s, hint)
.into_iter()
.map(|cmd_args| -> Result<HlwmCommand, ParseError> {
let mut parser = ArgParser::from_strings(cmd_args.into_iter());
HlwmCommand::from_command_args(
&parser.must_string("hlwmcommand(command)")?,
parser.collect::<Vec<String>>().into_iter(),
)
})
.collect::<Result<Vec<HlwmCommand>, ParseError>>()?,
))
}
}
fn split_by_separator<I: Iterator<Item = String>>(s: I, separator: Separator) -> Vec<Vec<String>> {
let separator = separator.to_string();
let mut commands = Vec::new();
let mut working = Vec::new();
for itm in s {
if itm.len() == 1 && itm == separator {
if !working.is_empty() {
commands.push(working);
working = Vec::new();
}
continue;
}
working.push(itm);
}
if !working.is_empty() {
commands.push(working);
}
commands
}
#[cfg(test)]
mod test {
use crate::hlwm::Separator;
use super::split_by_separator;
use pretty_assertions::assert_eq;
#[test]
fn test_split_by_separator() {
let cases = [(
[
"or",
",",
"and",
".",
"compare",
"tags.focus.curframe_wcount",
"=",
"2",
".",
"cycle_layout",
"+1",
"vertical",
"horizontal",
"max",
"vertical",
"grid",
",",
"cycle_layout",
"+1",
]
.into_iter(),
Separator::Comma,
vec![
vec!["or"],
vec![
"and",
".",
"compare",
"tags.focus.curframe_wcount",
"=",
"2",
".",
"cycle_layout",
"+1",
"vertical",
"horizontal",
"max",
"vertical",
"grid",
],
vec!["cycle_layout", "+1"],
],
)];
for (input, separator, expected) in cases {
let input = input.map(|i| i.to_string());
let actual = split_by_separator(input, separator);
assert_eq!(expected, actual);
}
}
}

View File

@ -1,23 +1,26 @@
use std::str::FromStr; use std::{num::ParseIntError, str::FromStr};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use strum::IntoEnumIterator;
use thiserror::Error; use thiserror::Error;
use crate::hlwm::{command::HlwmCommand, hex::ParseHex}; use crate::hlwm::hex::ParseHex;
use super::{ use super::{
color::{Color, X11Color}, color::{self, Color, X11Color},
command::CommandError,
hex::HexError, hex::HexError,
hlwmbool,
octal::{OctalError, ParseOctal}, octal::{OctalError, ParseOctal},
parser::{FromStringsHint, ParseError, ToOption}, StringParseError,
}; };
#[derive(Debug, Clone, Error)] #[derive(Debug, Clone, Error)]
pub enum AttributeError { pub enum AttributeError {
#[error("error parsing value: {0}")] #[error("error parsing integer value: {0:?}")]
ParseError(#[from] ParseError), 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}]")] #[error("unknown attribute type [{0}]")]
UnknownType(String), UnknownType(String),
#[error("not a valid rectangle: [{0}]")] #[error("not a valid rectangle: [{0}]")]
@ -28,15 +31,6 @@ pub enum AttributeError {
OctalError(#[from] OctalError), 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)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum AttributeOption { pub enum AttributeOption {
Bool(Option<bool>), Bool(Option<bool>),
@ -48,13 +42,6 @@ pub enum AttributeOption {
WindowID(Option<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 { impl Default for AttributeOption {
fn default() -> Self { fn default() -> Self {
Self::Int(None) Self::Int(None)
@ -62,21 +49,19 @@ impl Default for AttributeOption {
} }
impl AttributeOption { impl AttributeOption {
pub fn new( pub fn new(type_string: &str, value_string: &Option<String>) -> Result<Self, AttributeError> {
attr_type: AttributeType,
value_string: Option<&String>,
) -> Result<Self, AttributeError> {
if let Some(val) = value_string { if let Some(val) = value_string {
return Ok(Attribute::new(attr_type, val)?.into()); return Ok(Attribute::new(type_string, val)?.into());
} }
match attr_type { match type_string {
AttributeType::Int => Ok(Self::Int(None)), "int" => Ok(Self::Int(None)),
AttributeType::Bool => Ok(Self::Bool(None)), "bool" => Ok(Self::Bool(None)),
AttributeType::Uint => Ok(Self::Uint(None)), "uint" => Ok(Self::Uint(None)),
AttributeType::Color => Ok(Self::Color(None)), "color" => Ok(Self::Color(None)),
AttributeType::WindowID => Ok(Self::WindowID(None)), "windowid" => Ok(Self::WindowID(None)),
AttributeType::Rectangle => Ok(Self::Rectangle(None)), "rectangle" => Ok(Self::Rectangle(None)),
AttributeType::String => Ok(Self::String(None)), "string" | "names" | "regex" | "font" => Ok(Self::String(None)),
_ => Err(AttributeError::UnknownType(type_string.to_string())),
} }
} }
@ -184,57 +169,75 @@ impl From<(u32, u32)> for Attribute {
} }
} }
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 { impl Attribute {
pub fn new(attr_type: AttributeType, value_string: &str) -> Result<Self, AttributeError> { pub fn new(type_string: &str, value_string: &str) -> Result<Self, AttributeError> {
match attr_type { match type_string {
AttributeType::Bool => Ok(Self::Bool(hlwmbool::from_hlwm_string(value_string)?)), "bool" => match value_string {
AttributeType::Color => Ok(Attribute::Color(Color::from_str(value_string)?)), "on" | "true" => Ok(Attribute::Bool(true)),
AttributeType::Int => Ok(Attribute::Int( "off" | "false" => Ok(Attribute::Bool(false)),
value_string.parse().map_err(|err| ParseError::from(err))?, _ => Err(AttributeError::ParseBoolError(type_string.to_string())),
)), },
AttributeType::String => Ok(Attribute::String(value_string.to_string())), "color" => Ok(Attribute::Color(Color::from_str(value_string)?)),
AttributeType::Uint => Ok(Attribute::Uint( "int" => Ok(Attribute::Int(value_string.parse()?)),
value_string.parse().map_err(|err| ParseError::from(err))?, "string" | "names" | "regex" | "font" => {
)), Ok(Attribute::String(value_string.to_string()))
AttributeType::Rectangle => { }
"uint" => Ok(Attribute::Uint(value_string.parse()?)),
"rectangle" => {
let parts = value_string.split('x').collect::<Vec<_>>(); let parts = value_string.split('x').collect::<Vec<_>>();
if parts.len() != 2 { if parts.len() != 2 {
return Err(AttributeError::NotRectangle(value_string.to_string())); return Err(AttributeError::NotRectangle(value_string.to_string()));
} }
Ok(Attribute::Rectangle { Ok(Attribute::Rectangle {
x: parts x: parts.get(0).unwrap().parse()?,
.get(0) y: parts.get(1).unwrap().parse()?,
.unwrap()
.parse()
.map_err(|err| ParseError::from(err))?,
y: parts
.get(1)
.unwrap()
.parse()
.map_err(|err| ParseError::from(err))?,
}) })
} }
AttributeType::WindowID => { "windowid" => {
if let Some(hex) = value_string.strip_prefix("0x") { if let Some(hex) = value_string.strip_prefix("0x") {
Ok(Attribute::WindowID(hex.parse_hex()?)) Ok(Attribute::WindowID(hex.parse_hex()?))
} else if let Some(octal) = value_string.strip_prefix("0") { } else if let Some(octal) = value_string.strip_prefix("0") {
Ok(Attribute::WindowID(octal.parse_octal()?)) Ok(Attribute::WindowID(octal.parse_octal()?))
} else { } else {
Ok(Attribute::WindowID( Ok(Attribute::WindowID(value_string.parse()?))
value_string.parse().map_err(|err| ParseError::from(err))?,
))
} }
} }
_ => 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()))
} }
} }
@ -278,71 +281,11 @@ impl Default for AttributeType {
} }
impl FromStr for AttributeType { impl FromStr for AttributeType {
type Err = ParseError; type Err = StringParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
match s { Self::iter()
"bool" => Ok(Self::Bool), .find(|t| t.to_string() == s)
"color" => Ok(Self::Color), .ok_or(StringParseError::UnknownValue)
"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,
}
}
}
} }
} }

View File

@ -1,8 +1,9 @@
use std::str::FromStr; use std::str::FromStr;
use serde::{de::Unexpected, Deserialize, Serialize}; use serde::{de::Unexpected, Deserialize, Serialize};
use thiserror::Error;
use super::{hex::ParseHex, parser::ParseError}; use super::hex::{HexError, ParseHex};
mod x11; mod x11;
pub use x11::X11Color; pub use x11::X11Color;
@ -50,6 +51,14 @@ impl Default for Color {
} }
} }
#[derive(Debug, Clone, Error)]
pub enum ParseError {
#[error("length must be either 6 characters or 7 with '#' prefix")]
InvalidLength,
#[error("invalid hex value: [{0}]")]
HexError(#[from] HexError),
}
impl FromStr for Color { impl FromStr for Color {
type Err = ParseError; type Err = ParseError;
@ -67,10 +76,7 @@ impl Color {
pub fn from_hex(hex: &str) -> Result<Self, ParseError> { pub fn from_hex(hex: &str) -> Result<Self, ParseError> {
let expected_len = if hex.starts_with('#') { 7 } else { 6 }; let expected_len = if hex.starts_with('#') { 7 } else { 6 };
if hex.len() != expected_len { if hex.len() != expected_len {
return Err(ParseError::InvalidValue { return Err(ParseError::InvalidLength);
value: hex.to_string(),
expected: "a 6 digit hex value",
});
} }
let hex = hex.strip_prefix('#').unwrap_or(hex); let hex = hex.strip_prefix('#').unwrap_or(hex);

View File

@ -2,7 +2,7 @@ use std::str::FromStr;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::hlwm::parser::ParseError; use crate::hlwm::StringParseError;
macro_rules! color_impl { macro_rules! color_impl {
() => {}; () => {};
@ -50,13 +50,13 @@ macro_rules! color_impl {
} }
impl FromStr for X11Color { impl FromStr for X11Color {
type Err = ParseError; type Err = StringParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
X11Color::ALL X11Color::ALL
.into_iter() .into_iter()
.find(|c| c.color_names().into_iter().any(|n| n == s)) .find(|c| c.color_names().into_iter().any(|n| n == s))
.ok_or(ParseError::InvalidCommand(s.to_string())) .ok_or(StringParseError::UnknownValue)
} }
} }

View File

@ -1,35 +1,22 @@
use std::{ use std::{io, process::ExitStatus, str::FromStr, string::FromUtf8Error};
borrow::BorrowMut,
io,
process::{self, ExitStatus},
str::FromStr,
string::FromUtf8Error,
};
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use serde::{de::Expected, Deserialize, Serialize}; use serde::{de::Expected, Deserialize, Serialize};
use thiserror::Error; use thiserror::Error;
use crate::{ use crate::{gen_parse, hlwm::Client, split};
hlwm::{
parser::{either::Either, ParseError},
Client,
},
split,
};
use super::{ use super::{
and_or_command::AndOrCommands, attribute::{Attribute, AttributeError, AttributeOption},
attribute::{Attribute, AttributeError, AttributeOption, AttributeType},
hlwmbool::ToggleBool, hlwmbool::ToggleBool,
hook::Hook, hook::Hook,
key::{KeyUnbind, Keybind, Mousebind}, key::{KeyUnbind, Keybind, Mousebind},
pad::Pad, pad::Pad,
parser::{self, ArgParser, Flip, FromCommandArgs, FromStrings, ToOption},
rule::{Rule, Unrule}, rule::{Rule, Unrule},
setting::{FrameLayout, Setting, SettingName}, setting::{FrameLayout, Setting, SettingName},
window::Window, window::Window,
Align, Direction, Index, Monitor, Operator, Separator, TagSelect, ToCommandString, Align, Direction, Index, Monitor, Operator, Separator, StringParseError, TagSelect,
ToCommandString,
}; };
#[derive(Debug, Clone, strum::Display, strum::EnumIter, PartialEq)] #[derive(Debug, Clone, strum::Display, strum::EnumIter, PartialEq)]
@ -209,7 +196,6 @@ pub enum HlwmCommand {
filter_name: Option<String>, filter_name: Option<String>,
identifier: String, identifier: String,
object: String, object: String,
command: Box<HlwmCommand>,
}, },
#[strum(serialize = "sprintf")] #[strum(serialize = "sprintf")]
Sprintf(Vec<String>), Sprintf(Vec<String>),
@ -219,7 +205,7 @@ pub enum HlwmCommand {
} }
impl FromStr for Box<HlwmCommand> { impl FromStr for Box<HlwmCommand> {
type Err = ParseError; type Err = CommandParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Box::new(HlwmCommand::from_str(s)?)) Ok(Box::new(HlwmCommand::from_str(s)?))
@ -241,33 +227,42 @@ impl<'de> Deserialize<'de> for HlwmCommand {
D: serde::Deserializer<'de>, D: serde::Deserializer<'de>,
{ {
pub enum Expect { pub enum Expect {
ParseError(ParseError), NotEmpty,
} }
impl Expected for Expect { impl Expected for Expect {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self { match self {
Expect::ParseError(err) => f.write_str(&err.to_string()), Expect::NotEmpty => write!(f, "value not being empty"),
} }
} }
} }
let str_val: String = Deserialize::deserialize(deserializer)?; let str_val: String = Deserialize::deserialize(deserializer)?;
ArgParser::from_strings(split::tab_or_space(&str_val).into_iter())
.collect_command("hlwm_command") let parts = split::tab_or_space(&str_val);
.map_err(|err| { if parts.is_empty() {
serde::de::Error::invalid_value( return Err(serde::de::Error::invalid_length(0, &Expect::NotEmpty));
serde::de::Unexpected::Str(&str_val), }
&Expect::ParseError(err), let mut parts = parts.into_iter();
) let command = parts.next().unwrap();
}) let args = parts.collect();
Ok(Self::from_raw_parts(&command, args).map_err(|err| {
serde::de::Error::invalid_value(serde::de::Unexpected::Str(&str_val), &err)
})?)
} }
} }
impl FromStr for HlwmCommand { impl FromStr for HlwmCommand {
type Err = ParseError; type Err = CommandParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
ArgParser::from_strings(split::tab_or_space(s).into_iter()).collect_command("hlwm_command") let mut parts = s.split("\t");
let command = parts
.next()
.ok_or(CommandParseError::UnknownCommand(s.to_string()))?;
let args = parts.map(String::from).collect();
HlwmCommand::from_raw_parts(command, args)
} }
} }
@ -279,6 +274,12 @@ impl Default for HlwmCommand {
#[derive(Debug, Clone, Error)] #[derive(Debug, Clone, Error)]
pub enum CommandParseError { pub enum CommandParseError {
#[error("unknown command [{0}]")]
UnknownCommand(String),
#[error("bad argument for command [{command}]")]
BadArgument { command: String },
#[error("missing required argument")]
MissingArgument,
#[error("invalid argument count [{0}] at [{1}]")] #[error("invalid argument count [{0}] at [{1}]")]
InvalidArgumentCount(usize, String), InvalidArgumentCount(usize, String),
#[error("error parsing attribute: [{0}]")] #[error("error parsing attribute: [{0}]")]
@ -287,8 +288,8 @@ pub enum CommandParseError {
CommandError(#[from] CommandError), CommandError(#[from] CommandError),
#[error("string utf8 error")] #[error("string utf8 error")]
StringUtf8Error(String), StringUtf8Error(String),
#[error("parsing error: [{0}]")] #[error("parsing string value error: [{0}]")]
StringParseError(#[from] ParseError), StringParseError(#[from] StringParseError),
} }
impl From<FromUtf8Error> for CommandParseError { impl From<FromUtf8Error> for CommandParseError {
@ -303,6 +304,14 @@ impl serde::de::Expected for CommandParseError {
} }
} }
fn trim_quotes(itm: String) -> String {
if itm.starts_with('"') && itm.ends_with('"') {
itm.trim_matches('"').to_string()
} else {
itm
}
}
impl HlwmCommand { impl HlwmCommand {
pub fn silent(self) -> HlwmCommand { pub fn silent(self) -> HlwmCommand {
HlwmCommand::Silent(Box::new(self)) HlwmCommand::Silent(Box::new(self))
@ -312,33 +321,14 @@ impl HlwmCommand {
HlwmCommand::Try(Box::new(self)) HlwmCommand::Try(Box::new(self))
} }
pub fn execute(self) -> Result<process::Output, CommandError> { pub fn from_raw_parts(command: &str, args: Vec<String>) -> Result<Self, CommandParseError> {
Client::new().execute(self) let command = HlwmCommand::iter()
} .find(|cmd| cmd.to_string() == command)
.ok_or(CommandParseError::UnknownCommand(command.to_string()))?;
pub fn execute_str(self) -> Result<String, CommandError> { gen_parse!(command, args);
Ok(String::from_utf8(self.execute()?.stdout)?)
}
}
impl FromCommandArgs for HlwmCommand { let parsed_command = match command {
fn from_command_args<S: Into<String>, I: Iterator<Item = S>>(
cmd_name: &str,
args: I,
) -> Result<Self, super::parser::ParseError> {
let mut command = HlwmCommand::iter()
.find(|cmd| cmd.to_string() == cmd_name)
.ok_or(parser::ParseError::InvalidCommand(cmd_name.to_string()))?;
// Since HlwmCommand will often be parsed by its constituent commands (such as keybind)
// just passing in `args.map(|s| s.into())` results in the typechecker overflowing its
// recursion limit. So, to get around this, HlwmCommand will collect whatever type I
// is into a `Vec<String>`, which makes this loop not infinite.
//
// There really should be a better error for this. I'm lucky I figured it out fast.
let mut parser =
ArgParser::from_strings(args.map(|s| s.into()).collect::<Vec<_>>().into_iter());
match command.borrow_mut() {
HlwmCommand::Quit HlwmCommand::Quit
| HlwmCommand::Lock | HlwmCommand::Lock
| HlwmCommand::Cycle | HlwmCommand::Cycle
@ -352,199 +342,252 @@ impl FromCommandArgs for HlwmCommand {
| HlwmCommand::True | HlwmCommand::True
| HlwmCommand::Mouseunbind | HlwmCommand::Mouseunbind
| HlwmCommand::ListMonitors | HlwmCommand::ListMonitors
| HlwmCommand::ListKeybinds => (), | HlwmCommand::ListKeybinds => Ok::<_, CommandParseError>(command.clone()),
HlwmCommand::Echo(arg) => *arg = vec![parser.collect::<Vec<_>>().join(" ")], HlwmCommand::Echo(_) => Ok(Self::Echo(args)),
HlwmCommand::Close { window } => { HlwmCommand::Close { window: _ } => parse!(window: [Option<FromStr>] => Close),
*window = parser.optional_next_from_str("close(window)")? HlwmCommand::Spawn {
} executable: _,
HlwmCommand::Spawn { executable, args } => { args: _,
*executable = parser.must_string("spawn(executable)")?; } => {
*args = parser.collect(); parse!(executable: String, args: [Vec<String>] => Spawn).map(|spawn| match spawn {
} HlwmCommand::Spawn { executable, args } => HlwmCommand::Spawn {
HlwmCommand::GetAttr(attr) => *attr = parser.must_string("get_attr")?, executable: trim_quotes(executable),
HlwmCommand::SetAttr { path, new_value } => { args: args.into_iter().map(trim_quotes).collect(),
*path = parser.must_string("set_attr(path)")?; },
*new_value = _ => unreachable!(),
parser.collect_from_strings_hint(path.as_str(), "set_attr(new_value)")?;
}
HlwmCommand::Attr { path, new_value } => {
*path = parser.must_string("attr(path)")?;
*new_value = parser
.collect::<Vec<_>>()
.to_option()
.map(|t| {
let value = t.join(" ");
Attribute::new(AttributeType::get_type_or_guess(&path, &value), &value)
}) })
.flip()?;
} }
HlwmCommand::NewAttr { path, attr } => { HlwmCommand::GetAttr(_) => parse!(String => GetAttr),
let attr_type: AttributeType = parser.next_from_str("new_attr(attr_type)")?; HlwmCommand::SetAttr {
*path = parser.must_string("new_attr(path)")?; path: _,
let value = parser.collect::<Vec<_>>().to_option().map(|v| v.join(" ")); new_value: _,
*attr = AttributeOption::new(attr_type, value.as_ref())?; } => {
let mut args = args.into_iter();
let path = args.next().ok_or(CommandParseError::BadArgument {
command: command.to_string(),
})?;
Ok(HlwmCommand::SetAttr {
path: path.clone(),
new_value: {
Attribute::new(
&String::from_utf8(
Client::new()
.execute(HlwmCommand::AttrType(path.clone()))?
.stdout,
)?
.split('\n')
.next()
.ok_or(CommandParseError::CommandError(CommandError::Empty))?,
&args.collect::<Vec<_>>().join(" "),
)?
},
})
} }
HlwmCommand::AttrType(path) | HlwmCommand::RemoveAttr(path) => { HlwmCommand::Attr {
*path = parser.must_string(cmd_name)? path: _,
} new_value: _,
HlwmCommand::Set(setting) => *setting = parser.collect_command(cmd_name)?, } => {
HlwmCommand::EmitHook(hook) => *hook = parser.collect_command(cmd_name)?, let mut args = args.into_iter();
HlwmCommand::Keybind(keybind) => *keybind = parser.collect_from_strings(cmd_name)?, Ok(HlwmCommand::Attr {
HlwmCommand::Keyunbind(keyunbind) => *keyunbind = parser.next_from_str(cmd_name)?, path: args.next().ok_or(CommandParseError::BadArgument {
HlwmCommand::Mousebind(mouseunbind) => { command: command.to_string(),
*mouseunbind = parser.collect_from_strings(cmd_name)? })?,
} new_value: {
HlwmCommand::JumpTo(win) => *win = parser.next_from_str(cmd_name)?, let args = args.collect::<Vec<_>>();
HlwmCommand::AddTag(tag) => *tag = parser.must_string(cmd_name)?, if args.is_empty() {
HlwmCommand::MergeTag { tag, target } => { None
*tag = parser.must_string("merge_tag(tag)")?; } else {
*target = parser.optional_next_from_str("merge_tag(target)")?; Some(Attribute::guess_type(&args.join("\t")))
}
HlwmCommand::Focus(dir) | HlwmCommand::Shift(dir) => {
*dir = parser.next_from_str(cmd_name)?
}
HlwmCommand::Split(align) => *align = parser.collect_from_strings(cmd_name)?,
HlwmCommand::Fullscreen(set) => *set = parser.next_from_str(cmd_name)?,
HlwmCommand::CycleLayout { delta, layouts } => {
let cycle_res = parser.try_first(
|s| Index::<i32>::from_str(s),
|s| Ok(FrameLayout::from_str(s)?),
cmd_name,
);
match cycle_res {
Ok(res) => match res {
Either::Left(idx) => {
*delta = Some(idx);
*layouts = parser.collect_from_str("cycle_layout(layouts)")?;
}
Either::Right(layout) => {
*delta = None;
*layouts = [layout]
.into_iter()
.chain(parser.collect_from_str("cycle_layout(layouts)")?)
.collect();
} }
}, },
Err(err) => { })
if let ParseError::Empty = err { }
*delta = None; HlwmCommand::NewAttr { path: _, attr: _ } => {
*layouts = vec![]; let mut args = args.into_iter();
let attr_type = args.next().ok_or(CommandParseError::BadArgument {
command: command.to_string(),
})?;
let path = args.next().ok_or(CommandParseError::BadArgument {
command: command.to_string(),
})?;
Ok(HlwmCommand::NewAttr {
path,
attr: {
let attr = args.collect::<Vec<String>>();
let attr = if attr.len() == 0 {
None
} else { } else {
return Err(err); Some(attr.join("\t"))
};
AttributeOption::new(&attr_type, &attr)?
},
})
}
HlwmCommand::AttrType(_) => parse!(String => AttrType),
HlwmCommand::RemoveAttr(_) => parse!(String => RemoveAttr),
HlwmCommand::Set(_) => parse!(FromStrAll => Set),
HlwmCommand::EmitHook(_) => parse!(FromStr => EmitHook),
HlwmCommand::Keybind(_) => parse!(FromStrAll => Keybind),
HlwmCommand::Keyunbind(_) => parse!(FromStr => Keyunbind),
HlwmCommand::Mousebind(_) => parse!(FromStrAll => Mousebind),
HlwmCommand::JumpTo(_) => parse!(FromStr => JumpTo),
HlwmCommand::AddTag(_) => parse!(String => AddTag),
HlwmCommand::MergeTag { tag: _, target: _ } => {
parse!(tag: String, target: [Option<FromStr>] => MergeTag)
}
HlwmCommand::Focus(_) => parse!(FromStr => Focus),
HlwmCommand::Shift(_) => parse!(FromStr => Shift),
HlwmCommand::Split(_) => parse!(FromStrAll => Split),
HlwmCommand::Fullscreen(_) => parse!(FromStr => Fullscreen),
HlwmCommand::CycleLayout {
delta: _,
layouts: _,
} => {
if args.is_empty() {
Ok(HlwmCommand::CycleLayout {
delta: None,
layouts: vec![],
})
} else {
let first = args.first().ok_or(CommandParseError::BadArgument {
command: command.to_string(),
})?;
match FrameLayout::from_str(first) {
Ok(_) => {
// only frame layouts
Ok(HlwmCommand::CycleLayout {
delta: None,
layouts: args
.into_iter()
.map(|i| FrameLayout::from_str(&i))
.collect::<Result<_, _>>()
.map_err(|_| CommandParseError::BadArgument {
command: command.to_string(),
})?,
})
}
Err(_) => {
// Has index
let mut args = args.into_iter();
Ok(HlwmCommand::CycleLayout {
delta: Some(args.next().unwrap().parse().map_err(|_| {
CommandParseError::BadArgument {
command: command.to_string(),
}
})?),
layouts: args
.map(|i| FrameLayout::from_str(&i))
.collect::<Result<_, _>>()
.map_err(|_| CommandParseError::BadArgument {
command: command.to_string(),
})?,
})
} }
} }
} }
} }
HlwmCommand::Resize { HlwmCommand::Resize {
direction, direction: _,
fraction_delta, fraction_delta: _,
} => { } => parse!(direction: FromStr, fraction_delta: [Option<FromStr>] => Resize),
*direction = parser.next_from_str("resize(direction)")?;
*fraction_delta = parser.optional_next_from_str("resize(fraction_delta)")?;
}
HlwmCommand::Or { HlwmCommand::Or {
separator, separator: _,
commands, commands: _,
} } => parse!(And_Or => Or),
| HlwmCommand::And { HlwmCommand::And {
separator, separator: _,
commands, commands: _,
} => { } => parse!(And_Or => And),
*separator = parser.next_from_str(&format!("{cmd_name}(separator)"))?;
let commands_wrap: AndOrCommands = parser
.collect_from_strings_hint(*separator, &format!("{cmd_name}(commands)"))?;
*commands = commands_wrap.commands();
}
HlwmCommand::Compare { HlwmCommand::Compare {
attribute, attribute: _,
operator, operator: _,
value, value: _,
} => parse!(attribute: String, operator: FromStr, value: String => Compare),
HlwmCommand::TagStatus { monitor: _ } => {
parse!(monitor: [Option<FromStr>] => TagStatus)
}
HlwmCommand::Rule(_) => parse!(FromStrAll => Rule),
HlwmCommand::Get(_) => parse!(FromStr => Get),
HlwmCommand::MoveIndex {
index: _,
skip_visible: _,
} => { } => {
*attribute = parser.must_string("compare(attribute)")?; parse!(skip_visible: [Flag("--skip-visible")], index: FromStr => MoveIndex)
*operator = parser.next_from_str("compare(operator)")?;
*value = parser.must_string("compare(value)")?;
} }
HlwmCommand::TagStatus { monitor } => {
*monitor = parser.optional_next_from_str(cmd_name)?;
}
HlwmCommand::Rule(rule) => {
*rule = parser.collect_from_strings(cmd_name)?;
}
HlwmCommand::Get(set) => *set = parser.next_from_str(cmd_name)?,
HlwmCommand::UseIndex { HlwmCommand::UseIndex {
index, index: _,
skip_visible, skip_visible: _,
}
| HlwmCommand::MoveIndex {
index,
skip_visible,
} => { } => {
let (args, skip) = parser.collect_strings_with_flag("--skip-visible"); parse!(skip_visible: [Flag("--skip-visible")], index: FromStr => UseIndex)
*skip_visible = skip;
*index = ArgParser::from_strings(args.into_iter()).next_from_str(cmd_name)?;
}
HlwmCommand::MoveTag(tag) | HlwmCommand::UseTag(tag) => {
*tag = parser.must_string(cmd_name)?
}
HlwmCommand::Try(hlcmd) | HlwmCommand::Silent(hlcmd) => {
*hlcmd = Box::new(parser.collect_command(cmd_name)?);
} }
HlwmCommand::UseTag(_) => parse!(String => UseTag),
HlwmCommand::MoveTag(_) => parse!(String => MoveTag),
HlwmCommand::Try(_) => parse!(FromStrAll => Try),
HlwmCommand::Silent(_) => parse!(FromStrAll => Silent),
HlwmCommand::MonitorRect { HlwmCommand::MonitorRect {
monitor, monitor: _,
without_pad, without_pad: _,
} => { } => {
let (args, pad) = parser.collect_strings_with_flag("-p"); parse!(without_pad: [Flag("-p")], monitor: [Option<FromStr>] => MonitorRect)
*without_pad = pad;
*monitor = args.first().map(|s| u32::from_str(s)).flip()?;
} }
HlwmCommand::Pad { monitor, pad } => { HlwmCommand::Pad { monitor: _, pad: _ } => {
*monitor = parser.next_from_str("pad(monitor)")?; parse!(monitor: FromStr, pad: FromStrAll => Pad)
*pad = parser.collect_from_strings("pad(pad)")?;
} }
HlwmCommand::Substitute { HlwmCommand::Substitute {
identifier, identifier: _,
attribute_path, attribute_path: _,
command, command: _,
} => { } => {
*identifier = parser.must_string("substitute(identifier)")?; parse!(identifier: String, attribute_path: String, command: FromStrAll => Substitute)
*attribute_path = parser.must_string("substitute(attribute_path)")?;
*command = Box::new(parser.collect_command("substitute(command)")?);
} }
HlwmCommand::ForEach { HlwmCommand::ForEach {
unique: _,
recursive: _,
filter_name: _,
identifier: _,
object: _,
} => {
let (params, args): (Vec<String>, Vec<String>) =
args.into_iter().partition(|a| a.starts_with("--"));
if args.len() < 2 {
return Err(CommandParseError::BadArgument {
command: command.to_string(),
});
}
let mut args = args.into_iter();
let (identifier, object) =
(args.next().unwrap(), args.collect::<Vec<_>>().join("\t"));
let mut unique = false;
let mut recursive = false;
let mut filter_name: Option<String> = None;
for param in params {
match param.as_str() {
"--unique" => unique = true,
"--recursive" => recursive = true,
other => {
if let Some(name) = other.strip_prefix("--filter-name=") {
filter_name = Some(name.to_string());
}
}
}
}
Ok(HlwmCommand::ForEach {
unique, unique,
recursive, recursive,
filter_name, filter_name,
identifier, identifier,
object, object,
command, })
} => {
// Note: ForEach is still likely bugged due to this method of parsing the parts, as foreach may be nested
let (normal, flags): (Vec<_>, Vec<_>) = parser.inner().partition(|c| {
!c.starts_with("--unique")
&& !c.starts_with("--filter-name")
&& !c.starts_with("--recursive")
});
let mut normal = ArgParser::from_strings(normal.into_iter());
for flag in flags {
match flag.as_str() {
"--unique" => *unique = true,
"--recursive" => *recursive = true,
other => {
if let Some(name) = other.strip_prefix("--filter-name=") {
*filter_name = Some(name.to_string());
} }
} HlwmCommand::Sprintf(_) => parse!([Vec<String>] => Sprintf),
} HlwmCommand::Unrule(_) => parse!(FromStr => Unrule),
} }?;
*identifier = normal.must_string("for_each(identifier)")?;
*object = normal.must_string("for_each(object)")?;
*command = Box::new(normal.collect_command("for_each(command)")?);
}
HlwmCommand::Sprintf(s) => *s = parser.collect(),
HlwmCommand::Unrule(unrule) => *unrule = parser.next_from_str(cmd_name)?,
};
Ok(command) assert_eq!(command.to_string(), parsed_command.to_string());
Ok(parsed_command)
} }
} }
@ -574,12 +617,10 @@ pub enum CommandError {
UtfError(#[from] FromUtf8Error), UtfError(#[from] FromUtf8Error),
#[error("attribute error: {0:?}")] #[error("attribute error: {0:?}")]
AttributeError(#[from] AttributeError), AttributeError(#[from] AttributeError),
#[error("parse error: {0}")] #[error("string parse error: {0}")]
ParseError(#[from] ParseError), StringParseError(#[from] StringParseError),
#[error("unexpected empty result")] #[error("unexpected empty result")]
Empty, Empty,
#[error("invalid value")]
Invalid,
} }
impl From<io::Error> for CommandError { impl From<io::Error> for CommandError {
@ -768,9 +809,8 @@ impl ToCommandString for HlwmCommand {
filter_name, filter_name,
identifier, identifier,
object, object,
command,
} => { } => {
let mut parts = Vec::with_capacity(7); let mut parts = Vec::with_capacity(6);
parts.push(self.to_string()); parts.push(self.to_string());
parts.push(identifier.to_string()); parts.push(identifier.to_string());
parts.push(object.to_string()); parts.push(object.to_string());
@ -783,7 +823,6 @@ impl ToCommandString for HlwmCommand {
if *recursive { if *recursive {
parts.push("--recursive".into()); parts.push("--recursive".into());
} }
parts.push(command.to_command_string());
parts.join("\t") parts.join("\t")
} }
HlwmCommand::Sprintf(args) => [self.to_string()] HlwmCommand::Sprintf(args) => [self.to_string()]
@ -800,40 +839,6 @@ impl ToCommandString for HlwmCommand {
} }
} }
#[derive(Default)]
struct ForEach {
unique: bool,
recursive: bool,
filter_name: Option<String>,
identifier: String,
object: String,
command: HlwmCommand,
}
impl FromStrings for ForEach {
fn from_strings<I: Iterator<Item = String>>(s: I) -> Result<Self, ParseError> {
let mut for_each = Self::default();
let (normal, flags): (Vec<_>, Vec<_>) = s.partition(|c| !c.starts_with("--"));
let mut normal = ArgParser::from_strings(normal.into_iter());
for flag in flags {
match flag.as_str() {
"--unique" => for_each.unique = true,
"--recursive" => for_each.recursive = true,
other => {
if let Some(filter_name) = other.strip_prefix("--filter-name=") {
for_each.filter_name = Some(filter_name.to_string());
}
}
}
}
for_each.identifier = normal.must_string("for_each(identifier)")?;
for_each.object = normal.must_string("for_each(object)")?;
for_each.command = normal.collect_command("for_each(command)")?;
Ok(for_each)
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
@ -844,7 +849,7 @@ mod test {
command::{FrameLayout, HlwmCommand, Index, Operator, Separator}, command::{FrameLayout, HlwmCommand, Index, Operator, Separator},
hlwmbool::ToggleBool, hlwmbool::ToggleBool,
hook::Hook, hook::Hook,
key::{Key, KeyUnbind, Keybind, MouseButton, Mousebind, MousebindAction}, key::{Key, KeyUnbind, MouseButton, Mousebind, MousebindAction},
pad::Pad, pad::Pad,
rule::{Condition, Consequence, Rule, RuleOperator}, rule::{Condition, Consequence, Rule, RuleOperator},
setting::{Setting, SettingName}, setting::{Setting, SettingName},
@ -938,10 +943,7 @@ mod test {
"emit_hook\treload".into(), "emit_hook\treload".into(),
), ),
HlwmCommand::Keybind(_) => ( HlwmCommand::Keybind(_) => (
HlwmCommand::Keybind(Keybind::new( HlwmCommand::Keybind("Mod4+1\treload".parse().unwrap()),
[Key::Mod4Super, Key::Char('1')],
HlwmCommand::Reload,
)),
"keybind\tMod4+1\treload".into(), "keybind\tMod4+1\treload".into(),
), ),
HlwmCommand::Keyunbind(_) => ( HlwmCommand::Keyunbind(_) => (
@ -1126,7 +1128,6 @@ mod test {
filter_name: _, filter_name: _,
identifier: _, identifier: _,
object: _, object: _,
command: _,
} => ( } => (
HlwmCommand::ForEach { HlwmCommand::ForEach {
unique: true, unique: true,
@ -1134,10 +1135,8 @@ mod test {
filter_name: Some(".+".into()), filter_name: Some(".+".into()),
identifier: "CLIENT".into(), identifier: "CLIENT".into(),
object: "clients.".into(), object: "clients.".into(),
command: Box::new(HlwmCommand::Cycle),
}, },
"foreach\tCLIENT\tclients.\t--filter-name=.+\t--unique\t--recursive\tcycle" "foreach\tCLIENT\tclients.\t--filter-name=.+\t--unique\t--recursive".into(),
.into(),
), ),
HlwmCommand::Sprintf(_) => ( HlwmCommand::Sprintf(_) => (
HlwmCommand::Sprintf( HlwmCommand::Sprintf(

View File

@ -2,17 +2,14 @@ use std::{fmt::Display, str::FromStr};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::parser::ParseError; use super::StringParseError;
#[inline(always)] #[inline(always)]
pub fn from_hlwm_string(s: &str) -> Result<bool, ParseError> { pub fn from_hlwm_string(s: &str) -> Result<bool, StringParseError> {
match s { match s {
"on" | "true" => Ok(true), "on" | "true" => Ok(true),
"off" | "false" => Ok(false), "off" | "false" => Ok(false),
_ => Err(ParseError::PrimitiveError(format!( _ => Err(StringParseError::BoolError(s.to_string())),
"value [{s}] is not a {}",
std::any::type_name::<bool>()
))),
} }
} }
@ -29,7 +26,7 @@ impl From<bool> for ToggleBool {
} }
impl FromStr for ToggleBool { impl FromStr for ToggleBool {
type Err = ParseError; type Err = StringParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(b) = from_hlwm_string(s) { if let Ok(b) = from_hlwm_string(s) {
@ -37,10 +34,7 @@ impl FromStr for ToggleBool {
} else if s == "toggle" { } else if s == "toggle" {
Ok(Self::Toggle) Ok(Self::Toggle)
} else { } else {
Err(ParseError::PrimitiveError(format!( Err(StringParseError::UnknownValue)
"value [{s}] is not a {}",
std::any::type_name::<ToggleBool>()
)))
} }
} }
} }

View File

@ -1,13 +1,11 @@
use std::{borrow::BorrowMut, str::FromStr}; use std::str::FromStr;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use super::{ use crate::gen_parse;
parser::{ArgParser, FromCommandArgs, ParseError},
window::Window, use super::{command::CommandParseError, window::Window, Monitor, TagSelect, ToCommandString};
Monitor, TagSelect, ToCommandString,
};
#[derive(Debug, Clone, Serialize, Deserialize, strum::Display, strum::EnumIter, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, strum::Display, strum::EnumIter, PartialEq)]
#[strum(serialize_all = "snake_case")] #[strum(serialize_all = "snake_case")]
@ -61,55 +59,55 @@ pub enum Hook {
Reload, Reload,
} }
impl FromCommandArgs for Hook { impl Hook {
fn from_command_args<S: Into<String>, I: IntoIterator<Item = S>>( fn from_raw_parts(command: &str, args: Vec<String>) -> Result<Self, CommandParseError> {
cmd: &str, let command = Self::iter()
args: I, .find(|cmd| cmd.to_string() == command)
) -> Result<Self, super::parser::ParseError> { .ok_or(CommandParseError::UnknownCommand(command.to_string()))?;
let mut command = Self::iter() gen_parse!(command, args);
.find(|c: &Hook| c.to_string() == cmd)
.ok_or(ParseError::InvalidCommand(cmd.to_string()))?;
let x = args.into_iter().map(|i| i.into()).collect::<Vec<String>>(); match command {
let mut parser = ArgParser::from_strings(x.into_iter()); Hook::AttributeChanged {
path: _,
old: _,
new: _,
} => parse!(path: String, old: String, new: String => AttributeChanged),
Hook::Fullscreen { on: _, window: _ } => {
parse!(on: FromStr, window: FromStr => Fullscreen)
}
Hook::TagChanged { tag: _, monitor: _ } => {
parse!(tag: String, monitor: FromStr => TagChanged)
}
Hook::FocusChanged {
window: _,
title: _,
} => parse!(window: FromStr, title: String => FocusChanged),
Hook::WindowTitleChanged {
window: _,
title: _,
} => parse!(window: FromStr, title: String => WindowTitleChanged),
Hook::TagFlags => Ok(Hook::TagFlags),
Hook::TagAdded(_) => parse!(FromStr => TagAdded),
Hook::TagRenamed { old: _, new: _ } => parse!(old: String, new: String => TagRenamed),
Hook::Urgent { on: _, window: _ } => parse!(on: Bool, window: FromStr => Urgent),
Hook::Rule { hook: _, window: _ } => parse!(hook: String, window: FromStr => Rule),
Hook::QuitPanel => Ok(Hook::QuitPanel),
Hook::Reload => Ok(Hook::Reload),
}
}
}
match command.borrow_mut() { impl FromStr for Hook {
Hook::QuitPanel | Hook::Reload | Hook::TagFlags => (), type Err = CommandParseError;
Hook::AttributeChanged { path, old, new } => {
*path = parser.must_string("attribute_changed(path)")?;
*old = parser.must_string("attribute_changed(old)")?;
*new = parser.must_string("attribute_changed(new)")?;
}
Hook::Fullscreen { on, window } => {
*on = parser.next_from_str("fullscreen(on)")?;
*window = parser.next_from_str("fullscreen(window)")?;
}
Hook::TagChanged { tag, monitor } => {
*tag = parser.must_string("tag_changed(tag)")?;
*monitor = parser.next_from_str("tag_changed(monitor)")?;
}
Hook::FocusChanged { window, title } | Hook::WindowTitleChanged { window, title } => {
*window = parser.next_from_str(&format!("{cmd}(window)"))?;
*title = parser.must_string(&format!("{cmd}(title)"))?;
}
Hook::TagAdded(tag) => {
*tag = TagSelect::from_str(&parser.must_string(cmd)?).expect("infallible")
}
Hook::TagRenamed { old, new } => {
*old = parser.must_string("tag_renamed(old)")?;
*new = parser.must_string("tag_renamed(new)")?;
}
Hook::Urgent { on, window } => {
*on = parser.next_from_str("urgent(on)")?;
*window = parser.next_from_str("urgent(window)")?;
}
Hook::Rule { hook, window } => {
*hook = parser.must_string("rule(hook)")?;
*window = parser.next_from_str("rule(window)")?;
}
}
Ok(command) fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut split = s.split("\t");
Hook::from_raw_parts(
split
.next()
.ok_or(CommandParseError::UnknownCommand(format!("hook {s}")))?,
split.map(String::from).collect(),
)
} }
} }

View File

@ -1,14 +1,14 @@
use std::{borrow::BorrowMut, fmt::Display, str::FromStr}; use std::{fmt::Display, str::FromStr};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use thiserror::Error;
use crate::{logerr::UnwrapLog, split}; use crate::{logerr::UnwrapLog, split};
use super::{ use super::{
command::HlwmCommand, command::{CommandParseError, HlwmCommand},
parser::{ArgParser, FromCommandArgs, FromStrings, ParseError}, StringParseError, ToCommandString,
ToCommandString,
}; };
#[derive(Debug, Clone, Copy, PartialEq, strum::EnumIter)] #[derive(Debug, Clone, Copy, PartialEq, strum::EnumIter)]
@ -78,12 +78,7 @@ impl Key {
} }
} }
pub fn parse_keybind_keys(s: &str) -> Result<Vec<Key>, ParseError> { pub fn from_str_case_insensitive(s: &str) -> Result<Self, KeyParseError> {
s.split(['-', '+'])
.map(Key::from_str)
.collect::<Result<Vec<_>, _>>()
}
pub fn from_str_case_insensitive(s: &str) -> Result<Self, ParseError> {
Key::from_str(&title_case(s)) Key::from_str(&title_case(s))
} }
@ -132,7 +127,37 @@ impl Key {
impl Display for Key { impl Display for Key {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.aliases().first().unwrap()) let data = match self {
Key::Return => "Return".to_string(),
Key::Shift => "Shift".to_string(),
Key::Tab => "Tab".to_string(),
Key::Left => "Left".to_string(),
Key::Right => "Right".to_string(),
Key::Up => "Up".to_string(),
Key::Down => "Down".to_string(),
Key::Space => "space".to_string(),
Key::Control => "Control".to_string(),
Key::Backtick => "grave".to_string(),
Key::Char(c) => c.to_string(),
Key::Mouse(m) => m.to_string(),
Key::F1 => "F1".to_string(),
Key::F2 => "F2".to_string(),
Key::F3 => "F3".to_string(),
Key::F4 => "F4".to_string(),
Key::F5 => "F5".to_string(),
Key::F6 => "F6".to_string(),
Key::F7 => "F7".to_string(),
Key::F8 => "F8".to_string(),
Key::F9 => "F9".to_string(),
Key::F10 => "F10".to_string(),
Key::F11 => "F11".to_string(),
Key::F12 => "F12".to_string(),
Key::Home => "Home".to_string(),
Key::Delete => "Delete".to_string(),
Key::Mod1Alt => "Mod1".to_string(),
Key::Mod4Super => "Mod4".to_string(),
};
f.write_str(&data)
} }
} }
@ -176,7 +201,7 @@ impl<'de> Deserialize<'de> for Key {
} }
impl FromStr for Key { impl FromStr for Key {
type Err = ParseError; type Err = KeyParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
match s { match s {
@ -195,10 +220,7 @@ impl FromStr for Key {
s.chars().next().unwrap_log().to_ascii_lowercase(), s.chars().next().unwrap_log().to_ascii_lowercase(),
)) ))
} else { } else {
Err(ParseError::InvalidValue { Err(KeyParseError::ExpectedCharKey(s.to_string()))
value: s.to_string(),
expected: "a valid key",
})
} }
} }
} }
@ -221,7 +243,7 @@ impl Default for MouseButton {
} }
impl FromStr for MouseButton { impl FromStr for MouseButton {
type Err = ParseError; type Err = StringParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
match s { match s {
@ -230,7 +252,7 @@ impl FromStr for MouseButton {
"Button3" => Ok(Self::Button3), "Button3" => Ok(Self::Button3),
"Button4" => Ok(Self::Button4), "Button4" => Ok(Self::Button4),
"Button5" => Ok(Self::Button5), "Button5" => Ok(Self::Button5),
_ => Err(ParseError::InvalidCommand(s.to_string())), _ => Err(StringParseError::UnknownValue),
} }
} }
} }
@ -283,48 +305,27 @@ impl<'de> Deserialize<'de> for MousebindAction {
} }
} }
impl FromCommandArgs for MousebindAction {
fn from_command_args<S: Into<String>, I: Iterator<Item = S>>(
command: &str,
args: I,
) -> Result<Self, ParseError> {
let mut action = Self::iter()
.find(|i| i.to_string() == command)
.ok_or(ParseError::InvalidCommand(command.to_string()))?;
match action.borrow_mut() {
MousebindAction::Move | MousebindAction::Resize | MousebindAction::Zoom => (),
MousebindAction::Call(arg) => {
*arg = Box::new(
ArgParser::from_strings(args.map(|a| a.into()))
.collect_command("mousebind_args(command)")?,
)
}
}
Ok(action)
}
}
impl FromStr for MousebindAction { impl FromStr for MousebindAction {
type Err = ParseError; type Err = StringParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut parser = ArgParser::from_strings(split::tab_or_space(s).into_iter()); let mut parts = split::tab_or_space(s).into_iter();
let action_str = parser.must_string("mousebind_action(action)")?; let first = parts.next().ok_or(StringParseError::UnknownValue)?;
let act = Self::iter()
.find(|i| i.to_string() == first)
.ok_or(StringParseError::UnknownValue)?;
let mut action = Self::iter() match act {
.find(|i| i.to_string() == action_str) MousebindAction::Move | MousebindAction::Resize | MousebindAction::Zoom => Ok(act),
.ok_or(ParseError::InvalidCommand(s.to_string()))?; MousebindAction::Call(_) => {
let command = parts.next().ok_or(StringParseError::UnknownValue)?;
match action.borrow_mut() { let args = parts.collect();
MousebindAction::Move | MousebindAction::Resize | MousebindAction::Zoom => (), Ok(MousebindAction::Call(Box::new(
MousebindAction::Call(command) => { HlwmCommand::from_raw_parts(&command, args)
*command = Box::new(parser.collect_command("mousebind_action(command)")?); .map_err(|err| StringParseError::CommandParseError(err.to_string()))?,
)))
} }
} }
Ok(action)
} }
} }
@ -345,6 +346,32 @@ impl ToCommandString for MousebindAction {
} }
} }
#[derive(Debug, Clone, Error)]
pub enum KeyParseError {
#[error("value too short (expected >= 2 parts, got {0} parts)")]
TooShort(usize),
#[error("no keys in keybind")]
NoKeys,
#[error("command parse error: {0}")]
CommandParseError(String),
#[error("expected char key, got: [{0}]")]
ExpectedCharKey(String),
#[error("string parse error: [{0}]")]
StringParseError(String),
}
impl From<StringParseError> for KeyParseError {
fn from(value: StringParseError) -> Self {
KeyParseError::StringParseError(value.to_string())
}
}
impl From<CommandParseError> for KeyParseError {
fn from(value: CommandParseError) -> Self {
KeyParseError::CommandParseError(value.to_string())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Keybind { pub struct Keybind {
pub keys: Vec<Key>, pub keys: Vec<Key>,
@ -359,13 +386,29 @@ impl Default for Keybind {
} }
} }
} }
impl FromStrings for Keybind {
fn from_strings<I: Iterator<Item = String>>(s: I) -> Result<Self, ParseError> {
let mut parser = ArgParser::from_strings(s);
impl FromStr for Keybind {
type Err = KeyParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts = s.split('\t').collect::<Vec<&str>>();
if parts.len() < 2 {
return Err(KeyParseError::TooShort(s.len()));
}
let mut parts = parts.into_iter();
let keys = parts
.next()
.unwrap()
.split("+")
.map(|key| Key::from_str(key))
.collect::<Result<Vec<Key>, _>>()?;
let command = parts.next().unwrap();
let args: Vec<String> = parts.map(String::from).collect();
Ok(Self { Ok(Self {
keys: Key::parse_keybind_keys(&parser.must_string("keybind(keys)")?)?, keys,
command: Box::new(parser.collect_command("keybind(command)")?), command: Box::new(HlwmCommand::from_raw_parts(command, args)?),
}) })
} }
} }
@ -403,12 +446,29 @@ pub struct Mousebind {
pub action: MousebindAction, pub action: MousebindAction,
} }
impl FromStrings for Mousebind { impl FromStr for Mousebind {
fn from_strings<I: Iterator<Item = String>>(s: I) -> Result<Self, ParseError> { type Err = StringParseError;
let mut parser = ArgParser::from_strings(s);
let keys = Key::parse_keybind_keys(&parser.must_string("mousebind(keys)")?)?;
let action: MousebindAction = parser.collect_command("mousebind(action)")?; fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut parts = s.split("\t");
let keys = parts
.next()
.ok_or(StringParseError::UnknownValue)?
.split("-")
.map(|key| key.parse())
.collect::<Result<Vec<Key>, _>>()?;
let action = parts.next().ok_or(StringParseError::UnknownValue)?;
let mut action = MousebindAction::iter()
.into_iter()
.find(|act| act.to_string() == action)
.ok_or(StringParseError::UnknownValue)?;
if let MousebindAction::Call(_) = action {
let command = parts.next().ok_or(StringParseError::UnknownValue)?;
action = MousebindAction::Call(Box::new(
HlwmCommand::from_raw_parts(command, parts.map(String::from).collect())
.map_err(|_| StringParseError::UnknownValue)?,
));
}
Ok(Self { keys, action }) Ok(Self { keys, action })
} }
@ -452,12 +512,16 @@ pub enum KeyUnbind {
} }
impl FromStr for KeyUnbind { impl FromStr for KeyUnbind {
type Err = ParseError; type Err = StringParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
match s { match s {
"--all" | "-F" => Ok(Self::All), "--all" => Ok(Self::All),
_ => Ok(Self::Keybind(Key::parse_keybind_keys(s)?)), _ => Ok(KeyUnbind::Keybind(
s.split("-")
.map(|key| key.parse())
.collect::<Result<_, _>>()?,
)),
} }
} }
} }

153
src/hlwm/macros.rs Normal file
View File

@ -0,0 +1,153 @@
#[macro_export]
macro_rules! gen_parse {
($command:ident, $args:ident) => {
macro_rules! parse {
(And_Or => $$val:tt) => {
{
let mut args = $args.into_iter();
let separator: Separator = args
.next()
.ok_or(CommandParseError::MissingArgument)?
.parse()?;
let sep_str = separator.to_string();
let args = args.collect::<Vec<_>>();
let commands = args
.split(|itm| itm.eq(&sep_str))
.map(|itm| match itm.len() {
0 => Err(CommandParseError::MissingArgument),
1 => HlwmCommand::from_str(itm.first().unwrap()),
_ => {
let mut args = itm.into_iter();
HlwmCommand::from_raw_parts(
&args.next().unwrap(),
args.map(|i| i.to_owned()).collect(),
)
}
})
.collect::<Result<Vec<_>, _>>()?;
Ok(Self::$$val{
separator,
commands,
})}
};
($$arg_type:tt => $$val:tt) => {
{
#[allow(unused_mut)]
let mut args_iter = $args.into_iter();
Ok(Self::$$val(parse!(Argument args_iter: $$arg_type)))
}
};
($$($$arg:tt: $$arg_type:tt),+ => $val:tt) => {
{
#[allow(unused_mut)]
let mut args_iter = $args.into_iter();
Ok(Self::$val{
$$(
$$arg: parse!(Argument args_iter: $$arg_type)
),+
})
}
};
(Argument $$args:ident: [Flag($pat:literal)]) => {
{
let mut args = $$args.clone().into_iter();
let mut flag = false;
if args.any(|i| i == $pat) {
$$args = $$args.filter(|a| a != $pat).collect::<Vec<_>>().into_iter();
flag = true;
}
flag
}
};
(Argument $$args:ident: String) => {
$$args
.next()
.ok_or(CommandParseError::BadArgument {
command: $command.to_string(),
})?
};
(Argument $$args:ident: FromStrAll) => {
$$args
.collect::<Vec<_>>()
.join("\t")
.parse()
.map_err(|_| CommandParseError::BadArgument {
command: $command.to_string(),
})?
};
(Argument $$args:ident: FromStr) => {
parse!(Argument $$args: String)
.parse()
.map_err(|_| CommandParseError::BadArgument {
command: $command.to_string(),
})?
};
(Argument $$args:ident: [Option<FromStr>]) => {
{
let args: Vec<_> = $$args.collect();
if args.len() == 0 {
None
} else {
let mut args = args.into_iter();
Some(parse!(Argument args: String)
.parse()
.map_err(|_| CommandParseError::BadArgument {
command: $command.to_string(),
})?)
}
}
};
(Argument $$args:ident: [Vec<String>]) => {
$$args.collect::<Vec<String>>()
};
(Argument $$args:ident: Bool) => {
match parse!(Argument $$args: String).as_str() {
"on" | "true" => true,
"off" | "false" => false,
_ => return Err(CommandParseError::BadArgument {
command: $command.to_string(),
}),
}
};
(Argument $$args:ident: [Option<String>]) => {
{
let args = $$args.collect::<Vec<String>>();
if args.len() == 0 {
None
} else {
Some(args.join("\t"))
}
}
};
(Argument $$args:ident: [Option<Vec<String>>]) => {
{
let args = $$args.collect::<Vec<String>>();
if args.len() == 0 {
None
} else {
Some(args)
}
}
};
(Argument $$args:ident: [Option<FromStr>]) => {
{
let args = $$args.map(|item| item.parse().map_err(|_| CommandParseError::BadArgument {
command: $command.to_string(),
})).collect::<Result<Vec<_>, _>>()?;
if args.len() == 0 {
None
} else {
Some(args)
}
}
};
(Argument $$args:ident: [Vec<FromStr>]) => {
{
$$args.map(|item| item.parse().map_err(|_| CommandParseError::BadArgument {
command: $command.to_string(),
})).collect::<Result<Vec<_>, _>>()?
}
};
}
};
}

View File

@ -1,25 +1,26 @@
use std::{ use std::{
convert::Infallible, convert::Infallible,
fmt::Display, fmt::Display,
num::{ParseFloatError, ParseIntError},
process::{self, Stdio}, process::{self, Stdio},
str::FromStr, str::FromStr,
}; };
use log::{error, trace}; use log::{debug, error};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use thiserror::Error;
use crate::cmd; use crate::cmd;
use self::{ use self::{
attribute::{Attribute, AttributeType}, attribute::{Attribute, AttributeError},
command::{CommandError, HlwmCommand}, command::{CommandError, HlwmCommand},
parser::{ArgParser, FromStrings, ParseError}, key::KeyParseError,
setting::{Setting, SettingName}, setting::{Setting, SettingName},
tag::TagStatus, tag::TagStatus,
}; };
mod and_or_command;
pub mod attribute; pub mod attribute;
pub mod color; pub mod color;
pub mod command; pub mod command;
@ -29,12 +30,13 @@ pub mod hook;
pub mod key; pub mod key;
mod octal; mod octal;
pub mod pad; pub mod pad;
pub mod parser;
pub mod rule; pub mod rule;
pub mod setting; pub mod setting;
pub mod tag; pub mod tag;
pub mod theme; pub mod theme;
pub mod window; pub mod window;
#[macro_use]
mod macros;
pub use hlwmbool::ToggleBool; pub use hlwmbool::ToggleBool;
@ -56,7 +58,7 @@ impl Client {
/// Run the command and wait for it to finish. /// Run the command and wait for it to finish.
pub fn execute(&self, command: HlwmCommand) -> Result<process::Output, CommandError> { pub fn execute(&self, command: HlwmCommand) -> Result<process::Output, CommandError> {
let args = command.args(); let args = command.args();
trace!("running command: [{}]", (&args).join(" "),); debug!("running command: [{}]", (&args).join(" "),);
let output = Self::herbstclient() let output = Self::herbstclient()
.arg("--no-newline") .arg("--no-newline")
.args(args) .args(args)
@ -88,37 +90,29 @@ impl Client {
.ok_or(CommandError::Empty) .ok_or(CommandError::Empty)
} }
pub fn get_from_str_attr<E: Into<ParseError>, T: FromStr<Err = E>>(
&self,
attr: String,
) -> Result<T, CommandError> {
Ok(self
.get_str_attr(attr)?
.parse()
.map_err(|err: E| err.into())?)
}
pub fn get_attr(&self, attr: String) -> Result<Attribute, CommandError> { pub fn get_attr(&self, attr: String) -> Result<Attribute, CommandError> {
let attr_type = AttributeType::get_type(&attr)?; let attr_type = self
.query(HlwmCommand::AttrType(attr.clone()))?
.first()
.cloned()
.ok_or(CommandError::Empty)?;
let attr_val = self let attr_val = self
.query(HlwmCommand::GetAttr(attr))? .query(HlwmCommand::GetAttr(attr))?
.first() .first()
.cloned() .cloned()
.ok_or(CommandError::Empty)?; .ok_or(CommandError::Empty)?;
Ok(Attribute::new(attr_type, &attr_val)?) Ok(Attribute::new(&attr_type, &attr_val)?)
} }
pub fn get_setting(&self, setting: SettingName) -> Result<Setting, CommandError> { pub fn get_setting(&self, setting: SettingName) -> Result<Setting, CommandError> {
let setting_value = String::from_utf8(self.execute(HlwmCommand::Get(setting))?.stdout)?; Ok(Setting::from_str(&format!(
Ok( "{setting}\t{}",
Setting::from_str(&format!("{setting}\t{}", setting_value)).map_err(|err| { String::from_utf8(self.execute(HlwmCommand::Get(setting))?.stdout,)?
))
.map_err(|err| {
error!("failed getting setting [{setting}]: {err}"); error!("failed getting setting [{setting}]: {err}");
ParseError::InvalidValue { StringParseError::UnknownValue
value: setting_value, })?)
expected: "setting value (get_setting)",
}
})?,
)
} }
pub fn query(&self, command: HlwmCommand) -> Result<Vec<String>, CommandError> { pub fn query(&self, command: HlwmCommand) -> Result<Vec<String>, CommandError> {
@ -151,6 +145,28 @@ pub trait ToCommandString {
fn to_command_string(&self) -> String; fn to_command_string(&self) -> String;
} }
#[derive(Debug, Clone, Error)]
pub enum StringParseError {
#[error("unknown value")]
UnknownValue,
#[error("failed parsing float: [{0}]")]
FloatError(#[from] ParseFloatError),
#[error("invalid bool value: [{0}]")]
BoolError(String),
#[error("failed parsing int: [{0}]")]
IntError(#[from] ParseIntError),
#[error("command parse error")]
CommandParseError(String),
#[error("invalid length for part [{1}]: [{0}]")]
InvalidLength(usize, &'static str),
#[error("required arguments missing: [{0}]")]
RequiredArgMissing(String),
#[error("attribute error: [{0}]")]
AttributeError(#[from] AttributeError),
#[error("key parse error: [{0}]")]
KeyParseError(#[from] KeyParseError),
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum TagSelect { pub enum TagSelect {
Index(i32), Index(i32),
@ -197,31 +213,22 @@ impl<N> FromStr for Index<N>
where where
N: PartialEq + FromStr, N: PartialEq + FromStr,
{ {
type Err = ParseError; type Err = StringParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut chars = s.chars(); let mut chars = s.chars();
let prefix = chars.next().ok_or(ParseError::Empty)?; let prefix = chars.next().ok_or(StringParseError::UnknownValue)?;
match prefix { match prefix {
'+' => Ok(Self::Relative({ '+' => Ok(Self::Relative(
let s = chars.collect::<String>(); N::from_str(&chars.collect::<String>())
N::from_str(&s).map_err(|_| ParseError::InvalidValue { .map_err(|_| StringParseError::UnknownValue)?,
value: s, )),
expected: std::any::type_name::<N>(), '-' => Ok(Self::Relative(
})? N::from_str(s).map_err(|_| StringParseError::UnknownValue)?,
})), )),
'-' => Ok(Self::Relative(N::from_str(s).map_err(|_| { _ => Ok(Self::Absolute(
ParseError::InvalidValue { N::from_str(s).map_err(|_| StringParseError::UnknownValue)?,
value: s.to_string(), )),
expected: std::any::type_name::<N>(),
}
})?)),
_ => Ok(Self::Absolute(N::from_str(s).map_err(|_| {
ParseError::InvalidValue {
value: s.to_string(),
expected: std::any::type_name::<N>(),
}
})?)),
} }
} }
} }
@ -253,20 +260,20 @@ where
} }
} }
#[derive(Debug, Clone, Copy, Serialize, Deserialize, strum::EnumIter, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, strum::EnumIter, PartialEq)]
pub enum Separator { pub enum Separator {
Comma, Comma,
Period, Period,
} }
impl FromStr for Separator { impl FromStr for Separator {
type Err = ParseError; type Err = StringParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::iter() Self::iter()
.into_iter() .into_iter()
.find(|i| i.to_string() == s) .find(|i| i.to_string() == s)
.ok_or(ParseError::InvalidCommand(s.to_string())) .ok_or(StringParseError::UnknownValue)
} }
} }
@ -302,13 +309,13 @@ pub enum Operator {
} }
impl FromStr for Operator { impl FromStr for Operator {
type Err = ParseError; type Err = StringParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::iter() Self::iter()
.into_iter() .into_iter()
.find(|i| i.to_string() == s) .find(|i| i.to_string() == s)
.ok_or(ParseError::InvalidCommand(s.to_string())) .ok_or(StringParseError::UnknownValue)
} }
} }
@ -341,13 +348,13 @@ pub enum Direction {
} }
impl FromStr for Direction { impl FromStr for Direction {
type Err = ParseError; type Err = StringParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::iter() Self::iter()
.into_iter() .into_iter()
.find(|dir| dir.to_string() == s) .find(|dir| dir.to_string() == s)
.ok_or(ParseError::InvalidCommand(s.to_string())) .ok_or(StringParseError::UnknownValue)
} }
} }
@ -368,20 +375,25 @@ pub enum Align {
Auto, Auto,
} }
impl FromStrings for Align { impl FromStr for Align {
fn from_strings<I: Iterator<Item = String>>(s: I) -> Result<Self, ParseError> { type Err = StringParseError;
let mut args = ArgParser::from_strings(s);
let alignment = args.must_string("align(align)")?;
let fraction = args.optional_next_from_str("align(fraction)")?;
match alignment.as_str() { fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut parts = s.split("\t");
let align = parts.next().ok_or(StringParseError::UnknownValue)?;
let fraction = match parts.next().map(|f| f64::from_str(f)) {
Some(val) => Some(val?),
None => None,
};
match align {
"bottom" | "vertical" | "vert" | "v" => Ok(Self::Bottom(fraction)), "bottom" | "vertical" | "vert" | "v" => Ok(Self::Bottom(fraction)),
"left" => Ok(Self::Left(fraction)), "left" => Ok(Self::Left(fraction)),
"right" | "horizontal" | "horiz" | "h" => Ok(Self::Right(fraction)), "right" | "horizontal" | "horiz" | "h" => Ok(Self::Right(fraction)),
"top" => Ok(Self::Top(fraction)), "top" => Ok(Self::Top(fraction)),
"explode" => Ok(Self::Explode), "explode" => Ok(Self::Explode),
"auto" => Ok(Self::Auto), "auto" => Ok(Self::Auto),
_ => Err(ParseError::InvalidCommand(alignment)), _ => Err(StringParseError::UnknownValue),
} }
} }
} }

View File

@ -2,7 +2,7 @@ use std::{fmt::Display, str::FromStr};
use crate::split; use crate::split;
use super::parser::{FromStrings, ParseError}; use super::StringParseError;
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Pad { pub enum Pad {
@ -13,11 +13,13 @@ pub enum Pad {
UpRightDownLeft(u32, u32, u32, u32), UpRightDownLeft(u32, u32, u32, u32),
} }
impl FromStrings for Pad { impl FromStr for Pad {
fn from_strings<I: Iterator<Item = String>>(s: I) -> Result<Self, ParseError> { type Err = StringParseError;
let parts = s.collect::<Vec<_>>();
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts = split::tab_or_space(s);
match parts.len() { match parts.len() {
0 => Err(ParseError::Empty), 0 => Err(StringParseError::InvalidLength(0, "pad")),
1 => Ok(Pad::Up(parts[0].parse()?)), 1 => Ok(Pad::Up(parts[0].parse()?)),
2 => Ok(Pad::UpRight(parts[0].parse()?, parts[1].parse()?)), 2 => Ok(Pad::UpRight(parts[0].parse()?, parts[1].parse()?)),
3 => Ok(Pad::UpRightDown( 3 => Ok(Pad::UpRightDown(
@ -35,15 +37,6 @@ impl FromStrings for Pad {
} }
} }
impl FromStr for Pad {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts = split::tab_or_space(s);
Self::from_strings(parts.into_iter())
}
}
impl Display for Pad { impl Display for Pad {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {

View File

@ -1,4 +0,0 @@
pub enum Either<L, R> {
Left(L),
Right(R),
}

View File

@ -1,234 +0,0 @@
use std::{
convert::Infallible,
num::{ParseFloatError, ParseIntError},
str::{FromStr, ParseBoolError},
string::FromUtf8Error,
};
use log::error;
use thiserror::Error;
use self::either::Either;
use super::hex::HexError;
pub mod either;
mod traits;
pub use traits::*;
#[derive(Debug, Clone, Error, strum::EnumIs)]
pub enum ParseError {
#[error("no more items left in parser")]
Empty,
#[error("missing expected value")]
ValueMissing,
#[error("invalid command: [{0}]")]
InvalidCommand(String),
#[error("invalid value [{value}], expected [{expected}]")]
InvalidValue {
value: String,
expected: &'static str,
},
#[error("hex decoding error: [{0}]")]
HexError(#[from] HexError),
#[error("primitive type parsing error: [{0}]")]
PrimitiveError(String),
}
impl From<Infallible> for ParseError {
fn from(_: Infallible) -> Self {
// nonsense
unreachable!()
}
}
macro_rules! from_primitive {
($($err:ty,)*) => {
$(
impl From<$err> for ParseError {
fn from(value: $err) -> Self {
ParseError::PrimitiveError(value.to_string())
}
}
)*
};
}
from_primitive!(
ParseBoolError,
ParseIntError,
FromUtf8Error,
ParseFloatError,
);
impl From<strum::ParseError> for ParseError {
fn from(value: strum::ParseError) -> Self {
ParseError::InvalidCommand(value.to_string())
}
}
pub struct ArgParser<I, T>
where
I: Iterator<Item = T>,
{
inner: I,
}
impl<I, T> Clone for ArgParser<I, T>
where
I: Iterator<Item = T> + Clone,
{
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}
impl<I> ArgParser<I, String>
where
I: Iterator<Item = String>,
{
pub fn from_strings(src: I) -> Self {
Self { inner: src }
}
#[inline(always)]
pub fn next_string(&mut self) -> Option<String> {
self.inner.next()
}
#[inline(always)]
pub fn inner(self) -> I {
self.inner
}
/// Gets the next string, then tries the `try_this` function on it.
/// If that function returns [Err], it runs the `then_this` function
/// and returns that result.
pub fn try_first<F1, F2, T1, T2, E>(
&mut self,
try_this: F1,
then_this: F2,
on_error: &str,
) -> Result<Either<T1, T2>, ParseError>
where
F1: FnOnce(&String) -> Result<T1, E>,
F2: FnOnce(&String) -> Result<T2, E>,
E: Into<ParseError>,
{
let next = self.must_string(on_error)?;
if let Ok(t1) = try_this(&next) {
return Ok(Either::Left(t1));
}
match then_this(&next) {
Ok(t2) => Ok(Either::Right(t2)),
Err(err) => {
let err = err.into();
error!("{on_error}: {err}");
Err(err)
}
}
}
/// Get the next string, or [ParseError::Empty]
#[inline(always)]
pub fn must_string(&mut self, on_error: &str) -> Result<String, ParseError> {
self.inner
.next()
.ok_or(ParseError::Empty)
.inspect_err(|err| error!("{on_error}: {err}"))
}
pub fn next_from_str<E: Into<ParseError>, T: FromStr<Err = E>>(
&mut self,
on_error: &str,
) -> Result<T, ParseError> {
Ok(self.must_string(on_error)?.parse().map_err(|err: E| {
let err = err.into();
error!("{on_error}: {err}");
err
})?)
}
/// Consumes the remainder of the iterator and uses the [FromCommandArgs]
/// trait to parse it into the desired type
pub fn collect_command<C: FromCommandArgs>(mut self, on_error: &str) -> Result<C, ParseError> {
C::from_command_args(&self.must_string(on_error)?, self.inner)
}
/// Consumes the remainder of the iterator and uses the [FromStrings]
/// trait to parse it into the desired type
pub fn collect_from_strings<T: FromStrings>(self, on_error: &str) -> Result<T, ParseError> {
T::from_strings(self.inner).inspect_err(|err| {
error!("{on_error}: {err}");
})
}
/// Consumes the remainder of the iterator and uses the [FromStringsHint]
/// trait to parse it into the desired type.
///
/// Takes a `hint` that may aid in the parsing of that string
pub fn collect_from_strings_hint<H, T: FromStringsHint<H>>(
self,
hint: H,
on_error: &str,
) -> Result<T, ParseError> {
T::from_strings_hint(self.inner, hint).inspect_err(|err| {
error!("{on_error}: {err}");
})
}
#[inline(always)]
pub fn collect<B: FromIterator<String>>(self) -> B {
self.inner.collect()
}
/// Get the next [String] from the iterator and tries to parse it with [FromStr]
/// if there's any remaining items. Otherwise just returns [None]
pub fn optional_next_from_str<E: Into<ParseError>, T: FromStr<Err = E>>(
&mut self,
on_error: &str,
) -> Result<Option<T>, ParseError> {
match self.next_string() {
Some(next) => Ok(Some(next.parse().map_err(|err: E| {
let err = err.into();
error!("{on_error}: {err}");
err
})?)),
None => Ok(None),
}
}
/// Consumes the iterator and takes every remaining element, parses them with
/// [FromStr] and returns them in a [Vec]
pub fn collect_from_str<E: Into<ParseError>, T: FromStr<Err = E>>(
self,
on_error: &str,
) -> Result<Vec<T>, ParseError> {
self.inner
.map(|item| T::from_str(&item))
.collect::<Result<Vec<_>, _>>()
.map_err(|err| {
let err = err.into();
error!("{on_error}: {err}");
err
})
}
/// Consumes the iterator and takes every remaining element, checks if any match
/// `flag`, and returns the strings in the iterator that don't match `flag`,
/// as well as a boolean on whether `flag` was found.
pub fn collect_strings_with_flag(self, flag: &str) -> (Vec<String>, bool) {
let mut out = Vec::new();
let mut found = false;
for item in self.inner {
if item == flag {
found = true;
continue;
}
out.push(item);
}
(out, found)
}
}

View File

@ -1,67 +0,0 @@
use super::ParseError;
pub trait FromStrings: Sized {
fn from_strings<I: Iterator<Item = String>>(s: I) -> Result<Self, ParseError>;
}
/// Same as [FromStrings] but can pass a hint to the function which can help
/// in some determinations (e.g. passing in the path of the attribute for
/// parsing a [crate::hlwm::Attribute] so it can use the path to determine the
/// type of the attribute)
pub trait FromStringsHint<Hint>: Sized {
fn from_strings_hint<I: Iterator<Item = String>>(s: I, hint: Hint) -> Result<Self, ParseError>;
}
pub trait FromCommandArgs: Sized {
fn from_command_args<S: Into<String>, I: Iterator<Item = S>>(
command: &str,
args: I,
) -> Result<Self, ParseError>;
}
pub trait ToOption: Sized {
fn to_option(self) -> Option<Self>;
}
impl<T> ToOption for Vec<T> {
fn to_option(self) -> Option<Self> {
match self.is_empty() {
false => Some(self),
true => None,
}
}
}
impl ToOption for &str {
fn to_option(self) -> Option<Self> {
match self.is_empty() {
false => Some(self),
true => None,
}
}
}
impl ToOption for String {
fn to_option(self) -> Option<Self> {
match self.is_empty() {
false => Some(self),
true => None,
}
}
}
pub trait Flip {
type Target;
fn flip(self) -> Self::Target;
}
impl<T, E> Flip for Option<Result<T, E>> {
type Target = Result<Option<T>, E>;
fn flip(self) -> Self::Target {
match self {
Some(r) => r.map(Some),
None => Self::Target::Ok(None),
}
}
}

View File

@ -10,12 +10,7 @@ use strum::IntoEnumIterator;
use crate::split; use crate::split;
use super::{ use super::{hlwmbool, hook::Hook, StringParseError, ToCommandString};
hlwmbool,
hook::Hook,
parser::{FromCommandArgs, FromStrings, ParseError},
ToCommandString,
};
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Rule { pub struct Rule {
@ -83,17 +78,23 @@ impl Rule {
} }
} }
impl FromStrings for Rule { impl FromStr for Rule {
fn from_strings<I: Iterator<Item = String>>(s: I) -> Result<Self, ParseError> { type Err = StringParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts = split::tab_or_space(s);
if parts.is_empty() {
return Err(StringParseError::InvalidLength(0, "parse rule"));
}
let mut condition: Option<Condition> = None; let mut condition: Option<Condition> = None;
let mut consequences = vec![]; let mut consequences = vec![];
let mut label: Option<String> = None; let mut label: Option<String> = None;
let mut flag: Option<Flag> = None; let mut flag: Option<Flag> = None;
let mut args = s.map(|part| part.strip_prefix("--").unwrap_or(part.as_str()).to_string()); let mut args = parts
.into_iter()
.map(|part| part.strip_prefix("--").unwrap_or(part.as_str()).to_string());
let mut original = Vec::new(); // For InvalidValue error
while let Some(arg) = args.next() { while let Some(arg) = args.next() {
original.push(arg.clone());
if label.is_none() { if label.is_none() {
if let Some((name, value)) = arg.split_once('=') { if let Some((name, value)) = arg.split_once('=') {
if name.trim() == "label" { if name.trim() == "label" {
@ -121,10 +122,9 @@ impl FromStrings for Rule {
} }
if consequences.is_empty() { if consequences.is_empty() {
return Err(ParseError::InvalidValue { return Err(StringParseError::RequiredArgMissing(
value: original.join(" "), "condition and/or consequences".into(),
expected: "condition and/or consequences", ));
});
} }
Ok(Self { Ok(Self {
@ -158,9 +158,8 @@ impl<'de> Deserialize<'de> for Rule {
} }
let str_val: String = Deserialize::deserialize(deserializer)?; let str_val: String = Deserialize::deserialize(deserializer)?;
let strings = split::tab_or_space(&str_val);
Ok(Self::from_strings(strings.into_iter()).map_err(|_| { Ok(Self::from_str(&str_val).map_err(|_| {
serde::de::Error::invalid_value(serde::de::Unexpected::Str(&str_val), &Expect) serde::de::Error::invalid_value(serde::de::Unexpected::Str(&str_val), &Expect)
})?) })?)
} }
@ -188,12 +187,12 @@ impl RuleOperator {
} }
impl TryFrom<char> for RuleOperator { impl TryFrom<char> for RuleOperator {
type Error = ParseError; type Error = StringParseError;
fn try_from(value: char) -> Result<Self, Self::Error> { fn try_from(value: char) -> Result<Self, Self::Error> {
Self::iter() Self::iter()
.find(|i| i.char() == value) .find(|i| i.char() == value)
.ok_or(ParseError::InvalidCommand(value.to_string())) .ok_or(StringParseError::UnknownValue)
} }
} }
@ -210,10 +209,13 @@ impl Display for RuleOperator {
} }
impl FromStr for RuleOperator { impl FromStr for RuleOperator {
type Err = ParseError; type Err = StringParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
s.chars().next().ok_or(ParseError::Empty)?.try_into() s.chars()
.next()
.ok_or(StringParseError::InvalidLength(s.len(), "rule operator"))?
.try_into()
} }
} }
@ -292,7 +294,7 @@ impl ToCommandString for Condition {
} }
impl FromStr for Condition { impl FromStr for Condition {
type Err = ParseError; type Err = StringParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
// Handle the case for fixedsize first so that we can treat the rest // Handle the case for fixedsize first so that we can treat the rest
@ -305,11 +307,11 @@ impl FromStr for Condition {
let ((name, match_val), match_char) = let ((name, match_val), match_char) =
match split::on_first_match(s, &RuleOperator::match_set()) { match split::on_first_match(s, &RuleOperator::match_set()) {
Some(parts) => parts, Some(parts) => parts,
None => return Err(ParseError::Empty), None => return Err(StringParseError::InvalidLength(1, "property")),
}; };
let mut prop = Self::iter() let mut prop = Self::iter()
.find(|i| i.to_string() == name) .find(|i| i.to_string() == name)
.ok_or(ParseError::InvalidCommand(s.to_string()))?; .ok_or(StringParseError::UnknownValue)?;
match prop.borrow_mut() { match prop.borrow_mut() {
Condition::Instance { operator, value } Condition::Instance { operator, value }
@ -325,12 +327,7 @@ impl FromStr for Condition {
} }
// Should be handled at the top of the function. If it's here that's not a valid // Should be handled at the top of the function. If it's here that's not a valid
// use of fixedsize. // use of fixedsize.
Condition::FixedSize => { Condition::FixedSize => return Err(StringParseError::UnknownValue),
return Err(ParseError::InvalidValue {
value: s.to_string(),
expected: "[BUG] Should be handled at the top of the function",
})
}
}; };
Ok(prop) Ok(prop)
@ -436,7 +433,7 @@ impl ToCommandString for Consequence {
} }
impl FromStr for Consequence { impl FromStr for Consequence {
type Err = ParseError; type Err = StringParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts = split::tab_or_space(s); let parts = split::tab_or_space(s);
@ -445,7 +442,7 @@ impl FromStr for Consequence {
let (name, value_str) = if name.contains('=') { let (name, value_str) = if name.contains('=') {
let parts = name.split('=').collect::<Vec<_>>(); let parts = name.split('=').collect::<Vec<_>>();
if parts.len() != 2 { if parts.len() != 2 {
return Err(ParseError::InvalidCommand(s.to_string())); return Err(StringParseError::UnknownValue);
} }
let mut parts = parts.into_iter(); let mut parts = parts.into_iter();
( (
@ -456,10 +453,10 @@ impl FromStr for Consequence {
match parts.next() { match parts.next() {
Some(op) => { Some(op) => {
if op != "=" { if op != "=" {
return Err(ParseError::InvalidCommand(s.to_string())); return Err(StringParseError::UnknownValue);
} }
} }
None => return Err(ParseError::InvalidCommand(s.to_string())), None => return Err(StringParseError::UnknownValue),
}; };
let value = parts.collect::<Vec<_>>().join("\t"); let value = parts.collect::<Vec<_>>().join("\t");
(name, value) (name, value)
@ -467,7 +464,7 @@ impl FromStr for Consequence {
let mut cons = Self::iter() let mut cons = Self::iter()
.find(|i| i.to_string() == name) .find(|i| i.to_string() == name)
.ok_or(ParseError::InvalidCommand(s.to_string()))?; .ok_or(StringParseError::UnknownValue)?;
match cons.borrow_mut() { match cons.borrow_mut() {
Consequence::Focus(value) Consequence::Focus(value)
| Consequence::SwitchTag(value) | Consequence::SwitchTag(value)
@ -486,13 +483,15 @@ impl FromStr for Consequence {
| Consequence::KeysInactive(value) => *value = value_str, | Consequence::KeysInactive(value) => *value = value_str,
Consequence::Index(value) => *value = i32::from_str(&value_str)?, Consequence::Index(value) => *value = i32::from_str(&value_str)?,
Consequence::Hook(value) => { Consequence::Hook(value) => {
*value = Hook::from_command_args(&name, [value_str].into_iter())?; *value = Hook::from_str(&value_str).map_err(|_| StringParseError::UnknownValue)?
} }
Consequence::FloatPlacement(value) => *value = FloatPlacement::from_str(&value_str)?, Consequence::FloatPlacement(value) => *value = FloatPlacement::from_str(&value_str)?,
Consequence::FloatingGeometry { x, y } => { Consequence::FloatingGeometry { x, y } => {
let mut values = value_str.split('='); let mut values = value_str.split('=');
*x = u32::from_str(values.next().ok_or(ParseError::ValueMissing)?)?; *x = u32::from_str(values.next().ok_or(StringParseError::UnknownValue)?)
*y = u32::from_str(values.next().ok_or(ParseError::ValueMissing)?)?; .map_err(|_| StringParseError::UnknownValue)?;
*y = u32::from_str(values.next().ok_or(StringParseError::UnknownValue)?)
.map_err(|_| StringParseError::UnknownValue)?;
} }
}; };
@ -514,12 +513,12 @@ pub enum FloatPlacement {
} }
impl FromStr for FloatPlacement { impl FromStr for FloatPlacement {
type Err = ParseError; type Err = StringParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::iter() Self::iter()
.find(|i| i.to_string() == s) .find(|i| i.to_string() == s)
.ok_or(ParseError::InvalidCommand(s.to_string())) .ok_or(StringParseError::UnknownValue)
} }
} }
@ -554,7 +553,7 @@ impl Display for Flag {
} }
impl FromStr for Flag { impl FromStr for Flag {
type Err = ParseError; type Err = StringParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
match s { match s {
@ -562,7 +561,7 @@ impl FromStr for Flag {
"once" => Ok(Self::Once), "once" => Ok(Self::Once),
"printlabel" => Ok(Self::PrintLabel), "printlabel" => Ok(Self::PrintLabel),
"prepend" => Ok(Self::Prepend), "prepend" => Ok(Self::Prepend),
_ => Err(ParseError::InvalidCommand(s.to_string())), _ => Err(StringParseError::UnknownValue),
} }
} }
} }
@ -650,10 +649,7 @@ mod test {
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::hlwm::rule::FloatPlacement;
hlwm::{parser::FromStrings, rule::FloatPlacement},
split,
};
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use super::{Condition, Consequence, Flag, Rule, RuleOperator}; use super::{Condition, Consequence, Flag, Rule, RuleOperator};
@ -701,7 +697,7 @@ label=5 fixedsize=0 floating=true"#;
let parsed = INPUT let parsed = INPUT
.split('\n') .split('\n')
.map(|l| Rule::from_strings(split::tab_or_space(l).into_iter())) .map(|l| Rule::from_str(l))
.collect::<Result<Vec<_>, _>>() .collect::<Result<Vec<_>, _>>()
.expect("parsing error"); .expect("parsing error");

View File

@ -1,20 +1,12 @@
use std::{borrow::BorrowMut, str::FromStr}; use std::str::FromStr;
use log::trace; use log::debug;
use serde::{de::Expected, Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{gen_parse, hlwm::command::CommandParseError, split};
hlwm::{command::CommandParseError, parser::ArgParser},
split,
};
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use super::{ use super::{color::Color, hlwmbool::ToggleBool, StringParseError, ToCommandString};
color::Color,
hlwmbool::ToggleBool,
parser::{FromCommandArgs, ParseError},
ToCommandString,
};
#[derive(Debug, Clone, strum::Display, strum::EnumIter, PartialEq, strum::EnumDiscriminants)] #[derive(Debug, Clone, strum::Display, strum::EnumIter, PartialEq, strum::EnumDiscriminants)]
#[strum_discriminants( #[strum_discriminants(
@ -209,12 +201,12 @@ impl Default for SettingName {
} }
impl FromStr for SettingName { impl FromStr for SettingName {
type Err = ParseError; type Err = StringParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::iter() Self::iter()
.find(|i| i.to_string() == s) .find(|i| i.to_string() == s)
.ok_or(ParseError::InvalidCommand(s.to_string())) .ok_or(StringParseError::UnknownValue)
} }
} }
@ -232,18 +224,6 @@ impl<'de> Deserialize<'de> for Setting {
where where
D: serde::Deserializer<'de>, D: serde::Deserializer<'de>,
{ {
pub enum Expect {
Command(String),
}
impl Expected for Expect {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Expect::Command(err) => {
write!(f, "a valid [command][space/tab][value] string: {err}")
}
}
}
}
let str_val: String = Deserialize::deserialize(deserializer)?; let str_val: String = Deserialize::deserialize(deserializer)?;
let ((command, arg), _) = split::on_first_match(&str_val, &['=']) let ((command, arg), _) = split::on_first_match(&str_val, &['='])
.ok_or(CommandParseError::InvalidArgumentCount(0, "setting".into())) .ok_or(CommandParseError::InvalidArgumentCount(0, "setting".into()))
@ -251,92 +231,90 @@ impl<'de> Deserialize<'de> for Setting {
serde::de::Error::invalid_value(serde::de::Unexpected::Str(&str_val), &err) serde::de::Error::invalid_value(serde::de::Unexpected::Str(&str_val), &err)
})?; })?;
Ok( Ok(
Self::from_command_args(&command.trim(), [arg.trim()].into_iter()).map_err(|err| { Self::from_raw_parts(&command.trim(), &arg.trim()).map_err(|err| {
serde::de::Error::invalid_value( serde::de::Error::invalid_value(serde::de::Unexpected::Str(&str_val), &err)
serde::de::Unexpected::Str(&str_val),
&Expect::Command(err.to_string()),
)
})?, })?,
) )
} }
} }
impl FromStr for Setting { impl FromStr for Setting {
type Err = ParseError; type Err = CommandParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
trace!("[from_str] beginning to parse [{s}] as setting"); debug!("beginning to parse [{s}] as setting");
let ((command, arg), _) = let ((command, arg), _) = split::on_first_match(s, &['\t', ' '])
split::on_first_match(s, &['\t', ' ']).ok_or(ParseError::InvalidValue { .ok_or(CommandParseError::InvalidArgumentCount(0, "setting".into()))?;
value: s.to_string(),
expected: "[setting] [value]",
})?;
Self::from_command_args(&command.trim(), [arg].into_iter()) Self::from_raw_parts(&command.trim(), &arg.trim())
} }
} }
impl FromCommandArgs for Setting { impl Setting {
fn from_command_args<S: Into<String>, I: Iterator<Item = S>>( pub fn from_raw_parts(command: &str, arg: &str) -> Result<Self, CommandParseError> {
cmd: &str, let command = Self::iter()
args: I, .find(|cmd| cmd.to_string() == command)
) -> Result<Self, ParseError> { .ok_or(CommandParseError::UnknownCommand(command.to_string()))?;
let mut command = Self::iter()
.find(|s| s.to_string() == cmd)
.ok_or(ParseError::InvalidCommand(cmd.to_string()))?;
let mut parser = ArgParser::from_strings(args.map(|i| i.into())); let args = [arg.to_string()];
gen_parse!(command, args);
match command.borrow_mut() { match command {
Setting::Verbose(arg) Setting::Verbose(_) => parse!(FromStr => Verbose),
| Setting::TabbedMax(arg) Setting::TabbedMax(_) => parse!(FromStr => TabbedMax),
| Setting::GaplessGrid(arg) Setting::GaplessGrid(_) => parse!(FromStr => GaplessGrid),
| Setting::RaiseOnClick(arg) Setting::RaiseOnClick(_) => parse!(FromStr => RaiseOnClick),
| Setting::RaiseOnFocus(arg) Setting::RaiseOnFocus(_) => parse!(FromStr => RaiseOnFocus),
| Setting::AutoDetectPanels(arg) Setting::AutoDetectPanels(_) => parse!(FromStr => AutoDetectPanels),
| Setting::FocusFollowsMouse(arg) Setting::FocusFollowsMouse(_) => parse!(FromStr => FocusFollowsMouse),
| Setting::HideCoveredWindows(arg) Setting::HideCoveredWindows(_) => parse!(FromStr => HideCoveredWindows),
| Setting::AutoDetectMonitors(arg) Setting::AutoDetectMonitors(_) => parse!(FromStr => AutoDetectMonitors),
| Setting::FrameBgTransparent(arg) Setting::FrameBgTransparent(_) => parse!(FromStr => FrameBgTransparent),
| Setting::SwapMonitorsToGetTag(arg) Setting::SwapMonitorsToGetTag(_) => parse!(FromStr => SwapMonitorsToGetTag),
| Setting::UpdateDraggedClients(arg) Setting::UpdateDraggedClients(_) => parse!(FromStr => UpdateDraggedClients),
| Setting::FocusStealingPrevention(arg) Setting::FocusStealingPrevention(_) => parse!(FromStr => FocusStealingPrevention),
| Setting::RaiseOnFocusTemporarily(arg) Setting::RaiseOnFocusTemporarily(_) => parse!(FromStr => RaiseOnFocusTemporarily),
| Setting::SmartWindowSurroundings(arg) Setting::SmartWindowSurroundings(_) => parse!(FromStr => SmartWindowSurroundings),
| Setting::DefaultDirectionExternalOnly(arg) Setting::DefaultDirectionExternalOnly(_) => {
| Setting::FocusCrossesMonitorBoundaries(arg) => *arg = parser.next_from_str(cmd)?, parse!(FromStr => DefaultDirectionExternalOnly)
Setting::FrameBgActiveColor(arg)
| Setting::FrameBgNormalColor(arg)
| Setting::FrameBorderInnerColor(arg)
| Setting::FrameBorderActiveColor(arg)
| Setting::FrameBorderNormalColor(arg)
| Setting::WindowBorderInnerColor(arg)
| Setting::WindowBorderActiveColor(arg)
| Setting::WindowBorderNormalColor(arg)
| Setting::WindowBorderUrgentColor(arg) => *arg = parser.next_from_str(cmd)?,
Setting::DefaultFrameLayout(arg) => *arg = parser.next_from_str(cmd)?,
Setting::ShowFrameDecorations(arg) => *arg = parser.next_from_str(cmd)?,
Setting::SmartFrameSurroundings(arg) => *arg = parser.next_from_str(cmd)?,
Setting::MonitorsLocked(arg)
| Setting::FrameActiveOpacity(arg)
| Setting::FrameNormalOpacity(arg) => *arg = parser.next_from_str(cmd)?,
Setting::SnapGap(arg)
| Setting::FrameGap(arg)
| Setting::WindowGap(arg)
| Setting::SnapDistance(arg)
| Setting::FramePadding(arg)
| Setting::MouseRecenterGap(arg)
| Setting::FrameBorderWidth(arg)
| Setting::WindowBorderWidth(arg)
| Setting::FrameTransparentWidth(arg)
| Setting::FrameBorderInnerWidth(arg)
| Setting::WindowBorderInnerWidth(arg)
| Setting::PseudotileCenterThreshold(arg) => *arg = parser.next_from_str(cmd)?,
Setting::Wmname(arg) | Setting::Ellipsis(arg) | Setting::TreeStyle(arg) => {
*arg = parser.must_string(cmd)?
} }
Setting::FocusCrossesMonitorBoundaries(_) => {
parse!(FromStr => FocusCrossesMonitorBoundaries)
}
Setting::FrameBgActiveColor(_) => parse!(FromStr => FrameBgActiveColor),
Setting::FrameBgNormalColor(_) => parse!(FromStr => FrameBgNormalColor),
Setting::DefaultFrameLayout(_) => parse!(FromStr => DefaultFrameLayout),
Setting::ShowFrameDecorations(_) => parse!(FromStr => ShowFrameDecorations),
Setting::FrameBorderInnerColor(_) => parse!(FromStr => FrameBorderInnerColor),
Setting::FrameBorderActiveColor(_) => parse!(FromStr => FrameBorderActiveColor),
Setting::FrameBorderNormalColor(_) => parse!(FromStr => FrameBorderNormalColor),
Setting::SmartFrameSurroundings(_) => parse!(FromStr => SmartFrameSurroundings),
Setting::WindowBorderInnerColor(_) => parse!(FromStr => WindowBorderInnerColor),
Setting::WindowBorderActiveColor(_) => parse!(FromStr => WindowBorderActiveColor),
Setting::WindowBorderNormalColor(_) => parse!(FromStr => WindowBorderNormalColor),
Setting::WindowBorderUrgentColor(_) => parse!(FromStr => WindowBorderUrgentColor),
Setting::SnapGap(_) => parse!(FromStr => SnapGap),
Setting::FrameGap(_) => parse!(FromStr => FrameGap),
Setting::WindowGap(_) => parse!(FromStr => WindowGap),
Setting::SnapDistance(_) => parse!(FromStr => SnapDistance),
Setting::FramePadding(_) => parse!(FromStr => FramePadding),
Setting::MonitorsLocked(_) => parse!(FromStr => MonitorsLocked),
Setting::MouseRecenterGap(_) => parse!(FromStr => MouseRecenterGap),
Setting::FrameBorderWidth(_) => parse!(FromStr => FrameBorderWidth),
Setting::WindowBorderWidth(_) => parse!(FromStr => WindowBorderWidth),
Setting::FrameActiveOpacity(_) => parse!(FromStr => FrameActiveOpacity),
Setting::FrameNormalOpacity(_) => parse!(FromStr => FrameNormalOpacity),
Setting::FrameTransparentWidth(_) => parse!(FromStr => FrameTransparentWidth),
Setting::FrameBorderInnerWidth(_) => parse!(FromStr => FrameBorderInnerWidth),
Setting::WindowBorderInnerWidth(_) => parse!(FromStr => WindowBorderInnerWidth),
Setting::PseudotileCenterThreshold(_) => parse!(FromStr => PseudotileCenterThreshold),
Setting::Wmname(_) => parse!(String => Wmname),
Setting::Ellipsis(_) => parse!(String => Ellipsis),
Setting::TreeStyle(_) => parse!(String => TreeStyle),
} }
Ok(command)
} }
} }

View File

@ -6,7 +6,7 @@ use std::{
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use super::parser::ParseError; use super::StringParseError;
pub struct TagStatus { pub struct TagStatus {
name: String, name: String,
@ -25,13 +25,13 @@ impl TagStatus {
} }
impl FromStr for TagStatus { impl FromStr for TagStatus {
type Err = ParseError; type Err = StringParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut parts = s.chars(); let mut parts = s.chars();
let state = parts let state = parts
.next() .next()
.ok_or(ParseError::InvalidCommand(s.to_string()))? .ok_or(StringParseError::UnknownValue)?
.try_into()?; .try_into()?;
let name = parts.collect(); let name = parts.collect();
@ -56,24 +56,24 @@ pub enum TagState {
} }
impl TryFrom<char> for TagState { impl TryFrom<char> for TagState {
type Error = ParseError; type Error = StringParseError;
fn try_from(value: char) -> Result<Self, Self::Error> { fn try_from(value: char) -> Result<Self, Self::Error> {
Self::iter() Self::iter()
.into_iter() .into_iter()
.find(|i| char::from(i) == value) .find(|i| char::from(i) == value)
.ok_or(ParseError::InvalidCommand(value.to_string())) .ok_or(StringParseError::UnknownValue)
} }
} }
impl FromStr for TagState { impl FromStr for TagState {
type Err = ParseError; type Err = StringParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::iter() Self::iter()
.into_iter() .into_iter()
.find(|i| i.to_string() == s) .find(|i| i.to_string() == s)
.ok_or(ParseError::InvalidCommand(s.to_string())) .ok_or(StringParseError::UnknownValue)
} }
} }

View File

@ -1,23 +1,36 @@
use std::borrow::BorrowMut; use std::{borrow::BorrowMut, num::ParseIntError};
use serde::{de::Expected, Deserialize, Serialize}; use serde::{de::Expected, Deserialize, Serialize};
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use thiserror::Error;
use crate::split; use crate::split;
use super::{ use super::{
attribute::Attribute, attribute::Attribute,
color::Color, color::{self, Color},
parser::{ArgParser, FromCommandArgs, ParseError}, 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 { macro_rules! theme_attr {
($($name:tt($ty:tt),)*) => { ($($name:tt($value:tt),)*) => {
#[derive(Debug, Clone, strum::Display, strum::EnumIter)] #[derive(Debug, Clone, strum::Display, strum::EnumIter)]
#[strum(serialize_all = "snake_case")] #[strum(serialize_all = "snake_case")]
pub enum ThemeAttr { pub enum ThemeAttr {
$( $(
$name($ty), $name($value),
)* )*
} }
@ -36,30 +49,26 @@ macro_rules! theme_attr {
fn from(value: ThemeAttr) -> Self { fn from(value: ThemeAttr) -> Self {
match value { match value {
$( $(
ThemeAttr::$name(val) => theme_attr!(Attr $ty)(val), ThemeAttr::$name(val) => theme_attr!(Attr $value)(val),
)* )*
} }
} }
} }
impl FromCommandArgs for ThemeAttr { impl ThemeAttr {
fn from_command_args<S: Into<String>, I: Iterator<Item = S>>( pub fn from_raw_parts(path: &str, value: &str) -> Result<Self, ThemeAttrParseError> {
command: &str,
args: I,
) -> Result<Self, ParseError> {
match ThemeAttr::iter() match ThemeAttr::iter()
.find(|attr| attr.attr_path() == command) .find(|attr| attr.attr_path() == path)
.map(|attr| -> Result<Self, ParseError> { .map(|attr| -> Result<Self, ThemeAttrParseError> {
let mut attr = attr.clone(); let mut attr = attr.clone();
let mut parser = ArgParser::from_strings(args.map(|a| a.into()));
match attr.borrow_mut() { match attr.borrow_mut() {
$( $(
ThemeAttr::$name(val) => *val = parser.next_from_str(concat!("theme_attr(", stringify!($name), ")"))?, ThemeAttr::$name(val) => *val = theme_attr!(Parse value: $value),
)+ )+
}; };
Ok(attr) Ok(attr)
}) })
.map(|res| if let Err(err) = res {panic!("::: {err}")} else {res}) .map(|res| if let Err(err) = res {panic!("::: {err}")} else {res})
.ok_or(ParseError::InvalidCommand(command.to_string())) .ok_or(ThemeAttrParseError::PathNotFound)
{ {
Ok(res) => res, Ok(res) => res,
Err(err) => return Err(err), Err(err) => return Err(err),
@ -82,6 +91,24 @@ macro_rules! theme_attr {
(Attr i32) => { (Attr i32) => {
Attribute::Int 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!( theme_attr!(
@ -151,13 +178,13 @@ impl<'de> Deserialize<'de> for ThemeAttr {
{ {
pub enum Expect { pub enum Expect {
NoEquals, NoEquals,
ParseError(ParseError), ThemeAttrParseError(ThemeAttrParseError),
} }
impl Expected for Expect { impl Expected for Expect {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self { match self {
Expect::NoEquals => write!(f, "value having an equals sign"), Expect::NoEquals => write!(f, "value having an equals sign"),
Expect::ParseError(err) => write!(f, "parsing error: {err}"), Expect::ThemeAttrParseError(err) => write!(f, "parsing error: {err}"),
} }
} }
} }
@ -171,10 +198,10 @@ impl<'de> Deserialize<'de> for ThemeAttr {
))?; ))?;
Ok( Ok(
Self::from_command_args(&first.trim(), [second.trim()].into_iter()).map_err(|err| { Self::from_raw_parts(&first.trim(), &second.trim()).map_err(|err| {
serde::de::Error::invalid_value( serde::de::Error::invalid_value(
serde::de::Unexpected::Str(&str_val), serde::de::Unexpected::Str(&str_val),
&Expect::ParseError(err), &Expect::ThemeAttrParseError(err),
) )
})?, })?,
) )

View File

@ -4,8 +4,8 @@ use serde::{Deserialize, Serialize};
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use super::{ use super::{
parser::ParseError,
rule::{Condition, RuleOperator}, rule::{Condition, RuleOperator},
StringParseError,
}; };
#[derive(Debug, Clone, Serialize, Deserialize, strum::EnumIter, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, strum::EnumIter, PartialEq)]
@ -21,7 +21,7 @@ pub enum Window {
} }
impl FromStr for Window { impl FromStr for Window {
type Err = ParseError; type Err = StringParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(val) = i32::from_str(s) { if let Ok(val) = i32::from_str(s) {
@ -29,7 +29,7 @@ impl FromStr for Window {
} else { } else {
Window::iter() Window::iter()
.find(|w| w.to_string() == s) .find(|w| w.to_string() == s)
.ok_or(ParseError::ValueMissing) .ok_or(StringParseError::UnknownValue)
} }
} }
} }

View File

@ -1,3 +1,4 @@
#![feature(macro_metavar_expr)]
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use add::Add; use add::Add;

View File

@ -38,37 +38,6 @@ impl<'a> From<&'a String> for SplitArg<'a> {
} }
} }
/// Assuming the input string is in format `({type_name}){path}`,
/// it will return `Some(("{type_name}", "{path}"))`
pub fn parens(s: &str) -> Option<(String, String)> {
let mut chars = s.chars();
match chars.next() {
Some(c) => {
if c != '(' {
return None;
}
}
None => return None,
}
let mut working = Vec::with_capacity(s.len());
let type_name = loop {
if let Some(next) = chars.next() {
if next == ')' {
break working.into_iter().collect::<String>();
}
if !next.is_alphanumeric() {
return None;
}
working.push(next);
} else {
return None;
}
};
Some((type_name, chars.collect()))
}
pub fn tab_or_space<'a, S>(s: S) -> Vec<String> pub fn tab_or_space<'a, S>(s: S) -> Vec<String>
where where
S: Into<SplitArg<'a>>, S: Into<SplitArg<'a>>,