diff options
Diffstat (limited to 'crates/shirabe/src')
37 files changed, 327 insertions, 147 deletions
diff --git a/crates/shirabe/src/command/about_command.rs b/crates/shirabe/src/command/about_command.rs index 74978e1c..799b889f 100644 --- a/crates/shirabe/src/command/about_command.rs +++ b/crates/shirabe/src/command/about_command.rs @@ -71,10 +71,8 @@ impl Command for AboutCommand { } impl BaseCommand for AboutCommand { - fn command_data( - &self, - ) -> &shirabe_external_packages::symfony::console::command::command::CommandData { - self.base_command_data.command_data() + fn base_command_data(&self) -> &crate::command::BaseCommandData { + &self.base_command_data } crate::delegate_base_command_trait_impls_to_inner!(base_command_data); diff --git a/crates/shirabe/src/command/archive_command.rs b/crates/shirabe/src/command/archive_command.rs index bf3ce01b..b6425a27 100644 --- a/crates/shirabe/src/command/archive_command.rs +++ b/crates/shirabe/src/command/archive_command.rs @@ -239,10 +239,8 @@ impl ArchiveCommand { } impl BaseCommand for ArchiveCommand { - fn command_data( - &self, - ) -> &shirabe_external_packages::symfony::console::command::command::CommandData { - self.base_command_data.command_data() + fn base_command_data(&self) -> &crate::command::BaseCommandData { + &self.base_command_data } crate::delegate_base_command_trait_impls_to_inner!(base_command_data); diff --git a/crates/shirabe/src/command/audit_command.rs b/crates/shirabe/src/command/audit_command.rs index 666ba4b0..52307297 100644 --- a/crates/shirabe/src/command/audit_command.rs +++ b/crates/shirabe/src/command/audit_command.rs @@ -230,10 +230,8 @@ impl Command for AuditCommand { } impl BaseCommand for AuditCommand { - fn command_data( - &self, - ) -> &shirabe_external_packages::symfony::console::command::command::CommandData { - self.base_command_data.command_data() + fn base_command_data(&self) -> &crate::command::BaseCommandData { + &self.base_command_data } crate::delegate_base_command_trait_impls_to_inner!(base_command_data); diff --git a/crates/shirabe/src/command/base_command.rs b/crates/shirabe/src/command/base_command.rs index e05478f4..7e8a3f5a 100644 --- a/crates/shirabe/src/command/base_command.rs +++ b/crates/shirabe/src/command/base_command.rs @@ -42,6 +42,15 @@ pub struct BaseCommandData { inner: CommandData, pub(crate) composer: std::cell::RefCell<Option<PartialComposerHandle>>, pub(crate) io: std::cell::RefCell<Option<std::rc::Rc<std::cell::RefCell<dyn IOInterface>>>>, + /// The definition entries that were registered through the Composer-typed + /// InputArgument/InputOption (which carry the suggested-values backport). PHP checks + /// `$definition->getArgument($name) instanceof Composer\Console\Input\InputArgument`; in + /// this port `set_definition` converts the entries to the Symfony types for storage, so + /// the Composer-typedness is tracked by name in these side maps instead. + pub(crate) composer_arguments: + std::cell::RefCell<IndexMap<String, std::rc::Rc<crate::console::input::InputArgument>>>, + pub(crate) composer_options: + std::cell::RefCell<IndexMap<String, std::rc::Rc<crate::console::input::InputOption>>>, } impl BaseCommandData { @@ -50,6 +59,8 @@ impl BaseCommandData { inner: CommandData::new(name), composer: std::cell::RefCell::new(None), io: std::cell::RefCell::new(None), + composer_arguments: std::cell::RefCell::new(IndexMap::new()), + composer_options: std::cell::RefCell::new(IndexMap::new()), } } @@ -65,17 +76,40 @@ impl BaseCommandData { /// `Command` trait. The Symfony state methods are inherited from the [`Command`] supertrait; /// only the Composer-specific behavior and the Composer-typed definition builders live here. pub trait BaseCommand: Command { - /// Access to the embedded Symfony command state. Each command returns - /// `self.base_command_data.command_data()`; this lets the Composer-typed definition - /// builders below forward to `CommandData`'s Symfony-typed entry points. `CommandData` is - /// interior-mutable, so a shared reference is enough. - fn command_data(&self) -> &CommandData; + /// Access to the embedded base-command state. Each command returns + /// `&self.base_command_data`; the definition builders and the completion dispatch below + /// go through it. The state is interior-mutable, so a shared reference is enough. + fn base_command_data(&self) -> &BaseCommandData; + + /// Access to the embedded Symfony command state, forwarding to `CommandData`'s + /// Symfony-typed entry points. + fn command_data(&self) -> &CommandData { + self.base_command_data().command_data() + } /// Sets the definition from Composer-typed argument/option instances. fn set_definition(&self, definition: &[InputDefinitionItem]) -> &Self where Self: Sized, { + let data = self.base_command_data(); + // PHP replaces the whole definition, so the Composer-typed side maps follow suit. + data.composer_arguments.borrow_mut().clear(); + data.composer_options.borrow_mut().clear(); + for item in definition { + match item { + InputDefinitionItem::Argument(argument) => { + data.composer_arguments + .borrow_mut() + .insert(argument.get_name(), argument.clone()); + } + InputDefinitionItem::Option(option) => { + data.composer_options + .borrow_mut() + .insert(option.get_name(), option.clone()); + } + } + } let items = definition .iter() .map(|item| item.to_definition_item()) @@ -255,8 +289,8 @@ impl Command for BaseCommandData { } impl BaseCommand for BaseCommandData { - fn command_data(&self) -> &CommandData { - &self.inner + fn base_command_data(&self) -> &BaseCommandData { + self } fn require_composer( @@ -736,6 +770,53 @@ fn apply_application_defaults( (disable_plugins, disable_scripts) } +/// \Composer\Command\BaseCommand::complete — the suggested-values backport dispatch +/// (BaseCommand.php:198-215). Shared via a free function because Rust has no inheritance; +/// each command's `Command::complete` forwards here. +pub fn base_command_complete( + cmd: &dyn BaseCommand, + input: &shirabe_external_packages::symfony::console::completion::completion_input::CompletionInput, + suggestions: &mut shirabe_external_packages::symfony::console::completion::completion_suggestions::CompletionSuggestions, +) -> anyhow::Result<()> { + use shirabe_external_packages::symfony::console::completion::completion_input::CompletionInput; + + // PHP: (string) $input->getCompletionName() + let name = input.get_completion_name().unwrap_or_default(); + // The definition Ref and the side-map Refs must be dropped before the suggestion closure + // runs: the closure may re-enter the command (require_composer etc.). + if CompletionInput::TYPE_OPTION_VALUE == input.get_completion_type() + && cmd.get_definition().has_option(&name) + { + // PHP: ($option = $definition->getOption($name)) instanceof InputOption — the + // Composer-typedness is tracked in the side map (see BaseCommandData). + let option = cmd + .base_command_data() + .composer_options + .borrow() + .get(&name) + .cloned(); + if let Some(option) = option { + return option.complete(cmd, input, suggestions); + } + } else if CompletionInput::TYPE_ARGUMENT_VALUE == input.get_completion_type() + && cmd + .get_definition() + .has_argument(&PhpMixed::String(name.clone())) + { + let argument = cmd + .base_command_data() + .composer_arguments + .borrow() + .get(&name) + .cloned(); + if let Some(argument) = argument { + return argument.complete(cmd, input, suggestions); + } + } + // PHP: parent::complete($input, $suggestions) — Symfony 5.4's Command::complete is a no-op. + Ok(()) +} + /// \Composer\Command\BaseCommand::initialize — runs for every Composer command after the /// input is bound. Shared via a free function because Rust has no inheritance; each command's /// `Command::initialize` forwards here so the leaf's `is_self_update_command()` override (and diff --git a/crates/shirabe/src/command/bump_command.rs b/crates/shirabe/src/command/bump_command.rs index 9dd830c4..5560a293 100644 --- a/crates/shirabe/src/command/bump_command.rs +++ b/crates/shirabe/src/command/bump_command.rs @@ -443,10 +443,8 @@ impl Command for BumpCommand { } impl BaseCommand for BumpCommand { - fn command_data( - &self, - ) -> &shirabe_external_packages::symfony::console::command::command::CommandData { - self.base_command_data.command_data() + fn base_command_data(&self) -> &crate::command::BaseCommandData { + &self.base_command_data } crate::delegate_base_command_trait_impls_to_inner!(base_command_data); diff --git a/crates/shirabe/src/command/check_platform_reqs_command.rs b/crates/shirabe/src/command/check_platform_reqs_command.rs index 1675358d..f4017350 100644 --- a/crates/shirabe/src/command/check_platform_reqs_command.rs +++ b/crates/shirabe/src/command/check_platform_reqs_command.rs @@ -409,10 +409,8 @@ impl Command for CheckPlatformReqsCommand { } impl BaseCommand for CheckPlatformReqsCommand { - fn command_data( - &self, - ) -> &shirabe_external_packages::symfony::console::command::command::CommandData { - self.base_command_data.command_data() + fn base_command_data(&self) -> &crate::command::BaseCommandData { + &self.base_command_data } crate::delegate_base_command_trait_impls_to_inner!(base_command_data); diff --git a/crates/shirabe/src/command/clear_cache_command.rs b/crates/shirabe/src/command/clear_cache_command.rs index a276ba13..d10bdb20 100644 --- a/crates/shirabe/src/command/clear_cache_command.rs +++ b/crates/shirabe/src/command/clear_cache_command.rs @@ -191,10 +191,8 @@ impl Command for ClearCacheCommand { } impl BaseCommand for ClearCacheCommand { - fn command_data( - &self, - ) -> &shirabe_external_packages::symfony::console::command::command::CommandData { - self.base_command_data.command_data() + fn base_command_data(&self) -> &crate::command::BaseCommandData { + &self.base_command_data } crate::delegate_base_command_trait_impls_to_inner!(base_command_data); diff --git a/crates/shirabe/src/command/config_command.rs b/crates/shirabe/src/command/config_command.rs index fe1fd19a..5f56b274 100644 --- a/crates/shirabe/src/command/config_command.rs +++ b/crates/shirabe/src/command/config_command.rs @@ -1280,10 +1280,8 @@ impl Command for ConfigCommand { } impl BaseCommand for ConfigCommand { - fn command_data( - &self, - ) -> &shirabe_external_packages::symfony::console::command::command::CommandData { - self.base_command_data.command_data() + fn base_command_data(&self) -> &crate::command::BaseCommandData { + &self.base_command_data } crate::delegate_base_command_trait_impls_to_inner!(base_command_data); diff --git a/crates/shirabe/src/command/create_project_command.rs b/crates/shirabe/src/command/create_project_command.rs index 687575b0..82924a95 100644 --- a/crates/shirabe/src/command/create_project_command.rs +++ b/crates/shirabe/src/command/create_project_command.rs @@ -269,10 +269,8 @@ impl Command for CreateProjectCommand { } impl BaseCommand for CreateProjectCommand { - fn command_data( - &self, - ) -> &shirabe_external_packages::symfony::console::command::command::CommandData { - self.base_command_data.command_data() + fn base_command_data(&self) -> &crate::command::BaseCommandData { + &self.base_command_data } crate::delegate_base_command_trait_impls_to_inner!(base_command_data); diff --git a/crates/shirabe/src/command/depends_command.rs b/crates/shirabe/src/command/depends_command.rs index f6723bd9..ff01b3af 100644 --- a/crates/shirabe/src/command/depends_command.rs +++ b/crates/shirabe/src/command/depends_command.rs @@ -120,10 +120,8 @@ impl Command for DependsCommand { } impl BaseCommand for DependsCommand { - fn command_data( - &self, - ) -> &shirabe_external_packages::symfony::console::command::command::CommandData { - self.base_command_data.command_data() + fn base_command_data(&self) -> &crate::command::BaseCommandData { + &self.base_command_data } crate::delegate_base_command_trait_impls_to_inner!(base_command_data); diff --git a/crates/shirabe/src/command/diagnose_command.rs b/crates/shirabe/src/command/diagnose_command.rs index 55a130bd..d0f8f592 100644 --- a/crates/shirabe/src/command/diagnose_command.rs +++ b/crates/shirabe/src/command/diagnose_command.rs @@ -451,10 +451,8 @@ impl Command for DiagnoseCommand { } impl BaseCommand for DiagnoseCommand { - fn command_data( - &self, - ) -> &shirabe_external_packages::symfony::console::command::command::CommandData { - self.base_command_data.command_data() + fn base_command_data(&self) -> &crate::command::BaseCommandData { + &self.base_command_data } crate::delegate_base_command_trait_impls_to_inner!(base_command_data); diff --git a/crates/shirabe/src/command/dump_autoload_command.rs b/crates/shirabe/src/command/dump_autoload_command.rs index 291ab7b2..02c25ba9 100644 --- a/crates/shirabe/src/command/dump_autoload_command.rs +++ b/crates/shirabe/src/command/dump_autoload_command.rs @@ -324,10 +324,8 @@ impl Command for DumpAutoloadCommand { } impl BaseCommand for DumpAutoloadCommand { - fn command_data( - &self, - ) -> &shirabe_external_packages::symfony::console::command::command::CommandData { - self.base_command_data.command_data() + fn base_command_data(&self) -> &crate::command::BaseCommandData { + &self.base_command_data } crate::delegate_base_command_trait_impls_to_inner!(base_command_data); diff --git a/crates/shirabe/src/command/exec_command.rs b/crates/shirabe/src/command/exec_command.rs index 3fa909a3..36101bc3 100644 --- a/crates/shirabe/src/command/exec_command.rs +++ b/crates/shirabe/src/command/exec_command.rs @@ -252,10 +252,8 @@ impl Command for ExecCommand { } impl BaseCommand for ExecCommand { - fn command_data( - &self, - ) -> &shirabe_external_packages::symfony::console::command::command::CommandData { - self.base_command_data.command_data() + fn base_command_data(&self) -> &crate::command::BaseCommandData { + &self.base_command_data } crate::delegate_base_command_trait_impls_to_inner!(base_command_data); diff --git a/crates/shirabe/src/command/fund_command.rs b/crates/shirabe/src/command/fund_command.rs index e9e75cb0..c4fdaa55 100644 --- a/crates/shirabe/src/command/fund_command.rs +++ b/crates/shirabe/src/command/fund_command.rs @@ -230,10 +230,8 @@ impl Command for FundCommand { } impl BaseCommand for FundCommand { - fn command_data( - &self, - ) -> &shirabe_external_packages::symfony::console::command::command::CommandData { - self.base_command_data.command_data() + fn base_command_data(&self) -> &crate::command::BaseCommandData { + &self.base_command_data } crate::delegate_base_command_trait_impls_to_inner!(base_command_data); diff --git a/crates/shirabe/src/command/global_command.rs b/crates/shirabe/src/command/global_command.rs index 7331ebf8..e70a2082 100644 --- a/crates/shirabe/src/command/global_command.rs +++ b/crates/shirabe/src/command/global_command.rs @@ -205,10 +205,8 @@ impl Command for GlobalCommand { } impl BaseCommand for GlobalCommand { - fn command_data( - &self, - ) -> &shirabe_external_packages::symfony::console::command::command::CommandData { - self.base_command_data.command_data() + fn base_command_data(&self) -> &crate::command::BaseCommandData { + &self.base_command_data } crate::delegate_base_command_trait_impls_to_inner!(base_command_data); diff --git a/crates/shirabe/src/command/home_command.rs b/crates/shirabe/src/command/home_command.rs index e61cc9c2..77abb6ac 100644 --- a/crates/shirabe/src/command/home_command.rs +++ b/crates/shirabe/src/command/home_command.rs @@ -267,10 +267,8 @@ impl Command for HomeCommand { } impl BaseCommand for HomeCommand { - fn command_data( - &self, - ) -> &shirabe_external_packages::symfony::console::command::command::CommandData { - self.base_command_data.command_data() + fn base_command_data(&self) -> &crate::command::BaseCommandData { + &self.base_command_data } crate::delegate_base_command_trait_impls_to_inner!(base_command_data); diff --git a/crates/shirabe/src/command/init_command.rs b/crates/shirabe/src/command/init_command.rs index b74a91f9..17ed8c09 100644 --- a/crates/shirabe/src/command/init_command.rs +++ b/crates/shirabe/src/command/init_command.rs @@ -907,10 +907,8 @@ impl Command for InitCommand { } impl BaseCommand for InitCommand { - fn command_data( - &self, - ) -> &shirabe_external_packages::symfony::console::command::command::CommandData { - self.base_command_data.command_data() + fn base_command_data(&self) -> &crate::command::BaseCommandData { + &self.base_command_data } crate::delegate_base_command_trait_impls_to_inner!(base_command_data); diff --git a/crates/shirabe/src/command/install_command.rs b/crates/shirabe/src/command/install_command.rs index 3e833c5b..2e65be1f 100644 --- a/crates/shirabe/src/command/install_command.rs +++ b/crates/shirabe/src/command/install_command.rs @@ -276,10 +276,8 @@ impl Command for InstallCommand { } impl BaseCommand for InstallCommand { - fn command_data( - &self, - ) -> &shirabe_external_packages::symfony::console::command::command::CommandData { - self.base_command_data.command_data() + fn base_command_data(&self) -> &crate::command::BaseCommandData { + &self.base_command_data } crate::delegate_base_command_trait_impls_to_inner!(base_command_data); diff --git a/crates/shirabe/src/command/licenses_command.rs b/crates/shirabe/src/command/licenses_command.rs index 75407d92..ba97a055 100644 --- a/crates/shirabe/src/command/licenses_command.rs +++ b/crates/shirabe/src/command/licenses_command.rs @@ -335,10 +335,8 @@ impl Command for LicensesCommand { } impl BaseCommand for LicensesCommand { - fn command_data( - &self, - ) -> &shirabe_external_packages::symfony::console::command::command::CommandData { - self.base_command_data.command_data() + fn base_command_data(&self) -> &crate::command::BaseCommandData { + &self.base_command_data } crate::delegate_base_command_trait_impls_to_inner!(base_command_data); diff --git a/crates/shirabe/src/command/outdated_command.rs b/crates/shirabe/src/command/outdated_command.rs index fd8a1497..1c03bfdb 100644 --- a/crates/shirabe/src/command/outdated_command.rs +++ b/crates/shirabe/src/command/outdated_command.rs @@ -237,10 +237,8 @@ impl Command for OutdatedCommand { } impl BaseCommand for OutdatedCommand { - fn command_data( - &self, - ) -> &shirabe_external_packages::symfony::console::command::command::CommandData { - self.base_command_data.command_data() + fn base_command_data(&self) -> &crate::command::BaseCommandData { + &self.base_command_data } fn is_proxy_command(&self) -> bool { diff --git a/crates/shirabe/src/command/prohibits_command.rs b/crates/shirabe/src/command/prohibits_command.rs index 7de16831..23b693f3 100644 --- a/crates/shirabe/src/command/prohibits_command.rs +++ b/crates/shirabe/src/command/prohibits_command.rs @@ -127,10 +127,8 @@ impl Command for ProhibitsCommand { } impl BaseCommand for ProhibitsCommand { - fn command_data( - &self, - ) -> &shirabe_external_packages::symfony::console::command::command::CommandData { - self.base_command_data.command_data() + fn base_command_data(&self) -> &crate::command::BaseCommandData { + &self.base_command_data } crate::delegate_base_command_trait_impls_to_inner!(base_command_data); diff --git a/crates/shirabe/src/command/reinstall_command.rs b/crates/shirabe/src/command/reinstall_command.rs index dc94c4ca..6b5b021a 100644 --- a/crates/shirabe/src/command/reinstall_command.rs +++ b/crates/shirabe/src/command/reinstall_command.rs @@ -364,10 +364,8 @@ impl Command for ReinstallCommand { } impl BaseCommand for ReinstallCommand { - fn command_data( - &self, - ) -> &shirabe_external_packages::symfony::console::command::command::CommandData { - self.base_command_data.command_data() + fn base_command_data(&self) -> &crate::command::BaseCommandData { + &self.base_command_data } crate::delegate_base_command_trait_impls_to_inner!(base_command_data); diff --git a/crates/shirabe/src/command/remove_command.rs b/crates/shirabe/src/command/remove_command.rs index 3fb99653..ebd332a8 100644 --- a/crates/shirabe/src/command/remove_command.rs +++ b/crates/shirabe/src/command/remove_command.rs @@ -718,10 +718,8 @@ impl Command for RemoveCommand { } impl BaseCommand for RemoveCommand { - fn command_data( - &self, - ) -> &shirabe_external_packages::symfony::console::command::command::CommandData { - self.base_command_data.command_data() + fn base_command_data(&self) -> &crate::command::BaseCommandData { + &self.base_command_data } crate::delegate_base_command_trait_impls_to_inner!(base_command_data); diff --git a/crates/shirabe/src/command/repository_command.rs b/crates/shirabe/src/command/repository_command.rs index fd8b4671..51089732 100644 --- a/crates/shirabe/src/command/repository_command.rs +++ b/crates/shirabe/src/command/repository_command.rs @@ -508,10 +508,8 @@ impl Command for RepositoryCommand { } impl BaseCommand for RepositoryCommand { - fn command_data( - &self, - ) -> &shirabe_external_packages::symfony::console::command::command::CommandData { - self.base_command_data.command_data() + fn base_command_data(&self) -> &crate::command::BaseCommandData { + &self.base_command_data } crate::delegate_base_command_trait_impls_to_inner!(base_command_data); diff --git a/crates/shirabe/src/command/require_command.rs b/crates/shirabe/src/command/require_command.rs index af9f56e4..d54ecaa4 100644 --- a/crates/shirabe/src/command/require_command.rs +++ b/crates/shirabe/src/command/require_command.rs @@ -657,10 +657,8 @@ impl Command for RequireCommand { } impl BaseCommand for RequireCommand { - fn command_data( - &self, - ) -> &shirabe_external_packages::symfony::console::command::command::CommandData { - self.base_command_data.command_data() + fn base_command_data(&self) -> &crate::command::BaseCommandData { + &self.base_command_data } crate::delegate_base_command_trait_impls_to_inner!(base_command_data); diff --git a/crates/shirabe/src/command/run_script_command.rs b/crates/shirabe/src/command/run_script_command.rs index 6590c19f..1ac9e9a4 100644 --- a/crates/shirabe/src/command/run_script_command.rs +++ b/crates/shirabe/src/command/run_script_command.rs @@ -337,10 +337,8 @@ impl Command for RunScriptCommand { } impl BaseCommand for RunScriptCommand { - fn command_data( - &self, - ) -> &shirabe_external_packages::symfony::console::command::command::CommandData { - self.base_command_data.command_data() + fn base_command_data(&self) -> &crate::command::BaseCommandData { + &self.base_command_data } crate::delegate_base_command_trait_impls_to_inner!(base_command_data); diff --git a/crates/shirabe/src/command/script_alias_command.rs b/crates/shirabe/src/command/script_alias_command.rs index da58234b..6a27507f 100644 --- a/crates/shirabe/src/command/script_alias_command.rs +++ b/crates/shirabe/src/command/script_alias_command.rs @@ -175,10 +175,8 @@ impl Command for ScriptAliasCommand { } impl BaseCommand for ScriptAliasCommand { - fn command_data( - &self, - ) -> &shirabe_external_packages::symfony::console::command::command::CommandData { - self.base_command_data.command_data() + fn base_command_data(&self) -> &crate::command::BaseCommandData { + &self.base_command_data } crate::delegate_base_command_trait_impls_to_inner!(base_command_data); diff --git a/crates/shirabe/src/command/search_command.rs b/crates/shirabe/src/command/search_command.rs index 83c7a8bf..0d5fa4be 100644 --- a/crates/shirabe/src/command/search_command.rs +++ b/crates/shirabe/src/command/search_command.rs @@ -308,10 +308,8 @@ impl Command for SearchCommand { } impl BaseCommand for SearchCommand { - fn command_data( - &self, - ) -> &shirabe_external_packages::symfony::console::command::command::CommandData { - self.base_command_data.command_data() + fn base_command_data(&self) -> &crate::command::BaseCommandData { + &self.base_command_data } crate::delegate_base_command_trait_impls_to_inner!(base_command_data); diff --git a/crates/shirabe/src/command/self_update_command.rs b/crates/shirabe/src/command/self_update_command.rs index 31d95263..29b08006 100644 --- a/crates/shirabe/src/command/self_update_command.rs +++ b/crates/shirabe/src/command/self_update_command.rs @@ -98,10 +98,8 @@ impl Command for SelfUpdateCommand { } impl BaseCommand for SelfUpdateCommand { - fn command_data( - &self, - ) -> &shirabe_external_packages::symfony::console::command::command::CommandData { - self.base_command_data.command_data() + fn base_command_data(&self) -> &crate::command::BaseCommandData { + &self.base_command_data } crate::delegate_base_command_trait_impls_to_inner!(base_command_data); diff --git a/crates/shirabe/src/command/show_command.rs b/crates/shirabe/src/command/show_command.rs index 18d47911..c5116b80 100644 --- a/crates/shirabe/src/command/show_command.rs +++ b/crates/shirabe/src/command/show_command.rs @@ -1448,10 +1448,8 @@ impl Command for ShowCommand { } impl BaseCommand for ShowCommand { - fn command_data( - &self, - ) -> &shirabe_external_packages::symfony::console::command::command::CommandData { - self.base_command_data.command_data() + fn base_command_data(&self) -> &crate::command::BaseCommandData { + &self.base_command_data } crate::delegate_base_command_trait_impls_to_inner!(base_command_data); diff --git a/crates/shirabe/src/command/status_command.rs b/crates/shirabe/src/command/status_command.rs index c412fa2f..323a61d8 100644 --- a/crates/shirabe/src/command/status_command.rs +++ b/crates/shirabe/src/command/status_command.rs @@ -392,10 +392,8 @@ impl Command for StatusCommand { } impl BaseCommand for StatusCommand { - fn command_data( - &self, - ) -> &shirabe_external_packages::symfony::console::command::command::CommandData { - self.base_command_data.command_data() + fn base_command_data(&self) -> &crate::command::BaseCommandData { + &self.base_command_data } crate::delegate_base_command_trait_impls_to_inner!(base_command_data); diff --git a/crates/shirabe/src/command/suggests_command.rs b/crates/shirabe/src/command/suggests_command.rs index 4fd52915..fc2b739a 100644 --- a/crates/shirabe/src/command/suggests_command.rs +++ b/crates/shirabe/src/command/suggests_command.rs @@ -226,10 +226,8 @@ impl Command for SuggestsCommand { } impl BaseCommand for SuggestsCommand { - fn command_data( - &self, - ) -> &shirabe_external_packages::symfony::console::command::command::CommandData { - self.base_command_data.command_data() + fn base_command_data(&self) -> &crate::command::BaseCommandData { + &self.base_command_data } crate::delegate_base_command_trait_impls_to_inner!(base_command_data); diff --git a/crates/shirabe/src/command/update_command.rs b/crates/shirabe/src/command/update_command.rs index c2f41d6a..7c89c19e 100644 --- a/crates/shirabe/src/command/update_command.rs +++ b/crates/shirabe/src/command/update_command.rs @@ -585,10 +585,8 @@ impl Command for UpdateCommand { } impl BaseCommand for UpdateCommand { - fn command_data( - &self, - ) -> &shirabe_external_packages::symfony::console::command::command::CommandData { - self.base_command_data.command_data() + fn base_command_data(&self) -> &crate::command::BaseCommandData { + &self.base_command_data } crate::delegate_base_command_trait_impls_to_inner!(base_command_data); diff --git a/crates/shirabe/src/command/validate_command.rs b/crates/shirabe/src/command/validate_command.rs index fbfc45dc..eee41018 100644 --- a/crates/shirabe/src/command/validate_command.rs +++ b/crates/shirabe/src/command/validate_command.rs @@ -314,10 +314,8 @@ impl Command for ValidateCommand { } impl BaseCommand for ValidateCommand { - fn command_data( - &self, - ) -> &shirabe_external_packages::symfony::console::command::command::CommandData { - self.base_command_data.command_data() + fn base_command_data(&self) -> &crate::command::BaseCommandData { + &self.base_command_data } crate::delegate_base_command_trait_impls_to_inner!(base_command_data); diff --git a/crates/shirabe/src/console/input.rs b/crates/shirabe/src/console/input.rs index 9141f0cb..9661c7ad 100644 --- a/crates/shirabe/src/console/input.rs +++ b/crates/shirabe/src/console/input.rs @@ -4,9 +4,86 @@ pub mod input_option; pub use input_argument::*; pub use input_option::*; +use shirabe_external_packages::symfony::console::completion::completion_input::CompletionInput; +use shirabe_external_packages::symfony::console::completion::completion_suggestions::{ + CompletionSuggestions, StringOrSuggestion, +}; + +/// PHP: `\Closure(CompletionInput,CompletionSuggestions):list<string|Suggestion>`. +/// +/// PHP closures are bound to the command instance (`$this`); commands cannot capture a handle +/// to themselves while `configure()` runs inside `new()`, so the bound command is passed in as +/// `this` at call time instead. +pub type SuggestedValuesClosure = Box< + dyn Fn( + &dyn crate::command::BaseCommand, + &CompletionInput, + &mut CompletionSuggestions, + ) -> anyhow::Result<Vec<String>>, +>; + +/// PHP: the `list<string>|\Closure(...)` union taken by the suggestedValues parameter of the +/// `Composer\Console\Input\InputArgument` / `InputOption` backport. +/// +/// The closure returns `Vec<String>` rather than `list<string|Suggestion>`: every Composer +/// closure returns plain strings, and `complete` lifts them into suggestions. PHP's runtime +/// "Closure must return an array" LogicException is statically guaranteed by the type. +pub enum SuggestedValues { + List(Vec<String>), + Closure(SuggestedValuesClosure), +} + +impl std::fmt::Debug for SuggestedValues { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SuggestedValues::List(values) => f.debug_tuple("List").field(values).finish(), + SuggestedValues::Closure(_) => f.write_str("Closure(..)"), + } + } +} + +impl SuggestedValues { + /// Whether PHP's `[] !== $suggestedValues` is false, i.e. no suggestions were declared. + pub(crate) fn is_empty(&self) -> bool { + matches!(self, SuggestedValues::List(values) if values.is_empty()) + } + + /// The shared body of the `InputArgument::complete` / `InputOption::complete` backport. + pub(crate) fn complete( + &self, + this: &dyn crate::command::BaseCommand, + input: &CompletionInput, + suggestions: &mut CompletionSuggestions, + ) -> anyhow::Result<()> { + let values = match self { + SuggestedValues::List(values) => values.clone(), + SuggestedValues::Closure(closure) => closure(this, input, suggestions)?, + }; + if !values.is_empty() { + suggestions + .suggest_values(values.into_iter().map(StringOrSuggestion::String).collect()); + } + Ok(()) + } + + /// PHP: `$this->suggestX()($input)` — invoking a suggestion closure directly. Calling this + /// on a List is a programming error (PHP would fatal on `$array()`). + pub fn call( + &self, + this: &dyn crate::command::BaseCommand, + input: &CompletionInput, + suggestions: &mut CompletionSuggestions, + ) -> anyhow::Result<Vec<String>> { + match self { + SuggestedValues::Closure(closure) => closure(this, input, suggestions), + SuggestedValues::List(_) => panic!("SuggestedValues::call on a non-closure"), + } + } +} + pub enum InputDefinitionItem { - Argument(input_argument::InputArgument), - Option(input_option::InputOption), + Argument(std::rc::Rc<input_argument::InputArgument>), + Option(std::rc::Rc<input_option::InputOption>), } impl InputDefinitionItem { @@ -26,12 +103,12 @@ impl InputDefinitionItem { impl From<input_argument::InputArgument> for InputDefinitionItem { fn from(value: input_argument::InputArgument) -> Self { - Self::Argument(value) + Self::Argument(std::rc::Rc::new(value)) } } impl From<input_option::InputOption> for InputDefinitionItem { fn from(value: input_option::InputOption) -> Self { - Self::Option(value) + Self::Option(std::rc::Rc::new(value)) } } diff --git a/crates/shirabe/src/console/input/input_argument.rs b/crates/shirabe/src/console/input/input_argument.rs index 5fd9cecc..ab32ffba 100644 --- a/crates/shirabe/src/console/input/input_argument.rs +++ b/crates/shirabe/src/console/input/input_argument.rs @@ -1,11 +1,15 @@ //! ref: composer/src/Composer/Console/Input/InputArgument.php +use crate::console::input::SuggestedValues; +use shirabe_external_packages::symfony::console::completion::completion_input::CompletionInput; +use shirabe_external_packages::symfony::console::completion::completion_suggestions::CompletionSuggestions; use shirabe_external_packages::symfony::console::input::InputArgument as BaseInputArgument; use shirabe_php_shim::PhpMixed; #[derive(Debug)] pub struct InputArgument { inner: BaseInputArgument, + suggested_values: SuggestedValues, } impl InputArgument { @@ -18,7 +22,23 @@ impl InputArgument { mode: Option<i64>, description: &str, default: Option<PhpMixed>, - // TODO(cli-completion): suggested_values closure / list dropped along with completion support + ) -> anyhow::Result<Self> { + Self::new5( + name, + mode, + description, + default, + SuggestedValues::List(Vec::new()), + ) + } + + /// PHP's constructor with the fifth parameter, `$suggestedValues`. + pub fn new5( + name: &str, + mode: Option<i64>, + description: &str, + default: Option<PhpMixed>, + suggested_values: SuggestedValues, ) -> anyhow::Result<Self> { let inner = BaseInputArgument::new( name.to_string(), @@ -26,7 +46,27 @@ impl InputArgument { description.to_string(), default.unwrap_or(PhpMixed::Null), )?; - Ok(Self { inner }) + Ok(Self { + inner, + suggested_values, + }) + } + + /// Adds suggestions to `suggestions` for the current completion input. + /// + /// PHP closures are bound to the command; `this` is the command dispatching the + /// completion (see [`SuggestedValues`]). + pub fn complete( + &self, + this: &dyn crate::command::BaseCommand, + input: &CompletionInput, + suggestions: &mut CompletionSuggestions, + ) -> anyhow::Result<()> { + self.suggested_values.complete(this, input, suggestions) + } + + pub(crate) fn get_name(&self) -> String { + self.inner.get_name().to_string() } /// Unwraps to the underlying Symfony `InputArgument` (used when forwarding a Composer-typed diff --git a/crates/shirabe/src/console/input/input_option.rs b/crates/shirabe/src/console/input/input_option.rs index 938f5a39..7fa3d699 100644 --- a/crates/shirabe/src/console/input/input_option.rs +++ b/crates/shirabe/src/console/input/input_option.rs @@ -1,11 +1,15 @@ //! ref: composer/src/Composer/Console/Input/InputOption.php +use crate::console::input::SuggestedValues; +use shirabe_external_packages::symfony::console::completion::completion_input::CompletionInput; +use shirabe_external_packages::symfony::console::completion::completion_suggestions::CompletionSuggestions; use shirabe_external_packages::symfony::console::input::InputOption as BaseInputOption; use shirabe_php_shim::PhpMixed; #[derive(Debug)] pub struct InputOption { inner: BaseInputOption, + suggested_values: SuggestedValues, } impl InputOption { @@ -21,13 +25,57 @@ impl InputOption { mode: Option<i64>, description: &str, default: Option<PhpMixed>, - // TODO(cli-completion): suggested_values closure / list dropped along with completion support + ) -> anyhow::Result<Self> { + Self::new6( + name, + shortcut, + mode, + description, + default, + SuggestedValues::List(Vec::new()), + ) + } + + /// PHP's constructor with the sixth parameter, `$suggestedValues`. + pub fn new6( + name: &str, + shortcut: Option<PhpMixed>, + mode: Option<i64>, + description: &str, + default: Option<PhpMixed>, + suggested_values: SuggestedValues, ) -> anyhow::Result<Self> { let shortcut = shortcut.unwrap_or(PhpMixed::Null); let default_mixed = default.unwrap_or(PhpMixed::Null); let inner = BaseInputOption::new(name, shortcut, mode, description.to_string(), default_mixed)?; - Ok(Self { inner }) + // PHP throws LogicException here; suggested values on a valueless option cannot happen + // at runtime unless a configure() is wrong, so this is a programming error. + assert!( + suggested_values.is_empty() || inner.accept_value(), + "Cannot set suggested values if the option does not accept a value." + ); + Ok(Self { + inner, + suggested_values, + }) + } + + /// Adds suggestions to `suggestions` for the current completion input. + /// + /// PHP closures are bound to the command; `this` is the command dispatching the + /// completion (see [`SuggestedValues`]). + pub fn complete( + &self, + this: &dyn crate::command::BaseCommand, + input: &CompletionInput, + suggestions: &mut CompletionSuggestions, + ) -> anyhow::Result<()> { + self.suggested_values.complete(this, input, suggestions) + } + + pub(crate) fn get_name(&self) -> String { + self.inner.get_name().to_string() } /// Unwraps to the underlying Symfony `InputOption` (used when forwarding a Composer-typed |
