diff options
Diffstat (limited to 'crates/shirabe-external-packages/src/symfony/console/input')
10 files changed, 0 insertions, 2327 deletions
diff --git a/crates/shirabe-external-packages/src/symfony/console/input/argv_input.rs b/crates/shirabe-external-packages/src/symfony/console/input/argv_input.rs deleted file mode 100644 index 65ab9053..00000000 --- a/crates/shirabe-external-packages/src/symfony/console/input/argv_input.rs +++ /dev/null @@ -1,657 +0,0 @@ -//! ref: composer/vendor/symfony/console/Input/ArgvInput.php - -use crate::symfony::console::exception::runtime_exception::RuntimeException; -use crate::symfony::console::input::input::Input; -use crate::symfony::console::input::input_definition::InputDefinition; -use crate::symfony::console::input::input_interface::InputInterface; -use crate::symfony::console::input::streamable_input_interface::StreamableInputInterface; -use indexmap::IndexMap; -use shirabe_php_shim::{PhpMixed, php_regex}; - -/// ArgvInput represents an input coming from the CLI arguments. -/// -/// Usage: -/// -/// ```php -/// $input = new ArgvInput(); -/// ``` -/// -/// By default, the `$_SERVER['argv']` array is used for the input values. -/// -/// This can be overridden by explicitly passing the input values in the constructor: -/// -/// ```php -/// $input = new ArgvInput($_SERVER['argv']); -/// ``` -/// -/// If you pass it yourself, don't forget that the first element of the array -/// is the name of the running application. -/// -/// When passing an argument to the constructor, be sure that it respects -/// the same rules as the argv one. It's almost always better to use the -/// `StringInput` when you want to provide your own input. -#[derive(Debug, Clone)] -pub struct ArgvInput { - pub(crate) inner: Input, - tokens: Vec<String>, - parsed: Vec<String>, -} - -impl ArgvInput { - pub fn new( - argv: Option<Vec<String>>, - definition: Option<InputDefinition>, - ) -> anyhow::Result<Self> { - // $argv = $argv ?? $_SERVER['argv'] ?? []; - let mut argv = match argv { - Some(argv) => argv, - None => std::env::args().collect(), - }; - - // strip the application name - if !argv.is_empty() { - argv.remove(0); - } - - let mut input = ArgvInput { - inner: Input::new(None)?, - tokens: argv, - parsed: vec![], - }; - - // parent::__construct($definition) - match definition { - None => {} - Some(definition) => { - input.bind(&definition)?; - input.inner.validate()?; - } - } - - Ok(input) - } - - pub(crate) fn set_tokens(&mut self, tokens: Vec<String>) { - self.tokens = tokens; - } - - pub fn bind(&mut self, definition: &InputDefinition) -> anyhow::Result<()> { - self.base_bind(definition, ArgvInput::parse_token) - } - - /// Shared body of `bind` (PHP `Input::bind`). PHP's `$this->parse()` ends in - /// `$this->parseToken()`, which late-binds to `CompletionInput::parseToken`; `parseToken` - /// is protected and not part of any trait, so the concrete implementation is threaded in - /// as a callback taking the embedded ArgvInput. - pub(crate) fn base_bind( - &mut self, - definition: &InputDefinition, - parse_token: impl FnMut(&mut ArgvInput, &str, bool) -> anyhow::Result<bool>, - ) -> anyhow::Result<()> { - self.inner.arguments = IndexMap::new(); - self.inner.options = IndexMap::new(); - self.inner.definition = definition.clone(); - - self.base_parse(parse_token)?; - - Ok(()) - } - - /// Shared body of `parse`; see `base_bind` for why `parse_token` is a parameter. - fn base_parse( - &mut self, - mut parse_token: impl FnMut(&mut ArgvInput, &str, bool) -> anyhow::Result<bool>, - ) -> anyhow::Result<()> { - let mut parse_options = true; - self.parsed = self.tokens.clone(); - while !self.parsed.is_empty() { - let token = self.parsed.remove(0); - parse_options = parse_token(self, &token, parse_options)?; - } - Ok(()) - } - - pub(crate) fn parse_token(&mut self, token: &str, parse_options: bool) -> anyhow::Result<bool> { - if parse_options && token.is_empty() { - self.parse_argument(token)?; - } else if parse_options && token == "--" { - return Ok(false); - } else if parse_options && shirabe_php_shim::str_starts_with(token, "--") { - self.parse_long_option(token)?; - } else if parse_options && token.as_bytes().first() == Some(&b'-') && token != "-" { - self.parse_short_option(token)?; - } else { - self.parse_argument(token)?; - } - - Ok(parse_options) - } - - /// Parses a short option. - fn parse_short_option(&mut self, token: &str) -> anyhow::Result<()> { - let name = shirabe_php_shim::substr(token, 1, None); - - if shirabe_php_shim::strlen(&name) > 1 { - let first = shirabe_php_shim::substr(&name, 0, Some(1)); - if self.inner.definition.has_shortcut(&first) - && self - .inner - .definition - .get_option_for_shortcut(&first)? - .accept_value() - { - // an option with a value (with no space) - self.add_short_option( - &first, - PhpMixed::String(shirabe_php_shim::substr(&name, 1, None)), - )?; - } else { - self.parse_short_option_set(&name)?; - } - } else { - self.add_short_option(&name, PhpMixed::Null)?; - } - - Ok(()) - } - - /// Parses a short option set. - fn parse_short_option_set(&mut self, name: &str) -> anyhow::Result<()> { - let len = shirabe_php_shim::strlen(name); - let mut i = 0; - while i < len { - let name_i = shirabe_php_shim::substr(name, i, Some(1)); - if !self.inner.definition.has_shortcut(&name_i) { - let encoding = shirabe_php_shim::mb_detect_encoding(name, None, true); - let bad = match encoding { - None => name_i, - Some(encoding) => { - shirabe_php_shim::mb_substr(name, i, Some(1), Some(&encoding)) - } - }; - return Err(RuntimeException::new(format!( - "The \"-{}\" option does not exist.", - bad - )) - .into()); - } - - let option = self.inner.definition.get_option_for_shortcut(&name_i)?; - if option.accept_value() { - let value = if i == len - 1 { - PhpMixed::Null - } else { - PhpMixed::String(shirabe_php_shim::substr(name, i + 1, None)) - }; - self.add_long_option(option.get_name(), value)?; - - break; - } else { - self.add_long_option(option.get_name(), PhpMixed::Null)?; - } - i += 1; - } - - Ok(()) - } - - /// Parses a long option. - fn parse_long_option(&mut self, token: &str) -> anyhow::Result<()> { - let name = shirabe_php_shim::substr(token, 2, None); - - match shirabe_php_shim::strpos(&name, "=") { - Some(pos) => { - let pos = pos as i64; - let value = shirabe_php_shim::substr(&name, pos + 1, None); - if value.is_empty() { - self.parsed.insert(0, value.clone()); - } - self.add_long_option( - &shirabe_php_shim::substr(&name, 0, Some(pos)), - PhpMixed::String(value), - )?; - } - None => { - self.add_long_option(&name, PhpMixed::Null)?; - } - } - - Ok(()) - } - - /// Parses an argument. - fn parse_argument(&mut self, token: &str) -> anyhow::Result<()> { - let c = self.inner.arguments.len() as i64; - - // if input is expecting another argument, add it - if self.inner.definition.has_argument(&PhpMixed::Int(c)) { - let arg = self.inner.definition.get_argument(&PhpMixed::Int(c))?; - let value = if arg.is_array() { - PhpMixed::List(vec![PhpMixed::String(token.to_string())]) - } else { - PhpMixed::String(token.to_string()) - }; - self.inner - .arguments - .insert(arg.get_name().to_string(), value); - - // if last argument isArray(), append token to last argument - } else if self.inner.definition.has_argument(&PhpMixed::Int(c - 1)) - && self - .inner - .definition - .get_argument(&PhpMixed::Int(c - 1))? - .is_array() - { - let arg = self.inner.definition.get_argument(&PhpMixed::Int(c - 1))?; - if let Some(PhpMixed::List(list)) = self.inner.arguments.get_mut(arg.get_name()) { - list.push(PhpMixed::String(token.to_string())); - } - - // unexpected argument - } else { - let mut all = self.inner.definition.get_arguments().clone(); - let mut symfony_command_name: Option<PhpMixed> = None; - let first_key = all.keys().next().cloned(); - if let Some(key) = &first_key { - let input_argument = &all[key]; - if input_argument.get_name() == "command" { - symfony_command_name = self.inner.arguments.get("command").cloned(); - all.shift_remove(key); - } - } - - let message = if !all.is_empty() { - let names: Vec<String> = all.keys().cloned().collect(); - match &symfony_command_name { - Some(symfony_command_name) - if !matches!(symfony_command_name, PhpMixed::Null) => - { - format!( - "Too many arguments to \"{}\" command, expected arguments \"{}\".", - symfony_command_name.clone(), - shirabe_php_shim::implode("\" \"", &names), - ) - } - _ => format!( - "Too many arguments, expected arguments \"{}\".", - shirabe_php_shim::implode("\" \"", &names), - ), - } - } else if symfony_command_name - .as_ref() - .map(|n| !matches!(n, PhpMixed::Null)) - .unwrap_or(false) - { - format!( - "No arguments expected for \"{}\" command, got \"{}\".", - symfony_command_name.unwrap(), - token, - ) - } else { - format!("No arguments expected, got \"{}\".", token) - }; - - return Err(RuntimeException::new(message).into()); - } - - Ok(()) - } - - /// Adds a short option value. - fn add_short_option(&mut self, shortcut: &str, value: PhpMixed) -> anyhow::Result<()> { - if !self.inner.definition.has_shortcut(shortcut) { - return Err(RuntimeException::new(format!( - "The \"-{}\" option does not exist.", - shortcut - )) - .into()); - } - - self.add_long_option( - self.inner - .definition - .get_option_for_shortcut(shortcut)? - .get_name(), - value, - ) - } - - /// Adds a long option value. - fn add_long_option(&mut self, name: &str, mut value: PhpMixed) -> anyhow::Result<()> { - if !self.inner.definition.has_option(name) { - if !self.inner.definition.has_negation(name) { - return Err(RuntimeException::new(format!( - "The \"--{}\" option does not exist.", - name - )) - .into()); - } - - let option_name = self.inner.definition.negation_to_name(name)?; - if !matches!(value, PhpMixed::Null) { - return Err(RuntimeException::new(format!( - "The \"--{}\" option does not accept a value.", - name - )) - .into()); - } - self.inner - .options - .insert(option_name, PhpMixed::Bool(false)); - - return Ok(()); - } - - let option = self.inner.definition.get_option(name)?; - - if !matches!(value, PhpMixed::Null) && !option.accept_value() { - return Err(RuntimeException::new(format!( - "The \"--{}\" option does not accept a value.", - name - )) - .into()); - } - - // in_array($value, ['', null], true) - let value_is_empty_or_null = matches!(&value, PhpMixed::String(s) if s.is_empty()) - || matches!(value, PhpMixed::Null); - if value_is_empty_or_null && option.accept_value() && !self.parsed.is_empty() { - // if option accepts an optional or mandatory argument - // let's see if there is one provided - let next = self.parsed.remove(0); - // (isset($next[0]) && '-' !== $next[0]) || in_array($next, ['', null], true) - let next_first = next.as_bytes().first().copied(); - if (next_first.is_some() && next_first != Some(b'-')) || next.is_empty() { - value = PhpMixed::String(next); - } else { - self.parsed.insert(0, next); - } - } - - if matches!(value, PhpMixed::Null) { - if option.is_value_required() { - return Err(RuntimeException::new(format!( - "The \"--{}\" option requires a value.", - name - )) - .into()); - } - - if !option.is_array() && !option.is_value_optional() { - value = PhpMixed::Bool(true); - } - } - - if option.is_array() { - match self.inner.options.get_mut(name) { - Some(PhpMixed::List(list)) => { - list.push(value); - } - _ => { - self.inner - .options - .insert(name.to_string(), PhpMixed::List(vec![value])); - } - } - } else { - self.inner.options.insert(name.to_string(), value); - } - - Ok(()) - } - - pub fn get_first_argument(&self) -> Option<String> { - let mut is_option = false; - for (i, token) in self.tokens.iter().enumerate() { - if !token.is_empty() && token.as_bytes()[0] == b'-' { - if shirabe_php_shim::str_contains(token, "=") || self.tokens.get(i + 1).is_none() { - continue; - } - - // If it's a long option, consider that everything after "--" is the option name. - // Otherwise, use the last char (if it's a short option set, only the last one can take a value with space separator) - let mut name = if token.as_bytes().get(1) == Some(&b'-') { - shirabe_php_shim::substr(token, 2, None) - } else { - shirabe_php_shim::substr(token, -1, None) - }; - if !self.inner.options.contains_key(&name) - && !self.inner.definition.has_shortcut(&name) - { - // noop - } else { - if !self.inner.options.contains_key(&name) - && let Ok(resolved) = self.inner.definition.shortcut_to_name(&name) - { - name = resolved; - } - if let Some(option_value) = self.inner.options.get(&name) - && self.tokens.get(i + 1).map(|t| t.as_str()) == option_value.as_string() - { - is_option = true; - } - } - - continue; - } - - if is_option { - is_option = false; - continue; - } - - return Some(token.clone()); - } - - None - } - - pub fn has_parameter_option(&self, values: PhpMixed, only_params: bool) -> bool { - let values = to_array(values); - - for token in &self.tokens { - if only_params && token == "--" { - return false; - } - for value in &values { - // Options with values: - // For long options, test for '--option=' at beginning - // For short options, test for '-o' at beginning - let leading = if shirabe_php_shim::str_starts_with(value, "--") { - format!("{}=", value) - } else { - value.clone() - }; - if token == value - || (!leading.is_empty() && shirabe_php_shim::str_starts_with(token, &leading)) - { - return true; - } - } - } - - false - } - - pub fn get_parameter_option( - &self, - values: PhpMixed, - default: PhpMixed, - only_params: bool, - ) -> PhpMixed { - let values = to_array(values); - let mut tokens = self.tokens.clone(); - - while !tokens.is_empty() { - let token = tokens.remove(0); - if only_params && token == "--" { - return default; - } - - for value in &values { - if &token == value { - return match tokens.first() { - Some(_) => PhpMixed::String(tokens.remove(0)), - None => PhpMixed::Null, - }; - } - // Options with values: - // For long options, test for '--option=' at beginning - // For short options, test for '-o' at beginning - let leading = if shirabe_php_shim::str_starts_with(value, "--") { - format!("{}=", value) - } else { - value.clone() - }; - if !leading.is_empty() && shirabe_php_shim::str_starts_with(&token, &leading) { - return PhpMixed::String(shirabe_php_shim::substr( - &token, - shirabe_php_shim::strlen(&leading), - None, - )); - } - } - } - - default - } -} - -/// Returns a stringified representation of the args passed to the command. -impl std::fmt::Display for ArgvInput { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let tokens: Vec<String> = self - .tokens - .iter() - .map(|token| { - let mut r#match: Vec<Option<String>> = Vec::new(); - if shirabe_php_shim::preg_match(php_regex!("{^(-[^=]+=)(.+)}"), token, &mut r#match) - { - return format!( - "{}{}", - r#match[1].as_deref().unwrap_or(""), - self.inner.escape_token(r#match[2].as_deref().unwrap_or("")) - ); - } - - if !token.is_empty() && token.as_bytes()[0] != b'-' { - return self.inner.escape_token(token); - } - - token.clone() - }) - .collect(); - - write!(f, "{}", shirabe_php_shim::implode(" ", &tokens)) - } -} - -impl InputInterface for ArgvInput { - fn dup(&self) -> std::rc::Rc<std::cell::RefCell<dyn InputInterface>> { - std::rc::Rc::new(std::cell::RefCell::new(self.clone())) - } - - fn get_first_argument(&self) -> Option<String> { - ArgvInput::get_first_argument(self) - } - - fn has_parameter_option(&self, values: PhpMixed, only_params: bool) -> bool { - ArgvInput::has_parameter_option(self, values, only_params) - } - - fn get_parameter_option( - &self, - values: PhpMixed, - default: PhpMixed, - only_params: bool, - ) -> PhpMixed { - ArgvInput::get_parameter_option(self, values, default, only_params) - } - - fn bind(&mut self, definition: &InputDefinition) -> anyhow::Result<()> { - ArgvInput::bind(self, definition) - } - - fn validate(&mut self) -> anyhow::Result<()> { - self.inner.validate() - } - - fn get_arguments(&self) -> IndexMap<String, PhpMixed> { - self.inner.get_arguments() - } - - fn get_argument(&self, name: &str) -> anyhow::Result<PhpMixed> { - self.inner.get_argument(name) - } - - fn set_argument(&mut self, name: &str, value: PhpMixed) -> anyhow::Result<()> { - self.inner.set_argument(name, value) - } - - fn has_argument(&self, name: &str) -> bool { - self.inner.has_argument(name) - } - - fn get_options(&self) -> IndexMap<String, PhpMixed> { - self.inner.get_options() - } - - fn get_option(&self, name: &str) -> anyhow::Result<PhpMixed> { - self.inner.get_option(name) - } - - fn set_option(&mut self, name: &str, value: PhpMixed) -> anyhow::Result<()> { - self.inner.set_option(name, value) - } - - fn has_option(&self, name: &str) -> bool { - self.inner.has_option(name) - } - - fn is_interactive(&self) -> bool { - self.inner.is_interactive() - } - - fn set_interactive(&mut self, interactive: bool) { - self.inner.set_interactive(interactive) - } - - fn __to_string(&self) -> String { - self.to_string() - } - - fn as_streamable(&self) -> Option<&dyn StreamableInputInterface> { - Some(self) - } - - fn as_streamable_mut(&mut self) -> Option<&mut dyn StreamableInputInterface> { - Some(self) - } -} - -impl StreamableInputInterface for ArgvInput { - fn set_stream(&mut self, stream: shirabe_php_shim::PhpResource) { - self.inner.set_stream(stream) - } - - fn get_stream(&self) -> Option<shirabe_php_shim::PhpResource> { - self.inner.get_stream() - } -} - -/// PHP `(array) $values` cast: a string becomes a single-element array. -fn to_array(values: PhpMixed) -> Vec<String> { - match values { - PhpMixed::List(list) => list - .into_iter() - .map(|v| shirabe_php_shim::php_to_string(&v)) - .collect(), - PhpMixed::Array(array) => array - .into_iter() - .map(|(_, v)| shirabe_php_shim::php_to_string(&v)) - .collect(), - PhpMixed::Null => vec![], - other => vec![shirabe_php_shim::php_to_string(&other)], - } -} diff --git a/crates/shirabe-external-packages/src/symfony/console/input/array_input.rs b/crates/shirabe-external-packages/src/symfony/console/input/array_input.rs deleted file mode 100644 index de89a340..00000000 --- a/crates/shirabe-external-packages/src/symfony/console/input/array_input.rs +++ /dev/null @@ -1,386 +0,0 @@ -//! ref: composer/vendor/symfony/console/Input/ArrayInput.php - -use crate::symfony::console::exception::invalid_argument_exception::InvalidArgumentException; -use crate::symfony::console::exception::invalid_option_exception::InvalidOptionException; -use crate::symfony::console::input::input::Input; -use crate::symfony::console::input::input_definition::InputDefinition; -use crate::symfony::console::input::input_interface::InputInterface; -use crate::symfony::console::input::streamable_input_interface::StreamableInputInterface; -use indexmap::IndexMap; -use shirabe_php_shim::PhpMixed; - -/// ArrayInput represents an input provided as an array. -/// -/// Usage: -/// -/// ```php -/// $input = new ArrayInput(['command' => 'foo:bar', 'foo' => 'bar', '--bar' => 'foobar']); -/// ``` -/// -/// PHP arrays can mix integer and string keys; `parameters` preserves both the -/// key type (`PhpMixed::Int` / `PhpMixed::String`) and the insertion order. -#[derive(Debug, Clone)] -pub struct ArrayInput { - pub(crate) inner: Input, - parameters: Vec<(PhpMixed, PhpMixed)>, -} - -impl ArrayInput { - pub fn new( - parameters: Vec<(PhpMixed, PhpMixed)>, - definition: Option<InputDefinition>, - ) -> anyhow::Result<Self> { - let mut array_input = ArrayInput { - inner: Input::new(None)?, - parameters, - }; - - // parent::__construct($definition) - match definition { - None => {} - Some(definition) => { - array_input.bind(&definition)?; - array_input.inner.validate()?; - } - } - - Ok(array_input) - } - - pub fn bind(&mut self, definition: &InputDefinition) -> anyhow::Result<()> { - self.inner.arguments = IndexMap::new(); - self.inner.options = IndexMap::new(); - self.inner.definition = definition.clone(); - - self.parse()?; - - Ok(()) - } - - pub fn get_first_argument(&self) -> Option<PhpMixed> { - for (param, value) in &self.parameters { - // $param && \is_string($param) && '-' === $param[0] - if let PhpMixed::String(param) = param - && !param.is_empty() - && param.as_bytes()[0] == b'-' - { - continue; - } - - return Some(value.clone()); - } - - None - } - - pub fn has_parameter_option(&self, values: PhpMixed, only_params: bool) -> bool { - let values = to_array(values); - - for (k, v) in &self.parameters { - // if (!\is_int($k)) { $v = $k; } - let v: PhpMixed = match k { - PhpMixed::Int(_) => v.clone(), - _ => k.clone(), - }; - - if only_params && matches!(&v, PhpMixed::String(s) if s == "--") { - return false; - } - - if values.iter().any(|x| x == &v) { - return true; - } - } - - false - } - - pub fn get_parameter_option( - &self, - values: PhpMixed, - default: PhpMixed, - only_params: bool, - ) -> PhpMixed { - let values = to_array(values); - - for (k, v) in &self.parameters { - // $onlyParams && ('--' === $k || (\is_int($k) && '--' === $v)) - if only_params { - let k_is_double_dash = matches!(k, PhpMixed::String(s) if s == "--"); - let int_v_double_dash = - matches!(k, PhpMixed::Int(_)) && matches!(v, PhpMixed::String(s) if s == "--"); - if k_is_double_dash || int_v_double_dash { - return default; - } - } - - match k { - PhpMixed::Int(_) => { - if values.iter().any(|x| x == v) { - return PhpMixed::Bool(true); - } - } - _ => { - if values.iter().any(|x| x == k) { - return v.clone(); - } - } - } - } - - default - } - - fn parse(&mut self) -> anyhow::Result<()> { - // Clone to avoid borrowing self while mutating; PHP iterates over a copy semantically. - let parameters = self.parameters.clone(); - for (key, value) in parameters { - let key = shirabe_php_shim::php_to_string(&key); - if key == "--" { - return Ok(()); - } - if shirabe_php_shim::str_starts_with(&key, "--") { - self.add_long_option(&shirabe_php_shim::substr(&key, 2, None), value)?; - } else if shirabe_php_shim::str_starts_with(&key, "-") { - self.add_short_option(&shirabe_php_shim::substr(&key, 1, None), value)?; - } else { - self.add_argument(&PhpMixed::String(key), value)?; - } - } - - Ok(()) - } - - /// Adds a short option value. - fn add_short_option(&mut self, shortcut: &str, value: PhpMixed) -> anyhow::Result<()> { - if !self.inner.definition.has_shortcut(shortcut) { - return Err(InvalidOptionException::new(format!( - "The \"-{}\" option does not exist.", - shortcut - )) - .into()); - } - - self.add_long_option( - self.inner - .definition - .get_option_for_shortcut(shortcut)? - .get_name(), - value, - ) - } - - /// Adds a long option value. - fn add_long_option(&mut self, name: &str, mut value: PhpMixed) -> anyhow::Result<()> { - if !self.inner.definition.has_option(name) { - if !self.inner.definition.has_negation(name) { - return Err(InvalidOptionException::new(format!( - "The \"--{}\" option does not exist.", - name - )) - .into()); - } - - let option_name = self.inner.definition.negation_to_name(name)?; - self.inner - .options - .insert(option_name, PhpMixed::Bool(false)); - - return Ok(()); - } - - let option = self.inner.definition.get_option(name)?; - - if matches!(value, PhpMixed::Null) { - if option.is_value_required() { - return Err(InvalidOptionException::new(format!( - "The \"--{}\" option requires a value.", - name - )) - .into()); - } - - if !option.is_value_optional() { - value = PhpMixed::Bool(true); - } - } - - self.inner.options.insert(name.to_string(), value); - - Ok(()) - } - - /// Adds an argument value. - fn add_argument(&mut self, name: &PhpMixed, value: PhpMixed) -> anyhow::Result<()> { - if !self.inner.definition.has_argument(name) { - return Err(InvalidArgumentException::new(format!( - "The \"{}\" argument does not exist.", - name.clone() - )) - .into()); - } - - self.inner - .arguments - .insert(shirabe_php_shim::php_to_string(name), value); - - Ok(()) - } -} - -/// Returns a stringified representation of the args passed to the command. -impl std::fmt::Display for ArrayInput { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut params: Vec<String> = vec![]; - for (param, val) in &self.parameters { - // $param && \is_string($param) && '-' === $param[0] - let is_option_key = - matches!(param, PhpMixed::String(s) if !s.is_empty() && s.as_bytes()[0] == b'-'); - if is_option_key { - let param = param.as_string().unwrap(); - let glue = if param.as_bytes().get(1) == Some(&b'-') { - "=" - } else { - " " - }; - if let PhpMixed::List(list) = val { - for v in list { - let v = shirabe_php_shim::php_to_string(v); - params.push(format!( - "{}{}", - param, - if !v.is_empty() { - format!("{}{}", glue, self.inner.escape_token(&v)) - } else { - String::new() - } - )); - } - } else { - let val = shirabe_php_shim::php_to_string(val); - params.push(format!( - "{}{}", - param, - if !val.is_empty() { - format!("{}{}", glue, self.inner.escape_token(&val)) - } else { - String::new() - } - )); - } - } else if let PhpMixed::List(list) = val { - let escaped: Vec<String> = list - .iter() - .map(|v| self.inner.escape_token(&shirabe_php_shim::php_to_string(v))) - .collect(); - params.push(shirabe_php_shim::implode(" ", &escaped)); - } else { - params.push( - self.inner - .escape_token(&shirabe_php_shim::php_to_string(val)), - ); - } - } - - write!(f, "{}", shirabe_php_shim::implode(" ", ¶ms)) - } -} - -impl InputInterface for ArrayInput { - fn dup(&self) -> std::rc::Rc<std::cell::RefCell<dyn InputInterface>> { - std::rc::Rc::new(std::cell::RefCell::new(self.clone())) - } - - fn get_first_argument(&self) -> Option<String> { - ArrayInput::get_first_argument(self).map(|v| shirabe_php_shim::php_to_string(&v)) - } - - fn has_parameter_option(&self, values: PhpMixed, only_params: bool) -> bool { - ArrayInput::has_parameter_option(self, values, only_params) - } - - fn get_parameter_option( - &self, - values: PhpMixed, - default: PhpMixed, - only_params: bool, - ) -> PhpMixed { - ArrayInput::get_parameter_option(self, values, default, only_params) - } - - fn bind(&mut self, definition: &InputDefinition) -> anyhow::Result<()> { - ArrayInput::bind(self, definition) - } - - fn validate(&mut self) -> anyhow::Result<()> { - self.inner.validate() - } - - fn get_arguments(&self) -> IndexMap<String, PhpMixed> { - self.inner.get_arguments() - } - - fn get_argument(&self, name: &str) -> anyhow::Result<PhpMixed> { - self.inner.get_argument(name) - } - - fn set_argument(&mut self, name: &str, value: PhpMixed) -> anyhow::Result<()> { - self.inner.set_argument(name, value) - } - - fn has_argument(&self, name: &str) -> bool { - self.inner.has_argument(name) - } - - fn get_options(&self) -> IndexMap<String, PhpMixed> { - self.inner.get_options() - } - - fn get_option(&self, name: &str) -> anyhow::Result<PhpMixed> { - self.inner.get_option(name) - } - - fn set_option(&mut self, name: &str, value: PhpMixed) -> anyhow::Result<()> { - self.inner.set_option(name, value) - } - - fn has_option(&self, name: &str) -> bool { - self.inner.has_option(name) - } - - fn is_interactive(&self) -> bool { - self.inner.is_interactive() - } - - fn set_interactive(&mut self, interactive: bool) { - self.inner.set_interactive(interactive) - } - - fn __to_string(&self) -> String { - self.to_string() - } - - fn as_streamable(&self) -> Option<&dyn StreamableInputInterface> { - Some(self) - } -} - -impl StreamableInputInterface for ArrayInput { - fn set_stream(&mut self, stream: shirabe_php_shim::PhpResource) { - self.inner.set_stream(stream) - } - - fn get_stream(&self) -> Option<shirabe_php_shim::PhpResource> { - self.inner.get_stream() - } -} - -/// PHP `(array) $values` cast: a string becomes a single-element array. -fn to_array(values: PhpMixed) -> Vec<PhpMixed> { - match values { - PhpMixed::List(list) => list.into_iter().collect(), - PhpMixed::Array(array) => array.into_iter().map(|(_, v)| v).collect(), - PhpMixed::Null => vec![], - other => vec![other], - } -} diff --git a/crates/shirabe-external-packages/src/symfony/console/input/input.rs b/crates/shirabe-external-packages/src/symfony/console/input/input.rs deleted file mode 100644 index ceff85f6..00000000 --- a/crates/shirabe-external-packages/src/symfony/console/input/input.rs +++ /dev/null @@ -1,225 +0,0 @@ -//! ref: composer/vendor/symfony/console/Input/Input.php - -use crate::symfony::console::exception::invalid_argument_exception::InvalidArgumentException; -use crate::symfony::console::exception::runtime_exception::RuntimeException; -use crate::symfony::console::input::input_definition::InputDefinition; -use indexmap::IndexMap; -use shirabe_php_shim::{PhpMixed, PhpResource, php_regex}; - -/// Input is the base class for all concrete Input classes. -/// -/// Three concrete classes are provided by default: -/// -/// * `ArgvInput`: The input comes from the CLI arguments (argv) -/// * `StringInput`: The input is provided as a string -/// * `ArrayInput`: The input is provided as an array -#[derive(Debug, Clone)] -pub struct Input { - pub(crate) definition: InputDefinition, - pub(crate) stream: Option<PhpResource>, - pub(crate) options: IndexMap<String, PhpMixed>, - pub(crate) arguments: IndexMap<String, PhpMixed>, - pub(crate) interactive: bool, -} - -impl Input { - pub fn new(definition: Option<InputDefinition>) -> anyhow::Result<Self> { - let mut input = Input { - definition: InputDefinition::new(vec![])?, - stream: None, - options: IndexMap::new(), - arguments: IndexMap::new(), - interactive: true, - }; - - match definition { - None => { - input.definition = InputDefinition::new(vec![])?; - } - Some(definition) => { - input.bind(&definition)?; - input.validate()?; - } - } - - Ok(input) - } - - pub fn bind(&mut self, definition: &InputDefinition) -> anyhow::Result<()> { - self.arguments = IndexMap::new(); - self.options = IndexMap::new(); - self.definition = definition.clone(); - - self.parse()?; - - Ok(()) - } - - /// Processes command line arguments. - /// - /// This is abstract in PHP; concrete subclasses provide their own `parse`. - /// Since `Input` is embedded via `inner` in the subclasses, the subclass - /// drives the parsing instead. - fn parse(&mut self) -> anyhow::Result<()> { - unreachable!("Input::parse is abstract and overridden by subclasses") - } - - pub fn validate(&mut self) -> anyhow::Result<()> { - let definition = &self.definition; - let given_arguments = &self.arguments; - - let missing_arguments: Vec<String> = shirabe_php_shim::array_filter( - &shirabe_php_shim::array_keys(definition.get_arguments()), - |argument: &String| { - !given_arguments.contains_key(argument) - && definition - .get_argument(&PhpMixed::String(argument.clone())) - .map(|a| a.is_required()) - .unwrap_or(false) - }, - ); - - if !missing_arguments.is_empty() { - return Err(RuntimeException::new(format!( - "Not enough arguments (missing: \"{}\").", - shirabe_php_shim::implode(", ", &missing_arguments), - )) - .into()); - } - - Ok(()) - } - - pub fn is_interactive(&self) -> bool { - self.interactive - } - - pub fn set_interactive(&mut self, interactive: bool) { - self.interactive = interactive; - } - - pub fn get_arguments(&self) -> IndexMap<String, PhpMixed> { - shirabe_php_shim::array_merge_map( - self.definition.get_argument_defaults(), - self.arguments.clone(), - ) - } - - pub fn get_argument(&self, name: &str) -> anyhow::Result<PhpMixed> { - if !self - .definition - .has_argument(&PhpMixed::String(name.to_string())) - { - return Err(InvalidArgumentException::new(format!( - "The \"{}\" argument does not exist.", - name - )) - .into()); - } - - Ok(match self.arguments.get(name) { - Some(value) => value.clone(), - None => self - .definition - .get_argument(&PhpMixed::String(name.to_string()))? - .get_default() - .clone(), - }) - } - - pub fn set_argument(&mut self, name: &str, value: PhpMixed) -> anyhow::Result<()> { - if !self - .definition - .has_argument(&PhpMixed::String(name.to_string())) - { - return Err(InvalidArgumentException::new(format!( - "The \"{}\" argument does not exist.", - name - )) - .into()); - } - - self.arguments.insert(name.to_string(), value); - - Ok(()) - } - - pub fn has_argument(&self, name: &str) -> bool { - self.definition - .has_argument(&PhpMixed::String(name.to_string())) - } - - pub fn get_options(&self) -> IndexMap<String, PhpMixed> { - shirabe_php_shim::array_merge_map( - self.definition.get_option_defaults(), - self.options.clone(), - ) - } - - pub fn get_option(&self, name: &str) -> anyhow::Result<PhpMixed> { - if self.definition.has_negation(name) { - let value = self.get_option(&self.definition.negation_to_name(name)?)?; - if matches!(value, PhpMixed::Null) { - return Ok(value); - } - - return Ok(PhpMixed::Bool(!value.as_bool().unwrap_or(false))); - } - - if !self.definition.has_option(name) { - return Err(InvalidArgumentException::new(format!( - "The \"{}\" option does not exist.", - name - )) - .into()); - } - - Ok(if self.options.contains_key(name) { - self.options[name].clone() - } else { - self.definition.get_option(name)?.get_default().clone() - }) - } - - pub fn set_option(&mut self, name: &str, value: PhpMixed) -> anyhow::Result<()> { - if self.definition.has_negation(name) { - let negated = self.definition.negation_to_name(name)?; - self.options - .insert(negated, PhpMixed::Bool(!value.as_bool().unwrap_or(false))); - - return Ok(()); - } else if !self.definition.has_option(name) { - return Err(InvalidArgumentException::new(format!( - "The \"{}\" option does not exist.", - name - )) - .into()); - } - - self.options.insert(name.to_string(), value); - - Ok(()) - } - - pub fn has_option(&self, name: &str) -> bool { - self.definition.has_option(name) || self.definition.has_negation(name) - } - - /// Escapes a token through escapeshellarg if it contains unsafe chars. - pub fn escape_token(&self, token: &str) -> String { - let mut matches: Vec<Option<String>> = vec![]; - if shirabe_php_shim::preg_match(php_regex!("{^[\\w-]+$}"), token, &mut matches) { - token.to_string() - } else { - shirabe_php_shim::escapeshellarg(token) - } - } - - pub fn set_stream(&mut self, stream: PhpResource) { - self.stream = Some(stream); - } - - pub fn get_stream(&self) -> Option<PhpResource> { - self.stream.clone() - } -} diff --git a/crates/shirabe-external-packages/src/symfony/console/input/input_argument.rs b/crates/shirabe-external-packages/src/symfony/console/input/input_argument.rs deleted file mode 100644 index 35f62389..00000000 --- a/crates/shirabe-external-packages/src/symfony/console/input/input_argument.rs +++ /dev/null @@ -1,96 +0,0 @@ -//! ref: composer/vendor/symfony/console/Input/InputArgument.php - -use crate::symfony::console::exception::invalid_argument_exception::InvalidArgumentException; -use crate::symfony::console::exception::logic_exception::LogicException; -use shirabe_php_shim::PhpMixed; - -#[derive(Debug, Clone)] -pub struct InputArgument { - name: String, - mode: i64, - default: PhpMixed, - description: String, -} - -impl InputArgument { - pub const REQUIRED: i64 = 1; - pub const OPTIONAL: i64 = 2; - pub const IS_ARRAY: i64 = 4; - - pub fn new( - name: String, - mode: Option<i64>, - description: String, - default: PhpMixed, - ) -> anyhow::Result<Self> { - let mode = match mode { - None => Self::OPTIONAL, - Some(m) if !(1..=7).contains(&m) => { - return Err(InvalidArgumentException::new(format!( - "Argument mode \"{}\" is not valid.", - m - )) - .into()); - } - Some(m) => m, - }; - - let mut argument = InputArgument { - name, - mode, - description, - default: PhpMixed::Null, - }; - - argument.set_default(default)?; - - Ok(argument) - } - - pub fn get_name(&self) -> &str { - &self.name - } - - pub fn is_required(&self) -> bool { - Self::REQUIRED == (Self::REQUIRED & self.mode) - } - - pub fn is_array(&self) -> bool { - Self::IS_ARRAY == (Self::IS_ARRAY & self.mode) - } - - pub fn set_default(&mut self, default: PhpMixed) -> anyhow::Result<()> { - if self.is_required() && !matches!(default, PhpMixed::Null) { - return Err(LogicException::new( - "Cannot set a default value except for InputArgument::OPTIONAL mode.".to_string(), - ) - .into()); - } - - let default = if self.is_array() { - match default { - PhpMixed::Null => PhpMixed::List(vec![]), - PhpMixed::List(_) => default, - _ => { - return Err(LogicException::new( - "A default value for an array argument must be an array.".to_string(), - ) - .into()); - } - } - } else { - default - }; - - self.default = default; - Ok(()) - } - - pub fn get_default(&self) -> &PhpMixed { - &self.default - } - - pub fn get_description(&self) -> &str { - &self.description - } -} diff --git a/crates/shirabe-external-packages/src/symfony/console/input/input_aware_interface.rs b/crates/shirabe-external-packages/src/symfony/console/input/input_aware_interface.rs deleted file mode 100644 index 0d042808..00000000 --- a/crates/shirabe-external-packages/src/symfony/console/input/input_aware_interface.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! ref: composer/vendor/symfony/console/Input/InputAwareInterface.php - -use crate::symfony::console::input::input_interface::InputInterface; - -pub trait InputAwareInterface { - fn set_input(&mut self, input: Box<dyn InputInterface>); -} diff --git a/crates/shirabe-external-packages/src/symfony/console/input/input_definition.rs b/crates/shirabe-external-packages/src/symfony/console/input/input_definition.rs deleted file mode 100644 index bd32a8f9..00000000 --- a/crates/shirabe-external-packages/src/symfony/console/input/input_definition.rs +++ /dev/null @@ -1,450 +0,0 @@ -//! ref: composer/vendor/symfony/console/Input/InputDefinition.php - -use crate::symfony::console::exception::invalid_argument_exception::InvalidArgumentException; -use crate::symfony::console::exception::logic_exception::LogicException; -use crate::symfony::console::input::input_argument::InputArgument; -use crate::symfony::console::input::input_option::InputOption; -use indexmap::IndexMap; -use shirabe_php_shim::PhpMixed; - -/// A InputDefinition represents a set of valid command line arguments and options. -/// -/// `InputArgument` and `InputOption` are stored behind `Rc` to model PHP's -/// shared object references; this also lets the definition be cloned cheaply -/// (PHP `bind` assigns the definition by reference). -#[derive(Debug, Clone)] -pub struct InputDefinition { - arguments: IndexMap<String, std::rc::Rc<InputArgument>>, - required_count: i64, - last_array_argument: Option<std::rc::Rc<InputArgument>>, - last_optional_argument: Option<std::rc::Rc<InputArgument>>, - options: IndexMap<String, std::rc::Rc<InputOption>>, - negations: IndexMap<String, String>, - shortcuts: IndexMap<String, String>, -} - -/// A definition entry is either an InputArgument or an InputOption. -#[derive(Debug)] -pub enum DefinitionItem { - InputArgument(InputArgument), - InputOption(InputOption), -} - -impl InputDefinition { - pub fn new(definition: Vec<DefinitionItem>) -> anyhow::Result<Self> { - let mut input_definition = InputDefinition { - arguments: IndexMap::new(), - required_count: 0, - last_array_argument: None, - last_optional_argument: None, - options: IndexMap::new(), - negations: IndexMap::new(), - shortcuts: IndexMap::new(), - }; - input_definition.set_definition(definition)?; - Ok(input_definition) - } - - /// Builds an option-only definition that shares the given options by - /// reference, mirroring `new InputDefinition($definition->getOptions())`. - /// `InputOption` is not `Clone` and lives behind `Rc`, so the options are - /// reused rather than reconstructed by value. - pub fn from_options(options: Vec<std::rc::Rc<InputOption>>) -> anyhow::Result<Self> { - let mut input_definition = InputDefinition { - arguments: IndexMap::new(), - required_count: 0, - last_array_argument: None, - last_optional_argument: None, - options: IndexMap::new(), - negations: IndexMap::new(), - shortcuts: IndexMap::new(), - }; - for option in options { - input_definition.add_option_rc(option)?; - } - Ok(input_definition) - } - - /// Sets the definition of the input. - pub fn set_definition(&mut self, definition: Vec<DefinitionItem>) -> anyhow::Result<()> { - let mut arguments = vec![]; - let mut options = vec![]; - for item in definition { - match item { - DefinitionItem::InputOption(option) => { - options.push(option); - } - DefinitionItem::InputArgument(argument) => { - arguments.push(argument); - } - } - } - - self.set_arguments(arguments)?; - self.set_options(options)?; - - Ok(()) - } - - /// Sets the InputArgument objects. - pub fn set_arguments(&mut self, arguments: Vec<InputArgument>) -> anyhow::Result<()> { - self.arguments = IndexMap::new(); - self.required_count = 0; - self.last_optional_argument = None; - self.last_array_argument = None; - self.add_arguments(Some(arguments))?; - Ok(()) - } - - /// Adds an array of InputArgument objects. - pub fn add_arguments(&mut self, arguments: Option<Vec<InputArgument>>) -> anyhow::Result<()> { - if let Some(arguments) = arguments { - for argument in arguments { - self.add_argument(argument)?; - } - } - Ok(()) - } - - pub fn add_argument(&mut self, argument: InputArgument) -> anyhow::Result<()> { - let argument = std::rc::Rc::new(argument); - - if self.arguments.contains_key(argument.get_name()) { - return Err(LogicException::new(format!( - "An argument with name \"{}\" already exists.", - argument.get_name(), - )) - .into()); - } - - if let Some(last_array_argument) = &self.last_array_argument { - return Err(LogicException::new(format!( - "Cannot add a required argument \"{}\" after an array argument \"{}\".", - argument.get_name(), - last_array_argument.get_name(), - )) - .into()); - } - - if argument.is_required() - && let Some(last_optional_argument) = &self.last_optional_argument - { - return Err(LogicException::new(format!( - "Cannot add a required argument \"{}\" after an optional one \"{}\".", - argument.get_name(), - last_optional_argument.get_name(), - )) - .into()); - } - - if argument.is_array() { - self.last_array_argument = Some(std::rc::Rc::clone(&argument)); - } - - if argument.is_required() { - self.required_count += 1; - } else { - self.last_optional_argument = Some(std::rc::Rc::clone(&argument)); - } - - self.arguments - .insert(argument.get_name().to_string(), argument); - - Ok(()) - } - - /// Returns an InputArgument by name or by position. - pub fn get_argument(&self, name: &PhpMixed) -> anyhow::Result<std::rc::Rc<InputArgument>> { - if !self.has_argument(name) { - return Err(InvalidArgumentException::new(format!( - "The \"{}\" argument does not exist.", - name.clone() - )) - .into()); - } - - match name { - PhpMixed::Int(index) => { - let arguments: Vec<std::rc::Rc<InputArgument>> = - self.arguments.values().cloned().collect(); - Ok(std::rc::Rc::clone(&arguments[*index as usize])) - } - _ => { - let key = shirabe_php_shim::php_to_string(name); - Ok(std::rc::Rc::clone(&self.arguments[&key])) - } - } - } - - /// Returns true if an InputArgument object exists by name or position. - pub fn has_argument(&self, name: &PhpMixed) -> bool { - match name { - PhpMixed::Int(index) => { - let arguments: Vec<std::rc::Rc<InputArgument>> = - self.arguments.values().cloned().collect(); - *index >= 0 && (*index as usize) < arguments.len() - } - _ => { - let key = shirabe_php_shim::php_to_string(name); - self.arguments.contains_key(&key) - } - } - } - - /// Gets the array of InputArgument objects. - pub fn get_arguments(&self) -> &IndexMap<String, std::rc::Rc<InputArgument>> { - &self.arguments - } - - /// Returns the number of InputArguments. - pub fn get_argument_count(&self) -> i64 { - if self.last_array_argument.is_some() { - i64::MAX - } else { - self.arguments.len() as i64 - } - } - - /// Returns the number of required InputArguments. - pub fn get_argument_required_count(&self) -> i64 { - self.required_count - } - - pub fn get_argument_defaults(&self) -> IndexMap<String, PhpMixed> { - let mut values = IndexMap::new(); - for argument in self.arguments.values() { - values.insert( - argument.get_name().to_string(), - argument.get_default().clone(), - ); - } - - values - } - - /// Sets the InputOption objects. - pub fn set_options(&mut self, options: Vec<InputOption>) -> anyhow::Result<()> { - self.options = IndexMap::new(); - self.shortcuts = IndexMap::new(); - self.negations = IndexMap::new(); - self.add_options(options)?; - Ok(()) - } - - /// Adds an array of InputOption objects. - pub fn add_options(&mut self, options: Vec<InputOption>) -> anyhow::Result<()> { - for option in options { - self.add_option(option)?; - } - Ok(()) - } - - pub fn add_option(&mut self, option: InputOption) -> anyhow::Result<()> { - self.add_option_rc(std::rc::Rc::new(option)) - } - - /// Adds an option that is already shared behind `Rc`, mirroring PHP passing - /// `InputOption` objects by reference. - pub fn add_option_rc(&mut self, option: std::rc::Rc<InputOption>) -> anyhow::Result<()> { - if let Some(existing) = self.options.get(option.get_name()) - && !option.equals(existing) - { - return Err(LogicException::new(format!( - "An option named \"{}\" already exists.", - option.get_name() - )) - .into()); - } - if self.negations.contains_key(option.get_name()) { - return Err(LogicException::new(format!( - "An option named \"{}\" already exists.", - option.get_name() - )) - .into()); - } - - if let Some(shortcut) = option.get_shortcut() { - for shortcut in shirabe_php_shim::explode("|", shortcut) { - if let Some(existing_name) = self.shortcuts.get(&shortcut) - && !option.equals(&self.options[existing_name]) - { - return Err(LogicException::new(format!( - "An option with shortcut \"{}\" already exists.", - shortcut.clone(), - )) - .into()); - } - } - } - - self.options - .insert(option.get_name().to_string(), std::rc::Rc::clone(&option)); - if let Some(shortcut) = option.get_shortcut() { - for shortcut in shirabe_php_shim::explode("|", shortcut) { - self.shortcuts - .insert(shortcut, option.get_name().to_string()); - } - } - - if option.is_negatable() { - let negated_name = format!("no-{}", option.get_name()); - if self.options.contains_key(&negated_name) { - return Err(LogicException::new(format!( - "An option named \"{}\" already exists.", - negated_name - )) - .into()); - } - self.negations - .insert(negated_name, option.get_name().to_string()); - } - - Ok(()) - } - - /// Returns an InputOption by name. - pub fn get_option(&self, name: &str) -> anyhow::Result<std::rc::Rc<InputOption>> { - if !self.has_option(name) { - return Err(InvalidArgumentException::new(format!( - "The \"--{}\" option does not exist.", - name - )) - .into()); - } - - Ok(std::rc::Rc::clone(&self.options[name])) - } - - /// Returns true if an InputOption object exists by name. - /// - /// This method can't be used to check if the user included the option when - /// executing the command (use getOption() instead). - pub fn has_option(&self, name: &str) -> bool { - self.options.contains_key(name) - } - - /// Gets the array of InputOption objects. - pub fn get_options(&self) -> &IndexMap<String, std::rc::Rc<InputOption>> { - &self.options - } - - /// Returns true if an InputOption object exists by shortcut. - pub fn has_shortcut(&self, name: &str) -> bool { - self.shortcuts.contains_key(name) - } - - /// Returns true if an InputOption object exists by negated name. - pub fn has_negation(&self, name: &str) -> bool { - self.negations.contains_key(name) - } - - /// Gets an InputOption by shortcut. - pub fn get_option_for_shortcut( - &self, - shortcut: &str, - ) -> anyhow::Result<std::rc::Rc<InputOption>> { - self.get_option(&self.shortcut_to_name(shortcut)?) - } - - pub fn get_option_defaults(&self) -> IndexMap<String, PhpMixed> { - let mut values = IndexMap::new(); - for option in self.options.values() { - values.insert(option.get_name().to_string(), option.get_default().clone()); - } - - values - } - - /// Returns the InputOption name given a shortcut. - pub fn shortcut_to_name(&self, shortcut: &str) -> anyhow::Result<String> { - match self.shortcuts.get(shortcut) { - None => Err(InvalidArgumentException::new(format!( - "The \"-{}\" option does not exist.", - shortcut - )) - .into()), - Some(name) => Ok(name.clone()), - } - } - - /// Returns the InputOption name given a negation. - pub fn negation_to_name(&self, negation: &str) -> anyhow::Result<String> { - match self.negations.get(negation) { - None => Err(InvalidArgumentException::new(format!( - "The \"--{}\" option does not exist.", - negation - )) - .into()), - Some(name) => Ok(name.clone()), - } - } - - /// Gets the synopsis. - pub fn get_synopsis(&self, short: bool) -> String { - let mut elements: Vec<String> = vec![]; - - if short && !self.get_options().is_empty() { - elements.push("[options]".to_string()); - } else if !short { - for option in self.get_options().values() { - let mut value = String::new(); - if option.accept_value() { - value = format!( - " {}{}{}", - if option.is_value_optional() { - "[".to_string() - } else { - String::new() - }, - shirabe_php_shim::strtoupper(option.get_name()), - if option.is_value_optional() { - "]".to_string() - } else { - String::new() - }, - ); - } - - let shortcut = match option.get_shortcut() { - Some(shortcut) => { - format!("-{}|", shortcut) - } - None => String::new(), - }; - let negation = if option.is_negatable() { - format!("|--no-{}", option.get_name()) - } else { - String::new() - }; - elements.push(format!( - "[{}--{}{}{}]", - shortcut, - option.get_name(), - value, - negation, - )); - } - } - - if !elements.is_empty() && !self.get_arguments().is_empty() { - elements.push("[--]".to_string()); - } - - let mut tail = String::new(); - for argument in self.get_arguments().values() { - let mut element = format!("<{}>", argument.get_name()); - if argument.is_array() { - element.push_str("..."); - } - - if !argument.is_required() { - element = format!("[{}", element); - tail.push(']'); - } - - elements.push(element); - } - - format!("{}{}", shirabe_php_shim::implode(" ", &elements), tail) - } -} diff --git a/crates/shirabe-external-packages/src/symfony/console/input/input_interface.rs b/crates/shirabe-external-packages/src/symfony/console/input/input_interface.rs deleted file mode 100644 index e4beca91..00000000 --- a/crates/shirabe-external-packages/src/symfony/console/input/input_interface.rs +++ /dev/null @@ -1,60 +0,0 @@ -//! ref: composer/vendor/symfony/console/Input/InputInterface.php - -use crate::symfony::console::input::input_definition::InputDefinition; -use crate::symfony::console::input::streamable_input_interface::StreamableInputInterface; -use shirabe_php_shim::PhpMixed; - -pub trait InputInterface: std::fmt::Debug + shirabe_php_shim::AsAny { - /// Models PHP's `clone` operatior. - fn dup(&self) -> std::rc::Rc<std::cell::RefCell<dyn InputInterface>>; - - fn get_first_argument(&self) -> Option<String>; - - fn has_parameter_option(&self, values: PhpMixed, only_params: bool) -> bool; - - fn get_parameter_option( - &self, - values: PhpMixed, - default: PhpMixed, - only_params: bool, - ) -> PhpMixed; - - fn bind(&mut self, definition: &InputDefinition) -> anyhow::Result<()>; - - fn validate(&mut self) -> anyhow::Result<()>; - - fn get_arguments(&self) -> indexmap::IndexMap<String, PhpMixed>; - - fn get_argument(&self, name: &str) -> anyhow::Result<PhpMixed>; - - fn set_argument(&mut self, name: &str, value: PhpMixed) -> anyhow::Result<()>; - - fn has_argument(&self, name: &str) -> bool; - - fn get_options(&self) -> indexmap::IndexMap<String, PhpMixed>; - - fn get_option(&self, name: &str) -> anyhow::Result<PhpMixed>; - - fn set_option(&mut self, name: &str, value: PhpMixed) -> anyhow::Result<()>; - - fn has_option(&self, name: &str) -> bool; - - fn is_interactive(&self) -> bool; - - fn set_interactive(&mut self, interactive: bool); - - /// PHP's `(string) $input` (the `__toString` magic method every Symfony input implements); - /// implementors forward to their `Display` impl. - fn __to_string(&self) -> String; - - /// Models PHP's `$input instanceof StreamableInputInterface` check. Streamable inputs override - /// this to return `Some(self)`; everything else falls back to `None`. - fn as_streamable(&self) -> Option<&dyn StreamableInputInterface> { - None - } - - /// Mutable counterpart of `as_streamable`, needed to call `set_stream`/`set_interactive`. - fn as_streamable_mut(&mut self) -> Option<&mut dyn StreamableInputInterface> { - None - } -} diff --git a/crates/shirabe-external-packages/src/symfony/console/input/input_option.rs b/crates/shirabe-external-packages/src/symfony/console/input/input_option.rs deleted file mode 100644 index 79eff582..00000000 --- a/crates/shirabe-external-packages/src/symfony/console/input/input_option.rs +++ /dev/null @@ -1,193 +0,0 @@ -//! ref: composer/vendor/symfony/console/Input/InputOption.php - -use crate::symfony::console::exception::invalid_argument_exception::InvalidArgumentException; -use crate::symfony::console::exception::logic_exception::LogicException; -use shirabe_php_shim::{PhpMixed, php_regex}; - -#[derive(Debug, Clone)] -pub struct InputOption { - name: String, - shortcut: Option<String>, - mode: i64, - default: PhpMixed, - 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: PhpMixed, - mode: Option<i64>, - description: String, - default: PhpMixed, - ) -> anyhow::Result<Self> { - 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 { - PhpMixed::String(ref s) if s.is_empty() => None, - PhpMixed::List(ref v) if v.is_empty() => None, - PhpMixed::Bool(false) => None, - PhpMixed::Null => None, - PhpMixed::List(ref arr) => { - let parts: Vec<String> = arr - .iter() - .filter_map(|v| { - if let PhpMixed::String(s) = v { - Some(s.clone()) - } else { - None - } - }) - .collect(); - let joined = shirabe_php_shim::implode("|", &parts); - Self::normalize_shortcut(joined)? - } - PhpMixed::String(s) => Self::normalize_shortcut(s)?, - _ => None, - }; - - 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: PhpMixed::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: String) -> anyhow::Result<Option<String>> { - let stripped = shirabe_php_shim::ltrim(&s, Some("-")); - let parts = shirabe_php_shim::preg_split(php_regex!(r"{(\|)-?}"), &stripped); - let filtered: Vec<String> = - 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: PhpMixed) -> anyhow::Result<()> { - if Self::VALUE_NONE == (Self::VALUE_NONE & self.mode) && !matches!(default, PhpMixed::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 { - PhpMixed::Null => PhpMixed::List(vec![]), - // PHP `is_array()` accepts both list-style and associative arrays. - PhpMixed::List(_) | PhpMixed::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 { - PhpMixed::Bool(false) - }; - Ok(()) - } - - pub fn get_default(&self) -> &PhpMixed { - &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() - } -} diff --git a/crates/shirabe-external-packages/src/symfony/console/input/streamable_input_interface.rs b/crates/shirabe-external-packages/src/symfony/console/input/streamable_input_interface.rs deleted file mode 100644 index d4bdbfd3..00000000 --- a/crates/shirabe-external-packages/src/symfony/console/input/streamable_input_interface.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! ref: composer/vendor/symfony/console/Input/StreamableInputInterface.php - -use crate::symfony::console::input::input_interface::InputInterface; -use shirabe_php_shim::PhpResource; - -pub trait StreamableInputInterface: InputInterface { - fn set_stream(&mut self, stream: PhpResource); - - fn get_stream(&self) -> Option<PhpResource>; -} diff --git a/crates/shirabe-external-packages/src/symfony/console/input/string_input.rs b/crates/shirabe-external-packages/src/symfony/console/input/string_input.rs deleted file mode 100644 index 0ca8c975..00000000 --- a/crates/shirabe-external-packages/src/symfony/console/input/string_input.rs +++ /dev/null @@ -1,243 +0,0 @@ -//! ref: composer/vendor/symfony/console/Input/StringInput.php - -use crate::symfony::console::exception::invalid_argument_exception::InvalidArgumentException; -use crate::symfony::console::input::argv_input::ArgvInput; -use crate::symfony::console::input::input_definition::InputDefinition; -use crate::symfony::console::input::input_interface::InputInterface; -use crate::symfony::console::input::streamable_input_interface::StreamableInputInterface; -use indexmap::IndexMap; -use shirabe_php_shim::{CaptureKey, PhpMixed, php_regex}; - -/// StringInput represents an input provided as a string. -/// -/// Usage: -/// -/// ```php -/// $input = new StringInput('foo --bar="foobar"'); -/// ``` -#[derive(Debug, Clone)] -pub struct StringInput { - pub(crate) inner: ArgvInput, -} - -impl StringInput { - pub const REGEX_STRING: &'static str = r#"([^\s]+?)(?:\s|(?<!\\)"|(?<!\\)'|$)"#; - pub const REGEX_UNQUOTED_STRING: &'static str = r#"([^\s\\]+?)"#; - pub const REGEX_QUOTED_STRING: &'static str = - r#"(?:"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)')"#; - - pub fn new(input: &str) -> anyhow::Result<Self> { - // parent::__construct([]) - let inner = ArgvInput::new(Some(vec![]), None)?; - - let mut string_input = StringInput { inner }; - - let tokens = string_input.tokenize(input)?; - string_input.inner.set_tokens(tokens); - - Ok(string_input) - } - - /// Tokenizes a string. - fn tokenize(&self, input: &str) -> anyhow::Result<Vec<String>> { - let bytes = input.as_bytes(); - let mut tokens: Vec<String> = vec![]; - let length = shirabe_php_shim::strlen(input); - let mut cursor: i64 = 0; - let mut token: Option<String> = None; - while cursor < length { - if bytes[cursor as usize] == b'\\' { - cursor += 1; - let next: String = match bytes.get(cursor as usize) { - Some(b) => String::from_utf8_lossy(&[*b]).into_owned(), - None => String::new(), - }; - token = Some(format!("{}{}", token.unwrap_or_default(), next)); - cursor += 1; - continue; - } - - let mut m: IndexMap<CaptureKey, Option<String>> = IndexMap::new(); - if shirabe_php_shim::preg_match2( - php_regex!(r"/\s+/A"), - input, - &mut m, - 0, - cursor as usize, - ) { - if token.is_some() { - tokens.push(token.take().unwrap()); - } - cursor += - shirabe_php_shim::strlen(m[&CaptureKey::ByIndex(0)].as_deref().unwrap_or("")); - } else if shirabe_php_shim::preg_match2( - format!(r#"/([^="'\s]+?)(=?)({}+)/A"#, Self::REGEX_QUOTED_STRING), - input, - &mut m, - 0, - cursor as usize, - ) { - let inner = shirabe_php_shim::substr( - m[&CaptureKey::ByIndex(3)].as_deref().unwrap_or(""), - 1, - Some(-1), - ); - let replaced = - shirabe_php_shim::str_replace_arr(&["\"'", "'\"", "''", "\"\""], "", &inner); - token = Some(format!( - "{}{}{}{}", - token.unwrap_or_default(), - m[&CaptureKey::ByIndex(1)].as_deref().unwrap_or(""), - m[&CaptureKey::ByIndex(2)].as_deref().unwrap_or(""), - shirabe_php_shim::stripcslashes(&replaced) - )); - cursor += - shirabe_php_shim::strlen(m[&CaptureKey::ByIndex(0)].as_deref().unwrap_or("")); - } else if shirabe_php_shim::preg_match2( - format!(r"/{}/A", Self::REGEX_QUOTED_STRING), - input, - &mut m, - 0, - cursor as usize, - ) { - token = Some(format!( - "{}{}", - token.unwrap_or_default(), - shirabe_php_shim::stripcslashes(&shirabe_php_shim::substr( - m[&CaptureKey::ByIndex(0)].as_deref().unwrap_or(""), - 1, - Some(-1) - )) - )); - cursor += - shirabe_php_shim::strlen(m[&CaptureKey::ByIndex(0)].as_deref().unwrap_or("")); - } else if shirabe_php_shim::preg_match2( - format!(r"/{}/A", Self::REGEX_UNQUOTED_STRING), - input, - &mut m, - 0, - cursor as usize, - ) { - token = Some(format!( - "{}{}", - token.unwrap_or_default(), - m[&CaptureKey::ByIndex(1)].as_deref().unwrap_or("") - )); - cursor += - shirabe_php_shim::strlen(m[&CaptureKey::ByIndex(0)].as_deref().unwrap_or("")); - } else { - // should never happen - return Err(InvalidArgumentException::new(format!( - "Unable to parse input near \"... {} ...\".", - shirabe_php_shim::substr(input, cursor, Some(10)), - )) - .into()); - } - } - - if let Some(token) = token { - tokens.push(token); - } - - Ok(tokens) - } -} - -impl std::fmt::Display for StringInput { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.inner.fmt(f) - } -} - -impl InputInterface for StringInput { - fn dup(&self) -> std::rc::Rc<std::cell::RefCell<dyn InputInterface>> { - std::rc::Rc::new(std::cell::RefCell::new(self.clone())) - } - - fn get_first_argument(&self) -> Option<String> { - self.inner.get_first_argument() - } - - fn has_parameter_option(&self, values: PhpMixed, only_params: bool) -> bool { - InputInterface::has_parameter_option(&self.inner, values, only_params) - } - - fn get_parameter_option( - &self, - values: PhpMixed, - default: PhpMixed, - only_params: bool, - ) -> PhpMixed { - InputInterface::get_parameter_option(&self.inner, values, default, only_params) - } - - fn bind(&mut self, definition: &InputDefinition) -> anyhow::Result<()> { - InputInterface::bind(&mut self.inner, definition) - } - - fn validate(&mut self) -> anyhow::Result<()> { - self.inner.validate() - } - - fn get_arguments(&self) -> IndexMap<String, PhpMixed> { - InputInterface::get_arguments(&self.inner) - } - - fn get_argument(&self, name: &str) -> anyhow::Result<PhpMixed> { - self.inner.get_argument(name) - } - - fn set_argument(&mut self, name: &str, value: PhpMixed) -> anyhow::Result<()> { - self.inner.set_argument(name, value) - } - - fn has_argument(&self, name: &str) -> bool { - self.inner.has_argument(name) - } - - fn get_options(&self) -> IndexMap<String, PhpMixed> { - InputInterface::get_options(&self.inner) - } - - fn get_option(&self, name: &str) -> anyhow::Result<PhpMixed> { - self.inner.get_option(name) - } - - fn set_option(&mut self, name: &str, value: PhpMixed) -> anyhow::Result<()> { - self.inner.set_option(name, value) - } - - fn has_option(&self, name: &str) -> bool { - self.inner.has_option(name) - } - - fn is_interactive(&self) -> bool { - self.inner.is_interactive() - } - - fn set_interactive(&mut self, interactive: bool) { - self.inner.set_interactive(interactive) - } - - fn __to_string(&self) -> String { - self.to_string() - } - - fn as_streamable(&self) -> Option<&dyn StreamableInputInterface> { - Some(self) - } - - fn as_streamable_mut(&mut self) -> Option<&mut dyn StreamableInputInterface> { - Some(self) - } -} - -impl StreamableInputInterface for StringInput { - fn set_stream(&mut self, stream: shirabe_php_shim::PhpResource) { - self.inner.set_stream(stream) - } - - fn get_stream(&self) -> Option<shirabe_php_shim::PhpResource> { - self.inner.get_stream() - } -} |
