use crate::*; use log::fatal; use std::path::PathBuf; pub struct NamedSwitchQuery<'a> { /// The borrowed switchboard instance. pub switchboard: &'a mut Switchboard, /// Long switch name. 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, } impl NamedSwitchQuery<'_> { pub fn short(mut self, short: char) -> Self { if !short.is_ascii_alphanumeric() { panic!("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 } fn debug_name(&self) -> String { let mut debug_name = format!("--{}", self.name); if let Some(short) = self.short { debug_name.push_str(&format!(" (-{})", short)); } debug_name } } 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, } impl PositionalSwitchQuery<'_> { pub fn default(mut self, value: &str) -> Self { self.default = Some(value.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}"); } fn missing(&self, ty: &str) -> ! { let name = self.get_name(); fatal!("The required {name} that takes a {ty} value was not provided"); } 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, } } 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)); } } return None; } 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 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()) } }