add substitute, foreach, sprintf. improve errors a bit
This commit is contained in:
		
							parent
							
								
									4036ed42f0
								
							
						
					
					
						commit
						22925fc738
					
				|  | @ -3,7 +3,7 @@ use std::{ | ||||||
|     cmp::Ordering, |     cmp::Ordering, | ||||||
|     convert::Infallible, |     convert::Infallible, | ||||||
|     env, |     env, | ||||||
|     fmt::Display, |     fmt::{Debug, Display}, | ||||||
|     fs::{self}, |     fs::{self}, | ||||||
|     io, |     io, | ||||||
|     ops::Deref, |     ops::Deref, | ||||||
|  | @ -33,6 +33,7 @@ use crate::{ | ||||||
|         window::Window, |         window::Window, | ||||||
|         Align, Client, Direction, Index, Operator, Separator, StringParseError, ToggleBool, |         Align, Client, Direction, Index, Operator, Separator, StringParseError, ToggleBool, | ||||||
|     }, |     }, | ||||||
|  |     logerr::UnwrapLog, | ||||||
|     rule, window_types, |     rule, window_types, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | @ -52,8 +53,8 @@ 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 keybind: {0}")] |     #[error("failed parsing keybind [{0}]: [{1}]")] | ||||||
|     KeyParseError(#[from] KeyParseError), |     KeyParseError(String, KeyParseError), | ||||||
|     #[error("failed parsing value from string: {0}")] |     #[error("failed parsing value from string: {0}")] | ||||||
|     StringParseError(#[from] StringParseError), |     StringParseError(#[from] StringParseError), | ||||||
| } | } | ||||||
|  | @ -387,9 +388,12 @@ impl Config { | ||||||
|             name: &str, |             name: &str, | ||||||
|             f: F, |             f: F, | ||||||
|             default: T, |             default: T, | ||||||
|         ) -> T { |         ) -> T | ||||||
|  |         where | ||||||
|  |             T: Debug, | ||||||
|  |         { | ||||||
|             match Client::new().get_attr(name.to_string()) { |             match Client::new().get_attr(name.to_string()) { | ||||||
|                 Ok(setting) => f(&setting.to_string()).unwrap_or(default), |                 Ok(setting) => f(&setting.to_string()).unwrap_or_log(default), | ||||||
|                 Err(_) => default, |                 Err(_) => default, | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | @ -401,11 +405,11 @@ impl Config { | ||||||
|                 |f| -> Result<_, Infallible> { Ok(f.to_string()) }, |                 |f| -> Result<_, Infallible> { Ok(f.to_string()) }, | ||||||
|                 default.font, |                 default.font, | ||||||
|             ), |             ), | ||||||
|             mod_key: setting(Self::MOD_KEY, Key::from_str, default.mod_key), |  | ||||||
|             font_bold: client |             font_bold: client | ||||||
|                 .get_attr(ThemeAttr::TitleFont(String::new()).attr_path()) |                 .get_attr(ThemeAttr::TitleFont(String::new()).attr_path()) | ||||||
|                 .map(|a| a.to_string()) |                 .map(|a| a.to_string()) | ||||||
|                 .unwrap_or(default.font_bold), |                 .unwrap_or_log(default.font_bold), | ||||||
|  |             mod_key: setting(Self::MOD_KEY, Key::from_str, default.mod_key), | ||||||
|             services: setting( |             services: setting( | ||||||
|                 Self::SERVICES, |                 Self::SERVICES, | ||||||
|                 |v| serde_json::de::from_str(v), |                 |v| serde_json::de::from_str(v), | ||||||
|  | @ -425,14 +429,14 @@ impl Config { | ||||||
|                                 &client |                                 &client | ||||||
|                                     .get_attr(attr_path.clone()) |                                     .get_attr(attr_path.clone()) | ||||||
|                                     .map(|t| t.to_string()) |                                     .map(|t| t.to_string()) | ||||||
|                                     .unwrap_or(default.to_string()), |                                     .unwrap_or_log(default.to_string()), | ||||||
|                             ) |                             ) | ||||||
|                             .unwrap_or(default.clone()) |                             .unwrap_or_log(default.clone()) | ||||||
|                         }) |                         }) | ||||||
|                         .collect() |                         .collect() | ||||||
|                 })(), |                 })(), | ||||||
|             }, |             }, | ||||||
|             keybinds: Self::active_keybinds(true).unwrap_or(default.keybinds), |             keybinds: Self::active_keybinds(true).unwrap_or_log(default.keybinds), | ||||||
|             tags: (|| -> Result<Vec<Tag>, _> { |             tags: (|| -> Result<Vec<Tag>, _> { | ||||||
|                 Result::<_, ConfigError>::Ok({ |                 Result::<_, ConfigError>::Ok({ | ||||||
|                     let mut tags = client |                     let mut tags = client | ||||||
|  | @ -451,7 +455,7 @@ impl Config { | ||||||
|                     tags |                     tags | ||||||
|                 }) |                 }) | ||||||
|             })() |             })() | ||||||
|             .unwrap_or(default.tags), |             .unwrap_or_log(default.tags), | ||||||
|             rules: (|| -> Result<Vec<Rule>, ConfigError> { |             rules: (|| -> Result<Vec<Rule>, ConfigError> { | ||||||
|                 Ok( |                 Ok( | ||||||
|                     String::from_utf8(client.execute(HlwmCommand::ListRules)?.stdout)? |                     String::from_utf8(client.execute(HlwmCommand::ListRules)?.stdout)? | ||||||
|  | @ -462,7 +466,7 @@ impl Config { | ||||||
|                         .collect::<Result<_, _>>()?, |                         .collect::<Result<_, _>>()?, | ||||||
|                 ) |                 ) | ||||||
|             })() |             })() | ||||||
|             .unwrap_or(default.rules), |             .unwrap_or_log(default.rules), | ||||||
|             settings: (|| -> Result<Vec<_>, CommandError> { |             settings: (|| -> Result<Vec<_>, CommandError> { | ||||||
|                 default |                 default | ||||||
|                     .settings |                     .settings | ||||||
|  | @ -471,7 +475,7 @@ impl Config { | ||||||
|                     .map(|s| Ok(client.get_setting(s.into())?)) |                     .map(|s| Ok(client.get_setting(s.into())?)) | ||||||
|                     .collect::<Result<Vec<_>, CommandError>>() |                     .collect::<Result<Vec<_>, CommandError>>() | ||||||
|             })() |             })() | ||||||
|             .unwrap_or(default.settings), |             .unwrap_or_log(default.settings), | ||||||
|             ..default |             ..default | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | @ -501,7 +505,10 @@ impl Config { | ||||||
|                         None => false, |                         None => false, | ||||||
|                     } |                     } | ||||||
|             }) |             }) | ||||||
|             .map(|row: &str| Keybind::from_str(row).map_err(|err| err.into())) |             .map(|row: &str| { | ||||||
|  |                 Keybind::from_str(row) | ||||||
|  |                     .map_err(|err| ConfigError::KeyParseError(row.to_string(), err)) | ||||||
|  |             }) | ||||||
|             .collect::<Result<Vec<_>, ConfigError>>() |             .collect::<Result<Vec<_>, ConfigError>>() | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -602,7 +609,7 @@ impl Inclusive<10> { | ||||||
| 
 | 
 | ||||||
| impl<const MAX: u8> Display for Inclusive<MAX> { | impl<const MAX: u8> Display for Inclusive<MAX> { | ||||||
|     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||||||
|         self.0.fmt(f) |         write!(f, "{}", self.0.to_string()) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -13,7 +13,7 @@ use super::{ | ||||||
|     StringParseError, |     StringParseError, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, Error)] | #[derive(Debug, Clone, Error)] | ||||||
| pub enum AttributeError { | pub enum AttributeError { | ||||||
|     #[error("error parsing integer value: {0:?}")] |     #[error("error parsing integer value: {0:?}")] | ||||||
|     ParseIntError(#[from] ParseIntError), |     ParseIntError(#[from] ParseIntError), | ||||||
|  | @ -60,7 +60,7 @@ impl AttributeOption { | ||||||
|             "color" => Ok(Self::Color(None)), |             "color" => Ok(Self::Color(None)), | ||||||
|             "windowid" => Ok(Self::WindowID(None)), |             "windowid" => Ok(Self::WindowID(None)), | ||||||
|             "rectangle" => Ok(Self::Rectangle(None)), |             "rectangle" => Ok(Self::Rectangle(None)), | ||||||
|             "string" | "names" | "regex" => Ok(Self::String(None)), |             "string" | "names" | "regex" | "font" => Ok(Self::String(None)), | ||||||
|             _ => Err(AttributeError::UnknownType(type_string.to_string())), |             _ => Err(AttributeError::UnknownType(type_string.to_string())), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | @ -179,7 +179,9 @@ impl Attribute { | ||||||
|             }, |             }, | ||||||
|             "color" => Ok(Attribute::Color(Color::from_str(value_string)?)), |             "color" => Ok(Attribute::Color(Color::from_str(value_string)?)), | ||||||
|             "int" => Ok(Attribute::Int(value_string.parse()?)), |             "int" => Ok(Attribute::Int(value_string.parse()?)), | ||||||
|             "string" | "names" | "regex" => Ok(Attribute::String(value_string.to_string())), |             "string" | "names" | "regex" | "font" => { | ||||||
|  |                 Ok(Attribute::String(value_string.to_string())) | ||||||
|  |             } | ||||||
|             "uint" => Ok(Attribute::Uint(value_string.parse()?)), |             "uint" => Ok(Attribute::Uint(value_string.parse()?)), | ||||||
|             "rectangle" => { |             "rectangle" => { | ||||||
|                 let parts = value_string.split('x').collect::<Vec<_>>(); |                 let parts = value_string.split('x').collect::<Vec<_>>(); | ||||||
|  |  | ||||||
|  | @ -95,7 +95,14 @@ pub enum HlwmCommand { | ||||||
|     Get(SettingName), |     Get(SettingName), | ||||||
|     /// Emits a custom `hook` to all idling herbstclients
 |     /// Emits a custom `hook` to all idling herbstclients
 | ||||||
|     EmitHook(Hook), |     EmitHook(Hook), | ||||||
|     Substitute, |     /// Replaces all exact occurrences of `identifier` in `command` and its `args` by the value of the `attribute`.
 | ||||||
|  |     /// Note that the `command` also is replaced by the attribute value if it equals `identifier`.
 | ||||||
|  |     /// The replaced command with its arguments then is executed
 | ||||||
|  |     Substitute { | ||||||
|  |         identifier: String, | ||||||
|  |         attribute_path: String, | ||||||
|  |         command: Box<HlwmCommand>, | ||||||
|  |     }, | ||||||
|     Keybind(Keybind), |     Keybind(Keybind), | ||||||
|     Keyunbind(KeyUnbind), |     Keyunbind(KeyUnbind), | ||||||
|     Mousebind(Mousebind), |     Mousebind(Mousebind), | ||||||
|  | @ -178,6 +185,20 @@ pub enum HlwmCommand { | ||||||
|         monitor: Monitor, |         monitor: Monitor, | ||||||
|         pad: Pad, |         pad: Pad, | ||||||
|     }, |     }, | ||||||
|  |     /// For each child of the given `object` the `command` is called with its `args`, where the `identifier` is replaced by the path of the child
 | ||||||
|  |     #[strum(serialize = "foreach")] | ||||||
|  |     ForEach { | ||||||
|  |         /// do not print duplicates (some objects can be reached via multiple paths, such as `clients.focus`)
 | ||||||
|  |         unique: bool, | ||||||
|  |         /// print `object` and all its children of arbitrary depth in breadth-first search order. This implicitly activates `unique`
 | ||||||
|  |         recursive: bool, | ||||||
|  |         /// consider children whose name match the specified REGEX
 | ||||||
|  |         filter_name: Option<String>, | ||||||
|  |         identifier: String, | ||||||
|  |         object: String, | ||||||
|  |     }, | ||||||
|  |     #[strum(serialize = "sprintf")] | ||||||
|  |     Sprintf(Vec<String>), | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl FromStr for Box<HlwmCommand> { | impl FromStr for Box<HlwmCommand> { | ||||||
|  | @ -310,7 +331,6 @@ impl HlwmCommand { | ||||||
|             | HlwmCommand::ListRules |             | HlwmCommand::ListRules | ||||||
|             | HlwmCommand::False |             | HlwmCommand::False | ||||||
|             | HlwmCommand::True |             | HlwmCommand::True | ||||||
|             | HlwmCommand::Substitute |  | ||||||
|             | HlwmCommand::Mouseunbind |             | HlwmCommand::Mouseunbind | ||||||
|             | HlwmCommand::ListMonitors |             | HlwmCommand::ListMonitors | ||||||
|             | HlwmCommand::ListKeybinds => Ok::<_, CommandParseError>(command.clone()), |             | HlwmCommand::ListKeybinds => Ok::<_, CommandParseError>(command.clone()), | ||||||
|  | @ -504,6 +524,55 @@ impl HlwmCommand { | ||||||
|             HlwmCommand::Pad { monitor: _, pad: _ } => { |             HlwmCommand::Pad { monitor: _, pad: _ } => { | ||||||
|                 parse!(monitor: FromStr, pad: FromStrAll => Pad) |                 parse!(monitor: FromStr, pad: FromStrAll => Pad) | ||||||
|             } |             } | ||||||
|  |             HlwmCommand::Substitute { | ||||||
|  |                 identifier: _, | ||||||
|  |                 attribute_path: _, | ||||||
|  |                 command: _, | ||||||
|  |             } => { | ||||||
|  |                 parse!(identifier: String, attribute_path: String, command: FromStrAll => Substitute) | ||||||
|  |             } | ||||||
|  |             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, | ||||||
|  |                     recursive, | ||||||
|  |                     filter_name, | ||||||
|  |                     identifier, | ||||||
|  |                     object, | ||||||
|  |                 }) | ||||||
|  |             } | ||||||
|  |             HlwmCommand::Sprintf(_) => parse!([Vec<String>] => Sprintf), | ||||||
|         }?; |         }?; | ||||||
| 
 | 
 | ||||||
|         assert_eq!(command.to_string(), parsed_command.to_string()); |         assert_eq!(command.to_string(), parsed_command.to_string()); | ||||||
|  | @ -528,10 +597,10 @@ impl HlwmCommand { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, Error)] | #[derive(Debug, Clone, Error)] | ||||||
| pub enum CommandError { | pub enum CommandError { | ||||||
|     #[error("IO error")] |     #[error("IO error")] | ||||||
|     IoError(#[from] io::Error), |     IoError(String), | ||||||
|     #[error("exited with status code {0}")] |     #[error("exited with status code {0}")] | ||||||
|     StatusCode(i32, Option<String>), |     StatusCode(i32, Option<String>), | ||||||
|     #[error("killed by signal ({signal}); core dumped: {core_dumped}")] |     #[error("killed by signal ({signal}); core dumped: {core_dumped}")] | ||||||
|  | @ -550,6 +619,12 @@ pub enum CommandError { | ||||||
|     Empty, |     Empty, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | impl From<io::Error> for CommandError { | ||||||
|  |     fn from(value: io::Error) -> Self { | ||||||
|  |         Self::IoError(value.to_string()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| impl ToCommandString for HlwmCommand { | impl ToCommandString for HlwmCommand { | ||||||
|     fn to_command_string(&self) -> String { |     fn to_command_string(&self) -> String { | ||||||
|         let cmd_string = match self { |         let cmd_string = match self { | ||||||
|  | @ -561,7 +636,6 @@ impl ToCommandString for HlwmCommand { | ||||||
|             | HlwmCommand::Reload |             | HlwmCommand::Reload | ||||||
|             | HlwmCommand::Version |             | HlwmCommand::Version | ||||||
|             | HlwmCommand::ListRules |             | HlwmCommand::ListRules | ||||||
|             | HlwmCommand::Substitute |  | ||||||
|             | HlwmCommand::Mouseunbind |             | HlwmCommand::Mouseunbind | ||||||
|             | HlwmCommand::True |             | HlwmCommand::True | ||||||
|             | HlwmCommand::False |             | HlwmCommand::False | ||||||
|  | @ -724,6 +798,41 @@ impl ToCommandString for HlwmCommand { | ||||||
|                 parts.join("\t") |                 parts.join("\t") | ||||||
|             } |             } | ||||||
|             HlwmCommand::Pad { monitor, pad } => format!("{self}\t{monitor}\t{pad}"), |             HlwmCommand::Pad { monitor, pad } => format!("{self}\t{monitor}\t{pad}"), | ||||||
|  |             HlwmCommand::Substitute { | ||||||
|  |                 identifier, | ||||||
|  |                 attribute_path, | ||||||
|  |                 command, | ||||||
|  |             } => format!( | ||||||
|  |                 "{self}\t{identifier}\t{attribute_path}\t{}", | ||||||
|  |                 command.to_command_string() | ||||||
|  |             ), | ||||||
|  |             HlwmCommand::ForEach { | ||||||
|  |                 unique, | ||||||
|  |                 recursive, | ||||||
|  |                 filter_name, | ||||||
|  |                 identifier, | ||||||
|  |                 object, | ||||||
|  |             } => { | ||||||
|  |                 let mut parts = Vec::with_capacity(6); | ||||||
|  |                 parts.push(self.to_string()); | ||||||
|  |                 parts.push(identifier.to_string()); | ||||||
|  |                 parts.push(object.to_string()); | ||||||
|  |                 if let Some(filter_name) = filter_name { | ||||||
|  |                     parts.push(format!("--filter-name={filter_name}")); | ||||||
|  |                 } | ||||||
|  |                 if *unique { | ||||||
|  |                     parts.push("--unique".into()); | ||||||
|  |                 } | ||||||
|  |                 if *recursive { | ||||||
|  |                     parts.push("--recursive".into()); | ||||||
|  |                 } | ||||||
|  |                 parts.join("\t") | ||||||
|  |             } | ||||||
|  |             HlwmCommand::Sprintf(args) => [self.to_string()] | ||||||
|  |                 .into_iter() | ||||||
|  |                 .chain(args.into_iter().cloned()) | ||||||
|  |                 .collect::<Vec<_>>() | ||||||
|  |                 .join("\t"), | ||||||
|         }; |         }; | ||||||
|         if let Some(s) = cmd_string.strip_suffix('\t') { |         if let Some(s) = cmd_string.strip_suffix('\t') { | ||||||
|             return s.to_string(); |             return s.to_string(); | ||||||
|  | @ -767,7 +876,6 @@ mod test { | ||||||
|                 | HlwmCommand::Unlock |                 | HlwmCommand::Unlock | ||||||
|                 | HlwmCommand::True |                 | HlwmCommand::True | ||||||
|                 | HlwmCommand::False |                 | HlwmCommand::False | ||||||
|                 | HlwmCommand::Substitute |  | ||||||
|                 | HlwmCommand::Mouseunbind => (cmd.clone(), cmd.to_string()), |                 | HlwmCommand::Mouseunbind => (cmd.clone(), cmd.to_string()), | ||||||
|                 HlwmCommand::Echo(_) => ( |                 HlwmCommand::Echo(_) => ( | ||||||
|                     HlwmCommand::Echo(vec!["Hello world!".into()]), |                     HlwmCommand::Echo(vec!["Hello world!".into()]), | ||||||
|  | @ -1004,6 +1112,43 @@ mod test { | ||||||
|                     }, |                     }, | ||||||
|                     "pad\t1\t2\t3\t4\t5".into(), |                     "pad\t1\t2\t3\t4\t5".into(), | ||||||
|                 ), |                 ), | ||||||
|  |                 HlwmCommand::Substitute { | ||||||
|  |                     identifier: _, | ||||||
|  |                     attribute_path: _, | ||||||
|  |                     command: _, | ||||||
|  |                 } => ( | ||||||
|  |                     HlwmCommand::Substitute { | ||||||
|  |                         identifier: "MYTITLE".into(), | ||||||
|  |                         attribute_path: "clients.focus.title".into(), | ||||||
|  |                         command: Box::new(HlwmCommand::Echo(vec!["MYTITLE".to_string()])), | ||||||
|  |                     }, | ||||||
|  |                     "substitute\tMYTITLE\tclients.focus.title\techo\tMYTITLE".into(), | ||||||
|  |                 ), | ||||||
|  |                 HlwmCommand::ForEach { | ||||||
|  |                     unique: _, | ||||||
|  |                     recursive: _, | ||||||
|  |                     filter_name: _, | ||||||
|  |                     identifier: _, | ||||||
|  |                     object: _, | ||||||
|  |                 } => ( | ||||||
|  |                     HlwmCommand::ForEach { | ||||||
|  |                         unique: true, | ||||||
|  |                         recursive: true, | ||||||
|  |                         filter_name: Some(".+".into()), | ||||||
|  |                         identifier: "CLIENT".into(), | ||||||
|  |                         object: "clients.".into(), | ||||||
|  |                     }, | ||||||
|  |                     "foreach\tCLIENT\tclients.\t--filter-name=.+\t--unique\t--recursive".into(), | ||||||
|  |                 ), | ||||||
|  |                 HlwmCommand::Sprintf(_) => ( | ||||||
|  |                     HlwmCommand::Sprintf( | ||||||
|  |                         ["X", "tag=%s", "tags.focus.name", "rule", "once", "X"] | ||||||
|  |                             .into_iter() | ||||||
|  |                             .map(|s| s.to_string()) | ||||||
|  |                             .collect(), | ||||||
|  |                     ), | ||||||
|  |                     "sprintf\tX\ttag=%s\ttags.focus.name\trule\tonce\tX".into(), | ||||||
|  |                 ), | ||||||
|             }) |             }) | ||||||
|             .collect::<Vec<_>>(); |             .collect::<Vec<_>>(); | ||||||
|         for (command, expected_string) in commands { |         for (command, expected_string) in commands { | ||||||
|  |  | ||||||
|  | @ -250,7 +250,7 @@ impl ToCommandString for MousebindAction { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, Error)] | #[derive(Debug, Clone, Error)] | ||||||
| pub enum KeyParseError { | pub enum KeyParseError { | ||||||
|     #[error("value too short (expected >= 2 parts, got {0} parts)")] |     #[error("value too short (expected >= 2 parts, got {0} parts)")] | ||||||
|     TooShort(usize), |     TooShort(usize), | ||||||
|  |  | ||||||
|  | @ -101,10 +101,14 @@ impl Client { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn get_setting(&self, setting: SettingName) -> Result<Setting, CommandError> { |     pub fn get_setting(&self, setting: SettingName) -> Result<Setting, CommandError> { | ||||||
|         Ok(Setting::from_str(&String::from_utf8( |         Ok(Setting::from_str(&format!( | ||||||
|             self.execute(HlwmCommand::Get(setting))?.stdout, |             "{setting}\t{}", | ||||||
|         )?) |             String::from_utf8(self.execute(HlwmCommand::Get(setting))?.stdout,)? | ||||||
|         .map_err(|_| StringParseError::UnknownValue)?) |         )) | ||||||
|  |         .map_err(|err| { | ||||||
|  |             error!("failed getting setting [{setting}]: {err}"); | ||||||
|  |             StringParseError::UnknownValue | ||||||
|  |         })?) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn query(&self, command: HlwmCommand) -> Result<Vec<String>, CommandError> { |     pub fn query(&self, command: HlwmCommand) -> Result<Vec<String>, CommandError> { | ||||||
|  | @ -136,7 +140,7 @@ pub trait ToCommandString { | ||||||
|     fn to_command_string(&self) -> String; |     fn to_command_string(&self) -> String; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, Error)] | #[derive(Debug, Clone, Error)] | ||||||
| pub enum StringParseError { | pub enum StringParseError { | ||||||
|     #[error("unknown value")] |     #[error("unknown value")] | ||||||
|     UnknownValue, |     UnknownValue, | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| use std::str::FromStr; | use std::str::FromStr; | ||||||
| 
 | 
 | ||||||
|  | use log::debug; | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
| 
 | 
 | ||||||
| use crate::{gen_parse, hlwm::command::CommandParseError}; | use crate::{gen_parse, hlwm::command::CommandParseError}; | ||||||
|  | @ -246,6 +247,7 @@ impl FromStr for Setting { | ||||||
|     type Err = CommandParseError; |     type Err = CommandParseError; | ||||||
| 
 | 
 | ||||||
|     fn from_str(s: &str) -> Result<Self, Self::Err> { |     fn from_str(s: &str) -> Result<Self, Self::Err> { | ||||||
|  |         debug!("beginning to parse [{s}] as setting"); | ||||||
|         let ((command, arg), _) = split::on_first_match(s, &['\t', ' ']) |         let ((command, arg), _) = split::on_first_match(s, &['\t', ' ']) | ||||||
|             .ok_or(CommandParseError::InvalidArgumentCount(0, "setting".into()))?; |             .ok_or(CommandParseError::InvalidArgumentCount(0, "setting".into()))?; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,31 @@ | ||||||
|  | use std::{error::Error, fmt::Debug}; | ||||||
|  | 
 | ||||||
|  | use log::{debug, error}; | ||||||
|  | 
 | ||||||
|  | pub trait UnwrapLog { | ||||||
|  |     type Target; | ||||||
|  | 
 | ||||||
|  |     fn unwrap_or_log(self, default: Self::Target) -> Self::Target; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<T, E> UnwrapLog for Result<T, E> | ||||||
|  | where | ||||||
|  |     T: Debug, | ||||||
|  |     E: Error, | ||||||
|  | { | ||||||
|  |     type Target = T; | ||||||
|  | 
 | ||||||
|  |     fn unwrap_or_log(self, default: Self::Target) -> Self::Target { | ||||||
|  |         match self { | ||||||
|  |             Ok(val) => val, | ||||||
|  |             Err(err) => { | ||||||
|  |                 error!( | ||||||
|  |                     "[{}] unwrap_or_log got error: {err}", | ||||||
|  |                     std::any::type_name::<Self::Target>() | ||||||
|  |                 ); | ||||||
|  |                 debug!("^ defaulting to {default:#?}"); | ||||||
|  |                 default | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -8,6 +8,7 @@ use log::{error, info}; | ||||||
| pub mod cmd; | pub mod cmd; | ||||||
| mod config; | mod config; | ||||||
| mod hlwm; | mod hlwm; | ||||||
|  | pub mod logerr; | ||||||
| mod panel; | mod panel; | ||||||
| 
 | 
 | ||||||
| #[derive(Parser, Debug, Clone)] | #[derive(Parser, Debug, Clone)] | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue