From 8a43a02b6950455aedbbdbee737bee1654aa91ef Mon Sep 17 00:00:00 2001 From: Ben Bridle Date: Mon, 3 Mar 2025 20:51:01 +1300 Subject: Implement error reporting The library has been redesigned so that all queries can be entered before any values are parsed. This allows all errors unrelated to value parsing to be displayed as a batch. --- src/query.rs | 236 ++++++++++++++++++++++++++++++----------------------------- 1 file changed, 119 insertions(+), 117 deletions(-) (limited to 'src/query.rs') diff --git a/src/query.rs b/src/query.rs index 4109488..5a9d4ae 100644 --- a/src/query.rs +++ b/src/query.rs @@ -1,9 +1,5 @@ use crate::*; -use log::fatal; - -use std::path::PathBuf; - pub struct NamedSwitchQuery<'a> { /// The borrowed switchboard instance. @@ -12,31 +8,99 @@ pub struct NamedSwitchQuery<'a> { pub name: String, /// Short switch name. pub short: Option, - /// The default value if the switch hasn't been provided. - pub default: Option, /// The default value if the switch has been provided with no value. pub quick: Option, + /// Documentation string. + pub doc: Option, + /// True if this query has already been committed. + pub committed: bool, } impl NamedSwitchQuery<'_> { + pub fn doc(mut self, doc: &str) -> Self { + self.doc = Some(doc.to_string()); + self + } + pub fn short(mut self, short: char) -> Self { if !short.is_ascii_alphanumeric() { - panic!("Invalid short name for argument: {short:?}"); + fatal!("Invalid short name for argument: {short:?}"); } self.short = Some(short); self } - pub fn default(mut self, value: &str) -> Self { - self.default = Some(value.to_string()); - self - } - pub fn quick(mut self, value: &str) -> Self { self.quick = Some(value.to_string()); self } + pub fn optional(&mut self) { + let value = self.get_value(); + self.insert(value); + } + + pub fn default(&mut self, default: &str) { + match self.get_value() { + Some(value) => self.insert(Some(value)), + None => self.insert(Some(Some(default.to_string()))), + }; + } + + pub fn required(&mut self) { + match self.get_value() { + Some(value) => self.insert(Some(value)), + None => { + let error = QueryError::MissingNamed(self.debug_name()); + self.switchboard.errors.push(error); + } + } + } + + fn insert(&mut self, value: Option>) { + let queried = QueriedValue { + doc: self.doc.clone(), + name: self.name.clone(), + variant: QueryVariant::Named(self.debug_name()), + value, + }; + if let Some(_) = self.switchboard.values.insert(self.name.clone(), queried) { + error!("Duplicate query for name {:?}", self.name.clone()); + } + self.committed = true; + } + + fn get_value(&mut self) -> Option> { + // Find all matches first. + let mut matches = Vec::new(); + for (i, switch) in self.switchboard.switches.iter().enumerate() { + match &switch.0 { + SwitchName::Short(other) => if let Some(short) = self.short { + if short == *other { matches.push(i) } + } + SwitchName::Long(other) => if self.name == *other { + matches.push(i); + }, + }; + } + // Return a value. + match matches.len() { + 0 => None, + 1 => { + let found = self.switchboard.switches.remove(matches[0]).1; + match found { + Some(string) => Some(Some(string)), + None => Some(self.quick.clone()), + } + } + _ => { + let error = QueryError::Repeated(self.debug_name()); + self.switchboard.errors.push(error); + None + } + } + } + fn debug_name(&self) -> String { let mut debug_name = format!("--{}", self.name); if let Some(short) = self.short { @@ -46,135 +110,73 @@ impl NamedSwitchQuery<'_> { } } +impl Drop for NamedSwitchQuery<'_> { + fn drop(&mut self) { + if !self.committed { + self.optional(); + } + } +} + + pub struct PositionalSwitchQuery<'a> { /// The borrowed switchboard instance. pub switchboard: &'a mut Switchboard, /// The display name of this argument. pub name: String, - /// The default value if the switch hasn't been provided. - pub default: Option, + /// Documentation string. + pub doc: Option, + /// True if this query has already been committed. + pub committed: bool, } impl PositionalSwitchQuery<'_> { - pub fn default(mut self, value: &str) -> Self { - self.default = Some(value.to_string()); + pub fn doc(mut self, doc: &str) -> Self { + self.doc = Some(doc.to_string()); self } -} - - -macro_rules! as_number { - ($type:ty, $name:expr, $error:ty) => { - paste::paste! { - fn [< as_ $type >](&mut self) -> $type { - self.[< as_ $type _opt >]().unwrap_or_else(|| self.missing("number")) - } - fn [< as_ $type _opt>](&mut self) -> Option<$type> { - self.get_value().map(|v| v.trim().parse().unwrap_or_else( - |e: $error| self.error(&v, $name, e.to_string()))) - } - } - }; -} - -pub trait SwitchQuery { - fn get_name(&self) -> String; - - fn get_value(&mut self) -> Option; - - fn error(&self, value: &str, ty: &str, err: String) -> ! { - let name = self.get_name(); - fatal!("The {name} with value {value:?} could not be parsed as {ty}: {err}"); + pub fn optional(&mut self) { + let value = self.switchboard.pop(); + self.insert(value); } - fn missing(&self, ty: &str) -> ! { - let name = self.get_name(); - fatal!("The required {name} that takes a {ty} value was not provided"); + pub fn default(&mut self, default: &str) { + match self.switchboard.pop() { + Some(value) => self.insert(Some(value)), + None => self.insert(Some(default.to_string())), + }; } - fn as_bool(&mut self) -> bool { - if let Some(value) = self.get_value() { - match value.to_lowercase().as_str() { - "y"|"yes"|"t"|"true" => true, - "n"|"no" |"f"|"false" => false, - _ => true, + pub fn required(&mut self) { + match self.switchboard.pop() { + Some(value) => self.insert(Some(value)), + None => { + let error = QueryError::MissingPositional(self.name.clone()); + self.switchboard.errors.push(error); } - } else { - false } } - fn as_path(&mut self) -> PathBuf { - self.as_path_opt().unwrap_or_else(|| self.missing("path")) - } - fn as_path_opt(&mut self) -> Option { - if let Some(value) = self.get_value() { - if !value.is_empty() { - return Some(PathBuf::from(value)); - } + fn insert(&mut self, value: Option) { + let queried = QueriedValue { + doc: self.doc.clone(), + name: self.name.clone(), + variant: QueryVariant::Positional(self.switchboard.i), + value: Some(value), + }; + if let Some(_) = self.switchboard.values.insert(self.name.clone(), queried) { + error!("Duplicate query for name {:?}", self.name.clone()); } - return None; + self.committed = true; } - - fn as_string(&mut self) -> String { - self.as_string_opt().unwrap_or_else(|| self.missing("string")) - } - fn as_string_opt(&mut self) -> Option { - self.get_value() - } - - as_number!{ f32, "f32" , std::num::ParseFloatError } - as_number!{ f64, "f64" , std::num::ParseFloatError } - as_number!{ u8, "u8" , std::num::ParseIntError } - as_number!{ u16, "u16" , std::num::ParseIntError } - as_number!{ u32, "u32" , std::num::ParseIntError } - as_number!{ u64, "u64" , std::num::ParseIntError } - as_number!{ usize, "usize", std::num::ParseIntError } - as_number!{ i8, "i8" , std::num::ParseIntError } - as_number!{ i16, "i16" , std::num::ParseIntError } - as_number!{ i32, "i32" , std::num::ParseIntError } - as_number!{ i64, "i64" , std::num::ParseIntError } - as_number!{ isize, "isize", std::num::ParseIntError } } - -impl SwitchQuery for NamedSwitchQuery<'_> { - fn get_name(&self) -> String { - format!("argument {}", self.debug_name()) - } - - fn get_value(&mut self) -> Option { - // Find all matches first. - let mut matches = Vec::new(); - for (i, switch) in self.switchboard.switches.iter().enumerate() { - match &switch.0 { - SwitchName::Short(other) => if let Some(short) = self.short { - if short == *other { matches.push(i) } } - SwitchName::Long(other) => if self.name == *other { - matches.push(i); }, - }; - } - match matches.len() { - 0 => self.default.clone(), - 1 => Some( - self.switchboard.switches.remove(matches[0]).1 - .or_else(|| self.quick.clone()) - .unwrap_or_else(String::new) - ), - _ => fatal!("The argument {} was passed more than once", self.debug_name()), +impl Drop for PositionalSwitchQuery<'_> { + fn drop(&mut self) { + if !self.committed { + self.optional(); } } } - - -impl SwitchQuery for PositionalSwitchQuery<'_> { - fn get_name(&self) -> String { - format!("positional argument {:?}", self.name) - } - - fn get_value(&mut self) -> Option { - self.switchboard.positional.pop().or_else(|| self.default.clone()) - } -} -- cgit v1.2.3-70-g09d2