aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe-external-packages/src/symfony/console/command
diff options
context:
space:
mode:
Diffstat (limited to 'crates/shirabe-external-packages/src/symfony/console/command')
-rw-r--r--crates/shirabe-external-packages/src/symfony/console/command/command.rs29
-rw-r--r--crates/shirabe-external-packages/src/symfony/console/command/complete_command.rs23
-rw-r--r--crates/shirabe-external-packages/src/symfony/console/command/dump_completion_command.rs119
-rw-r--r--crates/shirabe-external-packages/src/symfony/console/command/help_command.rs12
-rw-r--r--crates/shirabe-external-packages/src/symfony/console/command/list_command.rs12
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"
+ );
}