diff options
Diffstat (limited to 'crates/shirabe-external-packages/src/symfony/console/input/argv_input.rs')
| -rw-r--r-- | crates/shirabe-external-packages/src/symfony/console/input/argv_input.rs | 657 |
1 files changed, 0 insertions, 657 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)], - } -} |
