//! ref: composer/vendor/symfony/console/Input/InputOption.php use crate::exception::InvalidArgumentException; use crate::exception::LogicException; use crate::input::InputValue; use shirabe_php_shim::{php_regex, preg_split}; #[derive(Debug, Clone)] pub struct InputOption { name: String, shortcut: Option, mode: i64, default: InputValue, description: String, } impl InputOption { pub const VALUE_NONE: i64 = 1; pub const VALUE_REQUIRED: i64 = 2; pub const VALUE_OPTIONAL: i64 = 4; pub const VALUE_IS_ARRAY: i64 = 8; pub const VALUE_NEGATABLE: i64 = 16; pub fn new( name: &str, shortcut: Option<&str>, mode: Option, description: String, default: InputValue, ) -> anyhow::Result { let name = if let Some(stripped) = name.strip_prefix("--") { stripped.to_string() } else { name.to_string() }; if name.is_empty() { return Err(InvalidArgumentException::new( "An option name cannot be empty.".to_string(), ) .into()); } let shortcut = match shortcut { None | Some("") => None, Some(shortcut) => Self::normalize_shortcut(shortcut)?, }; let mode = match mode { None => Self::VALUE_NONE, Some(m) if !(1..(Self::VALUE_NEGATABLE << 1)).contains(&m) => { return Err(InvalidArgumentException::new(format!( "Option mode \"{}\" is not valid.", m )) .into()); } Some(m) => m, }; let mut option = InputOption { name, shortcut, mode, description, default: InputValue::Null, }; if option.is_array() && !option.accept_value() { return Err(InvalidArgumentException::new("Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.".to_string()) .into()); } if option.is_negatable() && option.accept_value() { return Err(InvalidArgumentException::new("Impossible to have an option mode VALUE_NEGATABLE if the option also accepts a value.".to_string()) .into()); } option.set_default(default)?; Ok(option) } fn normalize_shortcut(s: &str) -> anyhow::Result> { let stripped = shirabe_php_shim::ltrim(s, Some("-")); let parts = preg_split(php_regex!(r"{(\|)-?}"), &stripped); let filtered: Vec = shirabe_php_shim::array_filter(&parts, |s: &String| !s.is_empty()); let result = shirabe_php_shim::implode("|", &filtered); if result.is_empty() { return Err(InvalidArgumentException::new( "An option shortcut cannot be empty.".to_string(), ) .into()); } Ok(Some(result)) } pub fn get_shortcut(&self) -> Option<&str> { self.shortcut.as_deref() } pub fn get_name(&self) -> &str { &self.name } pub fn accept_value(&self) -> bool { self.is_value_required() || self.is_value_optional() } pub fn is_value_required(&self) -> bool { Self::VALUE_REQUIRED == (Self::VALUE_REQUIRED & self.mode) } pub fn is_value_optional(&self) -> bool { Self::VALUE_OPTIONAL == (Self::VALUE_OPTIONAL & self.mode) } pub fn is_array(&self) -> bool { Self::VALUE_IS_ARRAY == (Self::VALUE_IS_ARRAY & self.mode) } pub fn is_negatable(&self) -> bool { Self::VALUE_NEGATABLE == (Self::VALUE_NEGATABLE & self.mode) } pub fn set_default(&mut self, default: InputValue) -> anyhow::Result<()> { if Self::VALUE_NONE == (Self::VALUE_NONE & self.mode) && !default.is_null() { return Err(LogicException::new( "Cannot set a default value when using InputOption::VALUE_NONE mode.".to_string(), ) .into()); } let default = if self.is_array() { match default { InputValue::Null => InputValue::Array(vec![]), InputValue::Array(_) => default, _ => { return Err(LogicException::new( "A default value for an array option must be an array.".to_string(), ) .into()); } } } else { default }; self.default = if self.accept_value() || self.is_negatable() { default } else { InputValue::Bool(false) }; Ok(()) } pub fn get_default(&self) -> &InputValue { &self.default } pub fn get_description(&self) -> &str { &self.description } pub fn equals(&self, option: &InputOption) -> bool { option.get_name() == self.get_name() && option.get_shortcut() == self.get_shortcut() && option.get_default() == self.get_default() && option.is_negatable() == self.is_negatable() && option.is_array() == self.is_array() && option.is_value_required() == self.is_value_required() && option.is_value_optional() == self.is_value_optional() } }