diff options
Diffstat (limited to 'crates/shirabe-external-packages/src/symfony/console/command')
5 files changed, 141 insertions, 54 deletions
diff --git a/crates/shirabe-external-packages/src/symfony/console/command/command.rs b/crates/shirabe-external-packages/src/symfony/console/command/command.rs index 6e6c2212..1f1455f8 100644 --- a/crates/shirabe-external-packages/src/symfony/console/command/command.rs +++ b/crates/shirabe-external-packages/src/symfony/console/command/command.rs @@ -325,6 +325,13 @@ macro_rules! delegate_command_trait_impls_to_inner { $crate::delegate_to_inner!($field, fn ignore_validation_errors(&self)); $crate::delegate_to_inner!($field, fn get_ignore_validation_errors(&self) -> bool); }; + // Variant taking the command's PHP fully-qualified class name (see `Command::get_class`). + ($field:ident, $fqcn:literal) => { + $crate::delegate_command_trait_impls_to_inner!($field); + fn get_class(&self) -> String { + $fqcn.to_string() + } + }; } /// Polymorphic interface for all commands (PHP's `Command` base class as seen by @@ -369,8 +376,24 @@ pub trait Command: std::fmt::Debug + shirabe_php_shim::AsAny { Ok(()) } + /// The PHP fully-qualified class name of the concrete command. Port hook for PHP's + /// `\get_class($command)` (used by `CompleteCommand`'s debug log), which Rust cannot + /// reflect from a trait object; every concrete command supplies its FQCN, usually via + /// `delegate_command_trait_impls_to_inner!($field, "Fqcn")`. + fn get_class(&self) -> String; + /// Adds suggestions to `suggestions` for the current completion input. - fn complete(&self, _input: &CompletionInput, _suggestions: &mut CompletionSuggestions) {} + /// + /// PHP's `complete` is `void` but can throw; errors are surfaced through `anyhow::Result` + /// so they propagate to `CompleteCommand::execute`'s catch-all (which turns them into + /// exit code 2), matching the PHP exception flow. + fn complete( + &self, + _input: &CompletionInput, + _suggestions: &mut CompletionSuggestions, + ) -> anyhow::Result<()> { + Ok(()) + } /// Whether this command proxies to another application/command (Composer's /// `BaseCommand::isProxyCommand`). Exposed here so the `dyn Command` registry can detect proxy @@ -553,6 +576,10 @@ impl Command for CommandData { true } + fn get_class(&self) -> String { + panic!("get_class called on the base command state; concrete commands supply their FQCN"); + } + fn set_application( &self, application: Option<std::rc::Rc<std::cell::RefCell<dyn Application>>>, diff --git a/crates/shirabe-external-packages/src/symfony/console/command/complete_command.rs b/crates/shirabe-external-packages/src/symfony/console/command/complete_command.rs index 5d8b1a98..dcffb5f8 100644 --- a/crates/shirabe-external-packages/src/symfony/console/command/complete_command.rs +++ b/crates/shirabe-external-packages/src/symfony/console/command/complete_command.rs @@ -5,6 +5,7 @@ use crate::symfony::console::completion::completion_input::CompletionInput; use crate::symfony::console::completion::completion_suggestions::{ CompletionSuggestions, StringOrSuggestion, }; +use crate::symfony::console::completion::output::bash_completion_output::BashCompletionOutput; use crate::symfony::console::completion::output::completion_output_interface::CompletionOutputInterface; use crate::symfony::console::input::input_interface::InputInterface; use crate::symfony::console::input::input_option::InputOption; @@ -144,9 +145,7 @@ impl CompleteCommand { fn get_class_of_command(command: &std::rc::Rc<std::cell::RefCell<dyn Command>>) -> String { // LazyCommand is intentionally not ported. - // TODO: get_class() takes a PhpMixed but the command is a `dyn Command`; reflecting the - // concrete class name of a trait object requires a class-name hook on Command (Phase C). - todo!() + command.borrow().get_class() } fn get_definition_options( @@ -162,8 +161,15 @@ fn get_definition_options( } /// new $completionOutput(); -fn instantiate_completion_output(_class: &PhpMixed) -> Box<dyn CompletionOutputInterface> { - todo!() +fn instantiate_completion_output(class: &PhpMixed) -> Box<dyn CompletionOutputInterface> { + match class.to_string().as_str() { + "Symfony\\Component\\Console\\Completion\\Output\\BashCompletionOutput" => { + Box::new(BashCompletionOutput) + } + // completion_outputs only ever registers the bash output (Composer registers no extra + // ones), so any other FQCN is a programming error. + other => panic!("unknown completion output class: {}", other), + } } impl Command for CompleteCommand { @@ -355,7 +361,7 @@ impl Command for CompleteCommand { command .borrow() - .complete(&completion_input, &mut suggestions); + .complete(&completion_input, &mut suggestions)?; } } } @@ -407,5 +413,8 @@ impl Command for CompleteCommand { } } - crate::delegate_command_trait_impls_to_inner!(inner); + crate::delegate_command_trait_impls_to_inner!( + inner, + "Symfony\\Component\\Console\\Command\\CompleteCommand" + ); } diff --git a/crates/shirabe-external-packages/src/symfony/console/command/dump_completion_command.rs b/crates/shirabe-external-packages/src/symfony/console/command/dump_completion_command.rs index 23ce06f7..680ff962 100644 --- a/crates/shirabe-external-packages/src/symfony/console/command/dump_completion_command.rs +++ b/crates/shirabe-external-packages/src/symfony/console/command/dump_completion_command.rs @@ -9,9 +9,14 @@ use crate::symfony::console::input::input_argument::InputArgument; use crate::symfony::console::input::input_interface::InputInterface; use crate::symfony::console::input::input_option::InputOption; use crate::symfony::console::output::output_interface::{self, OutputInterface}; +use crate::symfony::process::process::Process; use shirabe_php_shim::PhpMixed; use std::ops::{Deref, DerefMut}; +/// __DIR__.'/../Resources/completion.bash', embedded at compile time (this port ships as a +/// single binary and does not install the Resources directory alongside it). +const COMPLETION_BASH: &str = include_str!("../Resources/completion.bash"); + /// Dumps the completion script for the current shell. #[derive(Debug)] pub struct DumpCompletionCommand { @@ -59,15 +64,20 @@ impl DumpCompletionCommand { command } - pub fn complete_impl(&self, input: &CompletionInput, suggestions: &mut CompletionSuggestions) { + pub fn complete_impl( + &self, + input: &CompletionInput, + suggestions: &mut CompletionSuggestions, + ) -> anyhow::Result<()> { if input.must_suggest_argument_values_for("shell") { - // TODO(phase-d): PHP's complete() lets a DirectoryIterator failure propagate, but the - // Command::complete trait is infallible; on error the suggestions are skipped instead. - if let Ok(shells) = self.get_supported_shells() { - suggestions - .suggest_values(shells.into_iter().map(StringOrSuggestion::String).collect()); - } + suggestions.suggest_values( + self.get_supported_shells()? + .into_iter() + .map(StringOrSuggestion::String) + .collect(), + ); } + Ok(()) } fn guess_shell() -> String { @@ -81,7 +91,13 @@ impl DumpCompletionCommand { ) } - fn tail_debug_log(&self, command_name: &str, _output: &dyn OutputInterface) { + /// The PHP closure captures `$output` by reference; `Process::run` needs a `'static` + /// callback, so the shared handle is moved into it instead. + fn tail_debug_log( + &self, + command_name: &str, + output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>, + ) -> anyhow::Result<()> { let debug_file = format!( "{}/sf_{}.log", shirabe_php_shim::sys_get_temp_dir(), @@ -90,28 +106,34 @@ impl DumpCompletionCommand { if !shirabe_php_shim::file_exists(&debug_file) { shirabe_php_shim::touch(&debug_file); } - // TODO: Process::run() expects a `'static` callback, but the PHP closure captures - // `$output` by reference and writes each line to it. Bridging the borrowed `output` - // into a `'static` callback requires shared ownership of the output (Phase C). - todo!() + // new Process(['tail', '-f', $debugFile], null, null, null, 0) — timeout 0 disables it; + // like PHP, this tails forever until the user interrupts. + let mut process = Process::new( + vec!["tail".to_string(), "-f".to_string(), debug_file], + None, + None, + PhpMixed::Null, + Some(0.0), + )?; + process.run( + Some(Box::new(move |_type: &str, line: &str| { + output.borrow_mut().write( + &[line.to_string()], + false, + output_interface::OUTPUT_NORMAL, + ); + false + })), + indexmap::IndexMap::new(), + )?; + Ok(()) } fn get_supported_shells(&self) -> anyhow::Result<Vec<String>> { - let mut shells = vec![]; - - // foreach (new \DirectoryIterator(__DIR__.'/../Resources/') as $file) - for file in shirabe_php_shim::directory_iterator(&format!( - "{}/../Resources/", - shirabe_php_shim::dir() - ))? { - if shirabe_php_shim::str_starts_with(&file.get_basename(), "completion.") - && file.is_file() - { - shells.push(file.get_extension()); - } - } - - Ok(shells) + // Deviation from PHP: the PHP implementation scans __DIR__.'/../Resources/' with a + // DirectoryIterator at runtime; the resources are embedded at compile time in this + // port, so the supported shells are a static list. + Ok(vec!["bash".to_string()]) } } @@ -192,7 +214,7 @@ impl Command for DumpCompletionCommand { ); if input.borrow().get_option("debug")?.to_bool() { - self.tail_debug_log(&command_name, &*output.borrow()); + self.tail_debug_log(&command_name, output.clone())?; return Ok(0); } @@ -201,17 +223,23 @@ impl Command for DumpCompletionCommand { Some(s) => s.to_string(), None => Self::guess_shell(), }; - let completion_file = format!( - "{}/../Resources/completion.{}", - shirabe_php_shim::dir(), - shell - ); - if !shirabe_php_shim::file_exists(&completion_file) { + // __DIR__.'/../Resources/completion.'.$shell — resolved against the embedded + // resources; a shell without an embedded script is PHP's !file_exists() branch. + let completion_file = match shell.as_str() { + "bash" => Some(COMPLETION_BASH), + _ => None, + }; + let Some(completion_file) = completion_file else { let supported_shells = self.get_supported_shells()?; - // TODO: PHP does `$output instanceof ConsoleOutputInterface ? $output->getErrorOutput() - // : $output`. There is no way to test trait membership through `&dyn OutputInterface` - // here; OutputInterface would need a downcast hook (Phase C). Writing to `output`. + // if ($output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } + let output = { + let error_output = output + .borrow() + .as_console_output() + .map(|console_output| console_output.get_error_output()); + error_output.unwrap_or_else(|| output.clone()) + }; if !shell.is_empty() { output.borrow_mut().writeln( &[format!( @@ -232,7 +260,7 @@ impl Command for DumpCompletionCommand { } return Ok(2); - } + }; let application = self.get_application().unwrap(); let version = application.borrow().get_version(); @@ -243,7 +271,7 @@ impl Command for DumpCompletionCommand { "{{ VERSION }}".to_string(), ], &[command_name, version], - &shirabe_php_shim::file_get_contents(&completion_file).unwrap_or_default(), + completion_file, )], false, output_interface::OUTPUT_NORMAL, @@ -252,9 +280,16 @@ impl Command for DumpCompletionCommand { Ok(0) } - fn complete(&self, input: &CompletionInput, suggestions: &mut CompletionSuggestions) { - self.complete_impl(input, suggestions); + fn complete( + &self, + input: &CompletionInput, + suggestions: &mut CompletionSuggestions, + ) -> anyhow::Result<()> { + self.complete_impl(input, suggestions) } - crate::delegate_command_trait_impls_to_inner!(inner); + crate::delegate_command_trait_impls_to_inner!( + inner, + "Symfony\\Component\\Console\\Command\\DumpCompletionCommand" + ); } diff --git a/crates/shirabe-external-packages/src/symfony/console/command/help_command.rs b/crates/shirabe-external-packages/src/symfony/console/command/help_command.rs index 7c32a733..83ed4f5b 100644 --- a/crates/shirabe-external-packages/src/symfony/console/command/help_command.rs +++ b/crates/shirabe-external-packages/src/symfony/console/command/help_command.rs @@ -155,9 +155,17 @@ impl Command for HelpCommand { Ok(0) } - fn complete(&self, input: &CompletionInput, suggestions: &mut CompletionSuggestions) { + fn complete( + &self, + input: &CompletionInput, + suggestions: &mut CompletionSuggestions, + ) -> anyhow::Result<()> { self.complete_impl(input, suggestions); + Ok(()) } - crate::delegate_command_trait_impls_to_inner!(inner); + crate::delegate_command_trait_impls_to_inner!( + inner, + "Symfony\\Component\\Console\\Command\\HelpCommand" + ); } diff --git a/crates/shirabe-external-packages/src/symfony/console/command/list_command.rs b/crates/shirabe-external-packages/src/symfony/console/command/list_command.rs index 4153a20b..79673000 100644 --- a/crates/shirabe-external-packages/src/symfony/console/command/list_command.rs +++ b/crates/shirabe-external-packages/src/symfony/console/command/list_command.rs @@ -156,9 +156,17 @@ impl Command for ListCommand { Ok(0) } - fn complete(&self, input: &CompletionInput, suggestions: &mut CompletionSuggestions) { + fn complete( + &self, + input: &CompletionInput, + suggestions: &mut CompletionSuggestions, + ) -> anyhow::Result<()> { self.complete_impl(input, suggestions); + Ok(()) } - crate::delegate_command_trait_impls_to_inner!(inner); + crate::delegate_command_trait_impls_to_inner!( + inner, + "Symfony\\Component\\Console\\Command\\ListCommand" + ); } |
