aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe-symfony-console/src/output/console_output.rs
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-symfony-console/src/output/console_output.rs
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-symfony-console/src/output/console_output.rs')
-rw-r--r--crates/shirabe-symfony-console/src/output/console_output.rs210
1 files changed, 210 insertions, 0 deletions
diff --git a/crates/shirabe-symfony-console/src/output/console_output.rs b/crates/shirabe-symfony-console/src/output/console_output.rs
new file mode 100644
index 00000000..5028eaf7
--- /dev/null
+++ b/crates/shirabe-symfony-console/src/output/console_output.rs
@@ -0,0 +1,210 @@
+//! ref: composer/vendor/symfony/console/Output/ConsoleOutput.php
+
+use crate::formatter::OutputFormatterInterface;
+use crate::output::ConsoleOutputInterface;
+use crate::output::OutputInterface;
+use crate::output::console_section_output::ConsoleSectionOutput;
+use crate::output::output_interface::VERBOSITY_NORMAL;
+use crate::output::stream_output::StreamOutput;
+
+/// ConsoleOutput is the default class for all CLI output. It uses STDOUT and STDERR.
+///
+/// This class is a convenient wrapper around `StreamOutput` for both STDOUT and STDERR.
+///
+/// ```php
+/// $output = new ConsoleOutput();
+/// ```
+///
+/// This is equivalent to:
+///
+/// ```php
+/// $output = new StreamOutput(fopen('php://stdout', 'w'));
+/// $stdErr = new StreamOutput(fopen('php://stderr', 'w'));
+/// ```
+#[derive(Debug)]
+pub struct ConsoleOutput {
+ inner: StreamOutput,
+ stderr: std::cell::RefCell<std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>>,
+ console_section_outputs:
+ std::rc::Rc<std::cell::RefCell<Vec<std::rc::Rc<std::cell::RefCell<ConsoleSectionOutput>>>>>,
+}
+
+impl ConsoleOutput {
+ /// `$verbosity` defaults to `self::VERBOSITY_NORMAL`; pass `None` to use it.
+ pub fn new(
+ verbosity: Option<i64>,
+ decorated: Option<bool>,
+ formatter: Option<std::rc::Rc<std::cell::RefCell<dyn OutputFormatterInterface>>>,
+ ) -> anyhow::Result<Self> {
+ let verbosity = verbosity.unwrap_or(VERBOSITY_NORMAL);
+
+ let inner = StreamOutput::new(
+ Self::open_output_stream(),
+ Some(verbosity),
+ decorated,
+ formatter.clone(),
+ )?
+ .expect("ConsoleOutput stdout stream is always valid");
+
+ let this = if formatter.is_none() {
+ // for BC reasons, stdErr has it own Formatter only when user don't inject a specific formatter.
+ let stderr =
+ StreamOutput::new(Self::open_error_stream(), Some(verbosity), decorated, None)?
+ .expect("ConsoleOutput stderr stream is always valid");
+ Self {
+ inner,
+ stderr: std::cell::RefCell::new(std::rc::Rc::new(std::cell::RefCell::new(stderr))),
+ console_section_outputs: std::rc::Rc::new(std::cell::RefCell::new(Vec::new())),
+ }
+ } else {
+ let actual_decorated = inner.is_decorated();
+ let stderr = StreamOutput::new(
+ Self::open_error_stream(),
+ Some(verbosity),
+ decorated,
+ Some(inner.get_formatter()),
+ )?
+ .expect("ConsoleOutput stderr stream is always valid");
+ let stderr_decorated = stderr.is_decorated();
+ let this = Self {
+ inner,
+ stderr: std::cell::RefCell::new(std::rc::Rc::new(std::cell::RefCell::new(stderr))),
+ console_section_outputs: std::rc::Rc::new(std::cell::RefCell::new(Vec::new())),
+ };
+
+ if decorated.is_none() {
+ this.set_decorated(actual_decorated && stderr_decorated);
+ }
+
+ this
+ };
+
+ Ok(this)
+ }
+
+ /// Returns true if current environment supports writing console output to
+ /// STDOUT.
+ fn has_stdout_support() -> bool {
+ !Self::is_running_os400()
+ }
+
+ /// Returns true if current environment supports writing console output to
+ /// STDERR.
+ fn has_stderr_support() -> bool {
+ !Self::is_running_os400()
+ }
+
+ /// Checks if current executing environment is IBM iSeries (OS400), which
+ /// doesn't properly convert character-encodings between ASCII to EBCDIC.
+ fn is_running_os400() -> bool {
+ let checks = [
+ if shirabe_php_shim::function_exists("php_uname") {
+ shirabe_php_shim::php_uname("s")
+ } else {
+ String::new()
+ },
+ shirabe_php_shim::getenv("OSTYPE")
+ .unwrap_or_default()
+ .to_string_lossy()
+ .into_owned(),
+ shirabe_php_shim::PHP_OS.to_string(),
+ ];
+
+ shirabe_php_shim::stripos(&shirabe_php_shim::implode(";", &checks), "OS400").is_some()
+ }
+
+ /// For testing only. Overwrites the inner `StreamOutput`'s private `stream` field, mirroring
+ /// what `Symfony\Component\Console\Tester\TesterTrait::initOutput` does via reflection on the
+ /// parent `StreamOutput::$stream` property of a `ConsoleOutput`.
+ pub fn __set_stream(&mut self, stream: shirabe_php_shim::PhpResource) {
+ self.inner.__set_stream(stream);
+ }
+
+ fn open_output_stream() -> shirabe_php_shim::PhpResource {
+ if !Self::has_stdout_support() {
+ return shirabe_php_shim::php_fopen_resource("php://output", "w");
+ }
+
+ // Use STDOUT when possible to prevent from opening too many file descriptors
+ shirabe_php_shim::php_stdout_resource()
+ }
+
+ fn open_error_stream() -> shirabe_php_shim::PhpResource {
+ if !Self::has_stderr_support() {
+ return shirabe_php_shim::php_fopen_resource("php://output", "w");
+ }
+
+ // Use STDERR when possible to prevent from opening too many file descriptors
+ shirabe_php_shim::php_stderr_resource()
+ }
+}
+
+impl ConsoleOutputInterface for ConsoleOutput {
+ /// Creates a new output section.
+ fn section(&self) -> std::rc::Rc<std::cell::RefCell<ConsoleSectionOutput>> {
+ // ConsoleSectionOutput::new pushes itself into the shared sections list.
+ ConsoleSectionOutput::new(
+ self.inner.get_stream().clone(),
+ &self.console_section_outputs,
+ self.get_verbosity(),
+ self.is_decorated(),
+ self.get_formatter(),
+ )
+ }
+
+ fn get_error_output(&self) -> std::rc::Rc<std::cell::RefCell<dyn OutputInterface>> {
+ self.stderr.borrow().clone()
+ }
+
+ fn set_error_output(&self, error: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>) {
+ *self.stderr.borrow_mut() = error;
+ }
+}
+
+impl OutputInterface for ConsoleOutput {
+ fn write(&self, messages: &[String], newline: bool, options: i64) {
+ self.inner.write(messages, newline, options);
+ }
+ fn writeln(&self, messages: &[String], options: i64) {
+ self.inner.writeln(messages, options);
+ }
+ fn set_verbosity(&self, level: i64) {
+ self.inner.set_verbosity(level);
+ self.stderr.borrow().borrow().set_verbosity(level);
+ }
+ fn get_verbosity(&self) -> i64 {
+ self.inner.get_verbosity()
+ }
+ fn is_quiet(&self) -> bool {
+ self.inner.is_quiet()
+ }
+ fn is_verbose(&self) -> bool {
+ self.inner.is_verbose()
+ }
+ fn is_very_verbose(&self) -> bool {
+ self.inner.is_very_verbose()
+ }
+ fn is_debug(&self) -> bool {
+ self.inner.is_debug()
+ }
+ fn set_decorated(&self, decorated: bool) {
+ self.inner.set_decorated(decorated);
+ self.stderr.borrow().borrow().set_decorated(decorated);
+ }
+ fn is_decorated(&self) -> bool {
+ self.inner.is_decorated()
+ }
+ fn set_formatter(
+ &self,
+ formatter: std::rc::Rc<std::cell::RefCell<dyn OutputFormatterInterface>>,
+ ) {
+ self.inner.set_formatter(formatter.clone());
+ self.stderr.borrow().borrow().set_formatter(formatter);
+ }
+ fn get_formatter(&self) -> std::rc::Rc<std::cell::RefCell<dyn OutputFormatterInterface>> {
+ self.inner.get_formatter()
+ }
+ fn as_console_output(&self) -> Option<&dyn ConsoleOutputInterface> {
+ Some(self)
+ }
+}