From 6051927c8fa32cfffa102d2a170c5a6cf747a1b9 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sun, 9 Aug 2026 11:19:03 +0900 Subject: refactor(symfony-console): extract symfony/console into the shirabe-symfony-console crate Move `Symfony\Component\Console` out of shirabe-external-packages and into its own crate, so the path is `shirabe_symfony_console::application::Application` instead of `shirabe_external_packages::symfony::console::application::Application`. The `delegate_to_inner!` and `delegate_command_trait_impls_to_inner!` macros move with it. Co-Authored-By: Claude Opus 5 (1M context) --- .../symfony/console/formatter/output_formatter.rs | 356 --------------------- .../formatter/output_formatter_interface.rs | 29 -- .../console/formatter/output_formatter_style.rs | 106 ------ .../formatter/output_formatter_style_interface.rs | 27 -- .../formatter/output_formatter_style_stack.rs | 100 ------ .../wrappable_output_formatter_interface.rs | 13 - 6 files changed, 631 deletions(-) delete mode 100644 crates/shirabe-external-packages/src/symfony/console/formatter/output_formatter.rs delete mode 100644 crates/shirabe-external-packages/src/symfony/console/formatter/output_formatter_interface.rs delete mode 100644 crates/shirabe-external-packages/src/symfony/console/formatter/output_formatter_style.rs delete mode 100644 crates/shirabe-external-packages/src/symfony/console/formatter/output_formatter_style_interface.rs delete mode 100644 crates/shirabe-external-packages/src/symfony/console/formatter/output_formatter_style_stack.rs delete mode 100644 crates/shirabe-external-packages/src/symfony/console/formatter/wrappable_output_formatter_interface.rs (limited to 'crates/shirabe-external-packages/src/symfony/console/formatter') diff --git a/crates/shirabe-external-packages/src/symfony/console/formatter/output_formatter.rs b/crates/shirabe-external-packages/src/symfony/console/formatter/output_formatter.rs deleted file mode 100644 index 08079a7c..00000000 --- a/crates/shirabe-external-packages/src/symfony/console/formatter/output_formatter.rs +++ /dev/null @@ -1,356 +0,0 @@ -//! ref: composer/vendor/symfony/console/Formatter/OutputFormatter.php - -use crate::symfony::console::exception::invalid_argument_exception::InvalidArgumentException; -use crate::symfony::console::formatter::output_formatter_interface::OutputFormatterInterface; -use crate::symfony::console::formatter::output_formatter_style::OutputFormatterStyle; -use crate::symfony::console::formatter::output_formatter_style_interface::OutputFormatterStyleInterface; -use crate::symfony::console::formatter::output_formatter_style_stack::OutputFormatterStyleStack; -use crate::symfony::console::formatter::wrappable_output_formatter_interface::WrappableOutputFormatterInterface; -use shirabe_php_shim::php_regex; -use shirabe_symfony_string::b; - -/// Formatter class for console output. -#[derive(Debug)] -pub struct OutputFormatter { - decorated: bool, - // PHP objects have reference semantics: get_style must hand out the shared - // style instance, so each style is held behind an Rc> handle. - styles: indexmap::IndexMap< - String, - std::rc::Rc>>, - >, - style_stack: OutputFormatterStyleStack, -} - -impl OutputFormatter { - /// Escapes "<" and ">" special chars in given text. - pub fn escape(text: &str) -> anyhow::Result { - let text = - shirabe_php_shim::preg_replace(php_regex!("/([^\\\\]|^)([<>])/"), "$1\\\\$2", text); - - Ok(Self::escape_trailing_backslash(&text)) - } - - /// Escapes trailing "\" in given text. - pub fn escape_trailing_backslash(text: &str) -> String { - let mut text = text.to_string(); - if shirabe_php_shim::str_ends_with(&text, "\\") { - let len = shirabe_php_shim::strlen(&text); - text = shirabe_php_shim::rtrim(&text, Some("\\")); - text = shirabe_php_shim::str_replace("\0", "", &text); - text.push_str(&shirabe_php_shim::str_repeat( - "\0", - (len - shirabe_php_shim::strlen(&text)) as usize, - )); - } - - text - } - - /// Initializes console output formatter. - /// - /// `styles` is an array of "name => FormatterStyle" instances. - pub fn new( - decorated: bool, - styles: indexmap::IndexMap>, - ) -> Self { - let mut this = Self { - decorated, - styles: indexmap::IndexMap::new(), - style_stack: OutputFormatterStyleStack::new(None), - }; - - this.set_style( - "error", - Box::new(OutputFormatterStyle::new( - Some("white"), - Some("red"), - vec![], - )), - ); - this.set_style( - "info", - Box::new(OutputFormatterStyle::new(Some("green"), None, vec![])), - ); - this.set_style( - "comment", - Box::new(OutputFormatterStyle::new(Some("yellow"), None, vec![])), - ); - this.set_style( - "question", - Box::new(OutputFormatterStyle::new( - Some("black"), - Some("cyan"), - vec![], - )), - ); - - for (name, style) in styles { - this.set_style(&name, style); - } - - this.style_stack = OutputFormatterStyleStack::new(None); - - this - } - - pub fn get_style_stack(&self) -> &OutputFormatterStyleStack { - &self.style_stack - } - - /// Tries to create new style instance from string. - fn create_style_from_string( - &self, - string: &str, - ) -> anyhow::Result>> { - if let Some(style) = self.styles.get(string) { - return Ok(Some(style.borrow().clone_box())); - } - - let mut matches: Vec> = vec![]; - if shirabe_php_shim::preg_match_all_set_order( - php_regex!("/([^=]+)=([^;]+)(;|$)/"), - string, - &mut matches, - ) == 0 - { - return Ok(None); - } - - let mut style = OutputFormatterStyle::new(None, None, vec![]); - for r#match in &matches { - let mut r#match: Vec = r#match.clone(); - shirabe_php_shim::array_shift(&mut r#match); - r#match[0] = shirabe_php_shim::strtolower(&r#match[0]); - - if r#match[0] == "fg" { - style.set_foreground(Some(&shirabe_php_shim::strtolower(&r#match[1]))); - } else if r#match[0] == "bg" { - style.set_background(Some(&shirabe_php_shim::strtolower(&r#match[1]))); - } else if r#match[0] == "href" { - let url = - shirabe_php_shim::preg_replace(php_regex!("{\\\\([<>])}"), "$1", &r#match[1]); - style.set_href(&url); - } else if r#match[0] == "options" { - let mut options = shirabe_php_shim::preg_match_all( - php_regex!("([^,;]+)"), - &shirabe_php_shim::strtolower(&r#match[1]), - ); - let options = shirabe_php_shim::array_shift(&mut options).unwrap_or_default(); - for option in &options { - style.set_option(option); - } - } else { - return Ok(None); - } - } - - Ok(Some(Box::new(style))) - } - - /// Applies current style from stack to text, if must be applied. - fn apply_current_style( - &mut self, - text: &str, - current: &str, - width: i64, - current_line_length: &mut i64, - ) -> String { - if text.is_empty() { - return String::new(); - } - - if width == 0 { - return if self.is_decorated() { - self.style_stack.get_current_mut().apply(text) - } else { - text.to_string() - }; - } - - let mut text = text.to_string(); - - if *current_line_length == 0 && !current.is_empty() { - text = shirabe_php_shim::ltrim(&text, None); - } - - let prefix; - if *current_line_length != 0 { - let i = width - *current_line_length; - prefix = format!("{}\n", shirabe_php_shim::substr(&text, 0, Some(i))); - text = shirabe_php_shim::substr(&text, i, None); - } else { - prefix = String::new(); - } - - let mut matches: Vec> = vec![]; - shirabe_php_shim::preg_match(php_regex!("~(\\n)$~"), &text, &mut matches); - text = format!("{}{}", prefix, self.add_line_breaks(&text, width)); - let trailing = matches.get(1).and_then(|m| m.clone()).unwrap_or_default(); - text = format!("{}{}", shirabe_php_shim::rtrim(&text, Some("\n")), trailing); - - if *current_line_length == 0 - && !current.is_empty() - && shirabe_php_shim::substr(current, -1, None) != "\n" - { - text = format!("\n{text}"); - } - - let mut lines = shirabe_php_shim::explode("\n", &text); - - for line in &lines { - *current_line_length += shirabe_php_shim::strlen(line); - if width <= *current_line_length { - *current_line_length = 0; - } - } - - if self.is_decorated() { - for line in lines.iter_mut() { - *line = self.style_stack.get_current_mut().apply(line); - } - } - - shirabe_php_shim::implode("\n", &lines) - } - - fn add_line_breaks(&self, text: &str, width: i64) -> String { - let encoding = shirabe_php_shim::mb_detect_encoding(text, None, true) - .filter(|s| !s.is_empty()) - .unwrap_or_else(|| "UTF-8".to_string()); - - b(text) - .to_code_point_string(&encoding) - .wordwrap(width, "\n", true) - .to_byte_string(&encoding) - } -} - -impl OutputFormatterInterface for OutputFormatter { - fn set_decorated(&mut self, decorated: bool) { - self.decorated = decorated; - } - - fn is_decorated(&self) -> bool { - self.decorated - } - - fn set_style(&mut self, name: &str, style: Box) { - self.styles.insert( - shirabe_php_shim::strtolower(name), - std::rc::Rc::new(std::cell::RefCell::new(style)), - ); - } - - fn has_style(&self, name: &str) -> bool { - self.styles - .contains_key(&shirabe_php_shim::strtolower(name)) - } - - fn get_style( - &self, - name: &str, - ) -> anyhow::Result>>> - { - if !self.has_style(name) { - return Err(InvalidArgumentException::new(format!( - "Undefined style: \"{}\".", - shirabe_php_shim::PhpMixed::String(name.to_string()), - )) - .into()); - } - - Ok(std::rc::Rc::clone( - &self.styles[&shirabe_php_shim::strtolower(name)], - )) - } - - fn format(&mut self, message: Option<&str>) -> anyhow::Result> { - self.format_and_wrap(message, 0) - } -} - -impl WrappableOutputFormatterInterface for OutputFormatter { - fn format_and_wrap( - &mut self, - message: Option<&str>, - width: i64, - ) -> anyhow::Result> { - let message = match message { - None => return Ok(Some(String::new())), - Some(message) => message, - }; - - let mut offset: i64 = 0; - let mut output = String::new(); - // Accurate PCRE patterns (possessive quantifiers `*+`), unsupported by the - // `regex` crate: - // let open_tag_regex = "[a-z](?:[^\\\\<>]*+ | \\\\.)*"; - // let close_tag_regex = "[a-z][^<>]*+"; - // TODO(phase-c): restore the possessive quantifiers once a PCRE-compatible - // engine is available; greedy quantifiers match the same tags here but may - // differ in pathological backtracking cases. - let open_tag_regex = "[a-z](?:[^\\\\<>]* | \\\\.)*"; - let close_tag_regex = "[a-z][^<>]*"; - let mut current_line_length: i64 = 0; - let mut matches: shirabe_php_shim::PregOffsetCaptureMatches = Default::default(); - shirabe_php_shim::preg_match_all_offset_capture( - format!("#<(({open_tag_regex}) | /({close_tag_regex})?)>#ix"), - message, - &mut matches, - ); - let count = matches.group(0).len(); - for i in 0..count { - let (text, pos) = matches.group(0)[i].clone(); - let pos = pos as i64; - - if pos != 0 && shirabe_php_shim::byte_at(message, (pos - 1) as usize) == b'\\' { - continue; - } - - // add the text up to the next tag - let segment = shirabe_php_shim::substr(message, offset, Some(pos - offset)); - let applied = - self.apply_current_style(&segment, &output, width, &mut current_line_length); - output.push_str(&applied); - offset = pos + shirabe_php_shim::strlen(&text); - - // opening tag? - let open = shirabe_php_shim::byte_at(&text, 1) != b'/'; - let tag = if open { - matches.group(1)[i].0.clone() - } else { - matches - .group(3) - .get(i) - .map(|m| m.0.clone()) - .unwrap_or_default() - }; - - if !open && tag.is_empty() { - // - self.style_stack.pop(None)?.ok(); - } else if let Some(style) = self.create_style_from_string(&tag)? { - if open { - self.style_stack.push(style); - } else { - self.style_stack.pop(Some(style))?.ok(); - } - } else { - let applied = - self.apply_current_style(&text, &output, width, &mut current_line_length); - output.push_str(&applied); - } - } - - let segment = shirabe_php_shim::substr(message, offset, None); - let applied = self.apply_current_style(&segment, &output, width, &mut current_line_length); - output.push_str(&applied); - - let mut pairs = indexmap::IndexMap::new(); - pairs.insert("\0".to_string(), "\\".to_string()); - pairs.insert("\\<".to_string(), "<".to_string()); - pairs.insert("\\>".to_string(), ">".to_string()); - Ok(Some(shirabe_php_shim::strtr_array(&output, &pairs))) - } -} diff --git a/crates/shirabe-external-packages/src/symfony/console/formatter/output_formatter_interface.rs b/crates/shirabe-external-packages/src/symfony/console/formatter/output_formatter_interface.rs deleted file mode 100644 index ad2b6954..00000000 --- a/crates/shirabe-external-packages/src/symfony/console/formatter/output_formatter_interface.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! ref: composer/vendor/symfony/console/Formatter/OutputFormatterInterface.php - -use crate::symfony::console::formatter::output_formatter_style_interface::OutputFormatterStyleInterface; - -/// Formatter interface for console output. -pub trait OutputFormatterInterface: shirabe_php_shim::AsAny { - /// Sets the decorated flag. - fn set_decorated(&mut self, decorated: bool); - - /// Whether the output will decorate messages. - fn is_decorated(&self) -> bool; - - /// Sets a new style. - fn set_style(&mut self, name: &str, style: Box); - - /// Checks if output formatter has style with specified name. - fn has_style(&self, name: &str) -> bool; - - /// Gets style options from style with specified name. - /// - /// Throws InvalidArgumentException when style isn't defined. - fn get_style( - &self, - name: &str, - ) -> anyhow::Result>>>; - - /// Formats a message according to the given styles. - fn format(&mut self, message: Option<&str>) -> anyhow::Result>; -} diff --git a/crates/shirabe-external-packages/src/symfony/console/formatter/output_formatter_style.rs b/crates/shirabe-external-packages/src/symfony/console/formatter/output_formatter_style.rs deleted file mode 100644 index dac390fe..00000000 --- a/crates/shirabe-external-packages/src/symfony/console/formatter/output_formatter_style.rs +++ /dev/null @@ -1,106 +0,0 @@ -//! ref: composer/vendor/symfony/console/Formatter/OutputFormatterStyle.php - -use crate::symfony::console::color::Color; -use crate::symfony::console::formatter::output_formatter_style_interface::OutputFormatterStyleInterface; - -/// Formatter style class for defining styles. -#[derive(Debug, Clone)] -pub struct OutputFormatterStyle { - color: Color, - foreground: String, - background: String, - options: Vec, - href: Option, - handles_href_gracefully: Option, -} - -impl OutputFormatterStyle { - /// Initializes output formatter style. - pub fn new(foreground: Option<&str>, background: Option<&str>, options: Vec) -> Self { - let foreground = foreground - .filter(|s| !s.is_empty()) - .unwrap_or("") - .to_string(); - let background = background - .filter(|s| !s.is_empty()) - .unwrap_or("") - .to_string(); - let color = Color::new(&foreground, &background, &options).unwrap(); - Self { - color, - foreground, - background, - options, - href: None, - handles_href_gracefully: None, - } - } - - pub fn set_href(&mut self, url: &str) { - self.href = Some(url.to_string()); - } -} - -impl OutputFormatterStyleInterface for OutputFormatterStyle { - fn set_foreground(&mut self, color: Option<&str>) { - self.foreground = color.filter(|s| !s.is_empty()).unwrap_or("").to_string(); - self.color = Color::new(&self.foreground, &self.background, &self.options.clone()).unwrap(); - } - - fn set_background(&mut self, color: Option<&str>) { - self.background = color.filter(|s| !s.is_empty()).unwrap_or("").to_string(); - self.color = Color::new(&self.foreground, &self.background, &self.options.clone()).unwrap(); - } - - fn set_option(&mut self, option: &str) { - self.options.push(option.to_string()); - self.color = Color::new(&self.foreground, &self.background, &self.options.clone()).unwrap(); - } - - fn unset_option(&mut self, option: &str) { - let pos = shirabe_php_shim::array_search_in_vec(option, &self.options); - if let Some(pos) = pos { - self.options.remove(pos); - } - - self.color = Color::new(&self.foreground, &self.background, &self.options.clone()).unwrap(); - } - - fn set_options(&mut self, options: Vec) { - self.options = options; - self.color = Color::new(&self.foreground, &self.background, &self.options.clone()).unwrap(); - } - - fn apply(&mut self, text: &str) -> String { - let mut text = text.to_string(); - - if self.handles_href_gracefully.is_none() { - self.handles_href_gracefully = Some( - shirabe_php_shim::getenv("TERMINAL_EMULATOR").as_deref() - != Some(std::ffi::OsStr::new("JetBrains-JediTerm")) - && (shirabe_php_shim::getenv("KONSOLE_VERSION").is_none_or(|v| v.is_empty()) - || shirabe_php_shim::getenv("KONSOLE_VERSION") - .map(|v| v.to_string_lossy().parse::().unwrap_or(0)) - .unwrap_or(0) - > 201100) - && shirabe_php_shim::PHP_SERVER - .lock() - .unwrap() - .get("IDEA_INITIAL_DIRECTORY") - .is_none(), - ); - } - - if let Some(href) = &self.href - && self.handles_href_gracefully == Some(true) - { - text = format!("\x1b]8;;{href}\x1b\\{text}\x1b]8;;\x1b\\"); - } - - self.color.apply(&text) - } - - fn clone_box(&self) -> Box { - Box::new(self.clone()) - } -} diff --git a/crates/shirabe-external-packages/src/symfony/console/formatter/output_formatter_style_interface.rs b/crates/shirabe-external-packages/src/symfony/console/formatter/output_formatter_style_interface.rs deleted file mode 100644 index dfbffa5d..00000000 --- a/crates/shirabe-external-packages/src/symfony/console/formatter/output_formatter_style_interface.rs +++ /dev/null @@ -1,27 +0,0 @@ -//! ref: composer/vendor/symfony/console/Formatter/OutputFormatterStyleInterface.php - -/// Formatter style interface for defining styles. -pub trait OutputFormatterStyleInterface: std::fmt::Debug { - /// Sets style foreground color. - fn set_foreground(&mut self, color: Option<&str>); - - /// Sets style background color. - fn set_background(&mut self, color: Option<&str>); - - /// Sets some specific style option. - fn set_option(&mut self, option: &str); - - /// Unsets some specific style option. - fn unset_option(&mut self, option: &str); - - /// Sets multiple style options at once. - fn set_options(&mut self, options: Vec); - - /// Applies the style to a given text. - fn apply(&mut self, text: &str) -> String; - - /// Clones the style into a new boxed trait object. PHP shares the style - /// instance by reference; styles are immutable once configured, so cloning - /// is behaviorally equivalent here. - fn clone_box(&self) -> Box; -} diff --git a/crates/shirabe-external-packages/src/symfony/console/formatter/output_formatter_style_stack.rs b/crates/shirabe-external-packages/src/symfony/console/formatter/output_formatter_style_stack.rs deleted file mode 100644 index acfd8878..00000000 --- a/crates/shirabe-external-packages/src/symfony/console/formatter/output_formatter_style_stack.rs +++ /dev/null @@ -1,100 +0,0 @@ -//! ref: composer/vendor/symfony/console/Formatter/OutputFormatterStyleStack.php - -use crate::symfony::console::exception::invalid_argument_exception::InvalidArgumentException; -use crate::symfony::console::formatter::output_formatter_style::OutputFormatterStyle; -use crate::symfony::console::formatter::output_formatter_style_interface::OutputFormatterStyleInterface; - -#[derive(Debug)] -pub struct OutputFormatterStyleStack { - styles: Vec>, - empty_style: Box, -} - -impl OutputFormatterStyleStack { - pub fn new(empty_style: Option>) -> Self { - let empty_style = - empty_style.unwrap_or_else(|| Box::new(OutputFormatterStyle::new(None, None, vec![]))); - let mut this = Self { - styles: vec![], - empty_style, - }; - this.reset(); - this - } - - /// Pushes a style in the stack. - pub fn push(&mut self, style: Box) { - self.styles.push(style); - } - - /// Pops a style from the stack. - /// - /// Throws InvalidArgumentException when style tags incorrectly nested. - pub fn pop( - &mut self, - mut style: Option>, - ) -> anyhow::Result, InvalidArgumentException>> - { - if self.styles.is_empty() { - return Ok(Ok(self.empty_style.clone_box())); - } - - let style = match style.as_mut() { - None => { - return Ok(Ok(shirabe_php_shim::array_pop(&mut self.styles).unwrap())); - } - Some(style) => style, - }; - - for index in (0..self.styles.len()).rev() { - if style.apply("") == self.styles[index].apply("") { - // PHP: array_slice($this->styles, 0, $index) keeps elements before $index, - // dropping the matched element and everything after it. - let stacked_style = self.styles.remove(index); - self.styles.truncate(index); - - return Ok(Ok(stacked_style)); - } - } - - Ok(Err(InvalidArgumentException::new( - "Incorrectly nested style tag found.".to_string(), - ))) - } - - /// Computes current style with stacks top codes. - pub fn get_current(&self) -> &dyn OutputFormatterStyleInterface { - if self.styles.is_empty() { - return self.empty_style.as_ref(); - } - - self.styles[self.styles.len() - 1].as_ref() - } - - /// Mutable variant of `get_current`, needed because `apply` lazily mutates style state. - pub fn get_current_mut(&mut self) -> &mut dyn OutputFormatterStyleInterface { - if self.styles.is_empty() { - return self.empty_style.as_mut(); - } - - let last = self.styles.len() - 1; - self.styles[last].as_mut() - } - - pub fn set_empty_style( - &mut self, - empty_style: Box, - ) -> &mut Self { - self.empty_style = empty_style; - - self - } - - pub fn get_empty_style(&self) -> &dyn OutputFormatterStyleInterface { - self.empty_style.as_ref() - } - - pub fn reset(&mut self) { - self.styles = vec![]; - } -} diff --git a/crates/shirabe-external-packages/src/symfony/console/formatter/wrappable_output_formatter_interface.rs b/crates/shirabe-external-packages/src/symfony/console/formatter/wrappable_output_formatter_interface.rs deleted file mode 100644 index 7c0d821e..00000000 --- a/crates/shirabe-external-packages/src/symfony/console/formatter/wrappable_output_formatter_interface.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! ref: composer/vendor/symfony/console/Formatter/WrappableOutputFormatterInterface.php - -use crate::symfony::console::formatter::output_formatter_interface::OutputFormatterInterface; - -/// Formatter interface for console output that supports word wrapping. -pub trait WrappableOutputFormatterInterface: OutputFormatterInterface { - /// Formats a message according to the given styles, wrapping at `width` (0 means no wrapping). - fn format_and_wrap( - &mut self, - message: Option<&str>, - width: i64, - ) -> anyhow::Result>; -} -- cgit v1.3.1-4-g156e