aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe-external-packages/src/symfony/console/helper
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-08-09 11:19:03 +0900
committernsfisis <nsfisis@gmail.com>2026-08-09 11:19:03 +0900
commit6051927c8fa32cfffa102d2a170c5a6cf747a1b9 (patch)
tree3fe6769c90cb03ca8f1b9b825e38ad459182361e /crates/shirabe-external-packages/src/symfony/console/helper
parente3e8806aec771e482899ed3470e920f7b291fa95 (diff)
downloadphp-shirabe-6051927c8fa32cfffa102d2a170c5a6cf747a1b9.tar.gz
php-shirabe-6051927c8fa32cfffa102d2a170c5a6cf747a1b9.tar.zst
php-shirabe-6051927c8fa32cfffa102d2a170c5a6cf747a1b9.zip
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) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe-external-packages/src/symfony/console/helper')
-rw-r--r--crates/shirabe-external-packages/src/symfony/console/helper/debug_formatter_helper.rs175
-rw-r--r--crates/shirabe-external-packages/src/symfony/console/helper/descriptor_helper.rs121
-rw-r--r--crates/shirabe-external-packages/src/symfony/console/helper/formatter_helper.rs99
-rw-r--r--crates/shirabe-external-packages/src/symfony/console/helper/helper.rs162
-rw-r--r--crates/shirabe-external-packages/src/symfony/console/helper/helper_interface.rs15
-rw-r--r--crates/shirabe-external-packages/src/symfony/console/helper/helper_set.rs76
-rw-r--r--crates/shirabe-external-packages/src/symfony/console/helper/process_helper.rs310
-rw-r--r--crates/shirabe-external-packages/src/symfony/console/helper/progress_bar.rs835
-rw-r--r--crates/shirabe-external-packages/src/symfony/console/helper/question_helper.rs916
-rw-r--r--crates/shirabe-external-packages/src/symfony/console/helper/symfony_question_helper.rs163
-rw-r--r--crates/shirabe-external-packages/src/symfony/console/helper/table.rs1443
-rw-r--r--crates/shirabe-external-packages/src/symfony/console/helper/table_cell.rs99
-rw-r--r--crates/shirabe-external-packages/src/symfony/console/helper/table_cell_style.rs114
-rw-r--r--crates/shirabe-external-packages/src/symfony/console/helper/table_rows.rs45
-rw-r--r--crates/shirabe-external-packages/src/symfony/console/helper/table_separator.rs47
-rw-r--r--crates/shirabe-external-packages/src/symfony/console/helper/table_style.rs289
16 files changed, 0 insertions, 4909 deletions
diff --git a/crates/shirabe-external-packages/src/symfony/console/helper/debug_formatter_helper.rs b/crates/shirabe-external-packages/src/symfony/console/helper/debug_formatter_helper.rs
deleted file mode 100644
index 6fcbce8e..00000000
--- a/crates/shirabe-external-packages/src/symfony/console/helper/debug_formatter_helper.rs
+++ /dev/null
@@ -1,175 +0,0 @@
-//! ref: composer/vendor/symfony/console/Helper/DebugFormatterHelper.php
-
-use crate::symfony::console::helper::helper::Helper;
-use crate::symfony::console::helper::helper_interface::HelperInterface;
-use crate::symfony::console::helper::helper_set::HelperSet;
-use indexmap::IndexMap;
-
-const COLORS: [&str; 9] = [
- "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white", "default",
-];
-
-/// Helps outputting debug information when running an external program from a command.
-///
-/// An external program can be a Process, an HTTP request, or anything else.
-#[derive(Debug)]
-pub struct DebugFormatterHelper {
- inner: Helper,
- started: IndexMap<String, DebugFormatterSession>,
- count: i64,
-}
-
-/// Per-id session state. PHP stores this as `['border' => int, 'out' => true, 'err' => true]`
-/// where presence of `out`/`err` keys is tested via `isset` and removed via `unset`.
-#[derive(Debug, Default)]
-struct DebugFormatterSession {
- border: i64,
- out: bool,
- err: bool,
-}
-
-impl Default for DebugFormatterHelper {
- fn default() -> Self {
- Self {
- inner: Helper::default(),
- started: IndexMap::new(),
- count: -1,
- }
- }
-}
-
-impl DebugFormatterHelper {
- /// Starts a debug formatting session.
- pub fn start(&mut self, id: &str, message: &str, prefix: &str) -> String {
- self.count += 1;
- self.started.insert(
- id.to_string(),
- DebugFormatterSession {
- border: self.count % COLORS.len() as i64,
- out: false,
- err: false,
- },
- );
-
- format!(
- "{}<bg=blue;fg=white> {} </> <fg=blue>{}</>\n",
- self.get_border(id),
- prefix,
- message,
- )
- }
-
- /// Adds progress to a formatting session.
- pub fn progress(
- &mut self,
- id: &str,
- buffer: &str,
- error: bool,
- prefix: &str,
- error_prefix: &str,
- ) -> String {
- let mut message = String::new();
-
- if error {
- if self.started[id].out {
- message.push('\n');
- self.started.get_mut(id).unwrap().out = false;
- }
- if !self.started[id].err {
- message.push_str(&format!(
- "{}<bg=red;fg=white> {} </> ",
- self.get_border(id),
- error_prefix,
- ));
- self.started.get_mut(id).unwrap().err = true;
- }
-
- message.push_str(&shirabe_php_shim::str_replace(
- "\n",
- &format!(
- "\n{}<bg=red;fg=white> {} </> ",
- self.get_border(id),
- error_prefix,
- ),
- buffer,
- ));
- } else {
- if self.started[id].err {
- message.push('\n');
- self.started.get_mut(id).unwrap().err = false;
- }
- if !self.started[id].out {
- message.push_str(&format!(
- "{}<bg=green;fg=white> {} </> ",
- self.get_border(id),
- prefix,
- ));
- self.started.get_mut(id).unwrap().out = true;
- }
-
- message.push_str(&shirabe_php_shim::str_replace(
- "\n",
- &format!(
- "\n{}<bg=green;fg=white> {} </> ",
- self.get_border(id),
- prefix,
- ),
- buffer,
- ));
- }
-
- message
- }
-
- /// Stops a formatting session.
- pub fn stop(&mut self, id: &str, message: &str, successful: bool, prefix: &str) -> String {
- let trailing_eol = if self.started[id].out || self.started[id].err {
- "\n"
- } else {
- ""
- };
-
- if successful {
- return format!(
- "{}{}<bg=green;fg=white> {} </> <fg=green>{}</>\n",
- trailing_eol,
- self.get_border(id),
- prefix,
- message,
- );
- }
-
- let message = format!(
- "{}{}<bg=red;fg=white> {} </> <fg=red>{}</>\n",
- trailing_eol,
- self.get_border(id),
- prefix,
- message,
- );
-
- if let Some(session) = self.started.get_mut(id) {
- session.out = false;
- session.err = false;
- }
-
- message
- }
-
- fn get_border(&self, id: &str) -> String {
- format!("<bg={}> </>", COLORS[self.started[id].border as usize])
- }
-}
-
-impl HelperInterface for DebugFormatterHelper {
- fn set_helper_set(&mut self, helper_set: Option<std::rc::Rc<std::cell::RefCell<HelperSet>>>) {
- self.inner.set_helper_set(helper_set);
- }
-
- fn get_helper_set(&self) -> Option<std::rc::Rc<std::cell::RefCell<HelperSet>>> {
- self.inner.get_helper_set()
- }
-
- fn get_name(&self) -> String {
- "debug_formatter".to_string()
- }
-}
diff --git a/crates/shirabe-external-packages/src/symfony/console/helper/descriptor_helper.rs b/crates/shirabe-external-packages/src/symfony/console/helper/descriptor_helper.rs
deleted file mode 100644
index 36603b06..00000000
--- a/crates/shirabe-external-packages/src/symfony/console/helper/descriptor_helper.rs
+++ /dev/null
@@ -1,121 +0,0 @@
-//! ref: composer/vendor/symfony/console/Helper/DescriptorHelper.php
-
-use crate::symfony::console::descriptor::descriptor_interface::{
- DescribableObject, DescriptorInterface,
-};
-use crate::symfony::console::descriptor::json_descriptor::JsonDescriptor;
-use crate::symfony::console::descriptor::markdown_descriptor::MarkdownDescriptor;
-use crate::symfony::console::descriptor::text_descriptor::TextDescriptor;
-use crate::symfony::console::descriptor::xml_descriptor::XmlDescriptor;
-use crate::symfony::console::exception::invalid_argument_exception::InvalidArgumentException;
-use crate::symfony::console::helper::helper::Helper;
-use crate::symfony::console::helper::helper_interface::HelperInterface;
-use crate::symfony::console::helper::helper_set::HelperSet;
-use crate::symfony::console::output::output_interface::OutputInterface;
-use indexmap::IndexMap;
-
-/// This class adds helper method to describe objects in various formats.
-#[derive(Default)]
-pub struct DescriptorHelper {
- inner: Helper,
- /// @var DescriptorInterface[]
- descriptors: IndexMap<String, Box<dyn DescriptorInterface>>,
-}
-
-// `DescriptorInterface` does not require `Debug`, so the derive cannot see
-// through the trait object; provide a minimal manual impl.
-impl std::fmt::Debug for DescriptorHelper {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- f.debug_struct("DescriptorHelper")
- .field("inner", &self.inner)
- .field("descriptors", &self.descriptors.keys().collect::<Vec<_>>())
- .finish()
- }
-}
-
-impl DescriptorHelper {
- pub fn new() -> Self {
- let mut this = Self {
- inner: Helper::default(),
- descriptors: IndexMap::new(),
- };
- this.register("txt", Box::new(TextDescriptor::default()))
- .register("xml", Box::new(XmlDescriptor::default()))
- .register("json", Box::new(JsonDescriptor::default()))
- .register("md", Box::new(MarkdownDescriptor::default()));
- this
- }
-
- /// Describes an object if supported.
- ///
- /// Available options are:
- /// * format: string, the output format name
- /// * raw_text: boolean, sets output type as raw
- ///
- /// @throws InvalidArgumentException when the given format is not supported
- pub fn describe2(
- &mut self,
- output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>,
- object: DescribableObject,
- options: IndexMap<String, shirabe_php_shim::PhpMixed>,
- ) -> anyhow::Result<()> {
- let mut merged: IndexMap<String, shirabe_php_shim::PhpMixed> = IndexMap::new();
- merged.insert(
- "raw_text".to_string(),
- shirabe_php_shim::PhpMixed::Bool(false),
- );
- merged.insert(
- "format".to_string(),
- shirabe_php_shim::PhpMixed::String("txt".to_string()),
- );
- for (key, value) in options {
- merged.insert(key, value);
- }
- let options = merged;
-
- let format = match &options["format"] {
- shirabe_php_shim::PhpMixed::String(format) => format.clone(),
- _ => String::new(),
- };
-
- if !self.descriptors.contains_key(&format) {
- return Err(InvalidArgumentException::new(format!(
- "Unsupported format \"{}\".",
- format.clone()
- ))
- .into());
- }
-
- let descriptor = self.descriptors.get_mut(&format).unwrap();
- descriptor.describe(output, object, options)
- }
-
- /// Registers a descriptor.
- pub fn register(
- &mut self,
- format: &str,
- descriptor: Box<dyn DescriptorInterface>,
- ) -> &mut Self {
- self.descriptors.insert(format.to_string(), descriptor);
-
- self
- }
-
- pub fn get_formats(&self) -> Vec<String> {
- self.descriptors.keys().cloned().collect()
- }
-}
-
-impl HelperInterface for DescriptorHelper {
- fn set_helper_set(&mut self, helper_set: Option<std::rc::Rc<std::cell::RefCell<HelperSet>>>) {
- self.inner.set_helper_set(helper_set);
- }
-
- fn get_helper_set(&self) -> Option<std::rc::Rc<std::cell::RefCell<HelperSet>>> {
- self.inner.get_helper_set()
- }
-
- fn get_name(&self) -> String {
- "descriptor".to_string()
- }
-}
diff --git a/crates/shirabe-external-packages/src/symfony/console/helper/formatter_helper.rs b/crates/shirabe-external-packages/src/symfony/console/helper/formatter_helper.rs
deleted file mode 100644
index 056a2ffe..00000000
--- a/crates/shirabe-external-packages/src/symfony/console/helper/formatter_helper.rs
+++ /dev/null
@@ -1,99 +0,0 @@
-//! ref: composer/vendor/symfony/console/Helper/FormatterHelper.php
-
-use crate::symfony::console::formatter::output_formatter::OutputFormatter;
-use crate::symfony::console::helper::helper::Helper;
-use crate::symfony::console::helper::helper_interface::HelperInterface;
-use crate::symfony::console::helper::helper_set::HelperSet;
-
-/// The Formatter class provides helpers to format messages.
-#[derive(Debug, Default)]
-pub struct FormatterHelper {
- inner: Helper,
-}
-
-impl FormatterHelper {
- /// Formats a message within a section.
- pub fn format_section(&self, section: &str, message: &str, style: &str) -> String {
- format!("<{}>[{}]</{}> {}", style, section, style, message)
- }
-
- /// Formats a message as a block of text.
- ///
- /// @param string|array $messages The message to write in the block
- pub fn format_block(&self, messages: FormatBlockMessages, style: &str, large: bool) -> String {
- let messages = match messages {
- FormatBlockMessages::String(message) => vec![message],
- FormatBlockMessages::Array(messages) => messages,
- };
-
- let mut len: i64 = 0;
- let mut lines: Vec<String> = Vec::new();
- for message in &messages {
- let message = OutputFormatter::escape(message).unwrap();
- lines.push(if large {
- format!(" {} ", message)
- } else {
- format!(" {} ", message)
- });
- len = std::cmp::max(Helper::width(&message) + (if large { 4 } else { 2 }), len);
- }
-
- let mut messages: Vec<String> = if large {
- vec![shirabe_php_shim::str_repeat(" ", len as usize)]
- } else {
- vec![]
- };
- let mut i = 0;
- while i < lines.len() {
- messages.push(format!(
- "{}{}",
- lines[i],
- shirabe_php_shim::str_repeat(" ", (len - Helper::width(&lines[i])) as usize)
- ));
- i += 1;
- }
- if large {
- messages.push(shirabe_php_shim::str_repeat(" ", len as usize));
- }
-
- let mut i = 0;
- while i < messages.len() {
- messages[i] = format!("<{}>{}</{}>", style, messages[i].clone(), style);
- i += 1;
- }
-
- messages.join("\n")
- }
-
- /// Truncates a message to the given length.
- pub fn truncate(&self, message: &str, length: i64, suffix: &str) -> String {
- let computed_length = length - Helper::width(suffix);
-
- if computed_length > Helper::width(message) {
- return message.to_string();
- }
-
- format!("{}{}", Helper::substr(message, 0, Some(length)), suffix)
- }
-}
-
-impl HelperInterface for FormatterHelper {
- fn set_helper_set(&mut self, helper_set: Option<std::rc::Rc<std::cell::RefCell<HelperSet>>>) {
- self.inner.set_helper_set(helper_set);
- }
-
- fn get_helper_set(&self) -> Option<std::rc::Rc<std::cell::RefCell<HelperSet>>> {
- self.inner.get_helper_set()
- }
-
- fn get_name(&self) -> String {
- "formatter".to_string()
- }
-}
-
-/// `formatBlock` accepts either a single string or an array of strings.
-#[derive(Debug)]
-pub enum FormatBlockMessages {
- String(String),
- Array(Vec<String>),
-}
diff --git a/crates/shirabe-external-packages/src/symfony/console/helper/helper.rs b/crates/shirabe-external-packages/src/symfony/console/helper/helper.rs
deleted file mode 100644
index 471a8d8a..00000000
--- a/crates/shirabe-external-packages/src/symfony/console/helper/helper.rs
+++ /dev/null
@@ -1,162 +0,0 @@
-//! ref: composer/vendor/symfony/console/Helper/Helper.php
-
-use crate::symfony::console::formatter::output_formatter_interface::OutputFormatterInterface;
-use crate::symfony::console::helper::helper_set::HelperSet;
-use shirabe_php_shim::php_regex;
-use shirabe_symfony_string::unicode_string::UnicodeString;
-
-/// Helper is the base class for all helper classes.
-#[derive(Debug, Default)]
-pub struct Helper {
- pub(crate) helper_set: Option<std::rc::Rc<std::cell::RefCell<HelperSet>>>,
-}
-
-impl Helper {
- pub fn set_helper_set(
- &mut self,
- helper_set: Option<std::rc::Rc<std::cell::RefCell<HelperSet>>>,
- ) {
- self.helper_set = helper_set;
- }
-
- pub fn get_helper_set(&self) -> Option<std::rc::Rc<std::cell::RefCell<HelperSet>>> {
- self.helper_set.clone()
- }
-
- /// Returns the length of a string, using mb_strwidth if it is available.
- ///
- /// @deprecated since Symfony 5.3
- pub fn strlen(string: &str) -> i64 {
- shirabe_php_shim::trigger_deprecation(
- "symfony/console",
- "5.3",
- "Method \"%s()\" is deprecated and will be removed in Symfony 6.0. Use Helper::width() or Helper::length() instead.",
- "Helper::strlen",
- );
-
- Self::width(string)
- }
-
- /// Returns the width of a string, using mb_strwidth if it is available.
- /// The width is how many characters positions the string will use.
- pub fn width(string: &str) -> i64 {
- if shirabe_php_shim::preg_match(php_regex!("//u"), string, &mut Vec::new()) {
- return UnicodeString::new(string).width(false);
- }
-
- let encoding = shirabe_php_shim::mb_detect_encoding(string, None, true);
- let encoding = match encoding {
- Some(encoding) => encoding,
- None => return shirabe_php_shim::strlen(string),
- };
-
- shirabe_php_shim::mb_strwidth(string, Some(&encoding))
- }
-
- /// Returns the length of a string, using mb_strlen if it is available.
- /// The length is related to how many bytes the string will use.
- pub fn length(string: &str) -> i64 {
- if shirabe_php_shim::preg_match(php_regex!("//u"), string, &mut Vec::new()) {
- return UnicodeString::new(string).length();
- }
-
- let encoding = shirabe_php_shim::mb_detect_encoding(string, None, true);
- let encoding = match encoding {
- Some(encoding) => encoding,
- None => return shirabe_php_shim::strlen(string),
- };
-
- shirabe_php_shim::mb_strlen(string, &encoding)
- }
-
- /// Returns the subset of a string, using mb_substr if it is available.
- pub fn substr(string: &str, from: i64, length: Option<i64>) -> String {
- let encoding = shirabe_php_shim::mb_detect_encoding(string, None, true);
- let encoding = match encoding {
- Some(encoding) => encoding,
- None => return shirabe_php_shim::substr(string, from, length),
- };
-
- shirabe_php_shim::mb_substr(string, from, length, Some(&encoding))
- }
-
- pub fn format_time(secs: f64) -> Option<String> {
- // [threshold, label, divisor?]
- let time_formats: [(f64, &str, Option<f64>); 9] = [
- (0.0, "< 1 sec", None),
- (1.0, "1 sec", None),
- (2.0, "secs", Some(1.0)),
- (60.0, "1 min", None),
- (120.0, "mins", Some(60.0)),
- (3600.0, "1 hr", None),
- (7200.0, "hrs", Some(3600.0)),
- (86400.0, "1 day", None),
- (172800.0, "days", Some(86400.0)),
- ];
-
- for (index, format) in time_formats.iter().enumerate() {
- if secs >= format.0
- && ((index + 1 < time_formats.len() && secs < time_formats[index + 1].0)
- || index == time_formats.len() - 1)
- {
- match format.2 {
- None => return Some(format.1.to_string()),
- Some(divisor) => {
- return Some(format!("{} {}", (secs / divisor).floor(), format.1));
- }
- }
- }
- }
-
- None
- }
-
- pub fn format_memory(memory: i64) -> String {
- if memory >= 1024 * 1024 * 1024 {
- return format!("{:.1} GiB", memory as f64 / 1024.0 / 1024.0 / 1024.0);
- }
-
- if memory >= 1024 * 1024 {
- return format!("{:.1} MiB", memory as f64 / 1024.0 / 1024.0);
- }
-
- if memory >= 1024 {
- return format!("{} KiB", memory / 1024);
- }
-
- format!("{} B", memory)
- }
-
- /// @deprecated since Symfony 5.3
- pub fn strlen_without_decoration(
- formatter: &mut dyn OutputFormatterInterface,
- string: &str,
- ) -> i64 {
- shirabe_php_shim::trigger_deprecation(
- "symfony/console",
- "5.3",
- "Method \"%s()\" is deprecated and will be removed in Symfony 6.0. Use Helper::removeDecoration() instead.",
- "Helper::strlenWithoutDecoration",
- );
-
- Self::width(&Self::remove_decoration(formatter, string))
- }
-
- pub fn remove_decoration(formatter: &mut dyn OutputFormatterInterface, string: &str) -> String {
- let is_decorated = formatter.is_decorated();
- formatter.set_decorated(false);
- // remove <...> formatting
- let string = formatter.format(Some(string)).unwrap().unwrap_or_default();
- // remove already formatted characters
- let string = shirabe_php_shim::preg_replace(php_regex!("/\u{1b}\\[[^m]*m/"), "", &string);
- // remove terminal hyperlinks
- let string = shirabe_php_shim::preg_replace(
- php_regex!("/\u{1b}]8;[^;]*;[^\u{1b}]*\u{1b}\\\\/"),
- "",
- &string,
- );
- formatter.set_decorated(is_decorated);
-
- string
- }
-}
diff --git a/crates/shirabe-external-packages/src/symfony/console/helper/helper_interface.rs b/crates/shirabe-external-packages/src/symfony/console/helper/helper_interface.rs
deleted file mode 100644
index 8dae6c85..00000000
--- a/crates/shirabe-external-packages/src/symfony/console/helper/helper_interface.rs
+++ /dev/null
@@ -1,15 +0,0 @@
-//! ref: composer/vendor/symfony/console/Helper/HelperInterface.php
-
-use crate::symfony::console::helper::helper_set::HelperSet;
-
-/// HelperInterface is the interface all helpers must implement.
-pub trait HelperInterface: std::fmt::Debug + shirabe_php_shim::AsAny {
- /// Sets the helper set associated with this helper.
- fn set_helper_set(&mut self, helper_set: Option<std::rc::Rc<std::cell::RefCell<HelperSet>>>);
-
- /// Gets the helper set associated with this helper.
- fn get_helper_set(&self) -> Option<std::rc::Rc<std::cell::RefCell<HelperSet>>>;
-
- /// Returns the canonical name of this helper.
- fn get_name(&self) -> String;
-}
diff --git a/crates/shirabe-external-packages/src/symfony/console/helper/helper_set.rs b/crates/shirabe-external-packages/src/symfony/console/helper/helper_set.rs
deleted file mode 100644
index b505fedf..00000000
--- a/crates/shirabe-external-packages/src/symfony/console/helper/helper_set.rs
+++ /dev/null
@@ -1,76 +0,0 @@
-//! ref: composer/vendor/symfony/console/Helper/HelperSet.php
-
-use crate::symfony::console::helper::debug_formatter_helper::DebugFormatterHelper;
-use crate::symfony::console::helper::formatter_helper::FormatterHelper;
-use crate::symfony::console::helper::helper_interface::HelperInterface;
-use crate::symfony::console::helper::process_helper::ProcessHelper;
-use crate::symfony::console::helper::question_helper::QuestionHelper;
-
-/// HelperSet represents a set of helpers to be used with a command.
-///
-/// Symfony lets arbitrary helpers be registered by name, but Composer only ever uses the four
-/// helpers `Application::getDefaultHelperSet()` installs. This port closes the set to exactly those
-/// four, instantiates them in the argument-less constructor, and exposes them through typed getters
-/// instead of Symfony's string-keyed `get()`/`has()`/`set()`.
-///
-/// TODO(plugin): a plugin-defined custom command may register extra helpers dynamically via
-/// `getApplication()->getHelperSet()`. Restoring that path (a `set()` equivalent plus name-based
-/// lookup) is deferred until the plugin API is implemented.
-#[derive(Debug)]
-pub struct HelperSet {
- formatter_helper: std::rc::Rc<std::cell::RefCell<FormatterHelper>>,
- debug_formatter_helper: std::rc::Rc<std::cell::RefCell<DebugFormatterHelper>>,
- process_helper: std::rc::Rc<std::cell::RefCell<ProcessHelper>>,
- question_helper: std::rc::Rc<std::cell::RefCell<QuestionHelper>>,
-}
-
-impl HelperSet {
- /// Builds the fixed set of helpers and wires each one's back-reference to the owning set,
- /// mirroring the `$helper->setHelperSet($this)` call PHP's `HelperSet::set()` performs.
- pub fn new() -> std::rc::Rc<std::cell::RefCell<HelperSet>> {
- let formatter_helper =
- std::rc::Rc::new(std::cell::RefCell::new(FormatterHelper::default()));
- let debug_formatter_helper =
- std::rc::Rc::new(std::cell::RefCell::new(DebugFormatterHelper::default()));
- let process_helper = std::rc::Rc::new(std::cell::RefCell::new(ProcessHelper::default()));
- let question_helper = std::rc::Rc::new(std::cell::RefCell::new(QuestionHelper::default()));
-
- let this = std::rc::Rc::new(std::cell::RefCell::new(HelperSet {
- formatter_helper: formatter_helper.clone(),
- debug_formatter_helper: debug_formatter_helper.clone(),
- process_helper: process_helper.clone(),
- question_helper: question_helper.clone(),
- }));
-
- formatter_helper
- .borrow_mut()
- .set_helper_set(Some(this.clone()));
- debug_formatter_helper
- .borrow_mut()
- .set_helper_set(Some(this.clone()));
- process_helper
- .borrow_mut()
- .set_helper_set(Some(this.clone()));
- question_helper
- .borrow_mut()
- .set_helper_set(Some(this.clone()));
-
- this
- }
-
- pub fn get_formatter(&self) -> std::rc::Rc<std::cell::RefCell<FormatterHelper>> {
- self.formatter_helper.clone()
- }
-
- pub fn get_debug_formatter(&self) -> std::rc::Rc<std::cell::RefCell<DebugFormatterHelper>> {
- self.debug_formatter_helper.clone()
- }
-
- pub fn get_process(&self) -> std::rc::Rc<std::cell::RefCell<ProcessHelper>> {
- self.process_helper.clone()
- }
-
- pub fn get_question(&self) -> std::rc::Rc<std::cell::RefCell<QuestionHelper>> {
- self.question_helper.clone()
- }
-}
diff --git a/crates/shirabe-external-packages/src/symfony/console/helper/process_helper.rs b/crates/shirabe-external-packages/src/symfony/console/helper/process_helper.rs
deleted file mode 100644
index 303a4569..00000000
--- a/crates/shirabe-external-packages/src/symfony/console/helper/process_helper.rs
+++ /dev/null
@@ -1,310 +0,0 @@
-//! ref: composer/vendor/symfony/console/Helper/ProcessHelper.php
-
-use crate::symfony::console::helper::debug_formatter_helper::DebugFormatterHelper;
-use crate::symfony::console::helper::helper::Helper;
-use crate::symfony::console::helper::helper_interface::HelperInterface;
-use crate::symfony::console::helper::helper_set::HelperSet;
-use crate::symfony::console::output::ConsoleOutputInterface;
-use crate::symfony::console::output::output_interface::{self, OutputInterface};
-use shirabe_symfony_process::exception::process_failed_exception::ProcessFailedException;
-use shirabe_symfony_process::process::Process;
-
-/// The ProcessHelper class provides helpers to run external processes.
-///
-/// @final
-#[derive(Debug, Default)]
-pub struct ProcessHelper {
- inner: Helper,
-}
-
-/// `$cmd` is either a `Process` instance or an array whose first element is a
-/// binary path (string) or a `Process`, followed by extra environment entries.
-#[derive(Debug)]
-pub enum ProcessHelperCmd {
- Process(Process),
- Array(Vec<ProcessHelperCmdElement>),
-}
-
-#[derive(Debug)]
-pub enum ProcessHelperCmdElement {
- String(String),
- Process(Process),
-}
-
-impl ProcessHelper {
- /// Runs an external process.
- ///
- /// @param array|Process $cmd An instance of Process or an array of the command and arguments
- /// @param callable|null $callback A PHP callback to run whenever there is some
- /// output available on STDOUT or STDERR
- pub fn run(
- &self,
- output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>,
- cmd: ProcessHelperCmd,
- error: Option<&str>,
- callback: Option<Box<dyn FnMut(&str, &str)>>,
- verbosity: i64,
- ) -> anyhow::Result<Process> {
- // `class_exists(Process::class)` guards against the optional symfony/process
- // component being absent; in this port the component is always available.
-
- // PHP: `if ($output instanceof ConsoleOutputInterface) { $output =
- // $output->getErrorOutput(); }`. ConsoleOutput is the only OutputInterface
- // implementor that also implements ConsoleOutputInterface, so the check
- // reduces to a downcast to the concrete type.
- let output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>> = {
- let redirected = shirabe_php_shim::AsAny::as_any(&*output.borrow())
- .downcast_ref::<crate::symfony::console::output::console_output::ConsoleOutput>()
- .map(|console| console.get_error_output());
- redirected.unwrap_or(output)
- };
-
- let formatter: std::rc::Rc<std::cell::RefCell<DebugFormatterHelper>> = self
- .get_helper_set()
- .unwrap()
- .borrow()
- .get_debug_formatter();
-
- // Normalize $cmd: a single Process becomes a one-element array.
- let mut cmd = match cmd {
- ProcessHelperCmd::Process(process) => {
- vec![ProcessHelperCmdElement::Process(process)]
- }
- ProcessHelperCmd::Array(cmd) => cmd,
- };
-
- // `!\is_array($cmd)` cannot happen given the enum, so the TypeError branch
- // is unreachable here.
-
- let mut process: Process;
- match cmd.first() {
- Some(ProcessHelperCmdElement::String(_)) => {
- let command: Vec<String> = cmd
- .iter()
- .map(|element| match element {
- ProcessHelperCmdElement::String(s) => s.clone(),
- ProcessHelperCmdElement::Process(_) => unreachable!(),
- })
- .collect();
- process = Process::new(
- command,
- None,
- None,
- shirabe_php_shim::PhpMixed::Null,
- Some(60.0),
- )?;
- cmd = vec![];
- }
- Some(ProcessHelperCmdElement::Process(_)) => {
- let first = cmd.remove(0);
- process = match first {
- ProcessHelperCmdElement::Process(process) => process,
- ProcessHelperCmdElement::String(_) => unreachable!(),
- };
- }
- None => {
- anyhow::bail!(shirabe_php_shim::InvalidArgumentException::new(format!(
- "Invalid command provided to \"{}()\": the command should be an array whose first element is either the path to the binary to run or a \"Process\" object.",
- shirabe_php_shim::PhpMixed::String("ProcessHelper::run".to_string()),
- )));
- }
- }
-
- if verbosity <= output.borrow().get_verbosity() {
- let started = Self::formatter_start(
- &formatter,
- &shirabe_php_shim::spl_object_hash(&process),
- &self.escape_string(&process.get_command_line()),
- );
- output
- .borrow()
- .write(&[started], false, output_interface::OUTPUT_NORMAL);
- }
-
- let callback = if output.borrow().is_debug() {
- Some(self.wrap_callback(output.clone(), &process, callback))
- } else {
- callback
- };
-
- // PHP passes the remaining `$cmd` array as the `$env` argument to Process::run.
- let env: indexmap::IndexMap<String, shirabe_php_shim::PhpMixed> = cmd
- .iter()
- .enumerate()
- .filter_map(|(i, element)| match element {
- ProcessHelperCmdElement::String(s) => {
- Some((i.to_string(), shirabe_php_shim::PhpMixed::String(s.clone())))
- }
- ProcessHelperCmdElement::Process(_) => None,
- })
- .collect();
- let callback: Option<Box<dyn FnMut(&str, &str) -> bool>> = callback.map(|mut cb| {
- Box::new(move |r#type: &str, buffer: &str| -> bool {
- cb(r#type, buffer);
- false
- }) as Box<dyn FnMut(&str, &str) -> bool>
- });
- process.run(callback, env)?;
-
- if verbosity <= output.borrow().get_verbosity() {
- let message = if process.is_successful() {
- "Command ran successfully".to_string()
- } else {
- format!(
- "{} Command did not run successfully",
- match process.get_exit_code() {
- Some(code) => shirabe_php_shim::PhpMixed::Int(code),
- None => shirabe_php_shim::PhpMixed::Null,
- },
- )
- };
- let stopped = Self::formatter_stop(
- &formatter,
- &shirabe_php_shim::spl_object_hash(&process),
- &message,
- process.is_successful(),
- );
- output
- .borrow()
- .write(&[stopped], false, output_interface::OUTPUT_NORMAL);
- }
-
- if !process.is_successful()
- && let Some(error) = error
- {
- output.borrow().writeln(
- &[format!("<error>{}</error>", self.escape_string(error))],
- output_interface::OUTPUT_NORMAL,
- );
- }
-
- Ok(process)
- }
-
- /// Runs the process.
- ///
- /// This is identical to run() except that an exception is thrown if the process
- /// exits with a non-zero exit code.
- ///
- /// @param array|Process $cmd An instance of Process or a command to run
- /// @param callable|null $callback A PHP callback to run whenever there is some
- /// output available on STDOUT or STDERR
- ///
- /// @throws ProcessFailedException
- ///
- /// @see run()
- pub fn must_run(
- &self,
- output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>,
- cmd: ProcessHelperCmd,
- error: Option<&str>,
- callback: Option<Box<dyn FnMut(&str, &str)>>,
- ) -> anyhow::Result<Process> {
- let mut process = self.run(
- output,
- cmd,
- error,
- callback,
- output_interface::VERBOSITY_VERY_VERBOSE,
- )?;
-
- if !process.is_successful() {
- anyhow::bail!(ProcessFailedException::new(&mut process)?);
- }
-
- Ok(process)
- }
-
- /// Wraps a Process callback to add debugging output.
- pub fn wrap_callback(
- &self,
- output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>,
- process: &Process,
- mut callback: Option<Box<dyn FnMut(&str, &str)>>,
- ) -> Box<dyn FnMut(&str, &str)> {
- // PHP: `if ($output instanceof ConsoleOutputInterface) { $output =
- // $output->getErrorOutput(); }`. ConsoleOutput is the only OutputInterface
- // implementor that also implements ConsoleOutputInterface, so the check
- // reduces to a downcast to the concrete type.
- let output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>> = {
- let redirected = shirabe_php_shim::AsAny::as_any(&*output.borrow())
- .downcast_ref::<crate::symfony::console::output::console_output::ConsoleOutput>()
- .map(|console| console.get_error_output());
- redirected.unwrap_or(output)
- };
-
- let formatter: std::rc::Rc<std::cell::RefCell<DebugFormatterHelper>> = self
- .get_helper_set()
- .unwrap()
- .borrow()
- .get_debug_formatter();
-
- let object_hash = shirabe_php_shim::spl_object_hash(process);
-
- Box::new(move |r#type: &str, buffer: &str| {
- let progressed = Self::formatter_progress(
- &formatter,
- &object_hash,
- &Self::escape_string_static(buffer),
- Process::ERR == r#type,
- );
- output
- .borrow()
- .write(&[progressed], false, output_interface::OUTPUT_NORMAL);
-
- if let Some(callback) = callback.as_mut() {
- callback(r#type, buffer);
- }
- })
- }
-
- fn escape_string(&self, str: &str) -> String {
- shirabe_php_shim::str_replace("<", "\\<", str)
- }
-
- fn escape_string_static(str: &str) -> String {
- shirabe_php_shim::str_replace("<", "\\<", str)
- }
-
- fn formatter_start(
- formatter: &std::rc::Rc<std::cell::RefCell<DebugFormatterHelper>>,
- id: &str,
- message: &str,
- ) -> String {
- formatter.borrow_mut().start(id, message, "RUN")
- }
-
- fn formatter_stop(
- formatter: &std::rc::Rc<std::cell::RefCell<DebugFormatterHelper>>,
- id: &str,
- message: &str,
- successful: bool,
- ) -> String {
- formatter.borrow_mut().stop(id, message, successful, "RES")
- }
-
- fn formatter_progress(
- formatter: &std::rc::Rc<std::cell::RefCell<DebugFormatterHelper>>,
- id: &str,
- buffer: &str,
- error: bool,
- ) -> String {
- formatter
- .borrow_mut()
- .progress(id, buffer, error, "OUT", "ERR")
- }
-}
-
-impl HelperInterface for ProcessHelper {
- fn set_helper_set(&mut self, helper_set: Option<std::rc::Rc<std::cell::RefCell<HelperSet>>>) {
- self.inner.set_helper_set(helper_set);
- }
-
- fn get_helper_set(&self) -> Option<std::rc::Rc<std::cell::RefCell<HelperSet>>> {
- self.inner.get_helper_set()
- }
-
- fn get_name(&self) -> String {
- "process".to_string()
- }
-}
diff --git a/crates/shirabe-external-packages/src/symfony/console/helper/progress_bar.rs b/crates/shirabe-external-packages/src/symfony/console/helper/progress_bar.rs
deleted file mode 100644
index 7bfbfbcd..00000000
--- a/crates/shirabe-external-packages/src/symfony/console/helper/progress_bar.rs
+++ /dev/null
@@ -1,835 +0,0 @@
-//! ref: composer/vendor/symfony/console/Helper/ProgressBar.php
-
-use crate::symfony::console::cursor::Cursor;
-use crate::symfony::console::exception::logic_exception::LogicException;
-use crate::symfony::console::helper::helper::Helper;
-use crate::symfony::console::output::ConsoleOutputInterface;
-use crate::symfony::console::output::ConsoleSectionOutput;
-use crate::symfony::console::output::OutputInterface;
-use crate::symfony::console::output::output_interface;
-use crate::symfony::console::terminal::Terminal;
-use indexmap::IndexMap;
-
-pub const FORMAT_VERBOSE: &str = "verbose";
-pub const FORMAT_VERY_VERBOSE: &str = "very_verbose";
-pub const FORMAT_DEBUG: &str = "debug";
-pub const FORMAT_NORMAL: &str = "normal";
-
-const FORMAT_VERBOSE_NOMAX: &str = "verbose_nomax";
-const FORMAT_VERY_VERBOSE_NOMAX: &str = "very_verbose_nomax";
-const FORMAT_DEBUG_NOMAX: &str = "debug_nomax";
-const FORMAT_NORMAL_NOMAX: &str = "normal_nomax";
-
-/// A placeholder formatter callable, receiving the bar and the output.
-pub type PlaceholderFormatter = Box<
- dyn Fn(
- &ProgressBar,
- &std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>,
- ) -> anyhow::Result<Result<shirabe_php_shim::PhpMixed, LogicException>>,
->;
-
-/// The ProgressBar provides helpers to display progress output.
-#[derive(Debug)]
-pub struct ProgressBar {
- bar_width: i64,
- bar_char: Option<String>,
- empty_bar_char: String,
- progress_char: String,
- format: Option<String>,
- internal_format: Option<String>,
- redraw_freq: Option<i64>,
- write_count: i64,
- last_write_time: f64,
- min_seconds_between_redraws: f64,
- max_seconds_between_redraws: f64,
- output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>,
- step: i64,
- max: i64,
- start_time: i64,
- step_width: i64,
- percent: f64,
- messages: IndexMap<String, String>,
- overwrite: bool,
- terminal: Terminal,
- previous_message: Option<String>,
- cursor: Cursor,
-}
-
-thread_local! {
- static FORMATTERS: std::cell::RefCell<Option<IndexMap<String, PlaceholderFormatter>>> = const { std::cell::RefCell::new(None) };
- static FORMATS: std::cell::RefCell<Option<IndexMap<String, String>>> = const { std::cell::RefCell::new(None) };
-}
-
-impl ProgressBar {
- /// `$max` Maximum steps (0 if unknown)
- pub fn new(
- output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>,
- max: i64,
- min_seconds_between_redraws: f64,
- ) -> Self {
- // PHP: `if ($output instanceof ConsoleOutputInterface) { $output =
- // $output->getErrorOutput(); }`. ConsoleOutput is the only OutputInterface
- // implementor that also implements ConsoleOutputInterface, so the check
- // reduces to a downcast to the concrete type.
- let output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>> = {
- let redirected = shirabe_php_shim::AsAny::as_any(&*output.borrow())
- .downcast_ref::<crate::symfony::console::output::console_output::ConsoleOutput>()
- .map(|console| console.get_error_output());
- redirected.unwrap_or(output)
- };
-
- let mut this = Self {
- bar_width: 28,
- bar_char: None,
- empty_bar_char: "-".to_string(),
- progress_char: ">".to_string(),
- format: None,
- internal_format: None,
- redraw_freq: Some(1),
- write_count: 0,
- last_write_time: 0.0,
- min_seconds_between_redraws: 0.0,
- max_seconds_between_redraws: 1.0,
- output: output.clone(),
- step: 0,
- max: 0,
- start_time: 0,
- step_width: 0,
- percent: 0.0,
- messages: IndexMap::new(),
- overwrite: true,
- terminal: Terminal::new(),
- previous_message: None,
- cursor: Cursor::new(output.clone(), None),
- };
-
- this.set_max_steps(max);
-
- if 0.0 < min_seconds_between_redraws {
- this.redraw_freq = None;
- this.min_seconds_between_redraws = min_seconds_between_redraws;
- }
-
- if !this.output.borrow().is_decorated() {
- // disable overwrite when output does not support ANSI codes.
- this.overwrite = false;
-
- // set a reasonable redraw frequency so output isn't flooded
- this.redraw_freq = None;
- }
-
- this.start_time = shirabe_php_shim::time();
-
- this
- }
-
- /// Sets a placeholder formatter for a given name.
- ///
- /// This method also allow you to override an existing placeholder.
- ///
- /// `$name` The placeholder name (including the delimiter char like %)
- /// `$callable` A PHP callable
- pub fn set_placeholder_formatter_definition(name: &str, callable: PlaceholderFormatter) {
- FORMATTERS.with(|formatters| {
- let mut formatters = formatters.borrow_mut();
- if formatters.is_none() {
- *formatters = Some(Self::init_placeholder_formatters());
- }
-
- formatters
- .as_mut()
- .unwrap()
- .insert(name.to_string(), callable);
- });
- }
-
- /// Gets the placeholder formatter for a given name.
- ///
- /// `$name` The placeholder name (including the delimiter char like %)
- pub fn get_placeholder_formatter_definition(name: &str) -> Option<()> {
- // Note: the returned callable cannot be cloned out of the thread-local
- // map; call sites invoke the formatter via the map directly.
- FORMATTERS.with(|formatters| {
- let mut formatters = formatters.borrow_mut();
- if formatters.is_none() {
- *formatters = Some(Self::init_placeholder_formatters());
- }
-
- formatters.as_ref().unwrap().get(name).map(|_| ())
- })
- }
-
- /// Sets a format for a given name.
- ///
- /// This method also allow you to override an existing format.
- ///
- /// `$name` The format name
- /// `$format` A format string
- pub fn set_format_definition(name: &str, format: &str) {
- FORMATS.with(|formats| {
- let mut formats = formats.borrow_mut();
- if formats.is_none() {
- *formats = Some(Self::init_formats());
- }
-
- formats
- .as_mut()
- .unwrap()
- .insert(name.to_string(), format.to_string());
- });
- }
-
- /// Gets the format for a given name.
- ///
- /// `$name` The format name
- pub fn get_format_definition(name: &str) -> Option<String> {
- FORMATS.with(|formats| {
- let mut formats = formats.borrow_mut();
- if formats.is_none() {
- *formats = Some(Self::init_formats());
- }
-
- formats.as_ref().unwrap().get(name).cloned()
- })
- }
-
- /// Associates a text with a named placeholder.
- ///
- /// The text is displayed when the progress bar is rendered but only
- /// when the corresponding placeholder is part of the custom format line
- /// (by wrapping the name with %).
- ///
- /// `$message` The text to associate with the placeholder
- /// `$name` The name of the placeholder
- pub fn set_message(&mut self, message: &str, name: &str) {
- self.messages.insert(name.to_string(), message.to_string());
- }
-
- pub fn get_message(&self, name: &str) -> Option<String> {
- self.messages.get(name).cloned()
- }
-
- pub fn get_start_time(&self) -> i64 {
- self.start_time
- }
-
- pub fn get_max_steps(&self) -> i64 {
- self.max
- }
-
- pub fn get_progress(&self) -> i64 {
- self.step
- }
-
- fn get_step_width(&self) -> i64 {
- self.step_width
- }
-
- pub fn get_progress_percent(&self) -> f64 {
- self.percent
- }
-
- pub fn get_bar_offset(&self) -> f64 {
- f64::floor(if self.max != 0 {
- self.percent * self.bar_width as f64
- } else if self.redraw_freq.is_none() {
- (((self.bar_width / 15).min(5) * self.write_count) % self.bar_width) as f64
- } else {
- (self.step % self.bar_width) as f64
- })
- }
-
- pub fn get_estimated(&self) -> f64 {
- if self.step == 0 {
- return 0.0;
- }
-
- shirabe_php_shim::round(
- (shirabe_php_shim::time() - self.start_time) as f64 / self.step as f64
- * self.max as f64,
- 0,
- )
- }
-
- pub fn get_remaining(&self) -> f64 {
- if self.step == 0 {
- return 0.0;
- }
-
- shirabe_php_shim::round(
- (shirabe_php_shim::time() - self.start_time) as f64 / self.step as f64
- * (self.max - self.step) as f64,
- 0,
- )
- }
-
- pub fn set_bar_width(&mut self, size: i64) {
- self.bar_width = size.max(1);
- }
-
- pub fn get_bar_width(&self) -> i64 {
- self.bar_width
- }
-
- pub fn set_bar_character(&mut self, char: &str) {
- self.bar_char = Some(char.to_string());
- }
-
- pub fn get_bar_character(&self) -> String {
- match &self.bar_char {
- Some(bar_char) => bar_char.clone(),
- None => {
- if self.max != 0 {
- "=".to_string()
- } else {
- self.empty_bar_char.clone()
- }
- }
- }
- }
-
- pub fn set_empty_bar_character(&mut self, char: &str) {
- self.empty_bar_char = char.to_string();
- }
-
- pub fn get_empty_bar_character(&self) -> String {
- self.empty_bar_char.clone()
- }
-
- pub fn set_progress_character(&mut self, char: &str) {
- self.progress_char = char.to_string();
- }
-
- pub fn get_progress_character(&self) -> String {
- self.progress_char.clone()
- }
-
- pub fn set_format(&mut self, format: &str) {
- self.format = None;
- self.internal_format = Some(format.to_string());
- }
-
- /// Sets the redraw frequency.
- ///
- /// `$freq` The frequency in steps
- pub fn set_redraw_frequency(&mut self, freq: Option<i64>) {
- self.redraw_freq = freq.map(|freq| freq.max(1));
- }
-
- pub fn min_seconds_between_redraws(&mut self, seconds: f64) {
- self.min_seconds_between_redraws = seconds;
- }
-
- pub fn max_seconds_between_redraws(&mut self, seconds: f64) {
- self.max_seconds_between_redraws = seconds;
- }
-
- /// Returns an iterator that will automatically update the progress bar when iterated.
- ///
- /// `$max` Number of steps to complete the bar (0 if indeterminate), if null it will be
- /// inferred from `$iterable`
- pub fn iterate(
- &mut self,
- iterable: Vec<(shirabe_php_shim::PhpMixed, shirabe_php_shim::PhpMixed)>,
- max: Option<i64>,
- ) -> anyhow::Result<Vec<(shirabe_php_shim::PhpMixed, shirabe_php_shim::PhpMixed)>> {
- self.start(Some(max.unwrap_or({
- // is_countable($iterable) ? \count($iterable) : 0
- iterable.len() as i64
- })))?;
-
- let mut yielded = Vec::new();
- for (key, value) in iterable {
- yielded.push((key, value));
-
- self.advance(1)?;
- }
-
- self.finish()?;
-
- Ok(yielded)
- }
-
- /// Starts the progress output.
- ///
- /// `$max` Number of steps to complete the bar (0 if indeterminate), null to leave unchanged
- pub fn start(&mut self, max: Option<i64>) -> anyhow::Result<()> {
- self.start_time = shirabe_php_shim::time();
- self.step = 0;
- self.percent = 0.0;
-
- if let Some(max) = max {
- self.set_max_steps(max);
- }
-
- self.display()
- }
-
- /// Advances the progress output X steps.
- ///
- /// `$step` Number of steps to advance
- pub fn advance(&mut self, step: i64) -> anyhow::Result<()> {
- self.set_progress(self.step + step)
- }
-
- /// Sets whether to overwrite the progressbar, false for new line.
- pub fn set_overwrite(&mut self, overwrite: bool) {
- self.overwrite = overwrite;
- }
-
- pub fn set_progress(&mut self, mut step: i64) -> anyhow::Result<()> {
- if self.max != 0 && step > self.max {
- self.max = step;
- } else if step < 0 {
- step = 0;
- }
-
- let redraw_freq = match self.redraw_freq {
- Some(redraw_freq) => redraw_freq as f64,
- None => (if self.max != 0 { self.max } else { 10 }) as f64 / 10.0,
- };
- let prev_period = (self.step as f64 / redraw_freq) as i64;
- let curr_period = (step as f64 / redraw_freq) as i64;
- self.step = step;
- self.percent = if self.max != 0 {
- self.step as f64 / self.max as f64
- } else {
- 0.0
- };
- let time_interval = shirabe_php_shim::microtime() - self.last_write_time;
-
- // Draw regardless of other limits
- if self.max == step {
- self.display()?;
-
- return Ok(());
- }
-
- // Throttling
- if time_interval < self.min_seconds_between_redraws {
- return Ok(());
- }
-
- // Draw each step period, but not too late
- if prev_period != curr_period || time_interval >= self.max_seconds_between_redraws {
- self.display()?;
- }
-
- Ok(())
- }
-
- pub fn set_max_steps(&mut self, max: i64) {
- self.format = None;
- self.max = max.max(0);
- self.step_width = if self.max != 0 {
- Helper::width(&self.max.to_string())
- } else {
- 4
- };
- }
-
- /// Finishes the progress output.
- pub fn finish(&mut self) -> anyhow::Result<()> {
- if self.max == 0 {
- self.max = self.step;
- }
-
- if self.step == self.max && !self.overwrite {
- // prevent double 100% output
- return Ok(());
- }
-
- self.set_progress(self.max)
- }
-
- /// Outputs the current progress string.
- pub fn display(&mut self) -> anyhow::Result<()> {
- if output_interface::VERBOSITY_QUIET == self.output.borrow().get_verbosity() {
- return Ok(());
- }
-
- if self.format.is_none() {
- let format = match &self.internal_format {
- Some(internal_format) if !internal_format.is_empty() => internal_format.clone(),
- _ => self.determine_best_format().to_string(),
- };
- self.set_real_format(&format);
- }
-
- let line = self.build_line()?;
- self.overwrite(&line);
-
- Ok(())
- }
-
- /// Removes the progress bar from the current line.
- ///
- /// This is useful if you wish to write some output
- /// while a progress bar is running.
- /// Call display() to show the progress bar again.
- pub fn clear(&mut self) -> anyhow::Result<()> {
- if !self.overwrite {
- return Ok(());
- }
-
- if self.format.is_none() {
- let format = match &self.internal_format {
- Some(internal_format) if !internal_format.is_empty() => internal_format.clone(),
- _ => self.determine_best_format().to_string(),
- };
- self.set_real_format(&format);
- }
-
- self.overwrite("");
-
- Ok(())
- }
-
- fn set_real_format(&mut self, format: &str) {
- // try to use the _nomax variant if available
- if self.max == 0 && Self::get_format_definition(&format!("{format}_nomax")).is_some() {
- self.format = Self::get_format_definition(&format!("{format}_nomax"));
- } else if Self::get_format_definition(format).is_some() {
- self.format = Self::get_format_definition(format);
- } else {
- self.format = Some(format.to_string());
- }
- }
-
- /// Overwrites a previous message to the output.
- fn overwrite(&mut self, message: &str) {
- if self.previous_message.as_deref() == Some(message) {
- return;
- }
-
- let original_message = message.to_string();
- let mut message = message.to_string();
-
- if self.overwrite {
- if let Some(previous_message) = self.previous_message.clone() {
- // PHP: `$this->output instanceof ConsoleSectionOutput`. Downcast the
- // shared output handle to the concrete section type.
- let output_ref = self.output.borrow();
- if let Some(section) = shirabe_php_shim::AsAny::as_any(&*output_ref)
- .downcast_ref::<ConsoleSectionOutput>()
- {
- let message_lines = shirabe_php_shim::explode("\n", &previous_message);
- let mut line_count = message_lines.len() as i64;
- for message_line in &message_lines {
- let formatter = section.get_formatter();
- let message_line_length = Helper::width(&Helper::remove_decoration(
- &mut *formatter.borrow_mut(),
- message_line,
- ));
- if message_line_length > self.terminal.get_width() {
- line_count += (message_line_length as f64
- / self.terminal.get_width() as f64)
- .floor() as i64;
- }
- }
- section.clear(Some(line_count));
- } else {
- drop(output_ref);
- let line_count = shirabe_php_shim::substr_count(&previous_message, "\n");
- for _i in 0..line_count {
- self.cursor.move_to_column(1);
- self.cursor.clear_line();
- self.cursor.move_up(1);
- }
-
- self.cursor.move_to_column(1);
- self.cursor.clear_line();
- }
- }
- } else if self.step > 0 {
- message = format!("{}{}", shirabe_php_shim::PHP_EOL, message);
- }
-
- self.previous_message = Some(original_message);
- self.last_write_time = shirabe_php_shim::microtime();
-
- self.output
- .borrow()
- .write(&[message], false, output_interface::OUTPUT_NORMAL);
- self.write_count += 1;
- }
-
- fn determine_best_format(&self) -> &'static str {
- match self.output.borrow().get_verbosity() {
- // OutputInterface::VERBOSITY_QUIET: display is disabled anyway
- output_interface::VERBOSITY_VERBOSE => {
- if self.max != 0 {
- FORMAT_VERBOSE
- } else {
- FORMAT_VERBOSE_NOMAX
- }
- }
- output_interface::VERBOSITY_VERY_VERBOSE => {
- if self.max != 0 {
- FORMAT_VERY_VERBOSE
- } else {
- FORMAT_VERY_VERBOSE_NOMAX
- }
- }
- output_interface::VERBOSITY_DEBUG => {
- if self.max != 0 {
- FORMAT_DEBUG
- } else {
- FORMAT_DEBUG_NOMAX
- }
- }
- _ => {
- if self.max != 0 {
- FORMAT_NORMAL
- } else {
- FORMAT_NORMAL_NOMAX
- }
- }
- }
- }
-
- fn init_placeholder_formatters() -> IndexMap<String, PlaceholderFormatter> {
- let mut formatters: IndexMap<String, PlaceholderFormatter> = IndexMap::new();
-
- formatters.insert(
- "bar".to_string(),
- Box::new(
- |bar: &ProgressBar,
- output: &std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>| {
- let complete_bars = bar.get_bar_offset();
- let mut display = shirabe_php_shim::str_repeat(
- &bar.get_bar_character(),
- complete_bars as usize,
- );
- if complete_bars < bar.get_bar_width() as f64 {
- let empty_bars = bar.get_bar_width() as f64
- - complete_bars
- - Helper::length(&Helper::remove_decoration(
- &mut *output.borrow().get_formatter().borrow_mut(),
- &bar.get_progress_character(),
- )) as f64;
- display.push_str(&format!(
- "{}{}",
- bar.get_progress_character(),
- shirabe_php_shim::str_repeat(
- &bar.get_empty_bar_character(),
- empty_bars as usize
- )
- ));
- }
-
- Ok(Ok(shirabe_php_shim::PhpMixed::String(display)))
- },
- ),
- );
-
- formatters.insert(
- "elapsed".to_string(),
- Box::new(
- |bar: &ProgressBar,
- _output: &std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>| {
- Ok(Ok(shirabe_php_shim::PhpMixed::String(
- Helper::format_time(
- (shirabe_php_shim::time() - bar.get_start_time()) as f64,
- )
- .unwrap_or_default(),
- )))
- },
- ),
- );
-
- formatters.insert(
- "remaining".to_string(),
- Box::new(|bar: &ProgressBar, _output: &std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>| {
- if bar.get_max_steps() == 0 {
- return Ok(Err(LogicException::new("Unable to display the remaining time if the maximum number of steps is not set.".to_string())));
- }
-
- Ok(Ok(shirabe_php_shim::PhpMixed::String(
- Helper::format_time(bar.get_remaining()).unwrap_or_default(),
- )))
- }),
- );
-
- formatters.insert(
- "estimated".to_string(),
- Box::new(|bar: &ProgressBar, _output: &std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>| {
- if bar.get_max_steps() == 0 {
- return Ok(Err(LogicException::new("Unable to display the estimated time if the maximum number of steps is not set.".to_string())));
- }
-
- Ok(Ok(shirabe_php_shim::PhpMixed::String(
- Helper::format_time(bar.get_estimated()).unwrap_or_default(),
- )))
- }),
- );
-
- formatters.insert(
- "memory".to_string(),
- Box::new(
- |_bar: &ProgressBar,
- _output: &std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>| {
- Ok(Ok(shirabe_php_shim::PhpMixed::String(
- Helper::format_memory(shirabe_php_shim::memory_get_usage()),
- )))
- },
- ),
- );
-
- formatters.insert(
- "current".to_string(),
- Box::new(
- |bar: &ProgressBar,
- _output: &std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>| {
- Ok(Ok(shirabe_php_shim::PhpMixed::String(
- shirabe_php_shim::str_pad(
- &bar.get_progress().to_string(),
- bar.get_step_width() as usize,
- " ",
- shirabe_php_shim::STR_PAD_LEFT,
- ),
- )))
- },
- ),
- );
-
- formatters.insert(
- "max".to_string(),
- Box::new(
- |bar: &ProgressBar,
- _output: &std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>| {
- Ok(Ok(shirabe_php_shim::PhpMixed::Int(bar.get_max_steps())))
- },
- ),
- );
-
- formatters.insert(
- "percent".to_string(),
- Box::new(
- |bar: &ProgressBar,
- _output: &std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>| {
- Ok(Ok(shirabe_php_shim::PhpMixed::Float(
- (bar.get_progress_percent() * 100.0).floor(),
- )))
- },
- ),
- );
-
- formatters
- }
-
- fn init_formats() -> IndexMap<String, String> {
- let mut formats: IndexMap<String, String> = IndexMap::new();
-
- formats.insert(
- FORMAT_NORMAL.to_string(),
- " %current%/%max% [%bar%] %percent:3s%%".to_string(),
- );
- formats.insert(
- FORMAT_NORMAL_NOMAX.to_string(),
- " %current% [%bar%]".to_string(),
- );
-
- formats.insert(
- FORMAT_VERBOSE.to_string(),
- " %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%".to_string(),
- );
- formats.insert(
- FORMAT_VERBOSE_NOMAX.to_string(),
- " %current% [%bar%] %elapsed:6s%".to_string(),
- );
-
- formats.insert(
- FORMAT_VERY_VERBOSE.to_string(),
- " %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%".to_string(),
- );
- formats.insert(
- FORMAT_VERY_VERBOSE_NOMAX.to_string(),
- " %current% [%bar%] %elapsed:6s%".to_string(),
- );
-
- formats.insert(
- FORMAT_DEBUG.to_string(),
- " %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%"
- .to_string(),
- );
- formats.insert(
- FORMAT_DEBUG_NOMAX.to_string(),
- " %current% [%bar%] %elapsed:6s% %memory:6s%".to_string(),
- );
-
- formats
- }
-
- fn build_line(&mut self) -> anyhow::Result<String> {
- let regex = "{%([a-z\\-_]+)(?:\\:([^%]+))?%}i";
-
- // The callback resolves a placeholder match into its replacement text.
- // It is invoked by preg_replace_callback over $this->format.
- let line = self.build_line_apply(regex)?;
-
- // gets string length for each sub line with multiline format
- let lines_length: Vec<i64> = shirabe_php_shim::explode("\n", &line)
- .iter()
- .map(|sub_line| {
- Helper::width(&Helper::remove_decoration(
- &mut *self.output.borrow().get_formatter().borrow_mut(),
- &shirabe_php_shim::rtrim(sub_line, Some("\r")),
- ))
- })
- .collect();
-
- let lines_width = *lines_length.iter().max().unwrap();
-
- let terminal_width = self.terminal.get_width();
- if lines_width <= terminal_width {
- return Ok(line);
- }
-
- self.set_bar_width(self.bar_width - lines_width + terminal_width);
-
- self.build_line_apply(regex)
- }
-
- /// Applies the placeholder-resolving callback over `$this->format`, mirroring
- /// the `preg_replace_callback` invocation in PHP's `buildLine()`.
- fn build_line_apply(&self, regex: &str) -> anyhow::Result<String> {
- let format = self.format.clone().unwrap_or_default();
-
- // $callback in PHP, expressed as a closure over $this and the matches.
- let callback = |matches: &[Option<String>]| -> anyhow::Result<String> {
- let name = matches[1].clone().unwrap_or_default();
-
- let text: shirabe_php_shim::PhpMixed =
- if Self::get_placeholder_formatter_definition(&name).is_some() {
- // $text = $formatter($this, $this->output);
- let formatter_result = FORMATTERS.with(|formatters| {
- let formatters = formatters.borrow();
- let formatter = formatters.as_ref().unwrap().get(&name).unwrap();
- formatter(self, &self.output)
- });
- formatter_result??
- } else if let Some(message) = self.messages.get(&name) {
- shirabe_php_shim::PhpMixed::String(message.clone())
- } else {
- return Ok(matches[0].clone().unwrap_or_default());
- };
-
- if let Some(modifier) = matches.get(2).and_then(|m| m.clone()) {
- return Ok(shirabe_php_shim::sprintf(&format!("%{modifier}"), &[text]));
- }
-
- // PHP implicitly casts the formatter result to string here.
- Ok(match text {
- shirabe_php_shim::PhpMixed::String(s) => s,
- shirabe_php_shim::PhpMixed::Int(i) => i.to_string(),
- shirabe_php_shim::PhpMixed::Float(f) => {
- format!("{}", f)
- }
- other => format!("{}", other),
- })
- };
-
- shirabe_php_shim::preg_replace_callback(regex, callback, &format)
- }
-}
diff --git a/crates/shirabe-external-packages/src/symfony/console/helper/question_helper.rs b/crates/shirabe-external-packages/src/symfony/console/helper/question_helper.rs
deleted file mode 100644
index 91fb5bf0..00000000
--- a/crates/shirabe-external-packages/src/symfony/console/helper/question_helper.rs
+++ /dev/null
@@ -1,916 +0,0 @@
-//! ref: composer/vendor/symfony/console/Helper/QuestionHelper.php
-
-use crate::symfony::console::cursor::Cursor;
-use crate::symfony::console::exception::missing_input_exception::MissingInputException;
-use crate::symfony::console::exception::runtime_exception::RuntimeException;
-use crate::symfony::console::formatter::output_formatter::OutputFormatter;
-use crate::symfony::console::formatter::output_formatter_style::OutputFormatterStyle;
-use crate::symfony::console::helper::formatter_helper::FormatBlockMessages;
-use crate::symfony::console::helper::helper::Helper;
-use crate::symfony::console::helper::helper_interface::HelperInterface;
-use crate::symfony::console::helper::helper_set::HelperSet;
-use crate::symfony::console::input::input_interface::InputInterface;
-use crate::symfony::console::output::console_output::ConsoleOutput;
-use crate::symfony::console::output::console_output_interface::ConsoleOutputInterface;
-use crate::symfony::console::output::console_section_output::ConsoleSectionOutput;
-use crate::symfony::console::output::output_interface;
-use crate::symfony::console::output::output_interface::OutputInterface;
-use crate::symfony::console::question::ChoiceQuestion;
-use crate::symfony::console::question::QuestionInterface;
-use crate::symfony::console::terminal::Terminal;
-use shirabe_php_shim::PhpMixed;
-use shirabe_symfony_string::s;
-
-/// The QuestionHelper class provides helpers to interact with the user.
-#[derive(Debug, Default)]
-pub struct QuestionHelper {
- pub(crate) inner: Helper,
-
- /// @var resource|null
- input_stream: Option<shirabe_php_shim::PhpResource>,
-}
-
-/// self::$stty
-static STTY: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
-/// self::$stdinIsInteractive
-static STDIN_IS_INTERACTIVE: std::sync::Mutex<Option<bool>> = std::sync::Mutex::new(None);
-
-/// PHP dispatches `$this->writePrompt()` / `$this->writeError()` virtually, and
-/// `SymfonyQuestionHelper` overrides both protected methods. The embedded-super port expresses
-/// that late binding as a trait: the template methods are provided here and reach the overridable
-/// hooks through `Self`, while `inner()` reaches the base-class state. PHP has no such interface;
-/// the invented name follows the `QuestionInterface` precedent.
-pub trait QuestionHelperInterface {
- fn inner(&self) -> &QuestionHelper;
-
- fn inner_mut(&mut self) -> &mut QuestionHelper;
-
- /// Asks a question to the user.
- ///
- /// @return mixed The user answer
- ///
- /// @throws RuntimeException If there is no data to read in the input stream
- fn ask(
- &mut self,
- input: &mut dyn InputInterface,
- output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>,
- question: &impl QuestionInterface,
- ) -> anyhow::Result<Result<PhpMixed, MissingInputException>> {
- let mut output = output;
- let error_output = {
- let borrowed = output.borrow();
- (*borrowed)
- .as_any()
- .downcast_ref::<ConsoleOutput>()
- .map(|console_output| console_output.get_error_output())
- };
- if let Some(error_output) = error_output {
- output = error_output;
- }
-
- if !input.is_interactive() {
- return Ok(Ok(self.inner().get_default_answer(question)));
- }
-
- if let Some(streamable) = input.as_streamable()
- && let Some(stream) = streamable.get_stream()
- {
- self.inner_mut().input_stream = Some(stream);
- }
-
- let result: anyhow::Result<Result<PhpMixed, MissingInputException>> = (|| {
- if question.get_validator().is_none() {
- return self.do_ask(std::rc::Rc::clone(&output), question);
- }
-
- let interviewer = || self.do_ask(std::rc::Rc::clone(&output), question);
-
- self.validate_attempts(&interviewer, std::rc::Rc::clone(&output), question)
- })();
-
- let result = result?;
- match result {
- Ok(value) => Ok(Ok(value)),
- Err(exception) => {
- input.set_interactive(false);
-
- let fallback_output = self.inner().get_default_answer(question);
- if matches!(fallback_output, PhpMixed::Null) {
- return Ok(Err(exception));
- }
-
- Ok(Ok(fallback_output))
- }
- }
- }
-
- /// Asks the question to the user (PHP private; on the trait so it can late-bind the hooks).
- ///
- /// @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
- fn do_ask(
- &self,
- output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>,
- question: &impl QuestionInterface,
- ) -> anyhow::Result<Result<PhpMixed, MissingInputException>> {
- self.write_prompt(std::rc::Rc::clone(&output), question);
-
- let input_stream = self
- .inner()
- .input_stream
- .clone()
- .unwrap_or_else(shirabe_php_shim::stdin);
- let autocomplete = question.get_autocompleter_callback();
-
- let ret: PhpMixed;
-
- if let Some(autocomplete) = autocomplete
- && STTY.load(std::sync::atomic::Ordering::SeqCst)
- && Terminal::has_stty_available()
- {
- let callback = autocomplete;
- // The autocompleter callback yields an iterable (Option here); PHP
- // treats a null result as an empty list of suggestions.
- let callback = move |input: &str| callback(input).unwrap_or_default();
- let autocomplete = match self.inner().autocomplete(
- std::rc::Rc::clone(&output),
- question,
- &input_stream,
- &callback,
- ) {
- Ok(value) => value,
- Err(exception) => return Ok(Err(exception)),
- };
- ret = PhpMixed::String(if question.is_trimmable() {
- shirabe_php_shim::trim(&autocomplete, None)
- } else {
- autocomplete
- });
- } else {
- let mut r: PhpMixed = PhpMixed::Bool(false);
- if question.is_hidden() {
- match self.inner().get_hidden_response(
- std::rc::Rc::clone(&output),
- &input_stream,
- question.is_trimmable(),
- )? {
- Ok(hidden_response) => {
- r = PhpMixed::String(if question.is_trimmable() {
- shirabe_php_shim::trim(&hidden_response, None)
- } else {
- hidden_response
- });
- }
- Err(e) => {
- if !question.is_hidden_fallback() {
- return Err(e.into());
- }
- }
- }
- }
-
- if matches!(r, PhpMixed::Bool(false)) {
- let is_blocked = shirabe_php_shim::stream_get_meta_data(&input_stream)
- .get("blocked")
- .cloned()
- .unwrap_or(PhpMixed::Bool(true));
-
- if !shirabe_php_shim::boolval(&is_blocked) {
- shirabe_php_shim::stream_set_blocking(&input_stream, true);
- }
-
- let read = self.inner().read_input(&input_stream, question);
-
- if !shirabe_php_shim::boolval(&is_blocked) {
- shirabe_php_shim::stream_set_blocking(&input_stream, false);
- }
-
- if matches!(read, PhpMixed::Bool(false)) {
- return Ok(Err(MissingInputException::new("Aborted.".to_string())));
- }
- r = read;
- if question.is_trimmable() {
- r = PhpMixed::String(shirabe_php_shim::trim(&r.to_string(), None));
- }
- }
- ret = r;
- }
-
- let mut ret = ret;
- {
- let borrowed = output.borrow();
- if let Some(section_output) =
- (*borrowed).as_any().downcast_ref::<ConsoleSectionOutput>()
- {
- section_output.add_content(&ret.to_string());
- }
- }
-
- ret = if shirabe_php_shim::strlen(&ret.to_string()) > 0 {
- ret
- } else {
- question.get_default()
- };
-
- if let Some(normalizer) = question.get_normalizer() {
- return Ok(Ok(normalizer(ret)));
- }
-
- Ok(Ok(ret))
- }
-
- /// Validates an attempt (PHP private; on the trait so it can late-bind the hooks).
- ///
- /// @param callable $interviewer A callable that will ask for a question and return the result
- ///
- /// @return mixed The validated response
- ///
- /// @throws \Exception In case the max number of attempts has been reached and no valid response has been given
- fn validate_attempts(
- &self,
- interviewer: &dyn Fn() -> anyhow::Result<Result<PhpMixed, MissingInputException>>,
- output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>,
- question: &impl QuestionInterface,
- ) -> anyhow::Result<Result<PhpMixed, MissingInputException>> {
- let mut error: Option<shirabe_php_shim::Exception> = None;
- let mut attempts = question.get_max_attempts();
-
- loop {
- // while (null === $attempts || $attempts--)
- match attempts {
- None => {}
- Some(0) => break,
- Some(n) => attempts = Some(n - 1),
- }
-
- if let Some(ref error) = error {
- self.write_error(std::rc::Rc::clone(&output), error);
- }
-
- let interviewed = match interviewer()? {
- Ok(value) => value,
- Err(missing) => return Ok(Err(missing)),
- };
-
- match question.get_validator().unwrap()(Some(interviewed)) {
- Ok(value) => return Ok(Ok(value)),
- Err(e) => {
- // PHP: `catch (RuntimeException $e) { throw $e; } catch (\Exception $error) {}`.
- // The validator return type is fixed to InvalidArgumentException here, so the
- // RuntimeException rethrow branch is statically unreachable; record the error
- // and retry.
- error = Some(shirabe_php_shim::Exception::with_code(
- e.get_message().to_string(),
- e.get_code(),
- ));
- }
- }
- }
-
- // throw $error;
- Err(anyhow::Error::msg(
- error
- .map(|e| e.get_message().to_string())
- .unwrap_or_default(),
- ))
- }
-
- /// Outputs the question prompt (PHP protected; the overridable hook).
- fn write_prompt(
- &self,
- output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>,
- question: &impl QuestionInterface,
- ) {
- self.inner().write_prompt(output, question);
- }
-
- /// Outputs an error message (PHP protected; the overridable hook).
- fn write_error(
- &self,
- output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>,
- error: &shirabe_php_shim::Exception,
- ) {
- self.inner().write_error(output, error);
- }
-}
-
-impl QuestionHelperInterface for QuestionHelper {
- fn inner(&self) -> &QuestionHelper {
- self
- }
-
- fn inner_mut(&mut self) -> &mut QuestionHelper {
- self
- }
-}
-
-impl QuestionHelper {
- pub fn get_name(&self) -> String {
- "question".to_string()
- }
-
- /// Prevents usage of stty.
- pub fn disable_stty() {
- STTY.store(false, std::sync::atomic::Ordering::SeqCst);
- }
-
- fn get_default_answer(&self, question: &impl QuestionInterface) -> PhpMixed {
- let default = question.get_default();
-
- if matches!(default, PhpMixed::Null) {
- return default;
- }
-
- if let Some(validator) = question.get_validator() {
- // call_user_func($question->getValidator(), $default)
- return validator(Some(default)).unwrap();
- } else if let Some(choice_question) = question.as_choice() {
- let choices = choice_question.get_choices();
-
- if !choice_question.is_multiselect() {
- return choices
- .get(&default.to_string())
- .cloned()
- .unwrap_or(default);
- }
-
- let default_parts = shirabe_php_shim::explode(",", &default.to_string());
- let mut resolved: indexmap::IndexMap<String, PhpMixed> = indexmap::IndexMap::new();
- for (k, v) in default_parts.iter().enumerate() {
- let v = if question.is_trimmable() {
- shirabe_php_shim::trim(v, None)
- } else {
- v.clone()
- };
- let value = choices.get(&v).cloned().unwrap_or(PhpMixed::String(v));
- resolved.insert(k.to_string(), value);
- }
-
- return PhpMixed::Array(resolved);
- }
-
- default
- }
-
- /// Outputs the question prompt.
- pub(crate) fn write_prompt(
- &self,
- output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>,
- question: &impl QuestionInterface,
- ) {
- let mut message = question.get_question().to_string();
-
- if let Some(choice_question) = question.as_choice() {
- let mut lines = vec![question.get_question().to_string()];
- lines.extend(self.format_choice_question_choices(choice_question, "info"));
- output
- .borrow()
- .writeln(&lines, output_interface::OUTPUT_NORMAL);
-
- message = choice_question.get_prompt().to_string();
- }
-
- output
- .borrow()
- .write(&[message], false, output_interface::OUTPUT_NORMAL);
- }
-
- pub(crate) fn format_choice_question_choices(
- &self,
- question: &ChoiceQuestion,
- tag: &str,
- ) -> Vec<String> {
- let mut messages: Vec<String> = vec![];
-
- let choices = question.get_choices();
- let max_width = choices
- .keys()
- .map(|key| Helper::width(key))
- .max()
- .unwrap_or(0);
-
- for (key, value) in choices {
- let padding =
- shirabe_php_shim::str_repeat(" ", (max_width - Helper::width(key)) as usize);
-
- messages.push(format!(
- " [<{tag}>{}{padding}</{tag}>] {}",
- key.clone(),
- value.clone(),
- ));
- }
-
- messages
- }
-
- /// Outputs an error message.
- pub(crate) fn write_error(
- &self,
- output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>,
- error: &shirabe_php_shim::Exception,
- ) {
- let message = if let Some(helper_set) = self.get_helper_set() {
- let formatter = helper_set.borrow().get_formatter();
-
- formatter.borrow().format_block(
- FormatBlockMessages::String(error.get_message().to_string()),
- "error",
- false,
- )
- } else {
- format!("<error>{}</error>", error.get_message())
- };
-
- output
- .borrow()
- .writeln(&[message], output_interface::OUTPUT_NORMAL);
- }
-
- /// Autocompletes a question.
- fn autocomplete(
- &self,
- output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>,
- question: &impl QuestionInterface,
- input_stream: &shirabe_php_shim::PhpResource,
- autocomplete: &dyn Fn(&str) -> Vec<PhpMixed>,
- ) -> Result<String, MissingInputException> {
- let cursor = Cursor::new(std::rc::Rc::clone(&output), Some(input_stream.clone()));
-
- let mut full_choice = String::new();
- let mut ret = String::new();
-
- let mut i: i64 = 0;
- let mut ofs: i64 = -1;
- let mut matches = autocomplete(&ret);
- let mut num_matches = matches.len() as i64;
-
- let stty_mode = shirabe_php_shim::shell_exec("stty -g").unwrap_or_default();
- let is_stdin = shirabe_php_shim::stream_get_meta_data(input_stream)
- .get("uri")
- .map(|uri| uri.to_string() == "php://stdin")
- .unwrap_or(false);
- let mut r = vec![input_stream.clone()];
- let w: Vec<shirabe_php_shim::PhpResource> = vec![];
-
- // Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead)
- shirabe_php_shim::shell_exec("stty -icanon -echo");
-
- // Add highlighted text style
- output.borrow().get_formatter().borrow_mut().set_style(
- "hl",
- Box::new(OutputFormatterStyle::new(
- Some("black"),
- Some("white"),
- vec![],
- )),
- );
-
- // Read a keypress
- while !shirabe_php_shim::feof(input_stream) {
- while is_stdin
- && Some(0)
- == shirabe_php_shim::stream_select(
- &mut r,
- &mut w.clone(),
- &mut w.clone(),
- 0,
- Some(100),
- )
- {
- // Give signal handlers a chance to run
- r = vec![input_stream.clone()];
- }
- let mut c = shirabe_php_shim::fread(input_stream, 1);
-
- // as opposed to fgets(), fread() returns an empty string when the stream content is empty, not false.
- if c.is_none()
- || (ret.is_empty()
- && c.as_deref() == Some("")
- && matches!(question.get_default(), PhpMixed::Null))
- {
- shirabe_php_shim::shell_exec(&format!("stty {}", stty_mode));
- return Err(MissingInputException::new("Aborted.".to_string()));
- } else if c.as_deref() == Some("\u{7f}") {
- // Backspace Character
- if 0 == num_matches && 0 != i {
- i -= 1;
- cursor.move_left(s(&full_choice).slice(-1, None).width(false));
-
- full_choice = QuestionHelper::substr(Some(&full_choice), 0, Some(i));
- }
-
- if 0 == i {
- ofs = -1;
- matches = autocomplete(&ret);
- num_matches = matches.len() as i64;
- } else {
- num_matches = 0;
- }
-
- // Pop the last character off the end of our string
- ret = QuestionHelper::substr(Some(&ret), 0, Some(i));
- } else if c.as_deref() == Some("\u{1b}") {
- // Did we read an escape sequence?
- let escape = shirabe_php_shim::fread(input_stream, 2).unwrap_or_default();
- let cc = format!("{}{}", c.clone().unwrap_or_default(), escape);
- c = Some(cc.clone());
-
- // A = Up Arrow. B = Down Arrow
- let c2 = cc.as_bytes().get(2).copied();
- if c2 == Some(b'A') || c2 == Some(b'B') {
- if c2 == Some(b'A') && -1 == ofs {
- ofs = 0;
- }
-
- if 0 == num_matches {
- continue;
- }
-
- ofs += if c2 == Some(b'A') { -1 } else { 1 };
- ofs = (num_matches + ofs) % num_matches;
- }
- } else if shirabe_php_shim::ord(c.as_deref().unwrap_or("")) < 32 {
- if c.as_deref() == Some("\t") || c.as_deref() == Some("\n") {
- if num_matches > 0 && -1 != ofs {
- ret = matches[ofs as usize].to_string();
- // Echo out remaining chars for current match
- let remaining_characters = shirabe_php_shim::substr(
- &ret,
- shirabe_php_shim::strlen(&shirabe_php_shim::trim(
- &self.most_recently_entered_value(&full_choice),
- None,
- )),
- None,
- );
- output.borrow().write(
- std::slice::from_ref(&remaining_characters),
- false,
- output_interface::OUTPUT_NORMAL,
- );
- full_choice.push_str(&remaining_characters);
- i = match shirabe_php_shim::mb_detect_encoding(&full_choice, None, true) {
- None => shirabe_php_shim::strlen(&full_choice),
- Some(encoding) => shirabe_php_shim::mb_strlen(&full_choice, &encoding),
- };
-
- let ret_for_filter = ret.clone();
- matches = autocomplete(&ret)
- .into_iter()
- .filter(|m| {
- ret_for_filter.is_empty()
- || shirabe_php_shim::str_starts_with(
- &m.to_string(),
- &ret_for_filter,
- )
- })
- .collect();
- ofs = -1;
- }
-
- if c.as_deref() == Some("\n") {
- output.borrow().write(
- &[c.unwrap_or_default()],
- false,
- output_interface::OUTPUT_NORMAL,
- );
- break;
- }
-
- num_matches = 0;
- }
-
- continue;
- } else {
- let cur = c.clone().unwrap_or_default();
- if "\u{80}" <= cur.as_str() {
- let len = match shirabe_php_shim::str_bitand(&cur, "\u{f0}").as_str() {
- "\u{c0}" => 1,
- "\u{d0}" => 1,
- "\u{e0}" => 2,
- "\u{f0}" => 3,
- _ => 0,
- };
- let extra = shirabe_php_shim::fread(input_stream, len).unwrap_or_default();
- c = Some(format!("{}{}", cur, extra));
- }
-
- let cur = c.clone().unwrap_or_default();
- output.borrow().write(
- std::slice::from_ref(&cur),
- false,
- output_interface::OUTPUT_NORMAL,
- );
- ret.push_str(&cur);
- full_choice.push_str(&cur);
- i += 1;
-
- let mut temp_ret = ret.clone();
-
- if let Some(choice_question) = question.as_choice()
- && choice_question.is_multiselect()
- {
- temp_ret = self.most_recently_entered_value(&full_choice);
- }
-
- num_matches = 0;
- ofs = 0;
-
- for value in autocomplete(&ret) {
- // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle)
- if shirabe_php_shim::str_starts_with(&value.to_string(), &temp_ret) {
- if (num_matches as usize) < matches.len() {
- matches[num_matches as usize] = value;
- } else {
- matches.push(value);
- }
- num_matches += 1;
- }
- }
- }
-
- cursor.clear_line_after();
-
- if num_matches > 0 && -1 != ofs {
- cursor.save_position();
- // Write highlighted text, complete the partially entered response
- let characters_entered = shirabe_php_shim::strlen(&shirabe_php_shim::trim(
- &self.most_recently_entered_value(&full_choice),
- None,
- ));
- output.borrow().write(
- &[format!(
- "<hl>{}</hl>",
- OutputFormatter::escape_trailing_backslash(&shirabe_php_shim::substr(
- &matches[ofs as usize].to_string(),
- characters_entered,
- None,
- ))
- )],
- false,
- output_interface::OUTPUT_NORMAL,
- );
- cursor.restore_position();
- }
- }
-
- // Reset stty so it behaves normally again
- shirabe_php_shim::shell_exec(&format!("stty {}", stty_mode));
-
- Ok(full_choice)
- }
-
- fn most_recently_entered_value(&self, entered: &str) -> String {
- // Determine the most recent value that the user entered
- if !shirabe_php_shim::str_contains(entered, ",") {
- return entered.to_string();
- }
-
- let choices = shirabe_php_shim::explode(",", entered);
- let last_choice = shirabe_php_shim::trim(&choices[choices.len() - 1], None);
- if !last_choice.is_empty() {
- return last_choice;
- }
-
- entered.to_string()
- }
-
- /// Gets a hidden response from user.
- ///
- /// @param resource $inputStream The handler resource
- /// @param bool $trimmable Is the answer trimmable
- ///
- /// @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
- fn get_hidden_response(
- &self,
- output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>,
- input_stream: &shirabe_php_shim::PhpResource,
- trimmable: bool,
- ) -> anyhow::Result<Result<String, RuntimeException>> {
- if cfg!(windows) {
- let mut exe = format!(
- "{}/../Resources/bin/hiddeninput.exe",
- shirabe_php_shim::dir()
- );
-
- // handle code running from a phar
- let mut tmp_exe: Option<String> = None;
- if shirabe_php_shim::substr(&magic_file(), 0, Some(5)) == "phar:" {
- let tmp = format!("{}/hiddeninput.exe", shirabe_php_shim::sys_get_temp_dir());
- shirabe_php_shim::copy(&exe, &tmp);
- exe = tmp.clone();
- tmp_exe = Some(tmp);
- }
-
- let s_exec = shirabe_php_shim::shell_exec(&format!("\"{}\"", exe)).unwrap_or_default();
- let value = if trimmable {
- shirabe_php_shim::rtrim(&s_exec, None)
- } else {
- s_exec
- };
- output
- .borrow()
- .writeln(&["".to_string()], output_interface::OUTPUT_NORMAL);
-
- if let Some(tmp) = tmp_exe {
- shirabe_php_shim::unlink(&tmp);
- }
-
- return Ok(Ok(value));
- }
-
- let mut stty_mode = String::new();
- if STTY.load(std::sync::atomic::Ordering::SeqCst) && Terminal::has_stty_available() {
- stty_mode = shirabe_php_shim::shell_exec("stty -g").unwrap_or_default();
- shirabe_php_shim::shell_exec("stty -echo");
- } else if self.is_interactive_input(input_stream) {
- return Ok(Err(RuntimeException::new(
- "Unable to hide the response.".to_string(),
- )));
- }
-
- let value = shirabe_php_shim::fgets(input_stream, Some(4096));
-
- if STTY.load(std::sync::atomic::Ordering::SeqCst) && Terminal::has_stty_available() {
- shirabe_php_shim::shell_exec(&format!("stty {}", stty_mode));
- }
-
- let mut value = match value {
- Some(value) => value,
- None => {
- return Err(MissingInputException::new("Aborted.".to_string()).into());
- }
- };
- if trimmable {
- value = shirabe_php_shim::trim(&value, None);
- }
- output
- .borrow()
- .writeln(&["".to_string()], output_interface::OUTPUT_NORMAL);
-
- Ok(Ok(value))
- }
-
- fn is_interactive_input(&self, input_stream: &shirabe_php_shim::PhpResource) -> bool {
- let uri = shirabe_php_shim::stream_get_meta_data(input_stream)
- .get("uri")
- .map(|uri| uri.to_string());
- if uri.as_deref() != Some("php://stdin") {
- return false;
- }
-
- let mut stdin_is_interactive = STDIN_IS_INTERACTIVE.lock().unwrap();
- if let Some(value) = *stdin_is_interactive {
- return value;
- }
-
- let value = shirabe_php_shim::stream_isatty_resource(
- &shirabe_php_shim::php_fopen_resource("php://stdin", "r"),
- );
- *stdin_is_interactive = Some(value);
- value
- }
-
- /// Reads one or more lines of input and returns what is read.
- ///
- /// @param resource $inputStream The handler resource
- /// @param Question $question The question being asked
- ///
- /// @return string|false The input received, false in case input could not be read
- fn read_input(
- &self,
- input_stream: &shirabe_php_shim::PhpResource,
- question: &impl QuestionInterface,
- ) -> PhpMixed {
- if !question.is_multiline() {
- let cp = self.set_io_codepage();
- let ret = shirabe_php_shim::fgets(input_stream, Some(4096));
-
- return self.reset_io_codepage(
- cp,
- ret.map(PhpMixed::String).unwrap_or(PhpMixed::Bool(false)),
- );
- }
-
- let multi_line_stream_reader = self.clone_input_stream(input_stream);
- let multi_line_stream_reader = match multi_line_stream_reader {
- Some(reader) => reader,
- None => return PhpMixed::Bool(false),
- };
-
- let mut ret = String::new();
- let cp = self.set_io_codepage();
- loop {
- let char = shirabe_php_shim::fgetc(&multi_line_stream_reader);
- let char = match char {
- Some(char) => char,
- None => break,
- };
- if shirabe_php_shim::PHP_EOL == format!("{}{}", ret, char) {
- break;
- }
- ret.push_str(&char);
- }
-
- self.reset_io_codepage(cp, PhpMixed::String(ret))
- }
-
- /// Sets console I/O to the host code page.
- ///
- /// @return int Previous code page in IBM/EBCDIC format
- fn set_io_codepage(&self) -> i64 {
- if shirabe_php_shim::function_exists("sapi_windows_cp_set") {
- let cp = shirabe_php_shim::sapi_windows_cp_get(None);
- shirabe_php_shim::sapi_windows_cp_set(shirabe_php_shim::sapi_windows_cp_get(Some(
- "oem",
- )));
-
- return cp;
- }
-
- 0
- }
-
- /// Sets console I/O to the specified code page and converts the user input.
- fn reset_io_codepage(&self, cp: i64, input: PhpMixed) -> PhpMixed {
- let mut input = input;
- if 0 != cp {
- shirabe_php_shim::sapi_windows_cp_set(cp);
-
- if !matches!(input, PhpMixed::Bool(false)) && input.to_string() != "" {
- input = PhpMixed::String(shirabe_php_shim::sapi_windows_cp_conv(
- shirabe_php_shim::sapi_windows_cp_get(Some("oem")),
- cp,
- &input.to_string(),
- ));
- }
- }
-
- input
- }
-
- /// Clones an input stream in order to act on one instance of the same
- /// stream without affecting the other instance.
- ///
- /// @param resource $inputStream The handler resource
- ///
- /// @return resource|null The cloned resource, null in case it could not be cloned
- fn clone_input_stream(
- &self,
- input_stream: &shirabe_php_shim::PhpResource,
- ) -> Option<shirabe_php_shim::PhpResource> {
- let stream_meta_data = shirabe_php_shim::stream_get_meta_data(input_stream);
- let seekable = stream_meta_data
- .get("seekable")
- .cloned()
- .unwrap_or(PhpMixed::Bool(false));
- let mode = stream_meta_data
- .get("mode")
- .map(|m| m.to_string())
- .unwrap_or_else(|| "rb".to_string());
- let uri = stream_meta_data.get("uri").map(|u| u.to_string());
-
- let uri = uri?;
-
- let clone_stream = shirabe_php_shim::fopen(&uri, &mode).ok()?;
-
- // For seekable and writable streams, add all the same data to the
- // cloned stream and then seek to the same offset.
- if matches!(seekable, PhpMixed::Bool(true)) && !["r", "rb", "rt"].contains(&mode.as_str()) {
- let offset = shirabe_php_shim::ftell(input_stream).unwrap_or(0);
- shirabe_php_shim::rewind(input_stream);
- shirabe_php_shim::stream_copy_to_stream(input_stream, &clone_stream);
- shirabe_php_shim::fseek(input_stream, offset, shirabe_php_shim::SEEK_SET);
- shirabe_php_shim::fseek(&clone_stream, offset, shirabe_php_shim::SEEK_SET);
- }
-
- Some(clone_stream)
- }
-
- /// Helper::substr proxy (inherited static helper).
- fn substr(string: Option<&str>, from: i64, length: Option<i64>) -> String {
- Helper::substr(string.unwrap_or(""), from, length)
- }
-}
-
-/// PHP `__FILE__` magic constant. The executing code lives in the Shirabe binary itself, which is
-/// the closest analogue for a native executable; it never carries the `phar:` scheme, so the
-/// phar-relocation branch in `get_hidden_response` correctly never triggers.
-fn magic_file() -> String {
- std::env::current_exe()
- .expect("current executable path")
- .display()
- .to_string()
-}
-
-impl HelperInterface for QuestionHelper {
- fn set_helper_set(&mut self, helper_set: Option<std::rc::Rc<std::cell::RefCell<HelperSet>>>) {
- self.inner.set_helper_set(helper_set);
- }
-
- fn get_helper_set(&self) -> Option<std::rc::Rc<std::cell::RefCell<HelperSet>>> {
- self.inner.get_helper_set()
- }
-
- fn get_name(&self) -> String {
- self.get_name()
- }
-}
diff --git a/crates/shirabe-external-packages/src/symfony/console/helper/symfony_question_helper.rs b/crates/shirabe-external-packages/src/symfony/console/helper/symfony_question_helper.rs
deleted file mode 100644
index eda54243..00000000
--- a/crates/shirabe-external-packages/src/symfony/console/helper/symfony_question_helper.rs
+++ /dev/null
@@ -1,163 +0,0 @@
-//! ref: composer/vendor/symfony/console/Helper/SymfonyQuestionHelper.php
-
-use crate::symfony::console::formatter::output_formatter::OutputFormatter;
-use crate::symfony::console::helper::question_helper::{QuestionHelper, QuestionHelperInterface};
-use crate::symfony::console::output::output_interface;
-use crate::symfony::console::output::output_interface::OutputInterface;
-use crate::symfony::console::question::QuestionInterface;
-use crate::symfony::console::style::style_interface::StyleInterface;
-use crate::symfony::console::style::symfony_style::SymfonyStyle;
-use shirabe_php_shim::PhpMixed;
-use std::ops::{Deref, DerefMut};
-
-/// Symfony Style Guide compliant question helper.
-#[derive(Debug, Default)]
-pub struct SymfonyQuestionHelper {
- inner: QuestionHelper,
-}
-
-impl SymfonyQuestionHelper {
- pub fn new() -> Self {
- Self::default()
- }
-
- fn get_eof_shortcut(&self) -> String {
- if shirabe_php_shim::php_os_family() == "Windows" {
- return "<comment>Ctrl+Z</comment> then <comment>Enter</comment>".to_string();
- }
-
- "<comment>Ctrl+D</comment>".to_string()
- }
-}
-
-impl QuestionHelperInterface for SymfonyQuestionHelper {
- fn inner(&self) -> &QuestionHelper {
- &self.inner
- }
-
- fn inner_mut(&mut self) -> &mut QuestionHelper {
- &mut self.inner
- }
-
- /// {@inheritdoc}
- fn write_prompt(
- &self,
- output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>,
- question: &impl QuestionInterface,
- ) {
- let mut text = OutputFormatter::escape_trailing_backslash(question.get_question());
- let default = question.get_default();
-
- if question.is_multiline() {
- text += &format!(" (press {} to continue)", self.get_eof_shortcut());
- }
-
- // switch (true)
- if matches!(default, PhpMixed::Null) {
- text = format!(" <info>{}</info>:", text);
- } else if question.as_confirmation().is_some() {
- text = format!(
- " <info>{} (yes/no)</info> [<comment>{}</comment>]:",
- text,
- if shirabe_php_shim::boolval(&default) {
- "yes"
- } else {
- "no"
- },
- );
- } else if let Some(choice_question) = question.as_choice().filter(|q| q.is_multiselect()) {
- let choices = choice_question.get_choices();
- let default_parts = shirabe_php_shim::explode(",", &default.to_string());
-
- let resolved: Vec<String> = default_parts
- .iter()
- .map(|value| {
- choices
- .get(&shirabe_php_shim::trim(value, None))
- .map(|v| v.to_string())
- .unwrap()
- })
- .collect();
-
- text = format!(
- " <info>{}</info> [<comment>{}</comment>]:",
- text,
- OutputFormatter::escape(&resolved.join(", ")).unwrap(),
- );
- } else if let Some(choice_question) = question.as_choice() {
- let choices = choice_question.get_choices();
- text = format!(
- " <info>{}</info> [<comment>{}</comment>]:",
- text,
- OutputFormatter::escape(
- &choices
- .get(&default.to_string())
- .cloned()
- .unwrap_or(default)
- .to_string(),
- )
- .unwrap(),
- );
- } else {
- text = format!(
- " <info>{}</info> [<comment>{}</comment>]:",
- text,
- OutputFormatter::escape(&default.to_string()).unwrap(),
- );
- }
-
- output
- .borrow()
- .writeln(&[text], output_interface::OUTPUT_NORMAL);
-
- let mut prompt = " > ".to_string();
-
- if let Some(choice_question) = question.as_choice() {
- output.borrow().writeln(
- &self
- .inner
- .format_choice_question_choices(choice_question, "comment"),
- output_interface::OUTPUT_NORMAL,
- );
-
- prompt = choice_question.get_prompt().to_string();
- }
-
- output
- .borrow()
- .write(&[prompt], false, output_interface::OUTPUT_NORMAL);
- }
-
- /// {@inheritdoc}
- fn write_error(
- &self,
- output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>,
- error: &shirabe_php_shim::Exception,
- ) {
- {
- let mut borrowed = output.borrow_mut();
- if let Some(style) = (*borrowed).as_any_mut().downcast_mut::<SymfonyStyle>() {
- style.new_line(1);
- style.error(PhpMixed::String(error.get_message().to_string()));
-
- return;
- }
- }
-
- self.inner.write_error(output, error);
- }
-}
-
-impl Deref for SymfonyQuestionHelper {
- type Target = QuestionHelper;
-
- fn deref(&self) -> &Self::Target {
- &self.inner
- }
-}
-
-impl DerefMut for SymfonyQuestionHelper {
- fn deref_mut(&mut self) -> &mut Self::Target {
- &mut self.inner
- }
-}
diff --git a/crates/shirabe-external-packages/src/symfony/console/helper/table.rs b/crates/shirabe-external-packages/src/symfony/console/helper/table.rs
deleted file mode 100644
index 650dfffa..00000000
--- a/crates/shirabe-external-packages/src/symfony/console/helper/table.rs
+++ /dev/null
@@ -1,1443 +0,0 @@
-//! ref: composer/vendor/symfony/console/Helper/Table.php
-
-use crate::symfony::console::exception::invalid_argument_exception::InvalidArgumentException;
-use crate::symfony::console::exception::runtime_exception::RuntimeException;
-use crate::symfony::console::formatter::output_formatter::OutputFormatter;
-use crate::symfony::console::formatter::wrappable_output_formatter_interface::WrappableOutputFormatterInterface;
-use crate::symfony::console::helper::helper::Helper;
-use crate::symfony::console::helper::table_cell::{TableCell, TableCellOption};
-use crate::symfony::console::helper::table_cell_style::TableCellStyle;
-use crate::symfony::console::helper::table_rows::TableRows;
-use crate::symfony::console::helper::table_separator::TableSeparator;
-use crate::symfony::console::helper::table_style::TableStyle;
-use crate::symfony::console::output::console_section_output::ConsoleSectionOutput;
-use crate::symfony::console::output::output_interface::OutputInterface;
-use indexmap::IndexMap;
-use shirabe_pcre::preg::Preg;
-use shirabe_php_shim::{PhpMixed, php_regex};
-
-/// A single cell within a table row.
-///
-/// PHP types a cell as `TableCell|string|int|null` (scalars are stringified). Because
-/// `TableSeparator extends TableCell`, a separator can in principle appear as a cell, so it has its
-/// own variant; `is_table_cell` reports `true` for it, mirroring `instanceof TableCell`.
-#[derive(Debug, Clone)]
-pub enum Cell {
- Null,
- Value(String),
- Cell(TableCell),
- Separator(TableSeparator),
-}
-
-impl Cell {
- /// PHP `$cell instanceof TableCell` (true for `TableSeparator`, which extends `TableCell`).
- fn is_table_cell(&self) -> bool {
- matches!(self, Cell::Cell(_) | Cell::Separator(_))
- }
-
- /// PHP `$cell instanceof TableSeparator`.
- fn is_table_separator(&self) -> bool {
- matches!(self, Cell::Separator(_))
- }
-
- fn colspan(&self) -> i64 {
- match self {
- Cell::Cell(c) => c.get_colspan(),
- Cell::Separator(s) => s.get_colspan(),
- _ => 1,
- }
- }
-
- fn rowspan(&self) -> i64 {
- match self {
- Cell::Cell(c) => c.get_rowspan(),
- Cell::Separator(s) => s.get_rowspan(),
- _ => 1,
- }
- }
-
- fn style(&self) -> Option<std::rc::Rc<TableCellStyle>> {
- match self {
- Cell::Cell(c) => c.get_style(),
- Cell::Separator(s) => s.get_style(),
- _ => None,
- }
- }
-
- /// PHP `(string) $cell`.
- fn to_php_string(&self) -> String {
- match self {
- Cell::Null | Cell::Separator(_) => String::new(),
- Cell::Value(s) => s.clone(),
- Cell::Cell(c) => c.to_string(),
- }
- }
-
- /// PHP truthiness / `!empty($cell)` for a cell value: only "" and "0" (and null) are falsy;
- /// any object is truthy.
- fn is_truthy(&self) -> bool {
- match self {
- Cell::Null => false,
- Cell::Value(s) => !s.is_empty() && s != "0",
- Cell::Cell(_) | Cell::Separator(_) => true,
- }
- }
-
- fn is_null(&self) -> bool {
- matches!(self, Cell::Null)
- }
-}
-
-impl From<String> for Cell {
- fn from(value: String) -> Self {
- Cell::Value(value)
- }
-}
-
-impl From<&str> for Cell {
- fn from(value: &str) -> Self {
- Cell::Value(value.to_string())
- }
-}
-
-impl From<TableCell> for Cell {
- fn from(value: TableCell) -> Self {
- Cell::Cell(value)
- }
-}
-
-/// Bridges PHP-typed cell values built elsewhere in the codebase (where rows are still assembled as
-/// `PhpMixed`) onto the typed cell. A cell is a scalar or null; collections stringify as PHP would.
-impl From<PhpMixed> for Cell {
- fn from(value: PhpMixed) -> Self {
- match value {
- PhpMixed::Null => Cell::Null,
- other => Cell::Value(shirabe_php_shim::to_string(&other)),
- }
- }
-}
-
-/// A table row.
-///
-/// PHP types a row as `array<int, Cell>|TableSeparator`. The header/body boundary that PHP creates
-/// as a fresh `TableSeparator` and recognizes by object identity (`$divider === $row`) is modeled
-/// here by its own [`Row::HeaderDivider`] variant rather than by reference identity: it counts as a
-/// separator for layout (`is_table_separator`), but unlike a user-supplied [`Row::Separator`] it
-/// renders no separator line — it only flips the header/first-row state during rendering.
-#[derive(Debug, Clone)]
-pub enum Row {
- HeaderDivider,
- Separator(TableSeparator),
- Cells(Vec<Cell>),
-}
-
-impl Row {
- /// PHP `$row instanceof TableSeparator` (also true for the internal header/body divider).
- fn is_table_separator(&self) -> bool {
- matches!(self, Row::HeaderDivider | Row::Separator(_))
- }
-
- /// PHP `$divider === $row`: identifies the internal header/body boundary marker.
- fn is_divider(&self) -> bool {
- matches!(self, Row::HeaderDivider)
- }
-
- /// The row's cells, or an empty list for a separator (PHP `foreach` over a separator object,
- /// which exposes no public properties, yields nothing).
- fn cells(&self) -> Vec<Cell> {
- match self {
- Row::Cells(cells) => cells.clone(),
- _ => Vec::new(),
- }
- }
-
- /// PHP `!$row`: an empty cell array is falsy; a separator is an object and thus truthy.
- fn is_truthy(&self) -> bool {
- match self {
- Row::Cells(cells) => !cells.is_empty(),
- _ => true,
- }
- }
-
- /// PHP `isset($row[$i])`: `false` for a missing or null cell.
- fn get_cell_isset(&self, index: i64) -> Option<Cell> {
- match self {
- Row::Cells(cells) => match cells.get(index as usize) {
- Some(Cell::Null) | None => None,
- Some(cell) => Some(cell.clone()),
- },
- _ => None,
- }
- }
-}
-
-impl From<Vec<Cell>> for Row {
- fn from(cells: Vec<Cell>) -> Self {
- Row::Cells(cells)
- }
-}
-
-/// Bridges PHP-typed rows built elsewhere (still assembled as `PhpMixed`) onto the typed row.
-impl From<PhpMixed> for Row {
- fn from(value: PhpMixed) -> Self {
- match value {
- PhpMixed::List(items) => Row::Cells(items.into_iter().map(Cell::from).collect()),
- PhpMixed::Array(map) => Row::Cells(map.into_values().map(Cell::from).collect()),
- PhpMixed::Null => Row::Cells(Vec::new()),
- other => Row::Cells(vec![Cell::from(other)]),
- }
- }
-}
-
-/// A table or column style argument. PHP types it as `string|TableStyle`: either the name of a
-/// registered style or a `TableStyle` instance.
-#[derive(Debug, Clone)]
-pub enum StyleName {
- Name(String),
- Style(TableStyle),
-}
-
-impl From<&str> for StyleName {
- fn from(name: &str) -> Self {
- StyleName::Name(name.to_string())
- }
-}
-
-impl From<String> for StyleName {
- fn from(name: String) -> Self {
- StyleName::Name(name)
- }
-}
-
-impl From<TableStyle> for StyleName {
- fn from(style: TableStyle) -> Self {
- StyleName::Style(style)
- }
-}
-
-/// PHP `$row[$index] = $value`: assigns at the integer key, growing the (dense) row with null
-/// cells to reach `index` when necessary.
-fn set_cell(row: &mut Vec<Cell>, index: i64, value: Cell) {
- let index = index as usize;
- while row.len() <= index {
- row.push(Cell::Null);
- }
- row[index] = value;
-}
-
-/// Provides helpers to display a table.
-#[derive(Debug)]
-pub struct Table {
- header_title: Option<String>,
- footer_title: Option<String>,
-
- /// Table headers.
- headers: Vec<Row>,
-
- /// Table rows.
- rows: Vec<Row>,
- horizontal: bool,
-
- /// Column widths cache.
- effective_column_widths: IndexMap<i64, i64>,
-
- /// Number of columns cache.
- number_of_columns: Option<i64>,
-
- output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>,
-
- style: TableStyle,
-
- column_styles: IndexMap<i64, TableStyle>,
-
- /// User set column widths.
- column_widths: IndexMap<i64, i64>,
- column_max_widths: IndexMap<i64, i64>,
-
- rendered: bool,
-}
-
-const SEPARATOR_TOP: i64 = 0;
-const SEPARATOR_TOP_BOTTOM: i64 = 1;
-const SEPARATOR_MID: i64 = 2;
-const SEPARATOR_BOTTOM: i64 = 3;
-const BORDER_OUTSIDE: i64 = 0;
-const BORDER_INSIDE: i64 = 1;
-
-/// Global style definitions, lazily initialized.
-///
-/// In PHP this is `private static $styles`. Here it is a process-global cache.
-fn styles() -> &'static std::sync::Mutex<Option<IndexMap<String, TableStyle>>> {
- static STYLES: std::sync::Mutex<Option<IndexMap<String, TableStyle>>> =
- std::sync::Mutex::new(None);
- &STYLES
-}
-
-impl Table {
- pub fn new(output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>) -> Self {
- let mut styles_guard = styles().lock().unwrap();
- if styles_guard.is_none() {
- *styles_guard = Some(Self::init_styles());
- }
- drop(styles_guard);
-
- let mut this = Self {
- header_title: None,
- footer_title: None,
- headers: Vec::new(),
- rows: Vec::new(),
- horizontal: false,
- effective_column_widths: IndexMap::new(),
- number_of_columns: None,
- output,
- style: TableStyle::default(),
- column_styles: IndexMap::new(),
- column_widths: IndexMap::new(),
- column_max_widths: IndexMap::new(),
- rendered: false,
- };
-
- this.set_style(StyleName::from("default"));
-
- this
- }
-
- /// Sets a style definition.
- pub fn set_style_definition(name: String, style: TableStyle) {
- let mut styles_guard = styles().lock().unwrap();
- if styles_guard.is_none() {
- *styles_guard = Some(Self::init_styles());
- }
-
- styles_guard.as_mut().unwrap().insert(name, style);
- }
-
- /// Gets a style definition by name.
- pub fn get_style_definition(
- name: String,
- ) -> anyhow::Result<Result<TableStyle, InvalidArgumentException>> {
- let mut styles_guard = styles().lock().unwrap();
- if styles_guard.is_none() {
- *styles_guard = Some(Self::init_styles());
- }
-
- if let Some(style) = styles_guard.as_ref().unwrap().get(&name) {
- return Ok(Ok(style.clone()));
- }
-
- Ok(Err(InvalidArgumentException::new(format!(
- "Style \"{}\" is not defined.",
- name
- ))))
- }
-
- /// Sets table style.
- ///
- /// `$name` is the style name or a TableStyle instance.
- pub fn set_style(
- &mut self,
- name: StyleName,
- ) -> anyhow::Result<Result<&mut Self, InvalidArgumentException>> {
- match self.resolve_style(name)? {
- Ok(style) => {
- self.style = style;
- Ok(Ok(self))
- }
- Err(e) => Ok(Err(e)),
- }
- }
-
- /// Gets the current table style.
- pub fn get_style(&self) -> &TableStyle {
- &self.style
- }
-
- /// Sets table column style.
- ///
- /// `$name` is the style name or a TableStyle instance.
- pub fn set_column_style(
- &mut self,
- column_index: i64,
- name: StyleName,
- ) -> anyhow::Result<Result<&mut Self, InvalidArgumentException>> {
- match self.resolve_style(name)? {
- Ok(style) => {
- self.column_styles.insert(column_index, style);
- Ok(Ok(self))
- }
- Err(e) => Ok(Err(e)),
- }
- }
-
- /// Gets the current style for a column.
- ///
- /// If style was not set, it returns the global table style.
- pub fn get_column_style(&self, column_index: i64) -> &TableStyle {
- self.column_styles
- .get(&column_index)
- .unwrap_or_else(|| self.get_style())
- }
-
- /// Sets the minimum width of a column.
- pub fn set_column_width(&mut self, column_index: i64, width: i64) -> &mut Self {
- self.column_widths.insert(column_index, width);
-
- self
- }
-
- /// Sets the minimum width of all columns.
- pub fn set_column_widths(&mut self, widths: Vec<i64>) -> &mut Self {
- self.column_widths = IndexMap::new();
- for (index, width) in widths.into_iter().enumerate() {
- self.set_column_width(index as i64, width);
- }
-
- self
- }
-
- /// Sets the maximum width of a column.
- ///
- /// Any cell within this column which contents exceeds the specified width will be wrapped into
- /// multiple lines, while formatted strings are preserved.
- pub fn set_column_max_width(&mut self, column_index: i64, width: i64) -> &mut Self {
- if !Self::formatter_is_wrappable(&self.output) {
- // PHP throws \LogicException here. This represents a programming error: the caller must
- // supply a WrappableOutputFormatterInterface before setting a maximum column width.
- panic!(
- "Setting a maximum column width is only supported when using a \"{}\" formatter, got \"{}\".",
- "Symfony\\Component\\Console\\Formatter\\WrappableOutputFormatterInterface",
- shirabe_php_shim::get_debug_type(&PhpMixed::from(()))
- );
- }
-
- self.column_max_widths.insert(column_index, width);
-
- self
- }
-
- pub fn set_headers(&mut self, headers: Vec<Cell>) -> &mut Self {
- // PHP wraps a flat list of cells into a single header row. (Multi-row headers, which PHP
- // also accepts, are not used in this codebase and are not modeled by the typed API.)
- self.headers = if headers.is_empty() {
- Vec::new()
- } else {
- vec![Row::Cells(headers)]
- };
-
- self
- }
-
- pub fn set_rows(&mut self, rows: Vec<Row>) -> &mut Self {
- self.rows = Vec::new();
-
- self.add_rows(rows)
- }
-
- pub fn add_rows(&mut self, rows: Vec<Row>) -> &mut Self {
- for row in rows {
- self.add_row(row);
- }
-
- self
- }
-
- pub fn add_row(&mut self, row: Row) -> &mut Self {
- // PHP `array_values($row)` reindexing is the identity on a positional cell vector, and the
- // "row must be an array or a TableSeparator" check is now guaranteed by the type.
- self.rows.push(row);
-
- self
- }
-
- /// Adds a row to the table, and re-renders the table.
- pub fn append_row(&mut self, row: Row) -> anyhow::Result<Result<&mut Self, RuntimeException>> {
- if !Self::output_is_console_section(&self.output) {
- return Ok(Err(RuntimeException::new(format!(
- "Output should be an instance of \"{}\" when calling \"{}\".",
- "Symfony\\Component\\Console\\Output\\ConsoleSectionOutput",
- "Symfony\\Component\\Console\\Helper\\Table::appendRow",
- ))));
- }
-
- if self.rendered {
- // TODO(phase-c): downcast output to ConsoleSectionOutput to call clear().
- let _ = ConsoleSectionOutput::clear;
- let row_count = self.calculate_row_count();
- let _ = row_count;
- todo!()
- }
-
- self.add_row(row);
- self.render();
-
- Ok(Ok(self))
- }
-
- pub fn set_row(&mut self, column: i64, row: Vec<Cell>) -> &mut Self {
- // PHP indexes $this->rows by arbitrary key; sparse assignment over a positional Vec is not
- // modeled and has no callers.
- let _ = (column, row);
- // TODO(phase-c): sparse `$this->rows[$column] = $row` over a positional row vector.
- todo!()
- }
-
- pub fn set_header_title(&mut self, title: Option<String>) -> &mut Self {
- self.header_title = title;
-
- self
- }
-
- pub fn set_footer_title(&mut self, title: Option<String>) -> &mut Self {
- self.footer_title = title;
-
- self
- }
-
- pub fn set_horizontal(&mut self, horizontal: bool) -> &mut Self {
- self.horizontal = horizontal;
-
- self
- }
-
- /// Renders table to output.
- pub fn render(&mut self) {
- let rows: Vec<Row> = if self.horizontal {
- let mut horizontal_rows: IndexMap<i64, Vec<Cell>> = IndexMap::new();
- let header0 = self.headers.first().map(|h| h.cells()).unwrap_or_default();
- for (i, header) in header0.into_iter().enumerate() {
- let i = i as i64;
- horizontal_rows.insert(i, vec![header]);
- for row in &self.rows {
- if row.is_table_separator() {
- continue;
- }
- if let Some(cell) = row.get_cell_isset(i) {
- let entry = horizontal_rows.get_mut(&i).unwrap();
- entry.push(cell);
- } else {
- let first = horizontal_rows.get(&i).unwrap().first().cloned();
- let is_title_noop = match first {
- Some(ref c) if c.is_table_cell() => c.colspan() >= 2,
- _ => false,
- };
- if is_title_noop {
- // Noop, there is a "title"
- } else {
- let entry = horizontal_rows.get_mut(&i).unwrap();
- entry.push(Cell::Null);
- }
- }
- }
- }
- horizontal_rows.into_values().map(Row::Cells).collect()
- } else {
- let mut merged = self.headers.clone();
- merged.push(Row::HeaderDivider);
- merged.extend(self.rows.clone());
- merged
- };
-
- self.calculate_number_of_columns(&rows);
-
- let row_groups = self.build_table_rows(rows);
- self.calculate_columns_width(&row_groups);
-
- let mut is_header = !self.horizontal;
- let mut is_first_row = self.horizontal;
- let mut has_title =
- self.header_title.is_some() && !self.header_title.as_deref().unwrap_or("").is_empty();
-
- for row_group in &row_groups {
- let mut is_header_separator_rendered = false;
-
- for row in row_group {
- if row.is_divider() {
- is_header = false;
- is_first_row = true;
-
- continue;
- }
-
- if row.is_table_separator() {
- self.render_row_separator(SEPARATOR_MID, None, None);
-
- continue;
- }
-
- if !row.is_truthy() {
- continue;
- }
-
- if is_header && !is_header_separator_rendered {
- self.render_row_separator(
- if is_header {
- SEPARATOR_TOP
- } else {
- SEPARATOR_TOP_BOTTOM
- },
- if has_title {
- self.header_title.clone()
- } else {
- None
- },
- if has_title {
- Some(self.style.get_header_title_format())
- } else {
- None
- },
- );
- has_title = false;
- is_header_separator_rendered = true;
- }
-
- if is_first_row {
- self.render_row_separator(
- if is_header {
- SEPARATOR_TOP
- } else {
- SEPARATOR_TOP_BOTTOM
- },
- if has_title {
- self.header_title.clone()
- } else {
- None
- },
- if has_title {
- Some(self.style.get_header_title_format())
- } else {
- None
- },
- );
- is_first_row = false;
- has_title = false;
- }
-
- if self.horizontal {
- self.render_row(
- row.cells(),
- self.style.get_cell_row_format(),
- Some(self.style.get_cell_header_format()),
- );
- } else {
- self.render_row(
- row.cells(),
- if is_header {
- self.style.get_cell_header_format()
- } else {
- self.style.get_cell_row_format()
- },
- None,
- );
- }
- }
- }
- self.render_row_separator(
- SEPARATOR_BOTTOM,
- self.footer_title.clone(),
- Some(self.style.get_footer_title_format()),
- );
-
- self.cleanup();
- self.rendered = true;
- }
-
- /// Renders horizontal header separator.
- fn render_row_separator(
- &self,
- r#type: i64,
- title: Option<String>,
- title_format: Option<String>,
- ) {
- let count = match self.number_of_columns {
- Some(0) | None => return,
- Some(c) => c,
- };
-
- let borders = self.style.get_border_chars();
- if borders[0].is_empty()
- && borders[2].is_empty()
- && self.style.get_crossing_char().is_empty()
- {
- return;
- }
-
- let crossings = self.style.get_crossing_chars();
- let (horizontal, left_char, mid_char, right_char) = if SEPARATOR_MID == r#type {
- (
- borders[2].clone(),
- crossings[8].clone(),
- crossings[0].clone(),
- crossings[4].clone(),
- )
- } else if SEPARATOR_TOP == r#type {
- (
- borders[0].clone(),
- crossings[1].clone(),
- crossings[2].clone(),
- crossings[3].clone(),
- )
- } else if SEPARATOR_TOP_BOTTOM == r#type {
- (
- borders[0].clone(),
- crossings[9].clone(),
- crossings[10].clone(),
- crossings[11].clone(),
- )
- } else {
- (
- borders[0].clone(),
- crossings[7].clone(),
- crossings[6].clone(),
- crossings[5].clone(),
- )
- };
-
- let mut markup = left_char;
- let mut column = 0;
- while column < count {
- markup.push_str(&shirabe_php_shim::str_repeat(
- &horizontal,
- self.effective_column_widths[&column] as usize,
- ));
- markup.push_str(if column == count - 1 {
- &right_char
- } else {
- &mid_char
- });
- column += 1;
- }
-
- if let Some(title) = title {
- let title_format = title_format.unwrap();
- let formatted_title =
- shirabe_php_shim::sprintf(&title_format, &[PhpMixed::from(title.clone())]);
- let mut formatted_title = formatted_title;
- let mut title_length = Helper::width(&self.remove_decoration(&formatted_title));
- let markup_length = Helper::width(&markup);
- let limit = markup_length - 4;
- if title_length > limit {
- title_length = limit;
- let format_length = Helper::width(&self.remove_decoration(
- &shirabe_php_shim::sprintf(&title_format, &[PhpMixed::from("")]),
- ));
- formatted_title = shirabe_php_shim::sprintf(
- &title_format,
- &[PhpMixed::from(format!(
- "{}...",
- Helper::substr(&title, 0, Some(limit - format_length - 3))
- ))],
- );
- }
-
- let title_start = (markup_length - title_length) / 2;
- if shirabe_php_shim::mb_detect_encoding(&markup, None, true).is_none() {
- markup = shirabe_php_shim::substr_replace(
- &markup,
- &formatted_title,
- title_start as usize,
- title_length as usize,
- );
- } else {
- markup = format!(
- "{}{}{}",
- shirabe_php_shim::mb_substr(&markup, 0, Some(title_start), None),
- formatted_title,
- shirabe_php_shim::mb_substr(&markup, title_start + title_length, None, None),
- );
- }
- }
-
- self.output.borrow().writeln(
- &[shirabe_php_shim::sprintf(
- &self.style.get_border_format(),
- &[PhpMixed::from(markup)],
- )],
- crate::symfony::console::output::output_interface::OUTPUT_NORMAL,
- );
- }
-
- /// Renders vertical column separator.
- fn render_column_separator(&self, r#type: i64) -> String {
- let borders = self.style.get_border_chars();
-
- shirabe_php_shim::sprintf(
- &self.style.get_border_format(),
- &[PhpMixed::from(if BORDER_OUTSIDE == r#type {
- borders[1].clone()
- } else {
- borders[3].clone()
- })],
- )
- }
-
- /// Renders table row.
- fn render_row(&self, row: Vec<Cell>, cell_format: String, first_cell_format: Option<String>) {
- let mut row_content = self.render_column_separator(BORDER_OUTSIDE);
- let columns = self.get_row_columns(&row);
- let last = columns.len() as i64 - 1;
- for (i, column) in columns.into_iter().enumerate() {
- let i = i as i64;
- if first_cell_format.is_some() && 0 == i {
- row_content.push_str(&self.render_cell(
- &row,
- column,
- first_cell_format.clone().unwrap(),
- ));
- } else {
- row_content.push_str(&self.render_cell(&row, column, cell_format.clone()));
- }
- row_content.push_str(&self.render_column_separator(if last == i {
- BORDER_OUTSIDE
- } else {
- BORDER_INSIDE
- }));
- }
- self.output.borrow().writeln(
- &[row_content],
- crate::symfony::console::output::output_interface::OUTPUT_NORMAL,
- );
- }
-
- /// Renders table cell with padding.
- fn render_cell(&self, row: &[Cell], column: i64, cell_format: String) -> String {
- let cell = row
- .get(column as usize)
- .cloned()
- .unwrap_or(Cell::Value(String::new()));
- let mut width = self.effective_column_widths[&column];
- if cell.is_table_cell() && cell.colspan() > 1 {
- // add the width of the following columns(numbers of colspan).
- for next_column in (column + 1)..=(column + cell.colspan() - 1) {
- width +=
- self.get_column_separator_width() + self.effective_column_widths[&next_column];
- }
- }
-
- // str_pad won't work properly with multi-byte strings, we need to fix the padding
- let cell_str = cell.to_php_string();
- if let Some(encoding) = shirabe_php_shim::mb_detect_encoding(&cell_str, None, true) {
- width += shirabe_php_shim::strlen(&cell_str)
- - shirabe_php_shim::mb_strwidth(&cell_str, Some(&encoding));
- }
-
- let style = self.get_column_style(column);
-
- if cell.is_table_separator() {
- return shirabe_php_shim::sprintf(
- &style.get_border_format(),
- &[PhpMixed::from(shirabe_php_shim::str_repeat(
- &style.get_border_chars()[2],
- width as usize,
- ))],
- );
- }
-
- width += Helper::length(&cell_str) - Helper::length(&self.remove_decoration(&cell_str));
- let mut content = shirabe_php_shim::sprintf(
- &style.get_cell_row_content_format(),
- &[PhpMixed::from(cell_str.clone())],
- );
-
- let mut cell_format = cell_format;
- let mut pad_type = style.get_pad_type();
- if cell.is_table_cell() && cell.style().is_some() {
- let is_not_styled_by_tag = !Preg::is_match(
- php_regex!("/^<(\\w+|(\\w+=[\\w,]+;?)*)>.+<\\/(\\w+|(\\w+=\\w+;?)*)?>$/"),
- &cell_str,
- );
- if is_not_styled_by_tag {
- let cell_style = cell.style().unwrap();
- match cell_style.get_cell_format() {
- Some(fmt) => cell_format = fmt,
- None => {
- let tag = shirabe_php_shim::http_build_query_mixed(
- &cell_style.get_tag_options(),
- "",
- ";",
- );
- cell_format = format!("<{}>%s</>", tag);
- }
- }
-
- if shirabe_php_shim::strstr(&content, "</>").is_some() {
- content = shirabe_php_shim::str_replace("</>", "", &content);
- width -= 3;
- }
- if shirabe_php_shim::strstr(&content, "<fg=default;bg=default>").is_some() {
- content =
- shirabe_php_shim::str_replace("<fg=default;bg=default>", "", &content);
- width -= shirabe_php_shim::strlen("<fg=default;bg=default>");
- }
- }
-
- pad_type = cell.style().unwrap().get_pad_by_align();
- }
-
- shirabe_php_shim::sprintf(
- &cell_format,
- &[PhpMixed::from(shirabe_php_shim::str_pad(
- &content,
- width as usize,
- &style.get_padding_char(),
- pad_type,
- ))],
- )
- }
-
- /// Calculate number of columns for this table.
- fn calculate_number_of_columns(&mut self, rows: &[Row]) {
- let mut columns = vec![0i64];
- for row in rows {
- if row.is_table_separator() {
- continue;
- }
-
- columns.push(self.get_number_of_columns(&row.cells()));
- }
-
- self.number_of_columns = Some(*columns.iter().max().unwrap());
- }
-
- fn build_table_rows(&mut self, rows: Vec<Row>) -> TableRows {
- let mut rows = rows;
- let mut unmerged_rows: IndexMap<i64, IndexMap<i64, Vec<Cell>>> = IndexMap::new();
- let mut row_key = 0i64;
- while row_key < rows.len() as i64 {
- rows = self.fill_next_rows(rows, row_key);
-
- // Remove any new line breaks and replace it with a new line
- let current = rows[row_key as usize].cells();
- for (column, cell) in current.iter().enumerate() {
- let column = column as i64;
- let mut cell = cell.clone();
- let colspan = if cell.is_table_cell() {
- cell.colspan()
- } else {
- 1
- };
-
- if self.column_max_widths.contains_key(&column)
- && Helper::width(&self.remove_decoration(&cell.to_php_string()))
- > self.column_max_widths[&column]
- {
- let wrapped = self.format_and_wrap(
- &cell.to_php_string(),
- self.column_max_widths[&column] * colspan,
- );
- cell = Cell::Value(wrapped);
- }
- let cell_str = cell.to_php_string();
- if shirabe_php_shim::strstr(&cell_str, "\n").is_none() {
- continue;
- }
- let eol = if shirabe_php_shim::str_contains(&cell_str, "\r\n") {
- "\r\n"
- } else {
- "\n"
- };
- let escaped = shirabe_php_shim::implode(
- eol,
- &shirabe_php_shim::explode(eol, &cell_str)
- .iter()
- .map(|line| OutputFormatter::escape_trailing_backslash(line))
- .collect::<Vec<_>>(),
- );
- cell = if cell.is_table_cell() {
- Cell::Cell(TableCell::new2(
- &escaped,
- Self::table_cell_options_colspan(cell.colspan()),
- ))
- } else {
- Cell::Value(escaped.clone())
- };
- let lines = shirabe_php_shim::explode(
- eol,
- &shirabe_php_shim::str_replace(
- eol,
- &format!("<fg=default;bg=default></>{}", eol),
- &cell.to_php_string(),
- ),
- );
- for (line_key, line) in lines.into_iter().enumerate() {
- let line_key = line_key as i64;
- let mut line = Cell::Value(line);
- if colspan > 1 {
- line = Cell::Cell(TableCell::new2(
- &line.to_php_string(),
- Self::table_cell_options_colspan(colspan),
- ));
- }
- if 0 == line_key {
- let mut r = rows[row_key as usize].cells();
- set_cell(&mut r, column, line);
- rows[row_key as usize] = Row::Cells(r);
- } else {
- if !unmerged_rows.contains_key(&row_key)
- || !unmerged_rows[&row_key].contains_key(&line_key)
- {
- let copied = self.copy_row(&rows, row_key);
- unmerged_rows
- .entry(row_key)
- .or_default()
- .insert(line_key, copied);
- }
- let target = unmerged_rows
- .get_mut(&row_key)
- .unwrap()
- .get_mut(&line_key)
- .unwrap();
- set_cell(target, column, line);
- }
- }
- }
- row_key += 1;
- }
-
- // PHP returns a TableRows wrapping a generator that lazily yields row groups.
- // The generator borrows $this to call fillCells(). Here the row groups are
- // precomputed eagerly to preserve behavior, then handed to TableRows.
- let mut row_groups: Vec<Vec<Row>> = Vec::new();
- for (row_key, row) in rows.into_iter().enumerate() {
- let row_key = row_key as i64;
- let mut row_group: Vec<Row> = vec![if row.is_table_separator() {
- row
- } else {
- Row::Cells(self.fill_cells(row.cells()))
- }];
-
- if let Some(extra) = unmerged_rows.get(&row_key) {
- for r in extra.values() {
- row_group.push(Row::Cells(self.fill_cells(r.clone())));
- }
- }
- row_groups.push(row_group);
- }
-
- TableRows::from_row_groups(row_groups)
- }
-
- fn calculate_row_count(&mut self) -> i64 {
- let mut merged = self.headers.clone();
- merged.push(Row::Separator(TableSeparator::new()));
- merged.extend(self.rows.clone());
- let mut number_of_rows = self.build_table_rows(merged).into_row_groups().len() as i64;
-
- if !self.headers.is_empty() {
- number_of_rows += 1; // Add row for header separator
- }
-
- if !self.rows.is_empty() {
- number_of_rows += 1; // Add row for footer separator
- }
-
- number_of_rows
- }
-
- /// fill rows that contains rowspan > 1.
- fn fill_next_rows(&self, rows: Vec<Row>, line: i64) -> Vec<Row> {
- let mut rows = rows;
- let mut unmerged_rows: IndexMap<i64, IndexMap<i64, Cell>> = IndexMap::new();
- let current = rows[line as usize].cells();
- for (column, cell) in current.iter().enumerate() {
- let column = column as i64;
- let cell = cell.clone();
- // PHP validates here that a cell is null, a TableCell, a scalar, or a __toString object.
- // The `Cell` type makes every variant valid, so the InvalidArgumentException is dead.
- if cell.is_table_cell() && cell.rowspan() > 1 {
- let mut nb_lines = cell.rowspan() - 1;
- let cell_str = cell.to_php_string();
- let mut lines = vec![cell.clone()];
- if shirabe_php_shim::strstr(&cell_str, "\n").is_some() {
- let eol = if shirabe_php_shim::str_contains(&cell_str, "\r\n") {
- "\r\n"
- } else {
- "\n"
- };
- let exploded = shirabe_php_shim::explode(
- eol,
- &shirabe_php_shim::str_replace(
- eol,
- &format!("<fg=default;bg=default>{}</>", eol),
- &cell_str,
- ),
- );
- lines = exploded.into_iter().map(Cell::Value).collect();
- nb_lines = if (lines.len() as i64) > nb_lines {
- shirabe_php_shim::substr_count(&cell_str, eol)
- } else {
- nb_lines
- };
-
- let mut r = rows[line as usize].cells();
- set_cell(
- &mut r,
- column,
- Cell::Cell(TableCell::new2(
- &lines[0].to_php_string(),
- Self::table_cell_options_colspan_style(cell.colspan(), cell.style()),
- )),
- );
- rows[line as usize] = Row::Cells(r);
- lines.remove(0);
- }
-
- // create a two dimensional array (rowspan x colspan)
- for k in (line + 1)..=(line + nb_lines) {
- unmerged_rows.entry(k).or_default();
- }
- for unmerged_row_key in unmerged_rows.keys().cloned().collect::<Vec<_>>() {
- let idx = unmerged_row_key - line;
- let value = lines
- .get(idx as usize)
- .cloned()
- .unwrap_or(Cell::Value(String::new()));
- unmerged_rows.get_mut(&unmerged_row_key).unwrap().insert(
- column,
- Cell::Cell(TableCell::new2(
- &value.to_php_string(),
- Self::table_cell_options_colspan_style(cell.colspan(), cell.style()),
- )),
- );
- if nb_lines == unmerged_row_key - line {
- break;
- }
- }
- }
- }
-
- for (unmerged_row_key, unmerged_row) in unmerged_rows.clone() {
- // we need to know if $unmergedRow will be merged or inserted into $rows
- let fits = (unmerged_row_key as usize) < rows.len()
- && matches!(rows[unmerged_row_key as usize], Row::Cells(_))
- && (self.get_number_of_columns(&rows[unmerged_row_key as usize].cells())
- + self.get_number_of_columns(
- &unmerged_rows[&unmerged_row_key]
- .values()
- .cloned()
- .collect::<Vec<_>>(),
- )
- <= self.number_of_columns.unwrap());
- if fits {
- let mut target = rows[unmerged_row_key as usize].cells();
- for (cell_key, cell) in unmerged_row {
- // insert cell into row at cellKey position
- shirabe_php_shim::array_splice(&mut target, cell_key, Some(0), vec![cell]);
- }
- rows[unmerged_row_key as usize] = Row::Cells(target);
- } else {
- let mut row = self.copy_row(&rows, unmerged_row_key - 1);
- for (column, cell) in &unmerged_row {
- if cell.is_truthy() {
- set_cell(&mut row, *column, unmerged_row[column].clone());
- }
- }
- shirabe_php_shim::array_splice(
- &mut rows,
- unmerged_row_key,
- Some(0),
- vec![Row::Cells(row)],
- );
- }
- }
-
- rows
- }
-
- /// fill cells for a row that contains colspan > 1.
- fn fill_cells(&self, row: Vec<Cell>) -> Vec<Cell> {
- let mut new_row: Vec<Cell> = Vec::new();
-
- for (column, cell) in row.iter().enumerate() {
- let column = column as i64;
- new_row.push(cell.clone());
- if cell.is_table_cell() && cell.colspan() > 1 {
- for _position in (column + 1)..=(column + cell.colspan() - 1) {
- // insert empty value at column position
- new_row.push(Cell::Value(String::new()));
- }
- }
- }
-
- if new_row.is_empty() { row } else { new_row }
- }
-
- fn copy_row(&self, rows: &[Row], line: i64) -> Vec<Cell> {
- let mut row = rows[line as usize].cells();
- for cell in &mut row {
- let cell_value = cell.clone();
- *cell = Cell::Value(String::new());
- if cell_value.is_table_cell() {
- *cell = Cell::Cell(TableCell::new2(
- "",
- Self::table_cell_options_colspan(cell_value.colspan()),
- ));
- }
- }
-
- row
- }
-
- /// Gets number of columns by row.
- fn get_number_of_columns(&self, row: &[Cell]) -> i64 {
- let mut columns = row.len() as i64;
- for column in row {
- columns += if column.is_table_cell() {
- column.colspan() - 1
- } else {
- 0
- };
- }
-
- columns
- }
-
- /// Gets list of columns for the given row.
- fn get_row_columns(&self, row: &[Cell]) -> Vec<i64> {
- let mut columns: Vec<i64> = (0..self.number_of_columns.unwrap()).collect();
- for (cell_key, cell) in row.iter().enumerate() {
- let cell_key = cell_key as i64;
- if cell.is_table_cell() && cell.colspan() > 1 {
- // exclude grouped columns.
- let excluded: Vec<i64> =
- ((cell_key + 1)..=(cell_key + cell.colspan() - 1)).collect();
- columns.retain(|c| !excluded.contains(c));
- }
- }
-
- columns
- }
-
- /// Calculates columns widths.
- fn calculate_columns_width(&mut self, groups: &TableRows) {
- let mut column = 0;
- while column < self.number_of_columns.unwrap() {
- let mut lengths: Vec<i64> = Vec::new();
- for group in groups {
- for row in group {
- if row.is_table_separator() {
- continue;
- }
-
- let mut row_arr = row.cells();
- for i in 0..row_arr.len() {
- let cell = row_arr[i].clone();
- if cell.is_table_cell() {
- let text_content = self.remove_decoration(&cell.to_php_string());
- let text_length = Helper::width(&text_content);
- if text_length > 0 {
- let content_columns = shirabe_php_shim::mb_str_split(
- &text_content,
- (text_length as f64 / cell.colspan() as f64).ceil() as i64,
- );
- for (position, content) in content_columns.into_iter().enumerate() {
- set_cell(
- &mut row_arr,
- i as i64 + position as i64,
- Cell::Value(content),
- );
- }
- }
- }
- }
-
- lengths.push(self.get_cell_width(&row_arr, column));
- }
- }
-
- self.effective_column_widths.insert(
- column,
- *lengths.iter().max().unwrap()
- + Helper::width(&self.style.get_cell_row_content_format())
- - 2,
- );
- column += 1;
- }
- }
-
- fn get_column_separator_width(&self) -> i64 {
- Helper::width(&shirabe_php_shim::sprintf(
- &self.style.get_border_format(),
- &[PhpMixed::from(self.style.get_border_chars()[3].clone())],
- ))
- }
-
- fn get_cell_width(&self, row: &[Cell], column: i64) -> i64 {
- let mut cell_width = 0;
-
- if let Some(cell) = row.get(column as usize) {
- cell_width = Helper::width(&self.remove_decoration(&cell.to_php_string()));
- }
-
- let column_width = *self.column_widths.get(&column).unwrap_or(&0);
- cell_width = cell_width.max(column_width);
-
- if let Some(max) = self.column_max_widths.get(&column) {
- (*max).min(cell_width)
- } else {
- cell_width
- }
- }
-
- /// Called after rendering to cleanup cache data.
- fn cleanup(&mut self) {
- self.effective_column_widths = IndexMap::new();
- self.number_of_columns = None;
- }
-
- fn init_styles() -> IndexMap<String, TableStyle> {
- let mut borderless = TableStyle::default();
- borderless
- .set_horizontal_border_chars("=".to_string(), None)
- .set_vertical_border_chars(" ".to_string(), None)
- .set_default_crossing_char(" ".to_string());
-
- let mut compact = TableStyle::default();
- compact
- .set_horizontal_border_chars("".to_string(), None)
- .set_vertical_border_chars("".to_string(), None)
- .set_default_crossing_char("".to_string())
- .set_cell_row_content_format("%s ".to_string());
-
- let mut style_guide = TableStyle::default();
- style_guide
- .set_horizontal_border_chars("-".to_string(), None)
- .set_vertical_border_chars(" ".to_string(), None)
- .set_default_crossing_char(" ".to_string())
- .set_cell_header_format("%s".to_string());
-
- let mut r#box = TableStyle::default();
- r#box
- .set_horizontal_border_chars("─".to_string(), None)
- .set_vertical_border_chars("│".to_string(), None)
- .set_crossing_chars(
- "┼".to_string(),
- "┌".to_string(),
- "┬".to_string(),
- "┐".to_string(),
- "┤".to_string(),
- "┘".to_string(),
- "┴".to_string(),
- "└".to_string(),
- "├".to_string(),
- None,
- None,
- None,
- );
-
- let mut box_double = TableStyle::default();
- box_double
- .set_horizontal_border_chars("═".to_string(), Some("─".to_string()))
- .set_vertical_border_chars("║".to_string(), Some("│".to_string()))
- .set_crossing_chars(
- "┼".to_string(),
- "╔".to_string(),
- "╤".to_string(),
- "╗".to_string(),
- "╢".to_string(),
- "╝".to_string(),
- "╧".to_string(),
- "╚".to_string(),
- "╟".to_string(),
- Some("╠".to_string()),
- Some("╪".to_string()),
- Some("╣".to_string()),
- );
-
- let mut result: IndexMap<String, TableStyle> = IndexMap::new();
- result.insert("default".to_string(), TableStyle::default());
- result.insert("borderless".to_string(), borderless);
- result.insert("compact".to_string(), compact);
- result.insert("symfony-style-guide".to_string(), style_guide);
- result.insert("box".to_string(), r#box);
- result.insert("box-double".to_string(), box_double);
-
- result
- }
-
- fn resolve_style(
- &self,
- name: StyleName,
- ) -> anyhow::Result<Result<TableStyle, InvalidArgumentException>> {
- let name = match name {
- StyleName::Style(style) => return Ok(Ok(style)),
- StyleName::Name(name) => name,
- };
-
- let styles_guard = styles().lock().unwrap();
- if let Some(style) = styles_guard.as_ref().and_then(|s| s.get(&name)) {
- return Ok(Ok(style.clone()));
- }
-
- Ok(Err(InvalidArgumentException::new(format!(
- "Style \"{}\" is not defined.",
- name
- ))))
- }
-
- fn formatter_is_wrappable(
- _output: &std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>,
- ) -> bool {
- // PHP: $this->output->getFormatter() instanceof WrappableOutputFormatterInterface.
- // The sole OutputFormatterInterface implementor in this port is OutputFormatter, which
- // implements WrappableOutputFormatterInterface, so the instanceof check always holds.
- true
- }
-
- /// `setColumnMaxWidth` guarantees the formatter is a `WrappableOutputFormatterInterface`, and
- /// `OutputFormatter` is the sole implementor in this port, so `instanceof` reduces to this
- /// downcast.
- fn format_and_wrap(&self, string: &str, width: i64) -> String {
- let formatter = self.output.borrow().get_formatter();
- let mut formatter = formatter.borrow_mut();
- let formatter = formatter
- .as_any_mut()
- .downcast_mut::<OutputFormatter>()
- .expect("formatter must be a WrappableOutputFormatterInterface");
- formatter
- .format_and_wrap(Some(string), width)
- .unwrap()
- .unwrap_or_default()
- }
-
- /// PHP `Helper::removeDecoration($this->output->getFormatter(), $string)`.
- fn remove_decoration(&self, string: &str) -> String {
- let formatter = self.output.borrow().get_formatter();
- let mut formatter = formatter.borrow_mut();
- Helper::remove_decoration(&mut *formatter, string)
- }
-
- fn output_is_console_section(
- output: &std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>,
- ) -> bool {
- // PHP: $this->output instanceof ConsoleSectionOutput
- let borrowed = output.borrow();
- (*borrowed)
- .as_any()
- .downcast_ref::<ConsoleSectionOutput>()
- .is_some()
- }
-
- fn table_cell_options_colspan(colspan: i64) -> IndexMap<String, TableCellOption> {
- // PHP: ['colspan' => $colspan]
- let mut options = IndexMap::new();
- options.insert("colspan".to_string(), TableCellOption::Int(colspan));
- options
- }
-
- fn table_cell_options_colspan_style(
- colspan: i64,
- style: Option<std::rc::Rc<TableCellStyle>>,
- ) -> IndexMap<String, TableCellOption> {
- // PHP: ['colspan' => $colspan, 'style' => $style]
- let mut options = IndexMap::new();
- options.insert("colspan".to_string(), TableCellOption::Int(colspan));
- options.insert(
- "style".to_string(),
- match style {
- Some(style) => TableCellOption::Style(style),
- None => TableCellOption::Null,
- },
- );
- options
- }
-}
diff --git a/crates/shirabe-external-packages/src/symfony/console/helper/table_cell.rs b/crates/shirabe-external-packages/src/symfony/console/helper/table_cell.rs
deleted file mode 100644
index c899e5f7..00000000
--- a/crates/shirabe-external-packages/src/symfony/console/helper/table_cell.rs
+++ /dev/null
@@ -1,99 +0,0 @@
-//! ref: composer/vendor/symfony/console/Helper/TableCell.php
-
-use crate::symfony::console::exception::invalid_argument_exception::InvalidArgumentException;
-use crate::symfony::console::helper::table_cell_style::TableCellStyle;
-use indexmap::IndexMap;
-
-/// A `TableCell` option value: an integer span, a `TableCellStyle`, or null.
-#[derive(Debug, Clone)]
-pub enum TableCellOption {
- Int(i64),
- Style(std::rc::Rc<TableCellStyle>),
- Null,
-}
-
-#[derive(Debug, Clone)]
-pub struct TableCell {
- pub(crate) value: String,
- options: IndexMap<String, TableCellOption>,
-}
-
-impl TableCell {
- pub fn new(
- value: &str,
- options: IndexMap<String, TableCellOption>,
- ) -> Result<Self, InvalidArgumentException> {
- let mut this_options: IndexMap<String, TableCellOption> = IndexMap::new();
- this_options.insert("rowspan".to_string(), TableCellOption::Int(1));
- this_options.insert("colspan".to_string(), TableCellOption::Int(1));
- this_options.insert("style".to_string(), TableCellOption::Null);
-
- // check option names
- let diff: Vec<String> = options
- .keys()
- .filter(|key| !this_options.contains_key(*key))
- .cloned()
- .collect();
- if !diff.is_empty() {
- return Err(InvalidArgumentException::new(format!(
- "The TableCell does not support the following options: '{}'.",
- diff.join("', '"),
- )));
- }
-
- if let Some(style) = options.get("style")
- && !matches!(style, TableCellOption::Style(_))
- && !matches!(style, TableCellOption::Null)
- {
- return Err(InvalidArgumentException::new(
- "The style option must be an instance of \"TableCellStyle\".".to_string(),
- ));
- }
-
- for (key, option) in options {
- this_options.insert(key, option);
- }
-
- Ok(Self {
- value: value.to_string(),
- options: this_options,
- })
- }
-
- /// Two-argument constructor (`__construct(string $value, array $options)`).
- ///
- /// The options used by the Table helper are internally controlled, so a malformed-option
- /// error here would be a programming bug rather than a recoverable condition.
- pub fn new2(value: &str, options: IndexMap<String, TableCellOption>) -> Self {
- Self::new(value, options).expect("TableCell options built internally are always valid")
- }
-
- /// Gets number of colspan.
- pub fn get_colspan(&self) -> i64 {
- match self.options["colspan"] {
- TableCellOption::Int(colspan) => colspan,
- _ => 0,
- }
- }
-
- /// Gets number of rowspan.
- pub fn get_rowspan(&self) -> i64 {
- match self.options["rowspan"] {
- TableCellOption::Int(rowspan) => rowspan,
- _ => 0,
- }
- }
-
- pub fn get_style(&self) -> Option<std::rc::Rc<TableCellStyle>> {
- match &self.options["style"] {
- TableCellOption::Style(style) => Some(style.clone()),
- _ => None,
- }
- }
-}
-
-impl std::fmt::Display for TableCell {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "{}", self.value)
- }
-}
diff --git a/crates/shirabe-external-packages/src/symfony/console/helper/table_cell_style.rs b/crates/shirabe-external-packages/src/symfony/console/helper/table_cell_style.rs
deleted file mode 100644
index df24b404..00000000
--- a/crates/shirabe-external-packages/src/symfony/console/helper/table_cell_style.rs
+++ /dev/null
@@ -1,114 +0,0 @@
-//! ref: composer/vendor/symfony/console/Helper/TableCellStyle.php
-
-use crate::symfony::console::exception::invalid_argument_exception::InvalidArgumentException;
-use indexmap::IndexMap;
-
-pub const DEFAULT_ALIGN: &str = "left";
-
-const TAG_OPTIONS: [&str; 3] = ["fg", "bg", "options"];
-
-/// Maps an alignment name to the corresponding `STR_PAD_*` value.
-fn align_map(align: &str) -> Option<i64> {
- match align {
- "left" => Some(shirabe_php_shim::STR_PAD_RIGHT),
- "center" => Some(shirabe_php_shim::STR_PAD_BOTH),
- "right" => Some(shirabe_php_shim::STR_PAD_LEFT),
- _ => None,
- }
-}
-
-fn align_map_keys() -> Vec<&'static str> {
- vec!["left", "center", "right"]
-}
-
-#[derive(Debug)]
-pub struct TableCellStyle {
- options: IndexMap<String, shirabe_php_shim::PhpMixed>,
-}
-
-impl TableCellStyle {
- pub fn new(
- options: IndexMap<String, shirabe_php_shim::PhpMixed>,
- ) -> Result<Self, InvalidArgumentException> {
- let mut this_options: IndexMap<String, shirabe_php_shim::PhpMixed> = IndexMap::new();
- this_options.insert(
- "fg".to_string(),
- shirabe_php_shim::PhpMixed::String("default".to_string()),
- );
- this_options.insert(
- "bg".to_string(),
- shirabe_php_shim::PhpMixed::String("default".to_string()),
- );
- this_options.insert("options".to_string(), shirabe_php_shim::PhpMixed::Null);
- this_options.insert(
- "align".to_string(),
- shirabe_php_shim::PhpMixed::String(DEFAULT_ALIGN.to_string()),
- );
- this_options.insert("cellFormat".to_string(), shirabe_php_shim::PhpMixed::Null);
-
- let diff: Vec<String> = options
- .keys()
- .filter(|key| !this_options.contains_key(*key))
- .cloned()
- .collect();
- if !diff.is_empty() {
- return Err(InvalidArgumentException::new(format!(
- "The TableCellStyle does not support the following options: '{}'.",
- diff.join("', '"),
- )));
- }
-
- if let Some(align) = options.get("align") {
- let align = match align {
- shirabe_php_shim::PhpMixed::String(align) => align.clone(),
- _ => String::new(),
- };
- if align_map(&align).is_none() {
- return Err(InvalidArgumentException::new(format!(
- "Wrong align value. Value must be following: '{}'.",
- align_map_keys().join("', '"),
- )));
- }
- }
-
- for (key, value) in options {
- this_options.insert(key, value);
- }
-
- Ok(Self {
- options: this_options,
- })
- }
-
- pub fn get_options(&self) -> IndexMap<String, shirabe_php_shim::PhpMixed> {
- self.options.clone()
- }
-
- /// Gets options we need for tag for example fg, bg.
- pub fn get_tag_options(&self) -> IndexMap<String, shirabe_php_shim::PhpMixed> {
- let mut result: IndexMap<String, shirabe_php_shim::PhpMixed> = IndexMap::new();
- for (key, value) in self.get_options() {
- if TAG_OPTIONS.contains(&key.as_str())
- && !matches!(self.options[&key], shirabe_php_shim::PhpMixed::Null)
- {
- result.insert(key, value);
- }
- }
- result
- }
-
- pub fn get_pad_by_align(&self) -> i64 {
- let align = match &self.get_options()["align"] {
- shirabe_php_shim::PhpMixed::String(align) => align.clone(),
- _ => String::new(),
- };
- align_map(&align).unwrap()
- }
-
- pub fn get_cell_format(&self) -> Option<String> {
- match &self.get_options()["cellFormat"] {
- shirabe_php_shim::PhpMixed::String(format) => Some(format.clone()),
- _ => None,
- }
- }
-}
diff --git a/crates/shirabe-external-packages/src/symfony/console/helper/table_rows.rs b/crates/shirabe-external-packages/src/symfony/console/helper/table_rows.rs
deleted file mode 100644
index 1fc14bbf..00000000
--- a/crates/shirabe-external-packages/src/symfony/console/helper/table_rows.rs
+++ /dev/null
@@ -1,45 +0,0 @@
-//! ref: composer/vendor/symfony/console/Helper/TableRows.php
-
-use crate::symfony::console::helper::table::Row;
-
-/// @internal
-///
-/// In PHP this wraps a `\Closure` yielding a `\Traversable` of row groups. The generator
-/// borrows the Table to lazily call `fillCells()`. For the Rust port we precompute the row
-/// groups eagerly (see `Table::build_table_rows`) and store them here.
-#[derive(Debug)]
-pub struct TableRows {
- row_groups: Vec<Vec<Row>>,
-}
-
-impl TableRows {
- pub fn from_row_groups(row_groups: Vec<Vec<Row>>) -> Self {
- Self { row_groups }
- }
-
- pub fn get_iterator(&self) -> std::slice::Iter<'_, Vec<Row>> {
- self.row_groups.iter()
- }
-
- pub fn into_row_groups(self) -> Vec<Vec<Row>> {
- self.row_groups
- }
-}
-
-impl<'a> IntoIterator for &'a TableRows {
- type Item = &'a Vec<Row>;
- type IntoIter = std::slice::Iter<'a, Vec<Row>>;
-
- fn into_iter(self) -> Self::IntoIter {
- self.row_groups.iter()
- }
-}
-
-impl IntoIterator for TableRows {
- type Item = Vec<Row>;
- type IntoIter = std::vec::IntoIter<Vec<Row>>;
-
- fn into_iter(self) -> Self::IntoIter {
- self.row_groups.into_iter()
- }
-}
diff --git a/crates/shirabe-external-packages/src/symfony/console/helper/table_separator.rs b/crates/shirabe-external-packages/src/symfony/console/helper/table_separator.rs
deleted file mode 100644
index 34626cc6..00000000
--- a/crates/shirabe-external-packages/src/symfony/console/helper/table_separator.rs
+++ /dev/null
@@ -1,47 +0,0 @@
-//! ref: composer/vendor/symfony/console/Helper/TableSeparator.php
-
-use crate::symfony::console::exception::invalid_argument_exception::InvalidArgumentException;
-use crate::symfony::console::helper::table_cell::{TableCell, TableCellOption};
-use indexmap::IndexMap;
-
-/// Marks a row as being a separator.
-#[derive(Debug, Clone)]
-pub struct TableSeparator {
- inner: TableCell,
-}
-
-impl TableSeparator {
- pub fn new() -> Self {
- Self::new1(IndexMap::new()).expect("TableSeparator default options are always valid")
- }
-
- pub fn new1(
- options: IndexMap<String, TableCellOption>,
- ) -> Result<Self, InvalidArgumentException> {
- Ok(Self {
- inner: TableCell::new("", options)?,
- })
- }
-
- // PHP `TableSeparator extends TableCell`, so these inherited accessors remain available.
- pub fn get_colspan(&self) -> i64 {
- self.inner.get_colspan()
- }
-
- pub fn get_rowspan(&self) -> i64 {
- self.inner.get_rowspan()
- }
-
- pub fn get_style(
- &self,
- ) -> Option<std::rc::Rc<crate::symfony::console::helper::table_cell_style::TableCellStyle>>
- {
- self.inner.get_style()
- }
-}
-
-impl Default for TableSeparator {
- fn default() -> Self {
- Self::new()
- }
-}
diff --git a/crates/shirabe-external-packages/src/symfony/console/helper/table_style.rs b/crates/shirabe-external-packages/src/symfony/console/helper/table_style.rs
deleted file mode 100644
index bbe899af..00000000
--- a/crates/shirabe-external-packages/src/symfony/console/helper/table_style.rs
+++ /dev/null
@@ -1,289 +0,0 @@
-//! ref: composer/vendor/symfony/console/Helper/TableStyle.php
-
-use crate::symfony::console::exception::invalid_argument_exception::InvalidArgumentException;
-use crate::symfony::console::exception::logic_exception::LogicException;
-
-/// Defines the styles for a Table.
-#[derive(Debug, Clone)]
-pub struct TableStyle {
- padding_char: String,
- horizontal_outside_border_char: String,
- horizontal_inside_border_char: String,
- vertical_outside_border_char: String,
- vertical_inside_border_char: String,
- crossing_char: String,
- crossing_top_right_char: String,
- crossing_top_mid_char: String,
- crossing_top_left_char: String,
- crossing_mid_right_char: String,
- crossing_bottom_right_char: String,
- crossing_bottom_mid_char: String,
- crossing_bottom_left_char: String,
- crossing_mid_left_char: String,
- crossing_top_left_bottom_char: String,
- crossing_top_mid_bottom_char: String,
- crossing_top_right_bottom_char: String,
- header_title_format: String,
- footer_title_format: String,
- cell_header_format: String,
- cell_row_format: String,
- cell_row_content_format: String,
- border_format: String,
- pad_type: i64,
-}
-
-impl Default for TableStyle {
- fn default() -> Self {
- Self {
- padding_char: " ".to_string(),
- horizontal_outside_border_char: "-".to_string(),
- horizontal_inside_border_char: "-".to_string(),
- vertical_outside_border_char: "|".to_string(),
- vertical_inside_border_char: "|".to_string(),
- crossing_char: "+".to_string(),
- crossing_top_right_char: "+".to_string(),
- crossing_top_mid_char: "+".to_string(),
- crossing_top_left_char: "+".to_string(),
- crossing_mid_right_char: "+".to_string(),
- crossing_bottom_right_char: "+".to_string(),
- crossing_bottom_mid_char: "+".to_string(),
- crossing_bottom_left_char: "+".to_string(),
- crossing_mid_left_char: "+".to_string(),
- crossing_top_left_bottom_char: "+".to_string(),
- crossing_top_mid_bottom_char: "+".to_string(),
- crossing_top_right_bottom_char: "+".to_string(),
- header_title_format: "<fg=black;bg=white;options=bold> %s </>".to_string(),
- footer_title_format: "<fg=black;bg=white;options=bold> %s </>".to_string(),
- cell_header_format: "<info>%s</info>".to_string(),
- cell_row_format: "%s".to_string(),
- cell_row_content_format: " %s ".to_string(),
- border_format: "%s".to_string(),
- pad_type: shirabe_php_shim::STR_PAD_RIGHT,
- }
- }
-}
-
-impl TableStyle {
- /// Sets padding character, used for cell padding.
- pub fn set_padding_char(
- &mut self,
- padding_char: String,
- ) -> anyhow::Result<Result<&mut Self, LogicException>> {
- if padding_char.is_empty() {
- return Ok(Err(LogicException::new(
- "The padding char must not be empty.".to_string(),
- )));
- }
-
- self.padding_char = padding_char;
-
- Ok(Ok(self))
- }
-
- /// Gets padding character, used for cell padding.
- pub fn get_padding_char(&self) -> String {
- self.padding_char.clone()
- }
-
- /// Sets horizontal border characters.
- pub fn set_horizontal_border_chars(
- &mut self,
- outside: String,
- inside: Option<String>,
- ) -> &mut Self {
- self.horizontal_outside_border_char = outside.clone();
- self.horizontal_inside_border_char = inside.unwrap_or(outside);
-
- self
- }
-
- /// Sets vertical border characters.
- pub fn set_vertical_border_chars(
- &mut self,
- outside: String,
- inside: Option<String>,
- ) -> &mut Self {
- self.vertical_outside_border_char = outside.clone();
- self.vertical_inside_border_char = inside.unwrap_or(outside);
-
- self
- }
-
- /// Gets border characters.
- pub fn get_border_chars(&self) -> Vec<String> {
- vec![
- self.horizontal_outside_border_char.clone(),
- self.vertical_outside_border_char.clone(),
- self.horizontal_inside_border_char.clone(),
- self.vertical_inside_border_char.clone(),
- ]
- }
-
- /// Sets crossing characters.
- #[allow(clippy::too_many_arguments)]
- pub fn set_crossing_chars(
- &mut self,
- cross: String,
- top_left: String,
- top_mid: String,
- top_right: String,
- mid_right: String,
- bottom_right: String,
- bottom_mid: String,
- bottom_left: String,
- mid_left: String,
- top_left_bottom: Option<String>,
- top_mid_bottom: Option<String>,
- top_right_bottom: Option<String>,
- ) -> &mut Self {
- self.crossing_char = cross.clone();
- self.crossing_top_left_char = top_left;
- self.crossing_top_mid_char = top_mid;
- self.crossing_top_right_char = top_right;
- self.crossing_mid_right_char = mid_right.clone();
- self.crossing_bottom_right_char = bottom_right;
- self.crossing_bottom_mid_char = bottom_mid;
- self.crossing_bottom_left_char = bottom_left;
- self.crossing_mid_left_char = mid_left.clone();
- self.crossing_top_left_bottom_char = top_left_bottom.unwrap_or(mid_left);
- self.crossing_top_mid_bottom_char = top_mid_bottom.unwrap_or(cross);
- self.crossing_top_right_bottom_char = top_right_bottom.unwrap_or(mid_right);
-
- self
- }
-
- /// Sets default crossing character used for each cross.
- pub fn set_default_crossing_char(&mut self, char: String) -> &mut Self {
- self.set_crossing_chars(
- char.clone(),
- char.clone(),
- char.clone(),
- char.clone(),
- char.clone(),
- char.clone(),
- char.clone(),
- char.clone(),
- char,
- None,
- None,
- None,
- )
- }
-
- /// Gets crossing character.
- pub fn get_crossing_char(&self) -> String {
- self.crossing_char.clone()
- }
-
- /// Gets crossing characters.
- pub fn get_crossing_chars(&self) -> Vec<String> {
- vec![
- self.crossing_char.clone(),
- self.crossing_top_left_char.clone(),
- self.crossing_top_mid_char.clone(),
- self.crossing_top_right_char.clone(),
- self.crossing_mid_right_char.clone(),
- self.crossing_bottom_right_char.clone(),
- self.crossing_bottom_mid_char.clone(),
- self.crossing_bottom_left_char.clone(),
- self.crossing_mid_left_char.clone(),
- self.crossing_top_left_bottom_char.clone(),
- self.crossing_top_mid_bottom_char.clone(),
- self.crossing_top_right_bottom_char.clone(),
- ]
- }
-
- /// Sets header cell format.
- pub fn set_cell_header_format(&mut self, cell_header_format: String) -> &mut Self {
- self.cell_header_format = cell_header_format;
-
- self
- }
-
- /// Gets header cell format.
- pub fn get_cell_header_format(&self) -> String {
- self.cell_header_format.clone()
- }
-
- /// Sets row cell format.
- pub fn set_cell_row_format(&mut self, cell_row_format: String) -> &mut Self {
- self.cell_row_format = cell_row_format;
-
- self
- }
-
- /// Gets row cell format.
- pub fn get_cell_row_format(&self) -> String {
- self.cell_row_format.clone()
- }
-
- /// Sets row cell content format.
- pub fn set_cell_row_content_format(&mut self, cell_row_content_format: String) -> &mut Self {
- self.cell_row_content_format = cell_row_content_format;
-
- self
- }
-
- /// Gets row cell content format.
- pub fn get_cell_row_content_format(&self) -> String {
- self.cell_row_content_format.clone()
- }
-
- /// Sets table border format.
- pub fn set_border_format(&mut self, border_format: String) -> &mut Self {
- self.border_format = border_format;
-
- self
- }
-
- /// Gets table border format.
- pub fn get_border_format(&self) -> String {
- self.border_format.clone()
- }
-
- /// Sets cell padding type.
- pub fn set_pad_type(
- &mut self,
- pad_type: i64,
- ) -> anyhow::Result<Result<&mut Self, InvalidArgumentException>> {
- if ![
- shirabe_php_shim::STR_PAD_LEFT,
- shirabe_php_shim::STR_PAD_RIGHT,
- shirabe_php_shim::STR_PAD_BOTH,
- ]
- .contains(&pad_type)
- {
- return Ok(Err(InvalidArgumentException::new("Invalid padding type. Expected one of (STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH)."
- .to_string())));
- }
-
- self.pad_type = pad_type;
-
- Ok(Ok(self))
- }
-
- /// Gets cell padding type.
- pub fn get_pad_type(&self) -> i64 {
- self.pad_type
- }
-
- pub fn get_header_title_format(&self) -> String {
- self.header_title_format.clone()
- }
-
- pub fn set_header_title_format(&mut self, format: String) -> &mut Self {
- self.header_title_format = format;
-
- self
- }
-
- pub fn get_footer_title_format(&self) -> String {
- self.footer_title_format.clone()
- }
-
- pub fn set_footer_title_format(&mut self, format: String) -> &mut Self {
- self.footer_title_format = format;
-
- self
- }
-}