diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-06-12 01:01:35 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-06-12 01:01:43 +0900 |
| commit | 1e44f5723e4c0e0903d00a61f254901e612fe5e1 (patch) | |
| tree | 9c2e1eec364bbf6418a4072ee7767b04c3df0297 /crates/shirabe-external-packages/src/symfony/console/input/string_input.rs | |
| parent | ddf0a624145b618c05c2ee47414545b99b685f37 (diff) | |
| download | php-shirabe-1e44f5723e4c0e0903d00a61f254901e612fe5e1.tar.gz php-shirabe-1e44f5723e4c0e0903d00a61f254901e612fe5e1.tar.zst php-shirabe-1e44f5723e4c0e0903d00a61f254901e612fe5e1.zip | |
feat(symfony-console): port Symfony Console and make the workspace compile
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe-external-packages/src/symfony/console/input/string_input.rs')
| -rw-r--r-- | crates/shirabe-external-packages/src/symfony/console/input/string_input.rs | 193 |
1 files changed, 160 insertions, 33 deletions
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 index 34b2bfc..aad1d86 100644 --- a/crates/shirabe-external-packages/src/symfony/console/input/string_input.rs +++ b/crates/shirabe-external-packages/src/symfony/console/input/string_input.rs @@ -1,66 +1,193 @@ -use crate::symfony::console::input::InputDefinition; -use crate::symfony::console::input::InputInterface; +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 indexmap::IndexMap; use shirabe_php_shim::PhpMixed; +/// StringInput represents an input provided as a string. +/// +/// Usage: +/// +/// $input = new StringInput('foo --bar="foobar"'); #[derive(Debug)] -pub struct StringInput; +pub struct StringInput { + pub(crate) inner: ArgvInput, +} impl StringInput { - pub fn new(_input: &str) -> Self { - todo!() + 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: Vec<String> = vec![]; + if shirabe_php_shim::preg_match_offset(r"/\s+/A", input, &mut m, 0, cursor) { + if token.is_some() { + tokens.push(token.take().unwrap()); + } + cursor += shirabe_php_shim::strlen(&m[0]); + } else if shirabe_php_shim::preg_match_offset( + &format!(r#"/([^="'\s]+?)(=?)({}+)/A"#, Self::REGEX_QUOTED_STRING), + input, + &mut m, + 0, + cursor, + ) { + let inner = shirabe_php_shim::substr(&m[3], 1, Some(-1)); + let replaced = + shirabe_php_shim::str_replace_arr(&["\"'", "'\"", "''", "\"\""], "", &inner); + token = Some(format!( + "{}{}{}{}", + token.unwrap_or_default(), + m[1], + m[2], + shirabe_php_shim::stripcslashes(&replaced) + )); + cursor += shirabe_php_shim::strlen(&m[0]); + } else if shirabe_php_shim::preg_match_offset( + &format!(r"/{}/A", Self::REGEX_QUOTED_STRING), + input, + &mut m, + 0, + cursor, + ) { + token = Some(format!( + "{}{}", + token.unwrap_or_default(), + shirabe_php_shim::stripcslashes(&shirabe_php_shim::substr(&m[0], 1, Some(-1))) + )); + cursor += shirabe_php_shim::strlen(&m[0]); + } else if shirabe_php_shim::preg_match_offset( + &format!(r"/{}/A", Self::REGEX_UNQUOTED_STRING), + input, + &mut m, + 0, + cursor, + ) { + token = Some(format!("{}{}", token.unwrap_or_default(), m[1])); + cursor += shirabe_php_shim::strlen(&m[0]); + } else { + // should never happen + return Err( + InvalidArgumentException(shirabe_php_shim::InvalidArgumentException { + message: shirabe_php_shim::sprintf( + "Unable to parse input near \"... %s ...\".", + &[PhpMixed::String(shirabe_php_shim::substr( + input, + cursor, + Some(10), + ))], + ), + code: 0, + }) + .into(), + ); + } + } + + if let Some(token) = token { + tokens.push(token); + } + + Ok(tokens) } } impl InputInterface for StringInput { fn get_first_argument(&self) -> Option<String> { - todo!() + self.inner.get_first_argument() } - fn has_parameter_option(&self, _values: &[&str], _only_params: bool) -> bool { - todo!() + + 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: &[&str], - _default: PhpMixed, - _only_params: bool, + values: PhpMixed, + default: PhpMixed, + only_params: bool, ) -> PhpMixed { - todo!() + InputInterface::get_parameter_option(&self.inner, values, default, only_params) } - fn bind(&mut self, _definition: &InputDefinition) -> anyhow::Result<()> { - todo!() + + fn bind(&mut self, definition: &InputDefinition) -> anyhow::Result<()> { + InputInterface::bind(&mut self.inner, definition) } - fn validate(&self) -> anyhow::Result<()> { - todo!() + + fn validate(&mut self) -> anyhow::Result<()> { + self.inner.validate() } + fn get_arguments(&self) -> IndexMap<String, PhpMixed> { - todo!() + InputInterface::get_arguments(&self.inner) } - fn get_argument(&self, _name: &str) -> PhpMixed { - todo!() + + 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<()> { - todo!() + + fn set_argument(&mut self, name: &str, value: PhpMixed) -> anyhow::Result<()> { + self.inner.set_argument(name, value) } - fn has_argument(&self, _name: &str) -> bool { - todo!() + + fn has_argument(&self, name: &str) -> bool { + self.inner.has_argument(name) } + fn get_options(&self) -> IndexMap<String, PhpMixed> { - todo!() + InputInterface::get_options(&self.inner) } - fn get_option(&self, _name: &str) -> PhpMixed { - todo!() + + 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<()> { - todo!() + + fn set_option(&mut self, name: &str, value: PhpMixed) -> anyhow::Result<()> { + self.inner.set_option(name, value) } - fn has_option(&self, _name: &str) -> bool { - todo!() + + fn has_option(&self, name: &str) -> bool { + self.inner.has_option(name) } + fn is_interactive(&self) -> bool { - todo!() + self.inner.is_interactive() } - fn set_interactive(&mut self, _interactive: bool) { - todo!() + + fn set_interactive(&mut self, interactive: bool) { + self.inner.set_interactive(interactive) } } |
