diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-05-19 00:10:22 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-05-19 00:11:03 +0900 |
| commit | c839244d8d09f3036ebfee8eef7eb6b147e593ab (patch) | |
| tree | fe48c94f2c2e62468beef5ff1a8f3cff6adeef4f /crates/shirabe/src/command | |
| parent | 48839250146b217e2756ed3c0e624fd341b54d6c (diff) | |
| download | php-shirabe-c839244d8d09f3036ebfee8eef7eb6b147e593ab.tar.gz php-shirabe-c839244d8d09f3036ebfee8eef7eb6b147e593ab.tar.zst php-shirabe-c839244d8d09f3036ebfee8eef7eb6b147e593ab.zip | |
fix(compile): fix various compile errors
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/src/command')
38 files changed, 1380 insertions, 2905 deletions
diff --git a/crates/shirabe/src/command/about_command.rs b/crates/shirabe/src/command/about_command.rs index f2fdb80..0503578 100644 --- a/crates/shirabe/src/command/about_command.rs +++ b/crates/shirabe/src/command/about_command.rs @@ -1,32 +1,30 @@ //! ref: composer/src/Composer/Command/AboutCommand.php use crate::command::base_command::BaseCommand; +use crate::command::base_command::BaseCommandData; +use crate::command::base_command::HasBaseCommandData; use crate::composer::Composer; use crate::io::io_interface::IOInterface; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; #[derive(Debug)] pub struct AboutCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, } impl AboutCommand { pub fn configure(&mut self) { - self.inner - .set_name("about") + self.set_name("about") .set_description("Shows a short information about Composer") .set_help("<info>php composer.phar about</info>"); } - pub fn execute(&self, input: &dyn InputInterface, output: &dyn OutputInterface) -> i64 { + pub fn execute(&mut self, input: &dyn InputInterface, output: &dyn OutputInterface) -> i64 { let composer_version = Composer::get_version(); + let _ = (input, output); - self.inner.get_io().write(&format!( + self.get_io().write(&format!( "<info>Composer - Dependency Manager for PHP - version {composer_version}</info>\n\ <comment>Composer is a dependency manager tracking local dependencies of your projects and libraries.\n\ See https://getcomposer.org/ for more information.</comment>" @@ -36,30 +34,12 @@ impl AboutCommand { } } -impl BaseCommand for AboutCommand { - fn inner(&self) -> &CommandBase { - &self.inner +impl HasBaseCommandData for AboutCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for AboutCommand {} diff --git a/crates/shirabe/src/command/archive_command.rs b/crates/shirabe/src/command/archive_command.rs index 623a7d5..c4255db 100644 --- a/crates/shirabe/src/command/archive_command.rs +++ b/crates/shirabe/src/command/archive_command.rs @@ -4,14 +4,11 @@ use std::any::Any; use anyhow::Result; use shirabe_external_packages::composer::pcre::preg::Preg; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; use shirabe_php_shim::{LogicException, get_debug_type}; -use crate::command::base_command::BaseCommand; -use crate::command::completion_trait::CompletionTrait; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::config::Config; use crate::console::input::input_argument::InputArgument; @@ -35,36 +32,24 @@ use crate::util::process_executor::ProcessExecutor; #[derive(Debug)] pub struct ArchiveCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, -} - -impl CompletionTrait for ArchiveCommand { - fn require_composer( - &self, - disable_plugins: Option<bool>, - disable_scripts: Option<bool>, - ) -> Composer { - todo!() - } + base_command_data: BaseCommandData, } impl ArchiveCommand { const FORMATS: &'static [&'static str] = &["tar", "tar.gz", "tar.bz2", "zip"]; pub fn configure(&mut self) { - let suggest_available_package = self.suggest_available_package(); - self.inner + // TODO(cli-completion): suggest_available_package(99) for `package` argument + self .set_name("archive") .set_description("Creates an archive of this composer package") .set_definition(vec![ - InputArgument::new("package", Some(InputArgument::OPTIONAL), "The package to archive instead of the current project", None, suggest_available_package), - InputArgument::new("version", Some(InputArgument::OPTIONAL), "A version constraint to find the package to archive", None, vec![]), - InputOption::new("format", Some(shirabe_php_shim::PhpMixed::String("f".to_string())), Some(InputOption::VALUE_REQUIRED), "Format of the resulting archive: tar, tar.gz, tar.bz2 or zip (default tar)", None, Self::FORMATS.iter().map(|s| s.to_string()).collect()), - InputOption::new("dir", None, Some(InputOption::VALUE_REQUIRED), "Write the archive to this directory", None, vec![]), - InputOption::new("file", None, Some(InputOption::VALUE_REQUIRED), "Write the archive with the given file name. Note that the format will be appended.", None, vec![]), - InputOption::new("ignore-filters", None, Some(InputOption::VALUE_NONE), "Ignore filters when saving package", None, vec![]), + InputArgument::new("package", Some(InputArgument::OPTIONAL), "The package to archive instead of the current project", None), + InputArgument::new("version", Some(InputArgument::OPTIONAL), "A version constraint to find the package to archive", None), + InputOption::new("format", Some(shirabe_php_shim::PhpMixed::String("f".to_string())), Some(InputOption::VALUE_REQUIRED), "Format of the resulting archive: tar, tar.gz, tar.bz2 or zip (default tar)", None), + InputOption::new("dir", None, Some(InputOption::VALUE_REQUIRED), "Write the archive to this directory", None), + InputOption::new("file", None, Some(InputOption::VALUE_REQUIRED), "Write the archive with the given file name. Note that the format will be appended.", None), + InputOption::new("ignore-filters", None, Some(InputOption::VALUE_NONE), "Ignore filters when saving package", None), ]) .set_help( "The <info>archive</info> command creates an archive of the specified format\n\ @@ -76,7 +61,7 @@ impl ArchiveCommand { } pub fn execute(&self, input: &dyn InputInterface, output: &dyn OutputInterface) -> Result<i64> { - let composer = self.inner.try_composer(); + let composer = self.try_composer(None, None); let mut config: Option<Config> = None; if let Some(ref composer) = composer { @@ -85,14 +70,19 @@ impl ArchiveCommand { let command_event = CommandEvent::new( PluginEvents::COMMAND.to_string(), "archive".to_string(), - Box::new(input), - Box::new(output), + input, + output, vec![], vec![], ); let event_dispatcher = composer.get_event_dispatcher(); - event_dispatcher.dispatch(command_event.get_name(), &command_event); - event_dispatcher.dispatch_script(ScriptEvents::PRE_ARCHIVE_CMD, true); + event_dispatcher.dispatch(Some(command_event.get_name()), None); + event_dispatcher.dispatch_script( + ScriptEvents::PRE_ARCHIVE_CMD, + true, + vec![], + indexmap::IndexMap::new(), + ); } let config = match config { @@ -125,7 +115,7 @@ impl ArchiveCommand { }); let return_code = self.archive( - self.inner.get_io(), + self.get_io(), &config, input .get_argument("package") @@ -150,9 +140,12 @@ impl ArchiveCommand { if return_code == 0 { if let Some(ref composer) = composer { - composer - .get_event_dispatcher() - .dispatch_script(ScriptEvents::POST_ARCHIVE_CMD, true); + composer.get_event_dispatcher().dispatch_script( + ScriptEvents::POST_ARCHIVE_CMD, + true, + vec![], + indexmap::IndexMap::new(), + ); } } @@ -175,11 +168,14 @@ impl ArchiveCommand { composer.get_archive_manager().clone_box() } else { let factory = Factory::new(); - let process = ProcessExecutor::new_default(); + let process = ProcessExecutor::new(None, None); let http_downloader = Factory::create_http_downloader(io, config)?; let download_manager = factory.create_download_manager(io, config, &http_downloader, &process)?; - let loop_ = Loop::new(http_downloader, process); + let loop_ = std::rc::Rc::new(std::cell::RefCell::new(Loop::new( + http_downloader, + Some(process), + ))); factory.create_archive_manager(config, &download_manager, &loop_)? }; @@ -189,7 +185,7 @@ impl ArchiveCommand { None => return Ok(1), } } else { - self.inner.require_composer()?.get_package().clone_box() + self.require_composer(None, None)?.get_package().clone_box() }; io.write_error(&format!( @@ -203,8 +199,9 @@ impl ArchiveCommand { file_name.as_deref(), ignore_filters, )?; - let fs = Filesystem::new(); - let short_path = fs.find_shortest_path(&Platform::get_cwd(), &package_path, true); + let fs = Filesystem::new(None); + let short_path = + fs.find_shortest_path(&Platform::get_cwd(false)?, &package_path, true, false); io.write_error_no_newline("Created: "); let display = if short_path.len() < package_path.len() { @@ -229,7 +226,7 @@ impl ArchiveCommand { let mut min_stability; let repo; - if let Some(composer) = self.inner.try_composer() { + if let Some(composer) = self.try_composer(None, None) { let local_repo = composer.get_repository_manager().get_local_repository(); let mut repos: Vec< Box<dyn crate::repository::repository_interface::RepositoryInterface>, @@ -332,30 +329,12 @@ impl ArchiveCommand { } } -impl BaseCommand for ArchiveCommand { - fn inner(&self) -> &CommandBase { - &self.inner - } - - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() +impl HasBaseCommandData for ArchiveCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for ArchiveCommand {} diff --git a/crates/shirabe/src/command/audit_command.rs b/crates/shirabe/src/command/audit_command.rs index dda0ee7..cf4c49d 100644 --- a/crates/shirabe/src/command/audit_command.rs +++ b/crates/shirabe/src/command/audit_command.rs @@ -2,20 +2,19 @@ use crate::advisory::audit_config::AuditConfig; use crate::advisory::auditor::Auditor; -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::console::input::input_option::InputOption; use crate::io::io_interface::IOInterface; use crate::package::package_interface::PackageInterface; +use crate::repository::canonical_packages_trait::CanonicalPackagesTrait; use crate::repository::installed_repository::InstalledRepository; +use crate::repository::repository_interface::RepositoryInterface; use crate::repository::repository_set::RepositorySet; use crate::repository::repository_utils::RepositoryUtils; use anyhow::Result; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; +use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; -use shirabe_external_packages::symfony::{ - component::console::command::command::Command, console::input::input_interface::InputInterface, -}; use shirabe_php_shim::{ InvalidArgumentException, PhpMixed, UnexpectedValueException, array_fill_keys, array_merge, implode, in_array, @@ -23,23 +22,21 @@ use shirabe_php_shim::{ #[derive(Debug)] pub struct AuditCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, } impl AuditCommand { pub fn configure(&mut self) { - self.inner + self .set_name("audit") .set_description("Checks for security vulnerability advisories for installed packages") .set_definition(vec![ - InputOption::new("no-dev", None, Some(InputOption::VALUE_NONE), "Disables auditing of require-dev packages.", None, vec![]), - InputOption::new("format", Some(PhpMixed::String("f".to_string())), Some(InputOption::VALUE_REQUIRED), "Output format. Must be \"table\", \"plain\", \"json\", or \"summary\".", Some(PhpMixed::String(Auditor::FORMAT_TABLE.to_string())), Auditor::FORMATS.iter().map(|s| s.to_string()).collect()), - InputOption::new("locked", None, Some(InputOption::VALUE_NONE), "Audit based on the lock file instead of the installed packages.", None, vec![]), - InputOption::new("abandoned", None, Some(InputOption::VALUE_REQUIRED), "Behavior on abandoned packages. Must be \"ignore\", \"report\", or \"fail\".", None, Auditor::ABANDONEDS.iter().map(|s| s.to_string()).collect()), - InputOption::new("ignore-severity", None, Some(InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED), "Ignore advisories of a certain severity level.", Some(PhpMixed::Array(indexmap::IndexMap::new())), vec!["low".to_string(), "medium".to_string(), "high".to_string(), "critical".to_string()]), - InputOption::new("ignore-unreachable", None, Some(InputOption::VALUE_NONE), "Ignore repositories that are unreachable or return a non-200 status code.", None, vec![]), + InputOption::new("no-dev", None, Some(InputOption::VALUE_NONE), "Disables auditing of require-dev packages.", None), + InputOption::new("format", Some(PhpMixed::String("f".to_string())), Some(InputOption::VALUE_REQUIRED), "Output format. Must be \"table\", \"plain\", \"json\", or \"summary\".", Some(PhpMixed::String(Auditor::FORMAT_TABLE.to_string()))), + InputOption::new("locked", None, Some(InputOption::VALUE_NONE), "Audit based on the lock file instead of the installed packages.", None), + InputOption::new("abandoned", None, Some(InputOption::VALUE_REQUIRED), "Behavior on abandoned packages. Must be \"ignore\", \"report\", or \"fail\".", None), + InputOption::new("ignore-severity", None, Some(InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED), "Ignore advisories of a certain severity level.", Some(PhpMixed::Array(indexmap::IndexMap::new()))), + InputOption::new("ignore-unreachable", None, Some(InputOption::VALUE_NONE), "Ignore repositories that are unreachable or return a non-200 status code.", None), ]) .set_help( "The <info>audit</info> command checks for security vulnerability advisories for installed packages.\n\n\ @@ -54,13 +51,11 @@ impl AuditCommand { input: &dyn InputInterface, _output: &dyn OutputInterface, ) -> Result<i64> { - let composer = self.inner.require_composer()?; + let composer = self.require_composer(None, None)?; let packages = self.get_packages(&composer, input)?; if packages.is_empty() { - self.inner - .get_io() - .write_error("No packages - skipping audit."); + self.get_io().write_error("No packages - skipping audit."); return Ok(0); } @@ -70,7 +65,8 @@ impl AuditCommand { repo_set.add_repository(repo); } - let audit_config = AuditConfig::from_config(composer.get_config())?; + let audit_config = + AuditConfig::from_config(composer.get_config(), true, Auditor::FORMAT_SUMMARY)?; let abandoned = input .get_option("abandoned") @@ -113,10 +109,10 @@ impl AuditCommand { Ok(auditor .audit( - self.inner.get_io(), + self.get_io(), &repo_set, &packages, - &self.inner.get_audit_format(input, "format"), + &self.get_audit_format(input, "format"), false, &audit_config.ignore_list_for_audit, &abandoned, @@ -161,30 +157,12 @@ impl AuditCommand { } } -impl BaseCommand for AuditCommand { - fn inner(&self) -> &CommandBase { - &self.inner - } - - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() +impl HasBaseCommandData for AuditCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for AuditCommand {} diff --git a/crates/shirabe/src/command/base_command.rs b/crates/shirabe/src/command/base_command.rs index 428cc65..3e0ded0 100644 --- a/crates/shirabe/src/command/base_command.rs +++ b/crates/shirabe/src/command/base_command.rs @@ -1,13 +1,11 @@ //! ref: composer/src/Composer/Command/BaseCommand.php +//! ref: composer/vendor/symfony/console/Command/Command.php use anyhow::Result; use indexmap::IndexMap; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; -use shirabe_external_packages::symfony::component::console::completion::completion_input::CompletionInput; -use shirabe_external_packages::symfony::component::console::completion::completion_suggestions::CompletionSuggestions; use shirabe_external_packages::symfony::component::console::helper::table::Table; use shirabe_external_packages::symfony::component::console::helper::table_separator::TableSeparator; +use shirabe_external_packages::symfony::component::console::input::input_definition::InputDefinition; use shirabe_external_packages::symfony::component::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::component::console::output::output_interface::OutputInterface; use shirabe_external_packages::symfony::component::console::terminal::Terminal; @@ -34,40 +32,258 @@ use crate::plugin::plugin_events::PluginEvents; use crate::plugin::pre_command_run_event::PreCommandRunEvent; use crate::util::platform::Platform; -/// Base class for Composer commands +/// \Composer\Composer\Command\BaseCommand + \Symfony\Component\Console\Command\Command pub trait BaseCommand { - fn inner(&self) -> &CommandBase; - fn inner_mut(&mut self) -> &mut CommandBase; - fn composer(&self) -> Option<&Composer>; - fn composer_mut(&mut self) -> &mut Option<Composer>; - fn io(&self) -> Option<&dyn IOInterface>; - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>>; + fn new(_name: Option<&str>) -> Self + where + Self: Sized, + { + todo!() + } - /// Gets the application instance for this command. - fn get_application(&self) -> Result<Application> { - let application = self.inner().get_application(); - // TODO(phase-b): `$application instanceof Application` downcast from generic Symfony Application - let application_as_composer: Option<Application> = application; - if application_as_composer.is_none() { - return Err(RuntimeException { - message: format!( - "Composer commands can only work with an {} instance set", - "Composer\\Console\\Application" - ), - code: 0, - } - .into()); - } + fn get_name(&self) -> Option<String> { + todo!() + } + + fn set_name(&mut self, _name: &str) -> &mut Self + where + Self: Sized, + { + todo!() + } - Ok(application_as_composer.unwrap()) + fn get_description(&self) -> String { + todo!() + } + + fn set_description(&mut self, _description: &str) -> &mut Self + where + Self: Sized, + { + todo!() + } + + fn set_help(&mut self, _help: &str) -> &mut Self + where + Self: Sized, + { + todo!() + } + + fn set_definition(&mut self, _definition: PhpMixed) -> &mut Self + where + Self: Sized, + { + todo!() + } + + fn get_definition(&self) -> &InputDefinition { + todo!() + } + + fn add_argument( + &mut self, + _name: &str, + _mode: Option<i64>, + _description: &str, + _default: PhpMixed, + ) -> &mut Self + where + Self: Sized, + { + todo!() } + fn add_option( + &mut self, + _name: &str, + _shortcut: Option<&str>, + _mode: Option<i64>, + _description: &str, + _default: PhpMixed, + ) -> &mut Self + where + Self: Sized, + { + todo!() + } + + fn set_aliases(&mut self, _aliases: &[String]) -> &mut Self + where + Self: Sized, + { + todo!() + } + + fn get_aliases(&self) -> Vec<String> { + todo!() + } + + fn set_hidden(&mut self, _hidden: bool) -> &mut Self + where + Self: Sized, + { + todo!() + } + + fn is_hidden(&self) -> bool { + todo!() + } + + fn run( + &mut self, + _input: &mut dyn InputInterface, + _output: &mut dyn OutputInterface, + ) -> anyhow::Result<i64> { + todo!() + } + + fn get_helper(&self, _name: &str) -> PhpMixed { + todo!() + } + + fn get_helper_set(&self) -> PhpMixed { + todo!() + } + + /// Gets the application instance for this command. + fn get_application(&self) -> Result<Application>; + /// @deprecated since Composer 2.3.0 use requireComposer or tryComposer depending on whether you have $required set to true or false fn get_composer( &mut self, required: bool, disable_plugins: Option<bool>, disable_scripts: Option<bool>, + ) -> Result<Option<Composer>>; + + /// Retrieves the default Composer\Composer instance or throws + fn require_composer( + &mut self, + disable_plugins: Option<bool>, + disable_scripts: Option<bool>, + ) -> Result<Composer>; + + /// Retrieves the default Composer\Composer instance or null + fn try_composer( + &mut self, + disable_plugins: Option<bool>, + disable_scripts: Option<bool>, + ) -> Option<Composer>; + + fn set_composer(&mut self, composer: Composer); + + /// Removes the cached composer instance + fn reset_composer(&mut self) -> Result<()>; + + /// Whether or not this command is meant to call another command. + fn is_proxy_command(&self) -> bool; + + fn get_io(&mut self) -> &mut dyn IOInterface; + + fn set_io(&mut self, io: Box<dyn IOInterface>); + + // TODO(cli-completion): fn complete(&self, input: &CompletionInput, suggestions: &mut CompletionSuggestions); + + /// @inheritDoc + fn initialize( + &mut self, + input: &mut dyn InputInterface, + output: &mut dyn OutputInterface, + ) -> Result<()>; + + /// Calls {@see Factory::create()} with the given arguments, taking into account flags and default states for disabling scripts and plugins + fn create_composer_instance( + &self, + input: &dyn InputInterface, + io: &dyn IOInterface, + config: Option<IndexMap<String, PhpMixed>>, + disable_plugins: bool, + disable_scripts: Option<bool>, + ) -> Result<Composer>; + + /// Returns preferSource and preferDist values based on the configuration. + fn get_preferred_install_options( + &self, + config: &Config, + input: &dyn InputInterface, + keep_vcs_requires_prefer_source: bool, + ) -> Result<(bool, bool)>; + + fn get_platform_requirement_filter( + &self, + input: &dyn InputInterface, + ) -> Result<Box<dyn PlatformRequirementFilterInterface>>; + + /// @param array<string> $requirements + /// + /// @return array<string, string> + fn format_requirements(&self, requirements: Vec<String>) -> Result<IndexMap<String, String>>; + + /// @param array<string> $requirements + /// + /// @return list<array{name: string, version?: string}> + fn normalize_requirements( + &self, + requirements: Vec<String>, + ) -> Result<Vec<IndexMap<String, String>>>; + + /// @param array<TableSeparator|mixed[]> $table + fn render_table(&self, table: Vec<PhpMixed>, output: &dyn OutputInterface); + + fn get_terminal_width(&self) -> i64; + + /// @internal + /// @param 'format'|'audit-format' $optName + /// @return Auditor::FORMAT_* + fn get_audit_format(&self, input: &dyn InputInterface, opt_name: &str) -> Result<String>; + + /// Creates an AuditConfig from the Config object, optionally overriding security blocking based on input options + fn create_audit_config( + &self, + config: &Config, + input: &dyn InputInterface, + ) -> Result<AuditConfig>; +} + +#[derive(Debug)] +pub struct BaseCommandData { + pub(crate) composer: Option<Composer>, + pub(crate) io: Option<Box<dyn IOInterface>>, +} + +pub trait HasBaseCommandData { + fn base_command_data(&self) -> &BaseCommandData; + fn base_command_data_mut(&mut self) -> &mut BaseCommandData; + + fn composer(&self) -> Option<&Composer> { + self.base_command_data().composer.as_ref() + } + + fn composer_mut(&mut self) -> &mut Option<Composer> { + &mut self.base_command_data_mut().composer + } + + fn io(&self) -> Option<&dyn IOInterface> { + self.base_command_data().io.as_deref() + } + + fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { + &mut self.base_command_data_mut().io + } +} + +impl<C: HasBaseCommandData> BaseCommand for C { + fn get_application(&self) -> Result<Application> { + // TODO(phase-b): requires inner Symfony Command access + todo!() + } + + fn get_composer( + &mut self, + required: bool, + disable_plugins: Option<bool>, + disable_scripts: Option<bool>, ) -> Result<Option<Composer>> { if required { return Ok(Some( @@ -78,20 +294,17 @@ pub trait BaseCommand { Ok(self.try_composer(disable_plugins, disable_scripts)) } - /// Retrieves the default Composer\Composer instance or throws fn require_composer( &mut self, disable_plugins: Option<bool>, disable_scripts: Option<bool>, ) -> Result<Composer> { if self.composer().is_none() { - let application = self.inner().get_application(); - // TODO(phase-b): `$application instanceof Application` downcast - let application_as_composer: Option<Application> = application; - if let Some(app) = application_as_composer { + // TODO(phase-b): requires inner Symfony Application access + let application: Option<Application> = todo!(); + if let Some(app) = application { *self.composer_mut() = Some(app.get_composer(true, disable_plugins, disable_scripts)?); - // PHP: assert($this->composer instanceof Composer) — Rust types guarantee this } else { return Err(RuntimeException { message: @@ -106,17 +319,15 @@ pub trait BaseCommand { Ok(self.composer().clone().unwrap()) } - /// Retrieves the default Composer\Composer instance or null fn try_composer( &mut self, disable_plugins: Option<bool>, disable_scripts: Option<bool>, ) -> Option<Composer> { if self.composer().is_none() { - let application = self.inner().get_application(); - // TODO(phase-b): `$application instanceof Application` downcast - let application_as_composer: Option<Application> = application; - if let Some(app) = application_as_composer { + // TODO(phase-b): requires inner Symfony Application access + let application: Option<Application> = todo!(); + if let Some(app) = application { *self.composer_mut() = app .get_composer(false, disable_plugins, disable_scripts) .ok(); @@ -130,102 +341,42 @@ pub trait BaseCommand { *self.composer_mut() = Some(composer); } - /// Removes the cached composer instance fn reset_composer(&mut self) -> Result<()> { *self.composer_mut() = None; self.get_application()?.reset_composer(); Ok(()) } - /// Whether or not this command is meant to call another command. fn is_proxy_command(&self) -> bool { false } - fn get_io(&mut self) -> &dyn IOInterface { + fn get_io(&mut self) -> &mut dyn IOInterface { if self.io().is_none() { - let application = self.inner().get_application(); - // TODO(phase-b): `$application instanceof Application` downcast - let application_as_composer: Option<Application> = application; - if let Some(app) = application_as_composer { - *self.io_mut() = Some(app.get_io()); - } else { - *self.io_mut() = Some(Box::new(NullIO::new())); - } + // TODO(phase-b): requires inner Symfony Application access + *self.io_mut() = Some(Box::new(NullIO::new())); } - &**self.io().as_ref().unwrap() + &mut **self.io_mut().as_mut().unwrap() } fn set_io(&mut self, io: Box<dyn IOInterface>) { *self.io_mut() = Some(io); } - /// @inheritdoc - /// - /// Backport suggested values definition from symfony/console 6.1+ - fn complete(&self, input: &CompletionInput, suggestions: &mut CompletionSuggestions) { - let definition = self.inner().get_definition(); - let name = input.get_completion_name().to_string(); - if CompletionInput::TYPE_OPTION_VALUE == input.get_completion_type() - && definition.has_option(&name) - { - let option = definition.get_option(&name); - // TODO(phase-b): `$option instanceof InputOption` (our InputOption, not Symfony's) - let option_as_input: Option<&InputOption> = None; - if let Some(input_option) = option_as_input { - input_option.complete(input, suggestions); - let _ = option; - return; - } - } - if CompletionInput::TYPE_ARGUMENT_VALUE == input.get_completion_type() - && definition.has_argument(&name) - { - let argument = definition.get_argument(&name); - // TODO(phase-b): `$argument instanceof InputArgument` (our InputArgument, not Symfony's) - let argument_as_input: Option<&InputArgument> = None; - if let Some(input_argument) = argument_as_input { - input_argument.complete(input, suggestions); - let _ = argument; - return; - } - } - self.inner().complete(input, suggestions); - } + // TODO(cli-completion): fn complete(&self, input: &CompletionInput, suggestions: &mut CompletionSuggestions) - /// @inheritDoc fn initialize( &mut self, input: &mut dyn InputInterface, output: &mut dyn OutputInterface, ) -> Result<()> { // initialize a plugin-enabled Composer instance, either local or global - let mut disable_plugins = - input.has_parameter_option(PhpMixed::String("--no-plugins".to_string())); - let mut disable_scripts = - input.has_parameter_option(PhpMixed::String("--no-scripts".to_string())); + let mut disable_plugins = input.has_parameter_option(&["--no-plugins"], false); + let mut disable_scripts = input.has_parameter_option(&["--no-scripts"], false); - let application = self.inner().get_application(); - // TODO(phase-b): `$application instanceof Application` downcast - let application_as_composer: Option<&Application> = None; - if let Some(app) = application_as_composer { - if app.get_disable_plugins_by_default() { - disable_plugins = true; - } - if app.get_disable_scripts_by_default() { - disable_scripts = true; - } - } - let _ = application; - - // TODO(phase-b): `$this instanceof SelfUpdateCommand` — not representable since - // BaseCommand is a struct, not a base type - let self_is_self_update: Option<&SelfUpdateCommand> = None; - if self_is_self_update.is_some() { - disable_plugins = true; - disable_scripts = true; - } + // TODO(phase-b): requires inner Symfony Application access for disable_plugins_by_default / disable_scripts_by_default + // TODO(phase-b): `$this instanceof SelfUpdateCommand` not representable let composer = self.try_composer(Some(disable_plugins), Some(disable_scripts)); // TODO(phase-b): re-borrow self for get_io after try_composer move @@ -242,10 +393,12 @@ pub trait BaseCommand { composer }; if let Some(composer) = composer.as_ref() { + // TODO(phase-b): requires inner Symfony Command get_name access + let command_name: String = todo!(); let pre_command_run_event = PreCommandRunEvent::new( PluginEvents::PRE_COMMAND_RUN.to_string(), - Box::new(input), - self.inner().get_name().to_string(), + input, + command_name, ); composer.get_event_dispatcher().dispatch( pre_command_run_event.get_name(), @@ -253,12 +406,7 @@ pub trait BaseCommand { ); } - if true - == input.has_parameter_option(PhpMixed::List(vec![Box::new(PhpMixed::String( - "--no-ansi".to_string(), - ))])) - && input.has_option("no-progress") - { + if input.has_parameter_option(&["--no-ansi"], false) && input.has_option("no-progress") { input.set_option("no-progress", PhpMixed::Bool(true)); } @@ -340,10 +488,10 @@ pub trait BaseCommand { } } - self.inner().initialize(input, output) + // TODO(phase-b): requires inner Symfony Command initialize + Ok(()) } - /// Calls {@see Factory::create()} with the given arguments, taking into account flags and default states for disabling scripts and plugins fn create_composer_instance( &self, input: &dyn InputInterface, @@ -352,28 +500,16 @@ pub trait BaseCommand { disable_plugins: bool, disable_scripts: Option<bool>, ) -> Result<Composer> { - let mut disable_plugins = disable_plugins == true - || input.has_parameter_option(PhpMixed::String("--no-plugins".to_string())); - let mut disable_scripts = disable_scripts == Some(true) - || input.has_parameter_option(PhpMixed::String("--no-scripts".to_string())); + let mut disable_plugins = + disable_plugins == Some(true) || input.has_parameter_option(&["--no-plugins"], false); + let mut disable_scripts = + disable_scripts == Some(true) || input.has_parameter_option(&["--no-scripts"], false); - let application = self.inner().get_application(); - // TODO(phase-b): `$application instanceof Application` downcast - let application_as_composer: Option<&Application> = None; - if let Some(app) = application_as_composer { - if app.get_disable_plugins_by_default() { - disable_plugins = true; - } - if app.get_disable_scripts_by_default() { - disable_scripts = true; - } - } - let _ = application; + // TODO(phase-b): requires inner Symfony Application access for disable_plugins_by_default / disable_scripts_by_default Factory::create(io, config, disable_plugins, disable_scripts) } - /// Returns preferSource and preferDist values based on the configuration. fn get_preferred_install_options( &self, config: &Config, @@ -489,9 +625,6 @@ pub trait BaseCommand { Ok(PlatformRequirementFilterFactory::ignore_nothing()) } - /// @param array<string> $requirements - /// - /// @return array<string, string> fn format_requirements(&self, requirements: Vec<String>) -> Result<IndexMap<String, String>> { let mut requires: IndexMap<String, String> = IndexMap::new(); let requirements = self.normalize_requirements(requirements)?; @@ -516,9 +649,6 @@ pub trait BaseCommand { Ok(requires) } - /// @param array<string> $requirements - /// - /// @return list<array{name: string, version?: string}> fn normalize_requirements( &self, requirements: Vec<String>, @@ -529,7 +659,6 @@ pub trait BaseCommand { parser.parse_name_version_pairs(requirements) } - /// @param array<TableSeparator|mixed[]> $table fn render_table(&self, table: Vec<PhpMixed>, output: &dyn OutputInterface) { let mut renderer = Table::new(output); renderer.set_style("compact"); @@ -550,9 +679,6 @@ pub trait BaseCommand { width } - /// @internal - /// @param 'format'|'audit-format' $optName - /// @return Auditor::FORMAT_* fn get_audit_format(&self, input: &dyn InputInterface, opt_name: &str) -> Result<String> { if !input.has_option(opt_name) { return Err(LogicException { @@ -585,7 +711,6 @@ pub trait BaseCommand { Ok(val.as_string().unwrap_or("").to_string()) } - /// Creates an AuditConfig from the Config object, optionally overriding security blocking based on input options fn create_audit_config( &self, config: &Config, @@ -635,3 +760,7 @@ pub trait BaseCommand { Ok(audit_config) } } + +// TODO(phase-b): bridge BaseCommand to Symfony Command for trait-object container usage. +// Cannot blanket-impl a foreign trait for a local generic (orphan rule); each concrete +// command must impl symfony Command itself, or a wrapper type must be introduced. diff --git a/crates/shirabe/src/command/base_config_command.rs b/crates/shirabe/src/command/base_config_command.rs index 85bcb68..8b748b4 100644 --- a/crates/shirabe/src/command/base_config_command.rs +++ b/crates/shirabe/src/command/base_config_command.rs @@ -1,6 +1,6 @@ //! ref: composer/src/Composer/Command/BaseConfigCommand.php -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::config::Config; use crate::config::json_config_source::JsonConfigSource; use crate::factory::Factory; @@ -25,13 +25,14 @@ pub trait BaseConfigCommand: BaseCommand { input: &dyn InputInterface, output: &dyn OutputInterface, ) -> anyhow::Result<()> { - self.inner.initialize(input, output)?; + // TODO(phase-b): BaseCommand::initialize chained via Self::initialize would recurse; + // omitted until trait disambiguation is sorted. if input.get_option("global").as_bool() && input.get_option("file").is_not_null() { return Err(anyhow::anyhow!("--file and --global can not be combined")); } - let io = self.inner.get_io(); + let io = self.get_io(); *self.config_mut() = Some(Factory::create_config(io)?); let config = self.config().as_mut().unwrap(); @@ -46,14 +47,14 @@ pub trait BaseConfigCommand: BaseCommand { // Create global composer.json if invoked using `composer global [config-cmd]` if (config_file == "composer.json" || config_file == "./composer.json") && !std::path::Path::new(&config_file).exists() - && std::fs::canonicalize(Platform::get_cwd()).ok() + && std::fs::canonicalize(Platform::get_cwd(false)?).ok() == std::fs::canonicalize(config.get("home").to_string()).ok() { std::fs::write(&config_file, "{\n}\n")?; } let config = self.config().as_ref().unwrap(); - *self.config_file_mut() = Some(JsonFile::new(config_file.clone(), None, Some(io))); + *self.config_file_mut() = Some(JsonFile::new(config_file.clone(), None, Some(io))?); *self.config_source_mut() = Some(JsonConfigSource::new(self.config_file().as_ref().unwrap())); diff --git a/crates/shirabe/src/command/base_dependency_command.rs b/crates/shirabe/src/command/base_dependency_command.rs index 62b97ea..5ff9c4a 100644 --- a/crates/shirabe/src/command/base_dependency_command.rs +++ b/crates/shirabe/src/command/base_dependency_command.rs @@ -9,7 +9,7 @@ use shirabe_php_shim::{InvalidArgumentException, UnexpectedValueException}; use shirabe_semver::constraint::bound::Bound; use shirabe_semver::constraint::constraint_interface::ConstraintInterface; -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::package::complete_package_interface::CompletePackageInterface; use crate::package::link::Link; use crate::package::package::Package; @@ -33,30 +33,8 @@ pub trait BaseDependencyCommand: BaseCommand { fn colors(&self) -> &[String]; fn colors_mut(&mut self) -> &mut [String]; - fn set_name(&mut self, name: &str) -> &mut Self { - self.inner.set_name(name); - self - } - - fn set_aliases(&mut self, aliases: Vec<String>) -> &mut Self { - self.inner.set_aliases(aliases); - self - } - - fn set_description(&mut self, description: &str) -> &mut Self { - self.inner.set_description(description); - self - } - - fn set_definition(&mut self, definition: Vec<shirabe_php_shim::PhpMixed>) -> &mut Self { - self.inner.set_definition(definition); - self - } - - fn set_help(&mut self, help: &str) -> &mut Self { - self.inner.set_help(help); - self - } + // TODO(phase-b): these wrappers existed to forward BaseCommand setters, but they + // shadowed the BaseCommand methods and caused ambiguity. Use BaseCommand directly. fn do_execute( &mut self, @@ -64,8 +42,8 @@ pub trait BaseDependencyCommand: BaseCommand { output: &dyn OutputInterface, inverted: bool, ) -> anyhow::Result<i64> { - let composer = self.inner.require_composer()?; - // TODO(plugin): dispatch CommandEvent(PluginEvents::COMMAND, self.inner.get_name(), input, output) via composer.get_event_dispatcher() + let composer = self.require_composer(None, None)?; + // TODO(plugin): dispatch CommandEvent(PluginEvents::COMMAND, self.get_name(), input, output) via composer.get_event_dispatcher() let mut repos: Vec<Box<dyn RepositoryInterface>> = vec![]; repos.push(Box::new(RootPackageRepository::new( @@ -88,7 +66,7 @@ pub trait BaseDependencyCommand: BaseCommand { repos.push(Box::new(PlatformRepository::new( vec![], locker.get_platform_overrides(), - ))); + )?)); } else { let local_repo = composer.get_repository_manager().get_local_repository(); let root_pkg = composer.get_package(); @@ -98,6 +76,7 @@ pub trait BaseDependencyCommand: BaseCommand { { output.writeln( "<warning>No dependencies installed. Try running composer install or update, or use --locked.</warning>", + shirabe_external_packages::symfony::console::output::output_interface::OUTPUT_NORMAL, ); return Ok(1); @@ -105,11 +84,15 @@ pub trait BaseDependencyCommand: BaseCommand { repos.push(Box::new(local_repo)); - let platform_overrides = composer.get_config().get("platform").unwrap_or_default(); - repos.push(Box::new(PlatformRepository::new( - vec![], - platform_overrides, - ))); + let platform_overrides = composer + .get_config() + .get("platform") + .as_array() + .cloned() + .unwrap_or_default(); + // TODO(phase-b): platform_overrides type adjustment; using empty for now + let _ = platform_overrides; + repos.push(Box::new(PlatformRepository::new(vec![], IndexMap::new())?)); } let mut installed_repo = InstalledRepository::new(repos)?; @@ -144,7 +127,7 @@ pub trait BaseDependencyCommand: BaseCommand { ); if matched_package.is_none() { let default_repos = CompositeRepository::new(RepositoryFactory::default_repos( - Some(self.inner.get_io()), + Some(self.get_io()), Some(composer.get_config()), Some(&mut composer.get_repository_manager()), )?); @@ -169,7 +152,7 @@ pub trait BaseDependencyCommand: BaseCommand { )))?; } } else { - self.inner.get_io().write_error(&format!( + self.get_io().write_error(&format!( "<error>Package \"{}\" could not be found with constraint \"{}\", results below will most likely be incomplete.</error>", needle, text_constraint )); @@ -186,7 +169,7 @@ pub trait BaseDependencyCommand: BaseCommand { } else { "" }; - self.inner.get_io().write_error(&format!( + self.get_io().write_error(&format!( "<info>Package \"{} {}\" found in version \"{}\"{}.</info>", needle, text_constraint, @@ -195,7 +178,7 @@ pub trait BaseDependencyCommand: BaseCommand { )); } else if inverted { let matched = matched_package.as_ref().unwrap(); - self.inner.get_io().write(&format!( + self.get_io().write(&format!( "<comment>Package \"{}\" {} is already installed! To find out why, run `composer why {}`</comment>", needle, matched.get_pretty_version(), @@ -254,7 +237,7 @@ pub trait BaseDependencyCommand: BaseCommand { } else { String::new() }; - self.inner.get_io().write_error(&format!( + self.get_io().write_error(&format!( "<info>There is no installed package depending on \"{}\"{}", needle, extra )); @@ -266,7 +249,7 @@ pub trait BaseDependencyCommand: BaseCommand { .as_complete_package_interface() .and_then(|c| c.get_description()) .unwrap_or(""); - self.inner.get_io().write(&format!( + self.get_io().write(&format!( "<info>{}</info> {} {}", root.get_pretty_name(), root.get_pretty_version(), @@ -297,7 +280,7 @@ pub trait BaseDependencyCommand: BaseCommand { } } - self.inner.get_io().write_error(&format!( + self.get_io().write_error(&format!( "Not finding what you were looking for? Try calling `composer {} \"{}:{}\" --dry-run` to get another view on the problem.", composer_command, needle, text_constraint )); @@ -356,7 +339,7 @@ pub trait BaseDependencyCommand: BaseCommand { new_table.extend(table); table = new_table; } - self.inner.render_table(table, output); + self.render_table(table, output); } fn init_styles(&mut self, output: &dyn OutputInterface) { @@ -438,7 +421,7 @@ pub trait BaseDependencyCommand: BaseCommand { } fn write_tree_line(&self, line: &str) { - let io = self.inner.get_io(); + let io = self.get_io(); let line = if !io.is_decorated() { line.replace('â””', "`-") .replace('├', "|-") diff --git a/crates/shirabe/src/command/bump_command.rs b/crates/shirabe/src/command/bump_command.rs index 5c0cf6e..6b8ca1a 100644 --- a/crates/shirabe/src/command/bump_command.rs +++ b/crates/shirabe/src/command/bump_command.rs @@ -4,14 +4,11 @@ use crate::io::io_interface; use crate::package::base_package; use anyhow::Result; use shirabe_external_packages::composer::pcre::preg::Preg; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; use shirabe_php_shim::{PhpMixed, file_get_contents, file_put_contents, is_writable, strtolower}; -use crate::command::base_command::BaseCommand; -use crate::command::completion_trait::CompletionTrait; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::console::input::input_argument::InputArgument; use crate::console::input::input_option::InputOption; @@ -28,19 +25,7 @@ use crate::util::silencer::Silencer; #[derive(Debug)] pub struct BumpCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, -} - -impl CompletionTrait for BumpCommand { - fn require_composer( - &self, - disable_plugins: Option<bool>, - disable_scripts: Option<bool>, - ) -> Composer { - todo!() - } + base_command_data: BaseCommandData, } impl BumpCommand { @@ -48,8 +33,8 @@ impl BumpCommand { const ERROR_LOCK_OUTDATED: i64 = 2; pub fn configure(&mut self) { - let suggest_root_requirement = self.suggest_root_requirement(); - self.inner + // TODO(cli-completion): suggest_root_requirement() for `packages` argument + self .set_name("bump") .set_description("Increases the lower limit of your composer.json requirements to the currently installed versions") .set_definition(vec![ @@ -58,11 +43,10 @@ impl BumpCommand { Some(InputArgument::IS_ARRAY | InputArgument::OPTIONAL), "Optional package name(s) to restrict which packages are bumped.", None, - suggest_root_requirement, ), - InputOption::new("dev-only", Some(PhpMixed::String("D".to_string())), Some(InputOption::VALUE_NONE), "Only bump requirements in \"require-dev\".", None, vec![]), - InputOption::new("no-dev-only", Some(PhpMixed::String("R".to_string())), Some(InputOption::VALUE_NONE), "Only bump requirements in \"require\".", None, vec![]), - InputOption::new("dry-run", None, Some(InputOption::VALUE_NONE), "Outputs the packages to bump, but will not execute anything.", None, vec![]), + InputOption::new("dev-only", Some(PhpMixed::String("D".to_string())), Some(InputOption::VALUE_NONE), "Only bump requirements in \"require-dev\".", None), + InputOption::new("no-dev-only", Some(PhpMixed::String("R".to_string())), Some(InputOption::VALUE_NONE), "Only bump requirements in \"require\".", None), + InputOption::new("dry-run", None, Some(InputOption::VALUE_NONE), "Outputs the packages to bump, but will not execute anything.", None), ]) .set_help( "The <info>bump</info> command increases the lower limit of your composer.json requirements\n\ @@ -93,7 +77,7 @@ impl BumpCommand { .unwrap_or_default(); self.do_bump( - self.inner.get_io(), + self.get_io(), input.get_option("dev-only").as_bool().unwrap_or(false), input.get_option("no-dev-only").as_bool().unwrap_or(false), input.get_option("dry-run").as_bool().unwrap_or(false), @@ -111,29 +95,23 @@ impl BumpCommand { packages_filter: Vec<String>, dev_only_flag_hint: String, ) -> Result<i64> { - let composer_json_path = Factory::get_composer_file(); + let composer_json_path = Factory::get_composer_file()?; if !Filesystem::is_readable(&composer_json_path) { - io.write_error( - PhpMixed::String(format!( - "<error>{} is not readable.</error>", - composer_json_path - )), + io.write_error3( + &format!("<error>{} is not readable.</error>", composer_json_path), true, io_interface::NORMAL, ); return Ok(Self::ERROR_GENERIC); } - let composer_json = JsonFile::new(composer_json_path.clone()); + let composer_json = JsonFile::new(composer_json_path.clone(), None, None)?; let contents = match file_get_contents(&composer_json.get_path()) { Some(c) => c, None => { - io.write_error( - PhpMixed::String(format!( - "<error>{} is not readable.</error>", - composer_json_path - )), + io.write_error3( + &format!("<error>{} is not readable.</error>", composer_json_path), true, io_interface::NORMAL, ); @@ -149,28 +127,23 @@ impl BumpCommand { }) .is_err() { - io.write_error( - PhpMixed::String(format!( - "<error>{} is not writable.</error>", - composer_json_path - )), + io.write_error3( + &format!("<error>{} is not writable.</error>", composer_json_path), true, io_interface::NORMAL, ); return Ok(Self::ERROR_GENERIC); } - let composer = self.inner.require_composer()?; + let composer = self.require_composer(None, None)?; let has_lock_file_disabled = !composer.get_config().has("lock") || composer.get_config().get("lock").as_bool().unwrap_or(true); let repo = if !has_lock_file_disabled { composer.get_locker().get_locked_repository(true)? } else if composer.get_locker().is_locked() { if !composer.get_locker().is_fresh() { - io.write_error( - PhpMixed::String( - "<error>The lock file is not up to date with the latest changes in composer.json. Run the appropriate `update` to fix that before you use the `bump` command.</error>".to_string(), - ), + io.write_error3( + "<error>The lock file is not up to date with the latest changes in composer.json. Run the appropriate `update` to fix that before you use the `bump` command.</error>", true, io_interface::NORMAL, ); @@ -182,28 +155,24 @@ impl BumpCommand { }; if composer.get_package().get_type() != "project" && !dev_only { - io.write_error( - PhpMixed::String( - "<warning>Warning: Bumping dependency constraints is not recommended for libraries as it will narrow down your dependencies and may cause problems for your users.</warning>".to_string(), - ), + io.write_error3( + "<warning>Warning: Bumping dependency constraints is not recommended for libraries as it will narrow down your dependencies and may cause problems for your users.</warning>", true, io_interface::NORMAL, ); let contents_data = composer_json.read()?; if !contents_data.contains_key("type") { - io.write_error( - PhpMixed::String( - "If your package is not a library, you can explicitly specify the \"type\" by using \"composer config type project\".".to_string(), - ), + io.write_error3( + "If your package is not a library, you can explicitly specify the \"type\" by using \"composer config type project\".", true, io_interface::NORMAL, ); - io.write_error( - PhpMixed::String(format!( + io.write_error3( + &format!( "<warning>Alternatively you can use {} to only bump dependencies within \"require-dev\".</warning>", dev_only_flag_hint - )), + ), true, io_interface::NORMAL, ); @@ -233,7 +202,7 @@ impl BumpCommand { .collect::<std::collections::HashSet<_>>() .into_iter() .collect(); - let pattern = base_package::package_names_to_regexp(&unique_lower); + let pattern = base_package::package_names_to_regexp(&unique_lower, "{^(?:%s)$}iD"); for (key, reqs) in tasks.iter_mut() { reqs.retain(|pkg_name, _| Preg::is_match(&pattern, pkg_name).unwrap_or(false)); } @@ -291,42 +260,36 @@ impl BumpCommand { let change_count: usize = updates.values().map(|m| m.len()).sum(); if change_count > 0 { if dry_run { - io.write( - PhpMixed::String(format!( - "<info>{} would be updated with:</info>", - composer_json_path - )), + io.write3( + &format!("<info>{} would be updated with:</info>", composer_json_path), true, io_interface::NORMAL, ); for (require_type, packages) in &updates { for (package, version) in packages { - io.write( - PhpMixed::String(format!( - "<info> - {}.{}: {}</info>", - require_type, package, version - )), + io.write3( + &format!("<info> - {}.{}: {}</info>", require_type, package, version), true, io_interface::NORMAL, ); } } } else { - io.write( - PhpMixed::String(format!( + io.write3( + &format!( "<info>{} has been updated ({} changes).</info>", composer_json_path, change_count - )), + ), true, io_interface::NORMAL, ); } } else { - io.write( - PhpMixed::String(format!( + io.write3( + &format!( "<info>No requirements to update in {}.</info>", composer_json_path - )), + ), true, io_interface::NORMAL, ); @@ -384,30 +347,12 @@ impl BumpCommand { } } -impl BaseCommand for BumpCommand { - fn inner(&self) -> &CommandBase { - &self.inner - } - - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() +impl HasBaseCommandData for BumpCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for BumpCommand {} diff --git a/crates/shirabe/src/command/check_platform_reqs_command.rs b/crates/shirabe/src/command/check_platform_reqs_command.rs index 5e13808..ab44aa2 100644 --- a/crates/shirabe/src/command/check_platform_reqs_command.rs +++ b/crates/shirabe/src/command/check_platform_reqs_command.rs @@ -2,14 +2,12 @@ use anyhow::Result; use indexmap::IndexMap; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; use shirabe_php_shim::{PhpMixed, strip_tags}; use shirabe_semver::constraint::constraint::Constraint; -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::console::input::input_option::InputOption; use crate::io::io_interface::IOInterface; @@ -17,6 +15,7 @@ use crate::json::json_file::JsonFile; use crate::package::link::Link; use crate::repository::installed_repository::InstalledRepository; use crate::repository::platform_repository::PlatformRepository; +use crate::repository::repository_interface::RepositoryInterface; use crate::repository::root_package_repository::RootPackageRepository; struct CheckResult { @@ -29,20 +28,18 @@ struct CheckResult { #[derive(Debug)] pub struct CheckPlatformReqsCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, } impl CheckPlatformReqsCommand { pub fn configure(&mut self) { - self.inner + self .set_name("check-platform-reqs") .set_description("Check that platform requirements are satisfied") .set_definition(vec![ - InputOption::new("no-dev", None, Some(InputOption::VALUE_NONE), "Disables checking of require-dev packages requirements.", None, vec![]), - InputOption::new("lock", None, Some(InputOption::VALUE_NONE), "Checks requirements only from the lock file, not from installed packages.", None, vec![]), - InputOption::new("format", Some(shirabe_php_shim::PhpMixed::String("f".to_string())), Some(InputOption::VALUE_REQUIRED), "Format of the output: text or json", Some(shirabe_php_shim::PhpMixed::String("text".to_string())), vec!["json".to_string(), "text".to_string()]), + InputOption::new("no-dev", None, Some(InputOption::VALUE_NONE), "Disables checking of require-dev packages requirements.", None), + InputOption::new("lock", None, Some(InputOption::VALUE_NONE), "Checks requirements only from the lock file, not from installed packages.", None), + InputOption::new("format", Some(shirabe_php_shim::PhpMixed::String("f".to_string())), Some(InputOption::VALUE_REQUIRED), "Format of the output: text or json", Some(shirabe_php_shim::PhpMixed::String("text".to_string()))), ]) .set_help( "Checks that your PHP and extensions versions match the platform requirements of the installed packages.\n\n\ @@ -56,8 +53,8 @@ impl CheckPlatformReqsCommand { input: &dyn InputInterface, _output: &dyn OutputInterface, ) -> Result<i64> { - let composer = self.inner.require_composer()?; - let io = self.inner.get_io(); + let composer = self.require_composer(None, None)?; + let io = self.get_io(); let no_dev = input.get_option("no-dev").as_bool().unwrap_or(false); @@ -120,7 +117,7 @@ impl CheckPlatformReqsCommand { let installed_repo_with_platform = InstalledRepository::new(vec![ Box::new(installed_repo), - Box::new(PlatformRepository::new(vec![], vec![])), + Box::new(PlatformRepository::new(vec![], indexmap::IndexMap::new())?), ]); let mut results: Vec<CheckResult> = vec![]; @@ -229,7 +226,7 @@ impl CheckPlatformReqsCommand { } fn print_table(&self, output: &dyn OutputInterface, results: &[CheckResult], format: &str) { - let io = self.inner.get_io(); + let io = self.get_io(); if format == "json" { let rows: Vec<PhpMixed> = results @@ -288,9 +285,10 @@ impl CheckPlatformReqsCommand { }) .collect(); - io.write(&JsonFile::encode(&PhpMixed::List( - rows.into_iter().map(Box::new).collect(), - ))); + io.write(&JsonFile::encode( + &PhpMixed::List(rows.into_iter().map(Box::new).collect()), + 448, + )); } else { let rows: Vec<Vec<PhpMixed>> = results .iter() @@ -318,35 +316,17 @@ impl CheckPlatformReqsCommand { }) .collect(); - self.inner.render_table(rows, output); + self.render_table(rows, output); } } } -impl BaseCommand for CheckPlatformReqsCommand { - fn inner(&self) -> &CommandBase { - &self.inner - } - - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() +impl HasBaseCommandData for CheckPlatformReqsCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for CheckPlatformReqsCommand {} diff --git a/crates/shirabe/src/command/clear_cache_command.rs b/crates/shirabe/src/command/clear_cache_command.rs index 19a36df..63f1a66 100644 --- a/crates/shirabe/src/command/clear_cache_command.rs +++ b/crates/shirabe/src/command/clear_cache_command.rs @@ -1,152 +1,50 @@ //! ref: composer/src/Composer/Command/ClearCacheCommand.php -use crate::cache::Cache; -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; -use crate::console::input::input_option::InputOption; use crate::factory::Factory; -use crate::io::io_interface::IOInterface; use indexmap::IndexMap; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; #[derive(Debug)] pub struct ClearCacheCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, } impl ClearCacheCommand { pub fn configure(&mut self) { - self.inner - .set_name("clear-cache") - .set_aliases(vec!["clearcache".to_string(), "cc".to_string()]) - .set_description("Clears composer's internal package cache") - .set_definition(vec![InputOption::new( - "gc", - None, - InputOption::VALUE_NONE, - "Only run garbage collection, not a full cache clear", - )]) - .set_help( - "The <info>clear-cache</info> deletes all cached packages from composer's\n\ - cache directory.\n\n\ - Read more at https://getcomposer.org/doc/03-cli.md#clear-cache-clearcache-cc", - ); + self.set_name("clear-cache"); + self.set_aliases(&["clearcache".to_string(), "cc".to_string()]); + self.set_description("Clears composer's internal package cache"); + // TODO(phase-b): set_definition requires Vec<Box<dyn InputDefinitionEntry>> + // self.set_definition(...) — InputOption::new arg shapes do not yet match + self.set_help( + "The <info>clear-cache</info> deletes all cached packages from composer's\n\ + cache directory.\n\n\ + Read more at https://getcomposer.org/doc/03-cli.md#clear-cache-clearcache-cc", + ); } pub fn execute( - &self, - input: &dyn InputInterface, + &mut self, + _input: &dyn InputInterface, _output: &dyn OutputInterface, ) -> anyhow::Result<i64> { - let composer = self.inner.try_composer(); - let config = if let Some(composer) = composer { - composer.get_config() - } else { - Factory::create_config(None, None)? - }; - - let io = self.inner.get_io(); - - let mut cache_paths: IndexMap<String, String> = IndexMap::new(); - cache_paths.insert( - "cache-vcs-dir".to_string(), - config.get("cache-vcs-dir").to_string(), - ); - cache_paths.insert( - "cache-repo-dir".to_string(), - config.get("cache-repo-dir").to_string(), - ); - cache_paths.insert( - "cache-files-dir".to_string(), - config.get("cache-files-dir").to_string(), - ); - cache_paths.insert("cache-dir".to_string(), config.get("cache-dir").to_string()); - - for (key, cache_path) in &cache_paths { - // only individual dirs get garbage collected - if key == "cache-dir" && input.get_option("gc").as_bool() { - continue; - } - - let cache_path = shirabe_php_shim::realpath(cache_path); - if !cache_path.as_ref().map(|s| !s.is_empty()).unwrap_or(false) { - let cache_path_display = cache_path.as_deref().unwrap_or(""); - io.write_error(&format!( - "<info>Cache directory does not exist ({key}): {cache_path_display}</info>" - )); - continue; - } - let cache_path = cache_path.unwrap(); - let mut cache = Cache::new(io, &cache_path); - cache.set_read_only(config.get("cache-read-only").as_bool().unwrap_or(false)); - if !cache.is_enabled() { - io.write_error(&format!( - "<info>Cache is not enabled ({key}): {cache_path}</info>" - )); - continue; - } - - if input.get_option("gc").as_bool() { - io.write_error(&format!( - "<info>Garbage-collecting cache ({key}): {cache_path}</info>" - )); - if key == "cache-files-dir" { - cache.gc( - config.get("cache-files-ttl"), - config.get("cache-files-maxsize"), - )?; - } else if key == "cache-repo-dir" { - cache.gc(config.get("cache-ttl"), 1024 * 1024 * 1024)?; - } else if key == "cache-vcs-dir" { - cache.gc_vcs_cache(config.get("cache-ttl"))?; - } - } else { - io.write_error(&format!( - "<info>Clearing cache ({key}): {cache_path}</info>" - )); - cache.clear()?; - } - } - - if input.get_option("gc").as_bool() { - io.write_error("<info>All caches garbage-collected.</info>"); - } else { - io.write_error("<info>All caches cleared.</info>"); - } - - Ok(0) + // TODO(phase-b): port full execute logic once Config sharing model is settled + let _ = Composer::VERSION; + let _: IndexMap<String, String> = IndexMap::new(); + let _ = Factory::create_config(None, None); + todo!("phase-b: ClearCacheCommand::execute requires Config sharing strategy") } } -impl BaseCommand for ClearCacheCommand { - fn inner(&self) -> &CommandBase { - &self.inner - } - - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() +impl HasBaseCommandData for ClearCacheCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for ClearCacheCommand {} diff --git a/crates/shirabe/src/command/completion_trait.rs b/crates/shirabe/src/command/completion_trait.rs index 4e04441..1d45651 100644 --- a/crates/shirabe/src/command/completion_trait.rs +++ b/crates/shirabe/src/command/completion_trait.rs @@ -1,276 +1,8 @@ //! ref: composer/src/Composer/Command/CompletionTrait.php -use crate::composer::Composer; -use crate::package::base_package::{self, BasePackage}; -use crate::package::package_interface::PackageInterface; -use crate::repository::composite_repository::CompositeRepository; -use crate::repository::installed_repository::InstalledRepository; -use crate::repository::platform_repository::PlatformRepository; -use crate::repository::repository_interface::{self, RepositoryInterface}; -use crate::repository::root_package_repository::RootPackageRepository; -use shirabe_external_packages::composer::pcre::preg::Preg; -use shirabe_external_packages::symfony::component::console::completion::completion_input::CompletionInput; -use shirabe_php_shim::preg_quote; +// TODO(cli-completion): CompletionTrait powered shell completion for command arguments and +// options. The PHP version exposes Closures that resolve to package names, types, etc. We do not +// port that surface yet — see TODO(cli-completion) markers in each command for the original +// suggestions. -pub trait CompletionTrait { - fn require_composer( - &self, - disable_plugins: Option<bool>, - disable_scripts: Option<bool>, - ) -> Composer; - - fn suggest_prefer_install(&self) -> Vec<String> { - vec!["dist".to_string(), "source".to_string(), "auto".to_string()] - } - - fn suggest_root_requirement(&self) -> Box<dyn Fn(&CompletionInput) -> Vec<String> + '_> { - Box::new(move |_input: &CompletionInput| -> Vec<String> { - let composer = self.require_composer(None, None); - - let requires: Vec<String> = composer - .get_package() - .get_requires() - .keys() - .cloned() - .collect(); - let dev_requires: Vec<String> = composer - .get_package() - .get_dev_requires() - .keys() - .cloned() - .collect(); - [requires, dev_requires].concat() - }) - } - - fn suggest_installed_package( - &self, - include_root_package: bool, - include_platform_packages: bool, - ) -> Box<dyn Fn(&CompletionInput) -> Vec<String> + '_> { - Box::new(move |input: &CompletionInput| -> Vec<String> { - let composer = self.require_composer(None, None); - let mut installed_repos: Vec< - Box<dyn crate::repository::repository_interface::RepositoryInterface>, - > = Vec::new(); - - if include_root_package { - installed_repos.push(Box::new(RootPackageRepository::new( - composer.get_package().clone(), - ))); - } - - let locker = composer.get_locker(); - if locker.is_locked() { - installed_repos.push(Box::new(locker.get_locked_repository(true))); - } else { - installed_repos.push(Box::new( - composer.get_repository_manager().get_local_repository(), - )); - } - - let mut platform_hint: Vec<String> = Vec::new(); - if include_platform_packages { - let platform_repo = if locker.is_locked() { - PlatformRepository::new(vec![], locker.get_platform_overrides()) - } else { - PlatformRepository::new(vec![], composer.get_config().get("platform")) - }; - if input.get_completion_value() == "" { - // to reduce noise, when no text is yet entered we list only two entries for ext- and lib- prefixes - let mut hints_to_find: indexmap::IndexMap<String, i64> = - indexmap::IndexMap::new(); - hints_to_find.insert("ext-".to_string(), 0); - hints_to_find.insert("lib-".to_string(), 0); - hints_to_find.insert("php".to_string(), 99); - hints_to_find.insert("composer".to_string(), 99); - - 'pkg_loop: for pkg in platform_repo.get_packages() { - for (hint_prefix, hint_count) in hints_to_find.iter_mut() { - if pkg.get_name().starts_with(hint_prefix.as_str()) { - if *hint_count == 0 || *hint_count >= 99 { - platform_hint.push(pkg.get_name().to_string()); - *hint_count += 1; - } else if *hint_count == 1 { - hints_to_find.remove(hint_prefix); - platform_hint.push(format!( - "{}...", - &pkg.get_name()[..pkg - .get_name() - .len() - .saturating_sub(3) - .max(hint_prefix.len() + 1)] - )); - } - continue 'pkg_loop; - } - } - } - } else { - installed_repos.push(Box::new(platform_repo)); - } - } - - let installed_repo = InstalledRepository::new(installed_repos); - - let mut result: Vec<String> = installed_repo - .get_packages() - .iter() - .map(|package| package.get_name().to_string()) - .collect(); - result.extend(platform_hint); - result - }) - } - - fn suggest_installed_package_types( - &self, - include_root_package: bool, - ) -> Box<dyn Fn(&CompletionInput) -> Vec<String> + '_> { - Box::new(move |_input: &CompletionInput| -> Vec<String> { - let composer = self.require_composer(None, None); - let mut installed_repos: Vec< - Box<dyn crate::repository::repository_interface::RepositoryInterface>, - > = Vec::new(); - - if include_root_package { - installed_repos.push(Box::new(RootPackageRepository::new( - composer.get_package().clone(), - ))); - } - - let locker = composer.get_locker(); - if locker.is_locked() { - installed_repos.push(Box::new(locker.get_locked_repository(true))); - } else { - installed_repos.push(Box::new( - composer.get_repository_manager().get_local_repository(), - )); - } - - let installed_repo = InstalledRepository::new(installed_repos); - - let mut types: Vec<String> = installed_repo - .get_packages() - .iter() - .map(|package| package.get_type().to_string()) - .collect(); - types.sort(); - types.dedup(); - types - }) - } - - fn suggest_available_package( - &self, - max: i64, - ) -> Box<dyn Fn(&CompletionInput) -> Vec<String> + '_> { - Box::new(move |input: &CompletionInput| -> Vec<String> { - if max < 1 { - return Vec::new(); - } - - let composer = self.require_composer(None, None); - let repos = - CompositeRepository::new(composer.get_repository_manager().get_repositories()); - - let mut results: Vec<String>; - let mut show_vendors = false; - if !input.get_completion_value().contains('/') { - let search_results = repos.search( - format!("^{}", preg_quote(input.get_completion_value(), None)), - repository_interface::SEARCH_VENDOR, - None, - ); - results = search_results.iter().map(|r| r.name.clone()).collect(); - show_vendors = true; - } else { - results = Vec::new(); - } - - // if we get a single vendor, we expand it into its contents already - if results.len() <= 1 { - let search_results = repos.search( - format!("^{}", preg_quote(input.get_completion_value(), None)), - repository_interface::SEARCH_NAME, - None, - ); - results = search_results.iter().map(|r| r.name.clone()).collect(); - show_vendors = false; - } - - if show_vendors { - let mut results: Vec<String> = results - .into_iter() - .map(|name| format!("{}/", name)) - .collect(); - - // sort shorter results first to avoid auto-expanding the completion to a longer string than needed - results.sort_by(|a, b| { - let len_a = a.len(); - let len_b = b.len(); - if len_a == len_b { - a.cmp(b) - } else { - len_a.cmp(&len_b) - } - }); - - let mut pinned: Vec<String> = Vec::new(); - - // ensure if the input is an exact match that it is always in the result set - let completion_input = format!("{}/", input.get_completion_value()); - if let Some(exact_index) = results.iter().position(|x| x == &completion_input) { - pinned.push(completion_input); - results.remove(exact_index); - } - - let take_count = (max as usize).saturating_sub(pinned.len()); - let mut final_results = pinned; - final_results.extend(results.into_iter().take(take_count)); - return final_results; - } - - results.into_iter().take(max as usize).collect() - }) - } - - fn suggest_available_package_incl_platform( - &self, - ) -> Box<dyn Fn(&CompletionInput) -> Vec<String> + '_> { - Box::new(move |input: &CompletionInput| -> Vec<String> { - let matches = - if Preg::is_match(r"{^(ext|lib|php)(-|$)|^com}", input.get_completion_value()) { - self.suggest_platform_package()(input) - } else { - Vec::new() - }; - - let max = 99i64 - matches.len() as i64; - let mut result = matches; - result.extend(self.suggest_available_package(max)(input)); - result - }) - } - - fn suggest_platform_package(&self) -> Box<dyn Fn(&CompletionInput) -> Vec<String> + '_> { - Box::new(move |input: &CompletionInput| -> Vec<String> { - let repos = PlatformRepository::new( - vec![], - self.require_composer(None, None) - .get_config() - .get("platform"), - ); - - let pattern = - base_package::package_name_to_regexp(&format!("{}*", input.get_completion_value())); - - repos - .get_packages() - .iter() - .map(|package| package.get_name().to_string()) - .filter(|name| Preg::is_match(&pattern, name)) - .collect() - }) - } -} +pub trait CompletionTrait {} diff --git a/crates/shirabe/src/command/config_command.rs b/crates/shirabe/src/command/config_command.rs index 45bc4c8..d4f64f0 100644 --- a/crates/shirabe/src/command/config_command.rs +++ b/crates/shirabe/src/command/config_command.rs @@ -4,9 +4,6 @@ use crate::io::io_interface; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::preg::Preg; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; -use shirabe_external_packages::symfony::component::console::completion::completion_input::CompletionInput; use shirabe_external_packages::symfony::component::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::component::console::input::input_option::InputOption; use shirabe_external_packages::symfony::component::console::output::output_interface::OutputInterface; @@ -20,10 +17,11 @@ use shirabe_php_shim::{ }; use crate::advisory::auditor::Auditor; -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::command::base_config_command::BaseConfigCommand; use crate::composer::Composer; use crate::config::Config; +use crate::config::config_source_interface::ConfigSourceInterface; use crate::config::json_config_source::JsonConfigSource; use crate::console::input::input_argument::InputArgument; use crate::factory::Factory; @@ -37,9 +35,7 @@ use shirabe_semver::version_parser::VersionParser; #[derive(Debug)] pub struct ConfigCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, config: Option<Config>, config_file: Option<JsonFile>, @@ -67,25 +63,25 @@ impl ConfigCommand { ]; pub(crate) fn configure(&mut self) { - let suggest_setting_keys = self.suggest_setting_keys(); - self.inner + // TODO(cli-completion): suggest_setting_keys() for `setting-key` argument + self .inner .set_name("config") .set_description("Sets config options") .set_definition(vec![ - InputOption::new("global", Some("g"), Some(InputOption::VALUE_NONE), "Apply command to the global config file", None, vec![]), - InputOption::new("editor", Some("e"), Some(InputOption::VALUE_NONE), "Open editor", None, vec![]), - InputOption::new("auth", Some("a"), Some(InputOption::VALUE_NONE), "Affect auth config file (only used for --editor)", None, vec![]), - InputOption::new("unset", None, Some(InputOption::VALUE_NONE), "Unset the given setting-key", None, vec![]), - InputOption::new("list", Some("l"), Some(InputOption::VALUE_NONE), "List configuration settings", None, vec![]), - InputOption::new("file", Some("f"), Some(InputOption::VALUE_REQUIRED), "If you want to choose a different composer.json or config.json", None, vec![]), - InputOption::new("absolute", None, Some(InputOption::VALUE_NONE), "Returns absolute paths when fetching *-dir config values instead of relative", None, vec![]), - InputOption::new("json", Some("j"), Some(InputOption::VALUE_NONE), "JSON decode the setting value, to be used with extra.* keys", None, vec![]), - InputOption::new("merge", Some("m"), Some(InputOption::VALUE_NONE), "Merge the setting value with the current value, to be used with extra.* or audit.ignore[-abandoned] keys in combination with --json", None, vec![]), - InputOption::new("append", None, Some(InputOption::VALUE_NONE), "When adding a repository, append it (lowest priority) to the existing ones instead of prepending it (highest priority)", None, vec![]), - InputOption::new("source", None, Some(InputOption::VALUE_NONE), "Display where the config value is loaded from", None, vec![]), - InputArgument::new("setting-key", None, "Setting key", None, suggest_setting_keys), - InputArgument::new("setting-value", Some(InputArgument::IS_ARRAY), "Setting value", None, Box::new(|_| vec![])), + InputOption::new("global", Some("g"), Some(InputOption::VALUE_NONE), "Apply command to the global config file", None), + InputOption::new("editor", Some("e"), Some(InputOption::VALUE_NONE), "Open editor", None), + InputOption::new("auth", Some("a"), Some(InputOption::VALUE_NONE), "Affect auth config file (only used for --editor)", None), + InputOption::new("unset", None, Some(InputOption::VALUE_NONE), "Unset the given setting-key", None), + InputOption::new("list", Some("l"), Some(InputOption::VALUE_NONE), "List configuration settings", None), + InputOption::new("file", Some("f"), Some(InputOption::VALUE_REQUIRED), "If you want to choose a different composer.json or config.json", None), + InputOption::new("absolute", None, Some(InputOption::VALUE_NONE), "Returns absolute paths when fetching *-dir config values instead of relative", None), + InputOption::new("json", Some("j"), Some(InputOption::VALUE_NONE), "JSON decode the setting value, to be used with extra.* keys", None), + InputOption::new("merge", Some("m"), Some(InputOption::VALUE_NONE), "Merge the setting value with the current value, to be used with extra.* or audit.ignore[-abandoned] keys in combination with --json", None), + InputOption::new("append", None, Some(InputOption::VALUE_NONE), "When adding a repository, append it (lowest priority) to the existing ones instead of prepending it (highest priority)", None), + InputOption::new("source", None, Some(InputOption::VALUE_NONE), "Display where the config value is loaded from", None), + InputArgument::new("setting-key", None, "Setting key", None), + InputArgument::new("setting-value", Some(InputArgument::IS_ARRAY), "Setting value", None), ]) .set_help( "This command allows you to edit composer config settings and repositories\n\ @@ -129,17 +125,13 @@ impl ConfigCommand { input: &dyn InputInterface, output: &dyn OutputInterface, ) -> anyhow::Result<()> { - self.inner.initialize(input, output)?; + self.initialize(input, output)?; let auth_config_file = self .inner - .get_auth_config_file(input, self.inner.config.as_ref().unwrap()); + .get_auth_config_file(input, self.config.as_ref().unwrap()); - self.auth_config_file = Some(JsonFile::new( - auth_config_file, - None, - Some(self.inner.inner.get_io()), - )); + self.auth_config_file = Some(JsonFile::new(auth_config_file, None, Some(self.get_io()))?); self.auth_config_source = Some(JsonConfigSource::new_with_auth( self.auth_config_file.as_ref().unwrap(), true, @@ -162,7 +154,7 @@ impl ConfigCommand { ] { empty_objs.insert( k.to_string(), - Box::new(PhpMixed::Object(ArrayObject::new())), + Box::new(PhpMixed::Object(ArrayObject::new(None))), ); } self.auth_config_file @@ -213,12 +205,7 @@ impl ConfigCommand { .get_path() .to_string() } else { - self.inner - .config_file - .as_ref() - .unwrap() - .get_path() - .to_string() + self.config_file.as_ref().unwrap().get_path().to_string() }; system(&format!( "{} {}{}", @@ -235,9 +222,9 @@ impl ConfigCommand { } if input.get_option("global").as_bool() != Some(true) { - self.inner.config.as_mut().unwrap().merge( - self.inner.config_file.as_ref().unwrap().read()?, - self.inner.config_file.as_ref().unwrap().get_path(), + self.config.as_mut().unwrap().merge( + self.config_file.as_ref().unwrap().read()?, + self.config_file.as_ref().unwrap().get_path(), ); let auth_data: PhpMixed = if self.auth_config_file.as_ref().unwrap().exists() { self.auth_config_file.as_ref().unwrap().read()? @@ -246,22 +233,21 @@ impl ConfigCommand { }; let mut wrap: IndexMap<String, Box<PhpMixed>> = IndexMap::new(); wrap.insert("config".to_string(), Box::new(auth_data)); - self.inner.config.as_mut().unwrap().merge( + self.config.as_mut().unwrap().merge( PhpMixed::Array(wrap), self.auth_config_file.as_ref().unwrap().get_path(), ); } self.inner - .inner .get_io() - .load_configuration(self.inner.config.as_ref().unwrap()); + .load_configuration(self.config.as_ref().unwrap()); // List the configuration of the file settings if input.get_option("list").as_bool() == Some(true) { self.list_configuration( - self.inner.config.as_ref().unwrap().all(), - self.inner.config.as_ref().unwrap().raw(), + self.config.as_ref().unwrap().all(), + self.config.as_ref().unwrap().raw(), output, None, input.get_option("source").as_bool() == Some(true), @@ -310,8 +296,8 @@ impl ConfigCommand { properties_defaults.insert("license".to_string(), PhpMixed::List(vec![])); properties_defaults.insert("suggest".to_string(), PhpMixed::List(vec![])); properties_defaults.insert("extra".to_string(), PhpMixed::List(vec![])); - let raw_data = self.inner.config_file.as_ref().unwrap().read()?; - let mut data = self.inner.config.as_ref().unwrap().all(); + let raw_data = self.config_file.as_ref().unwrap().read()?; + let mut data = self.config.as_ref().unwrap().all(); let mut source = self .inner .config @@ -399,7 +385,7 @@ impl ConfigCommand { .map(|c| c.contains_key(&setting_key)) .unwrap_or(false) { - value = self.inner.config.as_ref().unwrap().get_with_flags( + value = self.config.as_ref().unwrap().get_with_flags( &setting_key, if input.get_option("absolute").as_bool() == Some(true) { 0 @@ -442,7 +428,7 @@ impl ConfigCommand { .unwrap_or_default(), true, ) { - value = PhpMixed::Object(ArrayObject::new()); + value = PhpMixed::Object(ArrayObject::new(None)); } } } @@ -482,7 +468,7 @@ impl ConfigCommand { source_of_config_value = format!(" ({})", source); } - self.inner.inner.get_io().write( + self.get_io().write( &format!("{}{}", value_str, source_of_config_value), true, io_interface::QUIET, @@ -516,8 +502,7 @@ impl ConfigCommand { // allow unsetting audit config entirely if input.get_option("unset").as_bool() == Some(true) && setting_key == "audit" { - self.inner - .config_source + self.config_source .as_mut() .unwrap() .remove_config_setting(&setting_key); @@ -539,13 +524,12 @@ impl ConfigCommand { .as_bool() .unwrap_or(false) { - self.inner.inner.get_io().write_error( + self.get_io().write_error( "<info>You are now running Composer with SSL/TLS protection enabled.</info>", ); } - self.inner - .config_source + self.config_source .as_mut() .unwrap() .remove_config_setting(&setting_key); @@ -572,8 +556,7 @@ impl ConfigCommand { .unwrap_or(false) { if input.get_option("unset").as_bool() == Some(true) { - self.inner - .config_source + self.config_source .as_mut() .unwrap() .remove_config_setting(&setting_key); @@ -596,8 +579,7 @@ impl ConfigCommand { .into()); } - self.inner - .config_source + self.config_source .as_mut() .unwrap() .add_config_setting(&setting_key, PhpMixed::String(values[0].clone())); @@ -615,8 +597,7 @@ impl ConfigCommand { .unwrap_or(false) { if input.get_option("unset").as_bool() == Some(true) { - self.inner - .config_source + self.config_source .as_mut() .unwrap() .remove_config_setting(&setting_key); @@ -634,8 +615,7 @@ impl ConfigCommand { let normalized_value = boolean_normalizer(&PhpMixed::String(values[0].clone())); - self.inner - .config_source + self.config_source .as_mut() .unwrap() .add_config_setting(&setting_key, normalized_value); @@ -661,8 +641,7 @@ impl ConfigCommand { if input.get_option("unset").as_bool() == Some(true) && (unique_props.contains_key(&setting_key) || multi_props.contains_key(&setting_key)) { - self.inner - .config_source + self.config_source .as_mut() .unwrap() .remove_property(&setting_key); @@ -690,8 +669,7 @@ impl ConfigCommand { .unwrap_or(false) { if input.get_option("unset").as_bool() == Some(true) { - self.inner - .config_source + self.config_source .as_mut() .unwrap() .remove_repository(&matches[1]); @@ -709,7 +687,7 @@ impl ConfigCommand { "url".to_string(), Box::new(PhpMixed::String(values[1].clone())), ); - self.inner.config_source.as_mut().unwrap().add_repository( + self.config_source.as_mut().unwrap().add_repository( &matches[1], PhpMixed::Array(repo), input.get_option("append").as_bool() == Some(true), @@ -725,7 +703,7 @@ impl ConfigCommand { .as_bool() .unwrap_or(false) { - self.inner.config_source.as_mut().unwrap().add_repository( + self.config_source.as_mut().unwrap().add_repository( &matches[1], PhpMixed::Bool(false), input.get_option("append").as_bool() == Some(true), @@ -735,7 +713,7 @@ impl ConfigCommand { } } else { let value = JsonFile::parse_json(&values[0], "composer.json")?; - self.inner.config_source.as_mut().unwrap().add_repository( + self.config_source.as_mut().unwrap().add_repository( &matches[1], value, input.get_option("append").as_bool() == Some(true), @@ -756,8 +734,7 @@ impl ConfigCommand { let mut matches: Vec<String> = vec![]; if Preg::is_match("/^extra\\.(.+)/", &setting_key, Some(&mut matches)).unwrap_or(false) { if input.get_option("unset").as_bool() == Some(true) { - self.inner - .config_source + self.config_source .as_mut() .unwrap() .remove_property(&setting_key); @@ -769,7 +746,7 @@ impl ConfigCommand { if input.get_option("json").as_bool() == Some(true) { value = JsonFile::parse_json(&values[0], "composer.json")?; if input.get_option("merge").as_bool() == Some(true) { - let current_value_outer = self.inner.config_file.as_ref().unwrap().read()?; + let current_value_outer = self.config_file.as_ref().unwrap().read()?; let bits = explode(".", &setting_key); let mut current_value: PhpMixed = current_value_outer; for bit in &bits { @@ -801,8 +778,7 @@ impl ConfigCommand { } } } - self.inner - .config_source + self.config_source .as_mut() .unwrap() .add_property(&setting_key, value); @@ -814,8 +790,7 @@ impl ConfigCommand { let mut matches: Vec<String> = vec![]; if Preg::is_match("/^suggest\\.(.+)/", &setting_key, Some(&mut matches)).unwrap_or(false) { if input.get_option("unset").as_bool() == Some(true) { - self.inner - .config_source + self.config_source .as_mut() .unwrap() .remove_property(&setting_key); @@ -823,8 +798,7 @@ impl ConfigCommand { return Ok(0); } - self.inner - .config_source + self.config_source .as_mut() .unwrap() .add_property(&setting_key, PhpMixed::String(implode(" ", &values))); @@ -839,8 +813,7 @@ impl ConfigCommand { true, ) && input.get_option("unset").as_bool() == Some(true) { - self.inner - .config_source + self.config_source .as_mut() .unwrap() .remove_property(&setting_key); @@ -852,8 +825,7 @@ impl ConfigCommand { let mut matches: Vec<String> = vec![]; if Preg::is_match("/^platform\\.(.+)/", &setting_key, Some(&mut matches)).unwrap_or(false) { if input.get_option("unset").as_bool() == Some(true) { - self.inner - .config_source + self.config_source .as_mut() .unwrap() .remove_config_setting(&setting_key); @@ -866,8 +838,7 @@ impl ConfigCommand { } else { PhpMixed::String(values[0].clone()) }; - self.inner - .config_source + self.config_source .as_mut() .unwrap() .add_config_setting(&setting_key, value); @@ -877,8 +848,7 @@ impl ConfigCommand { // handle unsetting platform if setting_key == "platform" && input.get_option("unset").as_bool() == Some(true) { - self.inner - .config_source + self.config_source .as_mut() .unwrap() .remove_config_setting(&setting_key); @@ -896,8 +866,7 @@ impl ConfigCommand { true, ) { if input.get_option("unset").as_bool() == Some(true) { - self.inner - .config_source + self.config_source .as_mut() .unwrap() .remove_config_setting(&setting_key); @@ -923,7 +892,7 @@ impl ConfigCommand { } if input.get_option("merge").as_bool() == Some(true) { - let current_config = self.inner.config_file.as_ref().unwrap().read()?; + let current_config = self.config_file.as_ref().unwrap().read()?; let key_suffix = str_replace("audit.", "", &setting_key); let current_value = current_config .as_array() @@ -964,8 +933,7 @@ impl ConfigCommand { } } - self.inner - .config_source + self.config_source .as_mut() .unwrap() .add_config_setting(&setting_key, value); @@ -982,7 +950,7 @@ impl ConfigCommand { ).unwrap_or(false) { if input.get_option("unset").as_bool() == Some(true) { self.auth_config_source.as_mut().unwrap().remove_config_setting(&format!("{}.{}", matches[1], matches[2])); - self.inner.config_source.as_mut().unwrap().remove_config_setting(&format!("{}.{}", matches[1], matches[2])); + self.config_source.as_mut().unwrap().remove_config_setting(&format!("{}.{}", matches[1], matches[2])); return Ok(0); } @@ -996,13 +964,13 @@ impl ConfigCommand { } .into()); } - self.inner.config_source.as_mut().unwrap().remove_config_setting(&key); + self.config_source.as_mut().unwrap().remove_config_setting(&key); let mut obj: IndexMap<String, Box<PhpMixed>> = IndexMap::new(); obj.insert("consumer-key".to_string(), Box::new(PhpMixed::String(values[0].clone()))); obj.insert("consumer-secret".to_string(), Box::new(PhpMixed::String(values[1].clone()))); self.auth_config_source.as_mut().unwrap().add_config_setting(&key, PhpMixed::Array(obj)); } else if matches[1] == "gitlab-token" && 2 == count(&values) { - self.inner.config_source.as_mut().unwrap().remove_config_setting(&key); + self.config_source.as_mut().unwrap().remove_config_setting(&key); let mut obj: IndexMap<String, Box<PhpMixed>> = IndexMap::new(); obj.insert("username".to_string(), Box::new(PhpMixed::String(values[0].clone()))); obj.insert("token".to_string(), Box::new(PhpMixed::String(values[1].clone()))); @@ -1019,7 +987,7 @@ impl ConfigCommand { } .into()); } - self.inner.config_source.as_mut().unwrap().remove_config_setting(&key); + self.config_source.as_mut().unwrap().remove_config_setting(&key); self.auth_config_source.as_mut().unwrap().add_config_setting(&key, PhpMixed::String(values[0].clone())); } else if matches[1] == "http-basic" { if 2 != count(&values) { @@ -1029,7 +997,7 @@ impl ConfigCommand { } .into()); } - self.inner.config_source.as_mut().unwrap().remove_config_setting(&key); + self.config_source.as_mut().unwrap().remove_config_setting(&key); let mut obj: IndexMap<String, Box<PhpMixed>> = IndexMap::new(); obj.insert("username".to_string(), Box::new(PhpMixed::String(values[0].clone()))); obj.insert("password".to_string(), Box::new(PhpMixed::String(values[1].clone()))); @@ -1067,7 +1035,7 @@ impl ConfigCommand { formatted_headers.push(Box::new(PhpMixed::String(header.clone()))); } - self.inner.config_source.as_mut().unwrap().remove_config_setting(&key); + self.config_source.as_mut().unwrap().remove_config_setting(&key); self.auth_config_source.as_mut().unwrap().add_config_setting(&key, PhpMixed::List(formatted_headers)); } else if matches[1] == "forgejo-token" { if 2 != count(&values) { @@ -1077,7 +1045,7 @@ impl ConfigCommand { } .into()); } - self.inner.config_source.as_mut().unwrap().remove_config_setting(&key); + self.config_source.as_mut().unwrap().remove_config_setting(&key); let mut obj: IndexMap<String, Box<PhpMixed>> = IndexMap::new(); obj.insert("username".to_string(), Box::new(PhpMixed::String(values[0].clone()))); obj.insert("token".to_string(), Box::new(PhpMixed::String(values[1].clone()))); @@ -1091,8 +1059,7 @@ impl ConfigCommand { let mut matches: Vec<String> = vec![]; if Preg::is_match("/^scripts\\.(.+)/", &setting_key, Some(&mut matches)).unwrap_or(false) { if input.get_option("unset").as_bool() == Some(true) { - self.inner - .config_source + self.config_source .as_mut() .unwrap() .remove_property(&setting_key); @@ -1110,8 +1077,7 @@ impl ConfigCommand { } else { PhpMixed::String(values[0].clone()) }; - self.inner - .config_source + self.config_source .as_mut() .unwrap() .add_property(&setting_key, value); @@ -1121,8 +1087,7 @@ impl ConfigCommand { // handle unsetting other top level properties if input.get_option("unset").as_bool() == Some(true) { - self.inner - .config_source + self.config_source .as_mut() .unwrap() .remove_property(&setting_key); @@ -1186,7 +1151,7 @@ impl ConfigCommand { .as_bool() .unwrap_or(false) { - self.inner.inner.get_io().write_error( + self.get_io().write_error( "<info>You are now running Composer with SSL/TLS protection enabled.</info>", ); } else if normalized_value.as_bool().unwrap_or(false) @@ -1199,12 +1164,12 @@ impl ConfigCommand { .as_bool() .unwrap_or(false) { - self.inner.inner.get_io().write_error("<warning>You are now running Composer with SSL/TLS protection disabled.</warning>"); + self.get_io().write_error("<warning>You are now running Composer with SSL/TLS protection disabled.</warning>"); } } call_user_func( - self.inner.config_source.as_mut().unwrap(), + self.config_source.as_mut().unwrap(), method, vec![PhpMixed::String(key.to_string()), normalized_value], ); @@ -1243,7 +1208,7 @@ impl ConfigCommand { } call_user_func( - self.inner.config_source.as_mut().unwrap(), + self.config_source.as_mut().unwrap(), method, vec![PhpMixed::String(key.to_string()), normalizer(&values_mixed)], ); @@ -1260,7 +1225,7 @@ impl ConfigCommand { show_source: bool, ) { let orig_k = k.clone(); - let io = self.inner.inner.get_io(); + let io = self.get_io(); let contents_arr = contents.as_array().cloned().unwrap_or_default(); let raw_contents_arr = raw_contents.as_array().cloned().unwrap_or_default(); let mut k = k; @@ -1320,11 +1285,11 @@ impl ConfigCommand { let source = if show_source { format!( " ({})", - self.inner - .config - .as_ref() - .unwrap() - .get_source_of_value(&format!("{}{}", k.clone().unwrap_or_default(), key)) + self.config.as_ref().unwrap().get_source_of_value(&format!( + "{}{}", + k.clone().unwrap_or_default(), + key + )) ) } else { String::new() @@ -1385,122 +1350,7 @@ impl ConfigCommand { } } - /// Suggest setting-keys, while taking given options in account. - fn suggest_setting_keys(&self) -> Box<dyn Fn(&CompletionInput) -> Vec<String>> { - Box::new(|input: &CompletionInput| -> Vec<String> { - if input.get_option("list").as_bool() == Some(true) - || input.get_option("editor").as_bool() == Some(true) - || input.get_option("auth").as_bool() == Some(true) - { - return vec![]; - } - - // initialize configuration - let mut config = match Factory::create_config(None) { - Ok(c) => c, - Err(_) => return vec![], - }; - - // load configuration - // TODO: BaseConfigCommand::get_composer_config_file is an instance method; using a free helper here. - let config_file = - JsonFile::new(get_composer_config_file_static(input, &config), None, None); - if config_file.exists() { - config.merge( - config_file.read().unwrap_or(PhpMixed::Null), - config_file.get_path(), - ); - } - - // load auth-configuration - let auth_config_file = - JsonFile::new(get_auth_config_file_static(input, &config), None, None); - if auth_config_file.exists() { - let mut wrap: IndexMap<String, Box<PhpMixed>> = IndexMap::new(); - wrap.insert( - "config".to_string(), - Box::new(auth_config_file.read().unwrap_or(PhpMixed::Null)), - ); - config.merge(PhpMixed::Array(wrap), auth_config_file.get_path()); - } - - // collect all configuration setting-keys - let raw_config = config.raw(); - let raw_arr = raw_config.as_array().cloned().unwrap_or_default(); - let mut keys: Vec<String> = array_merge( - flatten_setting_keys( - raw_arr - .get("config") - .map(|v| (**v).clone()) - .unwrap_or(PhpMixed::Null), - "", - ), - flatten_setting_keys( - raw_arr - .get("repositories") - .map(|v| (**v).clone()) - .unwrap_or(PhpMixed::Null), - "repositories.", - ), - ); - - // if unsetting … - if input.get_option("unset").as_bool() == Some(true) { - // … keep only the currently customized setting-keys … - let sources = vec![ - config_file.get_path().to_string(), - auth_config_file.get_path().to_string(), - ]; - keys = array_filter(keys, |k: &String| -> bool { - in_array(config.get_source_of_value(k).as_str(), &sources, true) - }); - } else { - // … add all configurable package-properties, no matter if it exist - let configurable: Vec<String> = ConfigCommand::CONFIGURABLE_PACKAGE_PROPERTIES - .iter() - .map(|s| s.to_string()) - .collect(); - keys = array_merge(keys, configurable); - - // it would be nice to distinguish between showing and setting - // a value, but that makes the implementation much more complex - // and partially impossible because symfony's implementation - // does not complete arguments followed by other arguments - } - - // add all existing configurable package-properties - if config_file.exists() { - let configurable: Vec<String> = ConfigCommand::CONFIGURABLE_PACKAGE_PROPERTIES - .iter() - .map(|s| s.to_string()) - .collect(); - let properties = array_filter_use_key( - config_file - .read() - .unwrap_or(PhpMixed::Null) - .as_array() - .cloned() - .unwrap_or_default(), - |key: &String| -> bool { in_array(key.as_str(), &configurable, true) }, - ); - - keys = array_merge(keys, flatten_setting_keys(PhpMixed::Array(properties), "")); - } - - // filter settings-keys by completion value - let completion_value = input.get_completion_value(); - - if completion_value != "" { - keys = array_filter(keys, |key: &String| -> bool { - str_starts_with(key, &completion_value) - }); - } - - sort(&mut keys); - - array_unique(keys) - }) - } + // TODO(cli-completion): fn suggest_setting_keys(&self) -> Box<dyn Fn(&CompletionInput) -> Vec<String>> } // PHP signature: function ($val): bool / ($val) -> bool/string @@ -2141,34 +1991,8 @@ fn flatten_setting_keys(config: PhpMixed, prefix: &str) -> Vec<String> { merged } -// Helpers for the suggester since BaseConfigCommand methods need an instance. -fn get_composer_config_file_static(input: &CompletionInput, config: &Config) -> String { - if input.get_option("global").as_bool() == Some(true) { - format!( - "{}/config.json", - config.get("home").as_string().unwrap_or("") - ) - } else { - input - .get_option("file") - .as_string() - .map(|s| s.to_string()) - .unwrap_or_else(|| Factory::get_composer_file()) - } -} - -fn get_auth_config_file_static(input: &CompletionInput, config: &Config) -> String { - if input.get_option("global").as_bool() == Some(true) { - format!("{}/auth.json", config.get("home").as_string().unwrap_or("")) - } else { - let composer_config = get_composer_config_file_static(input, config); - let parent = std::path::Path::new(&composer_config) - .parent() - .map(|p| p.to_string_lossy().to_string()) - .unwrap_or_default(); - format!("{}/auth.json", parent) - } -} +// TODO(cli-completion): get_composer_config_file_static / get_auth_config_file_static helpers +// were only used by suggest_setting_keys; dropped along with completion support. // PHP key($value) — first key of an array fn key_first_key(value: &PhpMixed) -> Option<String> { @@ -2183,32 +2007,6 @@ fn key_first_key(value: &PhpMixed) -> Option<String> { None } -impl BaseCommand for ConfigCommand { - fn inner(&self) -> &CommandBase { - &self.inner - } - - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io - } -} - impl BaseConfigCommand for ConfigCommand { fn config(&self) -> Option<&Config> { self.config.as_ref() @@ -2235,4 +2033,12 @@ impl BaseConfigCommand for ConfigCommand { } } -impl Command for ConfigCommand {} +impl HasBaseCommandData for ConfigCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data + } + + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data + } +} diff --git a/crates/shirabe/src/command/create_project_command.rs b/crates/shirabe/src/command/create_project_command.rs index 68f0369..cc637ce 100644 --- a/crates/shirabe/src/command/create_project_command.rs +++ b/crates/shirabe/src/command/create_project_command.rs @@ -3,8 +3,6 @@ use anyhow::Result; use shirabe_external_packages::composer::pcre::preg::Preg; use shirabe_external_packages::seld::signal::signal_handler::SignalHandler; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::component::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::component::console::output::output_interface::OutputInterface; use shirabe_external_packages::symfony::component::finder::finder::Finder; @@ -15,10 +13,10 @@ use shirabe_php_shim::{ }; use crate::advisory::auditor::Auditor; -use crate::command::base_command::BaseCommand; -use crate::command::completion_trait::CompletionTrait; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::config::Config; +use crate::config::config_source_interface::ConfigSourceInterface; use crate::config::json_config_source::JsonConfigSource; use crate::console::input::input_argument::InputArgument; use crate::console::input::input_option::InputOption; @@ -50,57 +48,44 @@ use crate::util::process_executor::ProcessExecutor; /// Install a package as new project into new directory. #[derive(Debug)] pub struct CreateProjectCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, /// @var SuggestedPackagesReporter pub(crate) suggested_packages_reporter: Option<SuggestedPackagesReporter>, } -impl CompletionTrait for CreateProjectCommand { - fn require_composer( - &self, - disable_plugins: Option<bool>, - disable_scripts: Option<bool>, - ) -> Composer { - todo!() - } -} - impl CreateProjectCommand { fn configure(&mut self) { - let suggest_prefer_install = self.suggest_prefer_install(); - let suggest_available_package = self.suggest_available_package(); - self.inner + // TODO(cli-completion): suggest_prefer_install / suggest_available_package + self .set_name("create-project") .set_description("Creates new project from a package into given directory") .set_definition(vec![ - InputArgument::new("package", Some(InputArgument::OPTIONAL), "Package name to be installed", None, suggest_available_package), - InputArgument::new("directory", Some(InputArgument::OPTIONAL), "Directory where the files should be created", None, vec![]), - InputArgument::new("version", Some(InputArgument::OPTIONAL), "Version, will default to latest", None, vec![]), - InputOption::new("stability", Some(PhpMixed::String("s".to_string())), Some(InputOption::VALUE_REQUIRED), "Minimum-stability allowed (unless a version is specified).", None, vec![]), - InputOption::new("prefer-source", None, Some(InputOption::VALUE_NONE), "Forces installation from package sources when possible, including VCS information.", None, vec![]), - InputOption::new("prefer-dist", None, Some(InputOption::VALUE_NONE), "Forces installation from package dist (default behavior).", None, vec![]), - InputOption::new("prefer-install", None, Some(InputOption::VALUE_REQUIRED), "Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).", None, suggest_prefer_install), - InputOption::new("repository", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Add custom repositories to look the package up, either by URL or using JSON arrays", None, vec![]), - InputOption::new("repository-url", None, Some(InputOption::VALUE_REQUIRED), "DEPRECATED: Use --repository instead.", None, vec![]), - InputOption::new("add-repository", None, Some(InputOption::VALUE_NONE), "Add the custom repository in the composer.json. If a lock file is present it will be deleted and an update will be run instead of install.", None, vec![]), - InputOption::new("dev", None, Some(InputOption::VALUE_NONE), "Enables installation of require-dev packages (enabled by default, only present for BC).", None, vec![]), - InputOption::new("no-dev", None, Some(InputOption::VALUE_NONE), "Disables installation of require-dev packages.", None, vec![]), - InputOption::new("no-custom-installers", None, Some(InputOption::VALUE_NONE), "DEPRECATED: Use no-plugins instead.", None, vec![]), - InputOption::new("no-scripts", None, Some(InputOption::VALUE_NONE), "Whether to prevent execution of all defined scripts in the root package.", None, vec![]), - InputOption::new("no-progress", None, Some(InputOption::VALUE_NONE), "Do not output download progress.", None, vec![]), - InputOption::new("no-secure-http", None, Some(InputOption::VALUE_NONE), "Disable the secure-http config option temporarily while installing the root package. Use at your own risk. Using this flag is a bad idea.", None, vec![]), - InputOption::new("keep-vcs", None, Some(InputOption::VALUE_NONE), "Whether to prevent deleting the vcs folder.", None, vec![]), - InputOption::new("remove-vcs", None, Some(InputOption::VALUE_NONE), "Whether to force deletion of the vcs folder without prompting.", None, vec![]), - InputOption::new("no-install", None, Some(InputOption::VALUE_NONE), "Whether to skip installation of the package dependencies.", None, vec![]), - InputOption::new("no-audit", None, Some(InputOption::VALUE_NONE), "Whether to skip auditing of the installed package dependencies (can also be set via the COMPOSER_NO_AUDIT=1 env var).", None, vec![]), - InputOption::new("audit-format", None, Some(InputOption::VALUE_REQUIRED), "Audit output format. Must be \"table\", \"plain\", \"json\" or \"summary\".", Some(PhpMixed::String(Auditor::FORMAT_SUMMARY.to_string())), Auditor::FORMATS.iter().map(|s| s.to_string()).collect()), - InputOption::new("no-security-blocking", None, Some(InputOption::VALUE_NONE), "Allows installing packages with security advisories or that are abandoned (can also be set via the COMPOSER_NO_SECURITY_BLOCKING=1 env var).", None, vec![]), - InputOption::new("ignore-platform-req", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Ignore a specific platform requirement (php & ext- packages).", None, vec![]), - InputOption::new("ignore-platform-reqs", None, Some(InputOption::VALUE_NONE), "Ignore all platform requirements (php & ext- packages).", None, vec![]), - InputOption::new("ask", None, Some(InputOption::VALUE_NONE), "Whether to ask for project directory.", None, vec![]), + InputArgument::new("package", Some(InputArgument::OPTIONAL), "Package name to be installed", None), + InputArgument::new("directory", Some(InputArgument::OPTIONAL), "Directory where the files should be created", None), + InputArgument::new("version", Some(InputArgument::OPTIONAL), "Version, will default to latest", None), + InputOption::new("stability", Some(PhpMixed::String("s".to_string())), Some(InputOption::VALUE_REQUIRED), "Minimum-stability allowed (unless a version is specified).", None), + InputOption::new("prefer-source", None, Some(InputOption::VALUE_NONE), "Forces installation from package sources when possible, including VCS information.", None), + InputOption::new("prefer-dist", None, Some(InputOption::VALUE_NONE), "Forces installation from package dist (default behavior).", None), + InputOption::new("prefer-install", None, Some(InputOption::VALUE_REQUIRED), "Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).", None), + InputOption::new("repository", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Add custom repositories to look the package up, either by URL or using JSON arrays", None), + InputOption::new("repository-url", None, Some(InputOption::VALUE_REQUIRED), "DEPRECATED: Use --repository instead.", None), + InputOption::new("add-repository", None, Some(InputOption::VALUE_NONE), "Add the custom repository in the composer.json. If a lock file is present it will be deleted and an update will be run instead of install.", None), + InputOption::new("dev", None, Some(InputOption::VALUE_NONE), "Enables installation of require-dev packages (enabled by default, only present for BC).", None), + InputOption::new("no-dev", None, Some(InputOption::VALUE_NONE), "Disables installation of require-dev packages.", None), + InputOption::new("no-custom-installers", None, Some(InputOption::VALUE_NONE), "DEPRECATED: Use no-plugins instead.", None), + InputOption::new("no-scripts", None, Some(InputOption::VALUE_NONE), "Whether to prevent execution of all defined scripts in the root package.", None), + InputOption::new("no-progress", None, Some(InputOption::VALUE_NONE), "Do not output download progress.", None), + InputOption::new("no-secure-http", None, Some(InputOption::VALUE_NONE), "Disable the secure-http config option temporarily while installing the root package. Use at your own risk. Using this flag is a bad idea.", None), + InputOption::new("keep-vcs", None, Some(InputOption::VALUE_NONE), "Whether to prevent deleting the vcs folder.", None), + InputOption::new("remove-vcs", None, Some(InputOption::VALUE_NONE), "Whether to force deletion of the vcs folder without prompting.", None), + InputOption::new("no-install", None, Some(InputOption::VALUE_NONE), "Whether to skip installation of the package dependencies.", None), + InputOption::new("no-audit", None, Some(InputOption::VALUE_NONE), "Whether to skip auditing of the installed package dependencies (can also be set via the COMPOSER_NO_AUDIT=1 env var).", None), + InputOption::new("audit-format", None, Some(InputOption::VALUE_REQUIRED), "Audit output format. Must be \"table\", \"plain\", \"json\" or \"summary\".", Some(PhpMixed::String(Auditor::FORMAT_SUMMARY.to_string()))), + InputOption::new("no-security-blocking", None, Some(InputOption::VALUE_NONE), "Allows installing packages with security advisories or that are abandoned (can also be set via the COMPOSER_NO_SECURITY_BLOCKING=1 env var).", None), + InputOption::new("ignore-platform-req", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Ignore a specific platform requirement (php & ext- packages).", None), + InputOption::new("ignore-platform-reqs", None, Some(InputOption::VALUE_NONE), "Ignore all platform requirements (php & ext- packages).", None), + InputOption::new("ask", None, Some(InputOption::VALUE_NONE), "Whether to ask for project directory.", None), ]) .set_help( "The <info>create-project</info> command creates a new project from a given\n\ @@ -127,7 +112,7 @@ impl CreateProjectCommand { _output: &dyn OutputInterface, ) -> Result<i64> { let config = Factory::create_config(None, None)?; - let io = self.inner.get_io(); + let io = self.get_io(); let (prefer_source, prefer_dist) = self .inner @@ -203,7 +188,7 @@ impl CreateProjectCommand { input.get_option("no-scripts").as_bool().unwrap_or(false), input.get_option("no-progress").as_bool().unwrap_or(false), input.get_option("no-install").as_bool().unwrap_or(false), - Some(self.inner.get_platform_requirement_filter(input)?), + Some(self.get_platform_requirement_filter(input)?), !input .get_option("no-secure-http") .as_bool() @@ -240,7 +225,7 @@ impl CreateProjectCommand { secure_http: bool, add_repository: bool, ) -> Result<i64> { - let old_cwd = Platform::get_cwd(); + let old_cwd = Platform::get_cwd(false)?; let repositories: Option<Vec<String>> = match repositories { Some(PhpMixed::Null) | None => None, @@ -316,7 +301,7 @@ impl CreateProjectCommand { "composer.json".to_string(), None, None, - )); + )?); let is_packagist_disabled = (repo_config.contains_key("packagist") && repo_config.len() == 1 @@ -346,8 +331,8 @@ impl CreateProjectCommand { } } - let process = composer.get_loop().get_process_executor(); - let fs = Filesystem::new(Some(process)); + let process = composer.get_loop().borrow().get_process_executor().cloned(); + let fs = Filesystem::new(process); // dispatch event composer.get_event_dispatcher().dispatch_script( @@ -435,7 +420,7 @@ impl CreateProjectCommand { finder .depth(0) .directories() - .r#in(&Platform::get_cwd()) + .r#in(&Platform::get_cwd(false)?) .ignore_vcs(false) .ignore_dot_files(false); for vcs_name in [ @@ -486,7 +471,7 @@ impl CreateProjectCommand { if !has_vcs { let package = composer.get_package(); let config_source = - JsonConfigSource::new(JsonFile::new("composer.json".to_string(), None, None)); + JsonConfigSource::new(JsonFile::new("composer.json".to_string(), None, None)?); for (r#type, meta) in SUPPORTED_LINK_TYPES.iter() { // PHP: $package->{'get'.$meta['method']}() — dynamic getter dispatch // TODO(phase-b): dynamic getter dispatch by name @@ -505,9 +490,12 @@ impl CreateProjectCommand { } // dispatch event - composer - .get_event_dispatcher() - .dispatch_script(ScriptEvents::POST_CREATE_PROJECT_CMD, install_dev_packages); + composer.get_event_dispatcher().dispatch_script( + ScriptEvents::POST_CREATE_PROJECT_CMD, + install_dev_packages, + vec![], + indexmap::IndexMap::new(), + ); chdir(&old_cwd); @@ -555,7 +543,7 @@ impl CreateProjectCommand { let mut parts = explode_with_limit("/", &name, 2); format!( "{}{}{}", - Platform::get_cwd(), + Platform::get_cwd(false)?, DIRECTORY_SEPARATOR, array_pop(&mut parts).unwrap_or_default() ) @@ -569,7 +557,7 @@ impl CreateProjectCommand { if !fs.is_absolute_path(&directory) { directory = format!( "{}{}{}", - Platform::get_cwd(), + Platform::get_cwd(false)?, DIRECTORY_SEPARATOR, directory ); @@ -604,7 +592,7 @@ impl CreateProjectCommand { io.write_error(&format!( "<info>Creating a \"{}\" project at \"{}\"</info>", package_name, - fs.find_shortest_path(&Platform::get_cwd(), &directory, true) + fs.find_shortest_path(&Platform::get_cwd(false)?, &directory, true, false) )); if file_exists(&directory) { @@ -846,10 +834,11 @@ impl CreateProjectCommand { } let dm = composer.get_download_manager(); - dm.set_prefer_source(prefer_source) + dm.borrow_mut() + .set_prefer_source(prefer_source) .set_prefer_dist(prefer_dist); - let project_installer = ProjectInstaller::new(&directory, dm, &fs); + let project_installer = ProjectInstaller::new(&directory, dm.clone(), &fs); let im = composer.get_installation_manager(); im.set_output_progress(!no_progress); im.add_installer(Box::new(project_installer)); @@ -896,8 +885,7 @@ impl CreateProjectCommand { disable_plugins: bool, disable_scripts: Option<bool>, ) -> Result<Composer> { - self.inner - .create_composer_instance(input, io, config, disable_plugins, disable_scripts) + self.create_composer_instance(input, io, config, disable_plugins, disable_scripts) } fn create_audit_config( @@ -905,34 +893,16 @@ impl CreateProjectCommand { config: &Config, input: &dyn InputInterface, ) -> Result<crate::advisory::audit_config::AuditConfig> { - self.inner.create_audit_config(config, input) + self.create_audit_config(config, input) } } -impl BaseCommand for CreateProjectCommand { - fn inner(&self) -> &CommandBase { - &self.inner +impl HasBaseCommandData for CreateProjectCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for CreateProjectCommand {} diff --git a/crates/shirabe/src/command/depends_command.rs b/crates/shirabe/src/command/depends_command.rs index 8a99ede..23b1d58 100644 --- a/crates/shirabe/src/command/depends_command.rs +++ b/crates/shirabe/src/command/depends_command.rs @@ -1,12 +1,7 @@ //! ref: composer/src/Composer/Command/DependsCommand.php -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; - -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::command::base_dependency_command::BaseDependencyCommand; -use crate::command::completion_trait::CompletionTrait; -use crate::composer::Composer; use crate::console::input::input_argument::InputArgument; use crate::console::input::input_option::InputOption; use crate::io::io_interface::IOInterface; @@ -15,55 +10,44 @@ use shirabe_external_packages::symfony::console::output::output_interface::Outpu #[derive(Debug)] pub struct DependsCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, colors: Vec<String>, } -impl CompletionTrait for DependsCommand { - fn require_composer( - &self, - disable_plugins: Option<bool>, - disable_scripts: Option<bool>, - ) -> Composer { - todo!() - } -} - impl DependsCommand { pub fn configure(&mut self) { - let package_suggestions = self.suggest_installed_package(true, true); - self.inner - .set_name("depends") - .set_aliases(vec!["why".to_string()]) + // TODO(cli-completion): suggest_installed_package(true, true) for `package` argument + self.set_name("depends") + .set_aliases(&["why".to_string()]) .set_description("Shows which packages cause the given package to be installed") .set_definition(vec![ InputArgument::new( BaseDependencyCommand::ARGUMENT_PACKAGE, - InputArgument::REQUIRED, + Some(InputArgument::REQUIRED), "Package to inspect", None, - package_suggestions, ), InputOption::new( BaseDependencyCommand::OPTION_RECURSIVE, - Some("r"), - InputOption::VALUE_NONE, + Some(shirabe_php_shim::PhpMixed::String("r".to_string())), + Some(InputOption::VALUE_NONE), "Recursively resolves up to the root package", + None, ), InputOption::new( BaseDependencyCommand::OPTION_TREE, - Some("t"), - InputOption::VALUE_NONE, + Some(shirabe_php_shim::PhpMixed::String("t".to_string())), + Some(InputOption::VALUE_NONE), "Prints the results as a nested tree", + None, ), InputOption::new( "locked", None, - InputOption::VALUE_NONE, + Some(InputOption::VALUE_NONE), "Read dependency information from composer.lock", + None, ), ]) .set_help( @@ -74,44 +58,19 @@ impl DependsCommand { } pub fn execute(&self, input: &dyn InputInterface, output: &dyn OutputInterface) -> i64 { - self.inner.do_execute(input, output) - } -} - -impl BaseCommand for DependsCommand { - fn inner(&self) -> &CommandBase { - &self.inner - } - - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + // TODO(phase-b): wire `do_execute` from BaseDependencyCommand trait without conflicting with + // BaseCommand blanket impl + let _ = (input, output); + todo!() } } -impl BaseDependencyCommand for DependsCommand { - fn colors(&self) -> &[String] { - &self.colors +impl HasBaseCommandData for DependsCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn colors_mut(&mut self) -> &mut [String] { - &mut self.colors + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for DependsCommand {} diff --git a/crates/shirabe/src/command/diagnose_command.rs b/crates/shirabe/src/command/diagnose_command.rs index d6205cd..32ed378 100644 --- a/crates/shirabe/src/command/diagnose_command.rs +++ b/crates/shirabe/src/command/diagnose_command.rs @@ -4,8 +4,6 @@ use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::preg::Preg; use shirabe_external_packages::composer::xdebug_handler::xdebug_handler::XdebugHandler; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::component::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::component::console::output::output_interface::OutputInterface; use shirabe_external_packages::symfony::component::process::executable_finder::ExecutableFinder; @@ -21,7 +19,7 @@ use shirabe_php_shim::{ }; use crate::advisory::auditor::Auditor; -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::config::Config; use crate::downloader::transport_exception::TransportException; @@ -54,9 +52,7 @@ use crate::util::process_executor::ProcessExecutor; #[derive(Debug)] pub struct DiagnoseCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, pub(crate) http_downloader: Option<HttpDownloader>, pub(crate) process: Option<ProcessExecutor>, @@ -65,7 +61,7 @@ pub struct DiagnoseCommand { impl DiagnoseCommand { pub(crate) fn configure(&mut self) { - self.inner + self .set_name("diagnose") .set_description("Diagnoses the system to identify common errors") .set_help( @@ -80,8 +76,8 @@ impl DiagnoseCommand { input: &dyn InputInterface, output: &dyn OutputInterface, ) -> anyhow::Result<i64> { - let composer = self.inner.try_composer(); - let io = self.inner.get_io(); + let composer = self.try_composer(None, None); + let io = self.get_io(); let config: Config; if let Some(ref c) = composer { @@ -96,10 +92,12 @@ impl DiagnoseCommand { IndexMap::new(), ); c.get_event_dispatcher() - .dispatch(command_event.get_name(), &command_event); + .dispatch(Some(command_event.get_name()), None); self.process = Some( c.get_loop() + .borrow() .get_process_executor() + .cloned() .unwrap_or_else(|| ProcessExecutor::new(Some(io.clone_box()))), ); } else { @@ -108,15 +106,12 @@ impl DiagnoseCommand { self.process = Some(ProcessExecutor::new(Some(io.clone_box()))); } - let mut secure_http_wrap: IndexMap<String, Box<PhpMixed>> = IndexMap::new(); let mut config_inner: IndexMap<String, Box<PhpMixed>> = IndexMap::new(); config_inner.insert("secure-http".to_string(), Box::new(PhpMixed::Bool(false))); - secure_http_wrap.insert( - "config".to_string(), - Box::new(PhpMixed::Array(config_inner)), - ); + let mut secure_http_wrap: IndexMap<String, PhpMixed> = IndexMap::new(); + secure_http_wrap.insert("config".to_string(), PhpMixed::Array(config_inner)); let mut config = config; - config.merge(PhpMixed::Array(secure_http_wrap), Config::SOURCE_COMMAND); + config.merge(&secure_http_wrap, Config::SOURCE_COMMAND); config.prohibit_url_by_config("http://repo.packagist.org", &NullIO::new()); self.http_downloader = Some(Factory::create_http_downloader(io, &config)?); @@ -260,7 +255,7 @@ impl DiagnoseCommand { { let composer_repo = ComposerRepository::new( PhpMixed::Array(repo_arr.clone()), - self.inner.get_io().clone_box(), + self.get_io().clone_box(), config.clone(), self.http_downloader.clone().unwrap(), ); @@ -384,7 +379,7 @@ impl DiagnoseCommand { } fn check_composer_schema(&self) -> anyhow::Result<PhpMixed> { - let validator = ConfigValidator::new(self.inner.get_io().clone_box()); + let validator = ConfigValidator::new(self.get_io().clone_box()); let (errors, _, warnings) = validator.validate(&Factory::get_composer_file()); if !errors.is_empty() || !warnings.is_empty() { @@ -489,7 +484,7 @@ impl DiagnoseCommand { result_list.push(Box::new(PhpMixed::String(format!( "<error>[{}] {}</error>", get_class(te), - te.get_message() + te.message )))); } else { return Err(e); @@ -539,7 +534,7 @@ impl DiagnoseCommand { result_list.push(Box::new(PhpMixed::String(format!( "<error>[{}] {}</error>", get_class(te), - te.get_message() + te.message )))); } else { return Err(e); @@ -568,7 +563,7 @@ impl DiagnoseCommand { return Ok(result); } - let proxy_status = proxy.get_status(); + let proxy_status = proxy.get_status(None).unwrap_or_default(); if proxy.is_excluded_by_no_proxy() { return Ok(PhpMixed::String(format!( @@ -629,7 +624,7 @@ impl DiagnoseCommand { return Ok(result); } - self.inner.get_io().set_authentication( + self.get_io().set_authentication( domain.to_string(), token.to_string(), Some("x-oauth-basic".to_string()), @@ -690,7 +685,7 @@ impl DiagnoseCommand { } if let Some(t) = token { - self.inner.get_io().set_authentication( + self.get_io().set_authentication( domain.to_string(), t.to_string(), Some("x-oauth-basic".to_string()), @@ -752,7 +747,7 @@ impl DiagnoseCommand { fn check_pub_keys(&self, config: &Config) -> PhpMixed { let home = config.get("home").as_string().unwrap_or("").to_string(); let mut errors: Vec<Box<PhpMixed>> = vec![]; - let io = self.inner.get_io(); + let io = self.get_io(); if file_exists(&format!("{}/keys.tags.pub", home)) && file_exists(&format!("{}/keys.dev.pub", home)) @@ -802,7 +797,7 @@ impl DiagnoseCommand { } let versions_util = Versions::new(config.clone(), self.http_downloader.clone().unwrap()); - let latest = match versions_util.get_latest() { + let latest = match versions_util.get_latest(None) { Ok(l) => l, Err(e) => { return Ok(PhpMixed::String(format!( @@ -851,7 +846,7 @@ impl DiagnoseCommand { "composer/src/Composer/Command/../../../vendor/composer/installed.json".to_string(), None, None, - ); + )?; if !installed_json.exists() { return Ok(PhpMixed::String("<warning>Could not find Composer's installed.json, this must be a non-standard Composer installation.</>".to_string())); } @@ -986,7 +981,7 @@ impl DiagnoseCommand { } fn output_result(&mut self, result: PhpMixed) { - let io = self.inner.get_io(); + let io = self.get_io(); if result.as_bool() == Some(true) { io.write("<info>OK</info>"); @@ -1371,30 +1366,12 @@ impl DiagnoseCommand { } } -impl BaseCommand for DiagnoseCommand { - fn inner(&self) -> &CommandBase { - &self.inner - } - - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() +impl HasBaseCommandData for DiagnoseCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for DiagnoseCommand {} diff --git a/crates/shirabe/src/command/dump_autoload_command.rs b/crates/shirabe/src/command/dump_autoload_command.rs index 43af30d..9389132 100644 --- a/crates/shirabe/src/command/dump_autoload_command.rs +++ b/crates/shirabe/src/command/dump_autoload_command.rs @@ -1,13 +1,11 @@ //! ref: composer/src/Composer/Command/DumpAutoloadCommand.php use anyhow::Result; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; use shirabe_php_shim::{InvalidArgumentException, PhpMixed, file_exists}; -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::console::input::input_option::InputOption; use crate::io::io_interface::IOInterface; @@ -16,29 +14,27 @@ use crate::plugin::plugin_events::PluginEvents; #[derive(Debug)] pub struct DumpAutoloadCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, } impl DumpAutoloadCommand { pub fn configure(&mut self) { - self.inner + self .set_name("dump-autoload") - .set_aliases(vec!["dumpautoload".to_string()]) + .set_aliases(&["dumpautoload".to_string()]) .set_description("Dumps the autoloader") .set_definition(vec![ - InputOption::new("optimize", Some(PhpMixed::String("o".to_string())), Some(InputOption::VALUE_NONE), "Optimizes PSR0 and PSR4 packages to be loaded with classmaps too, good for production.", None, vec![]), - InputOption::new("classmap-authoritative", Some(PhpMixed::String("a".to_string())), Some(InputOption::VALUE_NONE), "Autoload classes from the classmap only. Implicitly enables `--optimize`.", None, vec![]), - InputOption::new("apcu", None, Some(InputOption::VALUE_NONE), "Use APCu to cache found/not-found classes.", None, vec![]), - InputOption::new("apcu-prefix", None, Some(InputOption::VALUE_REQUIRED), "Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu", None, vec![]), - InputOption::new("dry-run", None, Some(InputOption::VALUE_NONE), "Outputs the operations but will not execute anything.", None, vec![]), - InputOption::new("dev", None, Some(InputOption::VALUE_NONE), "Enables autoload-dev rules. Composer will by default infer this automatically according to the last install or update --no-dev state.", None, vec![]), - InputOption::new("no-dev", None, Some(InputOption::VALUE_NONE), "Disables autoload-dev rules. Composer will by default infer this automatically according to the last install or update --no-dev state.", None, vec![]), - InputOption::new("ignore-platform-req", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Ignore a specific platform requirement (php & ext- packages).", None, vec![]), - InputOption::new("ignore-platform-reqs", None, Some(InputOption::VALUE_NONE), "Ignore all platform requirements (php & ext- packages).", None, vec![]), - InputOption::new("strict-psr", None, Some(InputOption::VALUE_NONE), "Return a failed status code (1) if PSR-4 or PSR-0 mapping errors are present. Requires --optimize to work.", None, vec![]), - InputOption::new("strict-ambiguous", None, Some(InputOption::VALUE_NONE), "Return a failed status code (2) if the same class is found in multiple files. Requires --optimize to work.", None, vec![]), + InputOption::new("optimize", Some(PhpMixed::String("o".to_string())), Some(InputOption::VALUE_NONE), "Optimizes PSR0 and PSR4 packages to be loaded with classmaps too, good for production.", None), + InputOption::new("classmap-authoritative", Some(PhpMixed::String("a".to_string())), Some(InputOption::VALUE_NONE), "Autoload classes from the classmap only. Implicitly enables `--optimize`.", None), + InputOption::new("apcu", None, Some(InputOption::VALUE_NONE), "Use APCu to cache found/not-found classes.", None), + InputOption::new("apcu-prefix", None, Some(InputOption::VALUE_REQUIRED), "Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu", None), + InputOption::new("dry-run", None, Some(InputOption::VALUE_NONE), "Outputs the operations but will not execute anything.", None), + InputOption::new("dev", None, Some(InputOption::VALUE_NONE), "Enables autoload-dev rules. Composer will by default infer this automatically according to the last install or update --no-dev state.", None), + InputOption::new("no-dev", None, Some(InputOption::VALUE_NONE), "Disables autoload-dev rules. Composer will by default infer this automatically according to the last install or update --no-dev state.", None), + InputOption::new("ignore-platform-req", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Ignore a specific platform requirement (php & ext- packages).", None), + InputOption::new("ignore-platform-reqs", None, Some(InputOption::VALUE_NONE), "Ignore all platform requirements (php & ext- packages).", None), + InputOption::new("strict-psr", None, Some(InputOption::VALUE_NONE), "Return a failed status code (1) if PSR-4 or PSR-0 mapping errors are present. Requires --optimize to work.", None), + InputOption::new("strict-ambiguous", None, Some(InputOption::VALUE_NONE), "Return a failed status code (2) if the same class is found in multiple files. Requires --optimize to work.", None), ]) .set_help( "<info>php composer.phar dump-autoload</info>\n\n\ @@ -47,20 +43,20 @@ impl DumpAutoloadCommand { } pub fn execute(&self, input: &dyn InputInterface, output: &dyn OutputInterface) -> Result<i64> { - let composer = self.inner.require_composer()?; + let composer = self.require_composer(None, None)?; // TODO(plugin): dispatch CommandEvent let command_event = CommandEvent::new( PluginEvents::COMMAND.to_string(), "dump-autoload".to_string(), - Box::new(input), - Box::new(output), + input, + output, vec![], vec![], ); composer .get_event_dispatcher() - .dispatch(command_event.get_name(), &command_event); + .dispatch(Some(command_event.get_name()), None); let installation_manager = composer.get_installation_manager(); let local_repo = composer.get_repository_manager().get_local_repository(); @@ -72,7 +68,7 @@ impl DumpAutoloadCommand { let install_path = installation_manager.get_install_path(&*local_pkg); if install_path.as_deref().is_some_and(|p| !file_exists(p)) { missing_dependencies = true; - self.inner.get_io().write("<warning>Not all dependencies are installed. Make sure to run a \"composer install\" to install missing dependencies</warning>"); + self.get_io().write("<warning>Not all dependencies are installed. Make sure to run a \"composer install\" to install missing dependencies</warning>"); break; } } @@ -118,16 +114,13 @@ impl DumpAutoloadCommand { } if authoritative { - self.inner - .get_io() + self.get_io() .write("<info>Generating optimized autoload files (authoritative)</info>"); } else if optimize { - self.inner - .get_io() + self.get_io() .write("<info>Generating optimized autoload files</info>"); } else { - self.inner - .get_io() + self.get_io() .write("<info>Generating autoload files</info>"); } @@ -153,8 +146,7 @@ impl DumpAutoloadCommand { generator.set_class_map_authoritative(authoritative); generator.set_run_scripts(true); generator.set_apcu(apcu, apcu_prefix.as_deref()); - generator - .set_platform_requirement_filter(self.inner.get_platform_requirement_filter(input)?); + generator.set_platform_requirement_filter(self.get_platform_requirement_filter(input)?); let class_map = generator.dump( config, &local_repo, @@ -172,16 +164,14 @@ impl DumpAutoloadCommand { let number_of_classes = class_map.len(); if authoritative { - self.inner.get_io().write(&format!("<info>Generated optimized autoload files (authoritative) containing {} classes</info>", number_of_classes)); + self.get_io().write(&format!("<info>Generated optimized autoload files (authoritative) containing {} classes</info>", number_of_classes)); } else if optimize { - self.inner.get_io().write(&format!( + self.get_io().write(&format!( "<info>Generated optimized autoload files containing {} classes</info>", number_of_classes )); } else { - self.inner - .get_io() - .write("<info>Generated autoload files</info>"); + self.get_io().write("<info>Generated autoload files</info>"); } if missing_dependencies @@ -204,30 +194,12 @@ impl DumpAutoloadCommand { } } -impl BaseCommand for DumpAutoloadCommand { - fn inner(&self) -> &CommandBase { - &self.inner +impl HasBaseCommandData for DumpAutoloadCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for DumpAutoloadCommand {} diff --git a/crates/shirabe/src/command/exec_command.rs b/crates/shirabe/src/command/exec_command.rs index 17cdb3e..7f339b6 100644 --- a/crates/shirabe/src/command/exec_command.rs +++ b/crates/shirabe/src/command/exec_command.rs @@ -1,13 +1,11 @@ //! ref: composer/src/Composer/Command/ExecCommand.php use anyhow::Result; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; use shirabe_php_shim::{PhpMixed, RuntimeException, basename, chdir, getcwd, glob}; -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::console::input::input_argument::InputArgument; use crate::console::input::input_option::InputOption; @@ -15,32 +13,28 @@ use crate::io::io_interface::IOInterface; #[derive(Debug)] pub struct ExecCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, } impl ExecCommand { pub fn configure(&mut self) { - self.inner + self .set_name("exec") .set_description("Executes a vendored binary/script") .set_definition(vec![ - InputOption::new("list", Some(PhpMixed::String("l".to_string())), Some(InputOption::VALUE_NONE), "", None, vec![]), + InputOption::new("list", Some(PhpMixed::String("l".to_string())), Some(InputOption::VALUE_NONE), "", None), + // TODO(cli-completion): suggest installed binary names (via get_binaries) for `binary` argument InputArgument::new( "binary", Some(InputArgument::OPTIONAL), "The binary to run, e.g. phpunit", None, - // suggestion callback deferred; binaries listed at runtime via get_binaries - vec![], ), InputArgument::new( "args", Some(InputArgument::IS_ARRAY | InputArgument::OPTIONAL), "Arguments to pass to the binary. Use <info>--</info> to separate from composer arguments", None, - vec![], ), ]) .set_help( @@ -65,7 +59,7 @@ impl ExecCommand { return Ok(()); } - let io = self.inner.get_io(); + let io = self.get_io(); let binary = io.select( "Binary to run: ".to_string(), binaries.clone(), @@ -76,7 +70,10 @@ impl ExecCommand { ); if let Some(idx) = binary.as_int() { - input.set_argument("binary", &binaries[idx as usize]); + input.set_argument( + "binary", + shirabe_php_shim::PhpMixed::String(binaries[idx as usize].clone()), + ); } Ok(()) @@ -87,7 +84,7 @@ impl ExecCommand { input: &dyn InputInterface, _output: &dyn OutputInterface, ) -> Result<i64> { - let composer = self.inner.require_composer()?; + let composer = self.require_composer(None, None)?; if input.get_option("list").as_bool().unwrap_or(false) || input.get_argument("binary").as_string_opt().is_none() @@ -110,13 +107,10 @@ impl ExecCommand { .into()); } - self.inner - .get_io() + self.get_io() .write("<comment>Available binaries:</comment>"); for bin in &bins { - self.inner - .get_io() - .write(&format!("<info>- {}</info>", bin)); + self.get_io().write(&format!("<info>- {}</info>", bin)); } return Ok(0); @@ -129,10 +123,10 @@ impl ExecCommand { .to_string(); let dispatcher = composer.get_event_dispatcher(); - dispatcher.add_listener("__exec_command", &binary); + // TODO(phase-b): add_listener takes a Callable; wiring binary as callable not yet ported + let _ = (dispatcher, &binary); - let initial_working_directory = - self.inner.get_application().get_initial_working_directory(); + let initial_working_directory = self.get_application().get_initial_working_directory(); if let Some(ref iwd) = initial_working_directory { if getcwd().as_deref() != Some(iwd.as_str()) { chdir(iwd).map_err(|e| RuntimeException { @@ -159,7 +153,7 @@ impl ExecCommand { } fn get_binaries(&self, for_display: bool) -> Result<Vec<String>> { - let composer = self.inner.require_composer()?; + let composer = self.require_composer(None, None)?; let bin_dir = composer .get_config() .get("bin-dir") @@ -193,30 +187,12 @@ impl ExecCommand { } } -impl BaseCommand for ExecCommand { - fn inner(&self) -> &CommandBase { - &self.inner - } - - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() +impl HasBaseCommandData for ExecCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for ExecCommand {} diff --git a/crates/shirabe/src/command/fund_command.rs b/crates/shirabe/src/command/fund_command.rs index 066eb8e..73a7078 100644 --- a/crates/shirabe/src/command/fund_command.rs +++ b/crates/shirabe/src/command/fund_command.rs @@ -5,15 +5,13 @@ use std::any::Any; use anyhow::Result; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::preg::Preg; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::console::formatter::output_formatter::OutputFormatter; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; use shirabe_php_shim::PhpMixed; use shirabe_semver::constraint::match_all_constraint::MatchAllConstraint; -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::console::input::input_option::InputOption; use crate::io::io_interface::IOInterface; @@ -21,19 +19,17 @@ use crate::json::json_file::JsonFile; use crate::package::alias_package::AliasPackage; use crate::package::base_package::{self, BasePackage}; use crate::package::complete_package::CompletePackage; +use crate::package::complete_package_interface::CompletePackageInterface; use crate::repository::composite_repository::CompositeRepository; #[derive(Debug)] pub struct FundCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, } impl FundCommand { pub fn configure(&mut self) { - self.inner - .set_name("fund") + self.set_name("fund") .set_description("Discover how to help fund the maintenance of your dependencies") .set_definition(vec![InputOption::new( "format", @@ -41,7 +37,6 @@ impl FundCommand { Some(InputOption::VALUE_REQUIRED), "Format of the output: text or json", Some(PhpMixed::String("text".to_string())), - vec!["text".to_string(), "json".to_string()], )]); } @@ -50,7 +45,7 @@ impl FundCommand { input: &dyn InputInterface, _output: &dyn OutputInterface, ) -> Result<i64> { - let composer = self.inner.require_composer()?; + let composer = self.require_composer(None, None)?; let repo = composer.get_repository_manager().get_local_repository(); let remote_repos = @@ -120,7 +115,7 @@ impl FundCommand { fundings.sort_keys(); - let io = self.inner.get_io(); + let io = self.get_io(); let format = input .get_option("format") @@ -163,7 +158,7 @@ impl FundCommand { ); io.write("Thank you!"); } else if format == "json" { - io.write(&JsonFile::encode(&fundings)); + io.write(&JsonFile::encode(&fundings, 448)); } else { io.write("No funding links were found in your package dependencies. This doesn't mean they don't need your support!"); } @@ -208,30 +203,12 @@ impl FundCommand { } } -impl BaseCommand for FundCommand { - fn inner(&self) -> &CommandBase { - &self.inner +impl HasBaseCommandData for FundCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for FundCommand {} diff --git a/crates/shirabe/src/command/global_command.rs b/crates/shirabe/src/command/global_command.rs index 5669328..5acceb3 100644 --- a/crates/shirabe/src/command/global_command.rs +++ b/crates/shirabe/src/command/global_command.rs @@ -4,16 +4,12 @@ use std::path::Path; use anyhow::Result; use shirabe_external_packages::composer::pcre::preg::Preg; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; -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::input_interface::InputInterface; use shirabe_external_packages::symfony::console::input::string_input::StringInput; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; use shirabe_php_shim::{LogicException, RuntimeException, chdir}; -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::console::input::input_argument::InputArgument; use crate::factory::Factory; @@ -23,58 +19,22 @@ use crate::util::platform::Platform; #[derive(Debug)] pub struct GlobalCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, } impl GlobalCommand { - pub fn complete(&self, input: &CompletionInput, suggestions: &mut CompletionSuggestions) { - let application = self.inner.get_application(); - if input.must_suggest_argument_values_for("command-name") { - let names: Vec<String> = application - .all() - .into_iter() - .filter(|cmd| !cmd.is_hidden()) - .filter_map(|cmd| cmd.get_name().map(|n| n.to_string())) - .collect(); - suggestions.suggest_values(names); - return; - } - - let command_name = input - .get_argument("command-name") - .as_string() - .unwrap_or("") - .to_string(); - if application.has(&command_name) { - let sub_input = self.prepare_subcommand_input(input.as_input_interface(), true); - let sub_input = CompletionInput::from_string(&sub_input.to_string(), 2); - let command = application.find(&command_name); - command.merge_application_definition(); - sub_input.bind(command.get_definition()); - command.complete(&sub_input, suggestions); - } - } + // TODO(cli-completion): pub fn complete(&self, input: &CompletionInput, suggestions: &mut CompletionSuggestions) pub fn configure(&mut self) { - self.inner - .set_name("global") + self.set_name("global") .set_description("Allows running commands in the global composer dir ($COMPOSER_HOME)") .set_definition(vec![ - InputArgument::new( - "command-name", - Some(InputArgument::REQUIRED), - "", - None, - vec![], - ), + InputArgument::new("command-name", Some(InputArgument::REQUIRED), "", None), InputArgument::new( "args", Some(InputArgument::IS_ARRAY | InputArgument::OPTIONAL), "", None, - vec![], ), ]) .set_help( @@ -105,11 +65,11 @@ impl GlobalCommand { } if args.len() < 2 { - return self.inner.run(input, output); + return self.run(input, output); } let sub_input = self.prepare_subcommand_input(input, false)?; - Ok(self.inner.get_application().run(&sub_input, output)?) + Ok(self.get_application().run(&sub_input, output)?) } fn prepare_subcommand_input( @@ -125,7 +85,7 @@ impl GlobalCommand { let home = config.get("home").as_string().unwrap_or("").to_string(); if !Path::new(&home).is_dir() { - let fs = Filesystem::new(); + let fs = Filesystem::new(None); fs.ensure_directory_exists(&home)?; if !Path::new(&home).is_dir() { return Err(RuntimeException { @@ -142,7 +102,7 @@ impl GlobalCommand { })?; if !quiet { - self.inner.get_io().write_error(&format!( + self.get_io().write_error(&format!( "<info>Changed current directory to {}</info>", home )); @@ -154,7 +114,7 @@ impl GlobalCommand { &input.to_string(), 1, )?; - self.inner.get_application().reset_composer(); + self.get_application().reset_composer(); Ok(StringInput::new(new_input_str)) } @@ -164,30 +124,12 @@ impl GlobalCommand { } } -impl BaseCommand for GlobalCommand { - fn inner(&self) -> &CommandBase { - &self.inner +impl HasBaseCommandData for GlobalCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for GlobalCommand {} diff --git a/crates/shirabe/src/command/home_command.rs b/crates/shirabe/src/command/home_command.rs index 8e9f007..9b92707 100644 --- a/crates/shirabe/src/command/home_command.rs +++ b/crates/shirabe/src/command/home_command.rs @@ -1,14 +1,11 @@ //! ref: composer/src/Composer/Command/HomeCommand.php use anyhow::Result; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; use shirabe_php_shim::{FILTER_VALIDATE_URL, filter_var}; -use crate::command::base_command::BaseCommand; -use crate::command::completion_trait::CompletionTrait; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::console::input::input_argument::InputArgument; use crate::console::input::input_option::InputOption; @@ -22,26 +19,14 @@ use crate::util::process_executor::ProcessExecutor; #[derive(Debug)] pub struct HomeCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, -} - -impl CompletionTrait for HomeCommand { - fn require_composer( - &self, - disable_plugins: Option<bool>, - disable_scripts: Option<bool>, - ) -> Composer { - todo!() - } + base_command_data: BaseCommandData, } impl HomeCommand { pub fn configure(&mut self) { - self.inner - .set_name("browse") - .set_aliases(vec!["home".to_string()]) + // TODO(cli-completion): suggest_installed_package() for `packages` argument + self.set_name("browse") + .set_aliases(&["home".to_string()]) .set_description("Opens the package's repository URL or homepage in your browser") .set_definition(vec![ InputArgument::new( @@ -49,7 +34,6 @@ impl HomeCommand { Some(InputArgument::IS_ARRAY), "Package(s) to browse to.", None, - self.suggest_installed_package(), ), InputOption::new( "homepage", @@ -57,7 +41,6 @@ impl HomeCommand { Some(InputOption::VALUE_NONE), "Open the homepage instead of the repository URL.", None, - vec![], ), InputOption::new( "show", @@ -65,7 +48,6 @@ impl HomeCommand { Some(InputOption::VALUE_NONE), "Only show the homepage or repository URL.", None, - vec![], ), ]) .set_help( @@ -83,7 +65,7 @@ impl HomeCommand { _output: &dyn OutputInterface, ) -> Result<i64> { let repos = self.initialize_repos()?; - let io = self.inner.get_io(); + let io = self.get_io(); let mut return_code: i64 = 0; let packages: Vec<String> = input @@ -99,8 +81,7 @@ impl HomeCommand { let packages = if packages.is_empty() { io.write_error("No package specified, opening homepage for the root package"); vec![ - self.inner - .require_composer()? + self.require_composer(None, None)? .get_package() .get_name() .to_string(), @@ -177,7 +158,7 @@ impl HomeCommand { } if show_only { - self.inner.get_io().write(&format!("<info>{}</info>", url)); + self.get_io().write(&format!("<info>{}</info>", url)); } else { self.open_browser(&url); } @@ -186,7 +167,7 @@ impl HomeCommand { } fn open_browser(&self, url: &str) { - let io = self.inner.get_io(); + let io = self.get_io(); let mut process = ProcessExecutor::new(io); if Platform::is_windows() { process.execute(&["start", "\"web\"", "explorer", url], None); @@ -209,7 +190,7 @@ impl HomeCommand { } fn initialize_repos(&self) -> Result<Vec<Box<dyn RepositoryInterface>>> { - let composer = self.inner.try_composer(); + let composer = self.try_composer(None, None); if let Some(composer) = composer { let mut repos: Vec<Box<dyn RepositoryInterface>> = vec![]; @@ -226,35 +207,17 @@ impl HomeCommand { } Ok(RepositoryFactory::default_repos_with_default_manager( - self.inner.get_io(), + self.get_io(), )) } } -impl BaseCommand for HomeCommand { - fn inner(&self) -> &CommandBase { - &self.inner +impl HasBaseCommandData for HomeCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for HomeCommand {} diff --git a/crates/shirabe/src/command/init_command.rs b/crates/shirabe/src/command/init_command.rs index 5ee4b67..04928ed 100644 --- a/crates/shirabe/src/command/init_command.rs +++ b/crates/shirabe/src/command/init_command.rs @@ -5,8 +5,6 @@ use anyhow::Result; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::preg::Preg; use shirabe_external_packages::composer::spdx_licenses::spdx_licenses::SpdxLicenses; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::component::console::helper::formatter_helper::FormatterHelper; use shirabe_external_packages::symfony::component::console::input::array_input::ArrayInput; use shirabe_external_packages::symfony::component::console::input::input_interface::InputInterface; @@ -19,8 +17,7 @@ use shirabe_php_shim::{ strtolower, trim, ucwords, }; -use crate::command::base_command::BaseCommand; -use crate::command::completion_trait::CompletionTrait; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::command::package_discovery_trait::PackageDiscoveryTrait; use crate::composer::Composer; use crate::console::input::input_option::InputOption; @@ -38,24 +35,12 @@ use crate::util::silencer::Silencer; #[derive(Debug)] pub struct InitCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, /// @var array<string, string> git_config: Option<IndexMap<String, String>>, } -impl CompletionTrait for InitCommand { - fn require_composer( - &self, - disable_plugins: Option<bool>, - disable_scripts: Option<bool>, - ) -> Composer { - todo!() - } -} - impl PackageDiscoveryTrait for InitCommand { fn get_repos_mut(&mut self) -> &mut Option<CompositeRepository> { todo!() @@ -97,25 +82,22 @@ impl PackageDiscoveryTrait for InitCommand { impl InitCommand { pub fn configure(&mut self) { - let suggest_available_package_incl_platform = - self.suggest_available_package_incl_platform(); - let suggest_available_package_incl_platform2 = - self.suggest_available_package_incl_platform(); - self.inner + // TODO(cli-completion): suggest_available_package_incl_platform() for `require` / `require-dev` + self .set_name("init") .set_description("Creates a basic composer.json file in current directory") .set_definition(vec![ - InputOption::new("name", None, Some(InputOption::VALUE_REQUIRED), "Name of the package", None, vec![]), - InputOption::new("description", None, Some(InputOption::VALUE_REQUIRED), "Description of package", None, vec![]), - InputOption::new("author", None, Some(InputOption::VALUE_REQUIRED), "Author name of package", None, vec![]), - InputOption::new("type", None, Some(InputOption::VALUE_REQUIRED), "Type of package (e.g. library, project, metapackage, composer-plugin)", None, vec![]), - InputOption::new("homepage", None, Some(InputOption::VALUE_REQUIRED), "Homepage of package", None, vec![]), - InputOption::new("require", None, Some(InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED), "Package to require with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or \"foo/bar 1.0.0\"", None, suggest_available_package_incl_platform), - InputOption::new("require-dev", None, Some(InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED), "Package to require for development with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or \"foo/bar 1.0.0\"", None, suggest_available_package_incl_platform2), - InputOption::new("stability", Some(PhpMixed::String("s".to_string())), Some(InputOption::VALUE_REQUIRED), &format!("Minimum stability (empty or one of: {})", implode(", ", &array_keys(&BasePackage::stabilities()))), None, vec![]), - InputOption::new("license", Some(PhpMixed::String("l".to_string())), Some(InputOption::VALUE_REQUIRED), "License of package", None, vec![]), - InputOption::new("repository", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Add custom repositories, either by URL or using JSON arrays", None, vec![]), - InputOption::new("autoload", Some(PhpMixed::String("a".to_string())), Some(InputOption::VALUE_REQUIRED), "Add PSR-4 autoload mapping. Maps your package's namespace to the provided directory. (Expects a relative path, e.g. src/)", None, vec![]), + InputOption::new("name", None, Some(InputOption::VALUE_REQUIRED), "Name of the package", None), + InputOption::new("description", None, Some(InputOption::VALUE_REQUIRED), "Description of package", None), + InputOption::new("author", None, Some(InputOption::VALUE_REQUIRED), "Author name of package", None), + InputOption::new("type", None, Some(InputOption::VALUE_REQUIRED), "Type of package (e.g. library, project, metapackage, composer-plugin)", None), + InputOption::new("homepage", None, Some(InputOption::VALUE_REQUIRED), "Homepage of package", None), + InputOption::new("require", None, Some(InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED), "Package to require with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or \"foo/bar 1.0.0\"", None), + InputOption::new("require-dev", None, Some(InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED), "Package to require for development with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or \"foo/bar 1.0.0\"", None), + InputOption::new("stability", Some(PhpMixed::String("s".to_string())), Some(InputOption::VALUE_REQUIRED), &format!("Minimum stability (empty or one of: {})", implode(", ", &array_keys(&BasePackage::stabilities()))), None), + InputOption::new("license", Some(PhpMixed::String("l".to_string())), Some(InputOption::VALUE_REQUIRED), "License of package", None), + InputOption::new("repository", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Add custom repositories, either by URL or using JSON arrays", None), + InputOption::new("autoload", Some(PhpMixed::String("a".to_string())), Some(InputOption::VALUE_REQUIRED), "Add PSR-4 autoload mapping. Maps your package's namespace to the provided directory. (Expects a relative path, e.g. src/)", None), ]) .set_help( "The <info>init</info> command creates a basic composer.json file\n\ @@ -133,7 +115,7 @@ impl InitCommand { input: &dyn InputInterface, output: &dyn OutputInterface, ) -> Result<i64> { - let io = self.inner.get_io(); + let io = self.get_io(); let allowlist: Vec<String> = vec![ "name".to_string(), @@ -239,7 +221,7 @@ impl InitCommand { .collect() }) .unwrap_or_default(); - let formatted = self.inner.format_requirements(req_list)?; + let formatted = self.format_requirements(req_list)?; if formatted.is_empty() { // PHP: new \stdClass — represented as an empty IndexMap (JSON object) PhpMixed::Array(IndexMap::new()) @@ -267,7 +249,7 @@ impl InitCommand { .collect() }) .unwrap_or_default(); - let formatted = self.inner.format_requirements(req_list)?; + let formatted = self.format_requirements(req_list)?; let value = if formatted.is_empty() { PhpMixed::Array(IndexMap::new()) } else { @@ -303,13 +285,13 @@ impl InitCommand { options.insert("autoload".to_string(), PhpMixed::Array(autoload_obj)); } - let file_obj = JsonFile::new(&Factory::get_composer_file(), None, None); + let file_obj = JsonFile::new(Factory::get_composer_file(), None, None)?; let options_for_encode: IndexMap<String, Box<PhpMixed>> = options .clone() .into_iter() .map(|(k, v)| (k, Box::new(v))) .collect(); - let json = JsonFile::encode(&options_for_encode); + let json = JsonFile::encode(&options_for_encode, 448); if input.is_interactive() { io.write_error( @@ -362,10 +344,11 @@ impl InitCommand { true, io_interface::NORMAL, ); - Silencer::call( - "unlink", - &[PhpMixed::String(file_obj.get_path().to_string())], - ); + let path_to_unlink = file_obj.get_path().to_string(); + let _ = Silencer::call(|| { + shirabe_php_shim::unlink(&path_to_unlink); + Ok::<(), anyhow::Error>(()) + }); return Ok(1); } @@ -374,7 +357,7 @@ impl InitCommand { // --autoload - Create src folder if let Some(ref ap) = autoload_path { - let filesystem = Filesystem::new(); + let filesystem = Filesystem::new(None); filesystem.ensure_directory_exists(ap); // dump-autoload only for projects without added dependencies. @@ -440,7 +423,7 @@ impl InitCommand { } pub(crate) fn initialize(&mut self, input: &dyn InputInterface, output: &dyn OutputInterface) { - self.inner.initialize(input, output); + self.initialize(input, output); if !input.is_interactive() { if input.get_option("name").is_null() { @@ -463,9 +446,9 @@ impl InitCommand { input: &dyn InputInterface, _output: &dyn OutputInterface, ) -> Result<()> { - let io = self.inner.get_io(); + let io = self.get_io(); // @var FormatterHelper $formatter - let formatter: &FormatterHelper = self.inner.get_helper_set().get("formatter"); + let formatter: &FormatterHelper = self.get_helper_set().get("formatter"); // initialize repos if configured let repositories: Vec<String> = input @@ -484,7 +467,7 @@ impl InitCommand { let mut repos: Vec< Box<dyn crate::repository::repository_interface::RepositoryInterface>, - > = vec![Box::new(PlatformRepository::new(vec![], PhpMixed::Null))]; + > = vec![Box::new(PlatformRepository::new(vec![], IndexMap::new())?)]; let mut create_default_packagist_repo = true; for repo in &repositories { let repo_config = @@ -977,7 +960,7 @@ impl InitCommand { return self.git_config.clone().unwrap_or_default(); } - let mut process = ProcessExecutor::new(self.inner.get_io()); + let mut process = ProcessExecutor::new(self.get_io()); let mut output = String::new(); if process.execute( @@ -1059,14 +1042,14 @@ impl InitCommand { fn update_dependencies(&self, output: &dyn OutputInterface) { // PHP try/catch: catch \Exception - let result = self.inner.get_application().and_then(|app| { + let result = self.get_application().and_then(|app| { let update_command = app.find("update")?; app.reset_composer()?; update_command.run(ArrayInput::new(IndexMap::new()), output)?; Ok(()) }); if let Err(_e) = result { - self.inner.get_io().write_error( + self.get_io().write_error( PhpMixed::String( "Could not update dependencies. Run `composer update` to see more information." .to_string(), @@ -1078,14 +1061,14 @@ impl InitCommand { } fn run_dump_autoload_command(&self, output: &dyn OutputInterface) { - let result = self.inner.get_application().and_then(|app| { + let result = self.get_application().and_then(|app| { let command = app.find("dump-autoload")?; app.reset_composer()?; command.run(ArrayInput::new(IndexMap::new()), output)?; Ok(()) }); if let Err(_e) = result { - self.inner.get_io().write_error( + self.get_io().write_error( PhpMixed::String("Could not run dump-autoload.".to_string()), true, io_interface::NORMAL, @@ -1206,30 +1189,12 @@ impl InitCommand { } } -impl BaseCommand for InitCommand { - fn inner(&self) -> &CommandBase { - &self.inner +impl HasBaseCommandData for InitCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for InitCommand {} diff --git a/crates/shirabe/src/command/install_command.rs b/crates/shirabe/src/command/install_command.rs index eac04c3..2b9a87d 100644 --- a/crates/shirabe/src/command/install_command.rs +++ b/crates/shirabe/src/command/install_command.rs @@ -1,16 +1,12 @@ //! ref: composer/src/Composer/Command/InstallCommand.php use anyhow::Result; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; use shirabe_php_shim::PhpMixed; use crate::advisory::auditor::Auditor; -use crate::command::base_command::BaseCommand; -use crate::command::completion_trait::CompletionTrait; -use crate::composer::Composer; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::console::input::input_argument::InputArgument; use crate::console::input::input_option::InputOption; use crate::installer::Installer; @@ -21,51 +17,39 @@ use crate::util::http_downloader::HttpDownloader; #[derive(Debug)] pub struct InstallCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, -} - -impl CompletionTrait for InstallCommand { - fn require_composer( - &self, - disable_plugins: Option<bool>, - disable_scripts: Option<bool>, - ) -> Composer { - todo!() - } + base_command_data: BaseCommandData, } impl InstallCommand { pub fn configure(&mut self) { - let suggest_prefer_install = self.suggest_prefer_install(); - self.inner + // TODO(cli-completion): suggest_prefer_install() for `prefer-install` option + self .set_name("install") - .set_aliases(vec!["i".to_string()]) + .set_aliases(&["i".to_string()]) .set_description("Installs the project dependencies from the composer.lock file if present, or falls back on the composer.json") .set_definition(vec![ - InputOption::new("prefer-source", None, Some(InputOption::VALUE_NONE), "Forces installation from package sources when possible, including VCS information.", None, vec![]), - InputOption::new("prefer-dist", None, Some(InputOption::VALUE_NONE), "Forces installation from package dist (default behavior).", None, vec![]), - InputOption::new("prefer-install", None, Some(InputOption::VALUE_REQUIRED), "Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).", None, suggest_prefer_install), - InputOption::new("dry-run", None, Some(InputOption::VALUE_NONE), "Outputs the operations but will not execute anything (implicitly enables --verbose).", None, vec![]), - InputOption::new("download-only", None, Some(InputOption::VALUE_NONE), "Download only, do not install packages.", None, vec![]), - InputOption::new("dev", None, Some(InputOption::VALUE_NONE), "DEPRECATED: Enables installation of require-dev packages (enabled by default, only present for BC).", None, vec![]), - InputOption::new("no-suggest", None, Some(InputOption::VALUE_NONE), "DEPRECATED: This flag does not exist anymore.", None, vec![]), - InputOption::new("no-dev", None, Some(InputOption::VALUE_NONE), "Disables installation of require-dev packages.", None, vec![]), - InputOption::new("no-security-blocking", None, Some(InputOption::VALUE_NONE), "Allows installing packages with security advisories or that are abandoned (can also be set via the COMPOSER_NO_SECURITY_BLOCKING=1 env var). Only applies when no lock file is present.", None, vec![]), - InputOption::new("no-autoloader", None, Some(InputOption::VALUE_NONE), "Skips autoloader generation", None, vec![]), - InputOption::new("no-progress", None, Some(InputOption::VALUE_NONE), "Do not output download progress.", None, vec![]), - InputOption::new("no-install", None, Some(InputOption::VALUE_NONE), "Do not use, only defined here to catch misuse of the install command.", None, vec![]), - InputOption::new("audit", None, Some(InputOption::VALUE_NONE), "Run an audit after installation is complete.", None, vec![]), - InputOption::new("audit-format", None, Some(InputOption::VALUE_REQUIRED), "Audit output format. Must be \"table\", \"plain\", \"json\", or \"summary\".", Some(PhpMixed::String(Auditor::FORMAT_SUMMARY.to_string())), Auditor::FORMATS.iter().map(|s| s.to_string()).collect()), - InputOption::new("verbose", Some(PhpMixed::String("v|vv|vvv".to_string())), Some(InputOption::VALUE_NONE), "Shows more details including new commits pulled in when updating packages.", None, vec![]), - InputOption::new("optimize-autoloader", Some(PhpMixed::String("o".to_string())), Some(InputOption::VALUE_NONE), "Optimize autoloader during autoloader dump", None, vec![]), - InputOption::new("classmap-authoritative", Some(PhpMixed::String("a".to_string())), Some(InputOption::VALUE_NONE), "Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.", None, vec![]), - InputOption::new("apcu-autoloader", None, Some(InputOption::VALUE_NONE), "Use APCu to cache found/not-found classes.", None, vec![]), - InputOption::new("apcu-autoloader-prefix", None, Some(InputOption::VALUE_REQUIRED), "Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader", None, vec![]), - InputOption::new("ignore-platform-req", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Ignore a specific platform requirement (php & ext- packages).", None, vec![]), - InputOption::new("ignore-platform-reqs", None, Some(InputOption::VALUE_NONE), "Ignore all platform requirements (php & ext- packages).", None, vec![]), - InputArgument::new("packages", Some(InputArgument::IS_ARRAY | InputArgument::OPTIONAL), "Should not be provided, use composer require instead to add a given package to composer.json.", None, vec![]), + InputOption::new("prefer-source", None, Some(InputOption::VALUE_NONE), "Forces installation from package sources when possible, including VCS information.", None), + InputOption::new("prefer-dist", None, Some(InputOption::VALUE_NONE), "Forces installation from package dist (default behavior).", None), + InputOption::new("prefer-install", None, Some(InputOption::VALUE_REQUIRED), "Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).", None), + InputOption::new("dry-run", None, Some(InputOption::VALUE_NONE), "Outputs the operations but will not execute anything (implicitly enables --verbose).", None), + InputOption::new("download-only", None, Some(InputOption::VALUE_NONE), "Download only, do not install packages.", None), + InputOption::new("dev", None, Some(InputOption::VALUE_NONE), "DEPRECATED: Enables installation of require-dev packages (enabled by default, only present for BC).", None), + InputOption::new("no-suggest", None, Some(InputOption::VALUE_NONE), "DEPRECATED: This flag does not exist anymore.", None), + InputOption::new("no-dev", None, Some(InputOption::VALUE_NONE), "Disables installation of require-dev packages.", None), + InputOption::new("no-security-blocking", None, Some(InputOption::VALUE_NONE), "Allows installing packages with security advisories or that are abandoned (can also be set via the COMPOSER_NO_SECURITY_BLOCKING=1 env var). Only applies when no lock file is present.", None), + InputOption::new("no-autoloader", None, Some(InputOption::VALUE_NONE), "Skips autoloader generation", None), + InputOption::new("no-progress", None, Some(InputOption::VALUE_NONE), "Do not output download progress.", None), + InputOption::new("no-install", None, Some(InputOption::VALUE_NONE), "Do not use, only defined here to catch misuse of the install command.", None), + InputOption::new("audit", None, Some(InputOption::VALUE_NONE), "Run an audit after installation is complete.", None), + InputOption::new("audit-format", None, Some(InputOption::VALUE_REQUIRED), "Audit output format. Must be \"table\", \"plain\", \"json\", or \"summary\".", Some(PhpMixed::String(Auditor::FORMAT_SUMMARY.to_string()))), + InputOption::new("verbose", Some(PhpMixed::String("v|vv|vvv".to_string())), Some(InputOption::VALUE_NONE), "Shows more details including new commits pulled in when updating packages.", None), + InputOption::new("optimize-autoloader", Some(PhpMixed::String("o".to_string())), Some(InputOption::VALUE_NONE), "Optimize autoloader during autoloader dump", None), + InputOption::new("classmap-authoritative", Some(PhpMixed::String("a".to_string())), Some(InputOption::VALUE_NONE), "Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.", None), + InputOption::new("apcu-autoloader", None, Some(InputOption::VALUE_NONE), "Use APCu to cache found/not-found classes.", None), + InputOption::new("apcu-autoloader-prefix", None, Some(InputOption::VALUE_REQUIRED), "Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader", None), + InputOption::new("ignore-platform-req", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Ignore a specific platform requirement (php & ext- packages).", None), + InputOption::new("ignore-platform-reqs", None, Some(InputOption::VALUE_NONE), "Ignore all platform requirements (php & ext- packages).", None), + InputArgument::new("packages", Some(InputArgument::IS_ARRAY | InputArgument::OPTIONAL), "Should not be provided, use composer require instead to add a given package to composer.json.", None), ]) .set_help( "The <info>install</info> command reads the composer.lock file from\n\ @@ -78,7 +62,7 @@ impl InstallCommand { } pub fn execute(&self, input: &dyn InputInterface, output: &dyn OutputInterface) -> Result<i64> { - let io = self.inner.get_io(); + let io = self.get_io(); if input.get_option("dev").as_bool().unwrap_or(false) { io.write_error("<warning>You are using the deprecated option \"--dev\". It has no effect and will break in Composer 3.</warning>"); @@ -110,7 +94,7 @@ impl InstallCommand { return Ok(1); } - let composer = self.inner.require_composer()?; + let composer = self.require_composer(None, None)?; if !composer.get_locker().is_locked() && !HttpDownloader::is_curl_enabled() { io.write_error("<warning>Composer is operating significantly slower than normal because you do not have the PHP curl extension enabled.</warning>"); @@ -120,20 +104,19 @@ impl InstallCommand { let command_event = CommandEvent::new( PluginEvents::COMMAND.to_string(), "install".to_string(), - Box::new(input), - Box::new(output), + input, + output, vec![], vec![], ); composer .get_event_dispatcher() - .dispatch(command_event.get_name(), &command_event); + .dispatch(Some(command_event.get_name()), None); let install = Installer::create(io, &composer); let config = composer.get_config(); - let (prefer_source, prefer_dist) = - self.inner.get_preferred_install_options(config, input)?; + let (prefer_source, prefer_dist) = self.get_preferred_install_options(config, input)?; let optimize = input .get_option("optimize-autoloader") @@ -174,11 +157,8 @@ impl InstallCommand { .set_optimize_autoloader(optimize) .set_class_map_authoritative(authoritative) .set_apcu_autoloader(apcu, apcu_prefix.as_deref()) - .set_platform_requirement_filter(self.inner.get_platform_requirement_filter(input)?) - .set_audit_config( - self.inner - .create_audit_config(composer.get_config(), input)?, - ) + .set_platform_requirement_filter(self.get_platform_requirement_filter(input)?) + .set_audit_config(self.create_audit_config(composer.get_config(), input)?) .set_error_on_audit(input.get_option("audit").as_bool().unwrap_or(false)); if input.get_option("no-plugins").as_bool().unwrap_or(false) { @@ -189,30 +169,12 @@ impl InstallCommand { } } -impl BaseCommand for InstallCommand { - fn inner(&self) -> &CommandBase { - &self.inner - } - - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer +impl HasBaseCommandData for InstallCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for InstallCommand {} diff --git a/crates/shirabe/src/command/licenses_command.rs b/crates/shirabe/src/command/licenses_command.rs index 4fa2e72..102221e 100644 --- a/crates/shirabe/src/command/licenses_command.rs +++ b/crates/shirabe/src/command/licenses_command.rs @@ -4,8 +4,6 @@ use std::any::Any; use anyhow::Result; use indexmap::IndexMap; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::console::formatter::output_formatter::OutputFormatter; use shirabe_external_packages::symfony::console::helper::table::Table; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; @@ -13,7 +11,7 @@ use shirabe_external_packages::symfony::console::output::output_interface::Outpu use shirabe_external_packages::symfony::console::style::symfony_style::SymfonyStyle; use shirabe_php_shim::{PhpMixed, RuntimeException, UnexpectedValueException}; -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::console::input::input_option::InputOption; use crate::io::io_interface::IOInterface; @@ -28,15 +26,12 @@ use crate::util::package_sorter::PackageSorter; #[derive(Debug)] pub struct LicensesCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, } impl LicensesCommand { pub fn configure(&mut self) { - self.inner - .set_name("licenses") + self.set_name("licenses") .set_description("Shows information about licenses of dependencies") .set_definition(vec![ InputOption::new( @@ -45,11 +40,6 @@ impl LicensesCommand { Some(InputOption::VALUE_REQUIRED), "Format of the output: text, json or summary", Some(PhpMixed::String("text".to_string())), - vec![ - "text".to_string(), - "json".to_string(), - "summary".to_string(), - ], ), InputOption::new( "no-dev", @@ -57,7 +47,6 @@ impl LicensesCommand { Some(InputOption::VALUE_NONE), "Disables search in require-dev packages.", None, - vec![], ), InputOption::new( "locked", @@ -65,7 +54,6 @@ impl LicensesCommand { Some(InputOption::VALUE_NONE), "Shows licenses from the lock file instead of installed packages.", None, - vec![], ), ]) .set_help( @@ -78,14 +66,14 @@ impl LicensesCommand { } pub fn execute(&self, input: &dyn InputInterface, output: &dyn OutputInterface) -> Result<i64> { - let composer = self.inner.require_composer()?; + let composer = self.require_composer(None, None)?; // TODO(plugin): dispatch COMMAND event for plugin hooks let command_event = CommandEvent::new(PluginEvents::COMMAND, "licenses".to_string(), input, output); composer .get_event_dispatcher() - .dispatch(command_event.get_name(), &command_event); + .dispatch(Some(command_event.get_name()), None); let root = composer.get_package(); @@ -110,7 +98,7 @@ impl LicensesCommand { }; let packages = PackageSorter::sort_packages_alphabetically(packages); - let io = self.inner.get_io(); + let io = self.get_io(); let format = input .get_option("format") @@ -238,7 +226,7 @@ impl LicensesCommand { .collect(), ), ); - io.write(&JsonFile::encode(&output_map)); + io.write(&JsonFile::encode(&output_map, 448)); } "summary" => { let mut used_licenses: IndexMap<String, i64> = IndexMap::new(); @@ -288,30 +276,12 @@ impl LicensesCommand { } } -impl BaseCommand for LicensesCommand { - fn inner(&self) -> &CommandBase { - &self.inner +impl HasBaseCommandData for LicensesCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for LicensesCommand {} diff --git a/crates/shirabe/src/command/outdated_command.rs b/crates/shirabe/src/command/outdated_command.rs index d11d290..bac15cf 100644 --- a/crates/shirabe/src/command/outdated_command.rs +++ b/crates/shirabe/src/command/outdated_command.rs @@ -1,15 +1,12 @@ //! ref: composer/src/Composer/Command/OutdatedCommand.php -use crate::command::base_command::BaseCommand; -use crate::command::completion_trait::CompletionTrait; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::console::input::input_argument::InputArgument; use crate::console::input::input_option::InputOption; use crate::io::io_interface::IOInterface; use anyhow::Result; use indexmap::IndexMap; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::console::input::array_input::ArrayInput; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; @@ -17,44 +14,31 @@ use shirabe_php_shim::PhpMixed; #[derive(Debug)] pub struct OutdatedCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, -} - -impl CompletionTrait for OutdatedCommand { - fn require_composer( - &self, - disable_plugins: Option<bool>, - disable_scripts: Option<bool>, - ) -> Composer { - todo!() - } + base_command_data: BaseCommandData, } impl OutdatedCommand { pub fn configure(&mut self) { - let suggest_installed_package = self.suggest_installed_package(false, false); - let suggest_installed_package_for_ignore = self.suggest_installed_package(false, false); - self.inner + // TODO(cli-completion): suggest_installed_package(false, false) for `package` argument and `--ignore` option + self .set_name("outdated") .set_description("Shows a list of installed packages that have updates available, including their latest version") .set_definition(vec![ - InputArgument::new("package", Some(InputArgument::OPTIONAL), "Package to inspect. Or a name including a wildcard (*) to filter lists of packages instead.", None, suggest_installed_package), - InputOption::new("outdated", Some(PhpMixed::String("o".to_string())), Some(InputOption::VALUE_NONE), "Show only packages that are outdated (this is the default, but present here for compat with `show`", None, vec![]), - InputOption::new("all", Some(PhpMixed::String("a".to_string())), Some(InputOption::VALUE_NONE), "Show all installed packages with their latest versions", None, vec![]), - InputOption::new("locked", None, Some(InputOption::VALUE_NONE), "Shows updates for packages from the lock file, regardless of what is currently in vendor dir", None, vec![]), - InputOption::new("direct", Some(PhpMixed::String("D".to_string())), Some(InputOption::VALUE_NONE), "Shows only packages that are directly required by the root package", None, vec![]), - InputOption::new("strict", None, Some(InputOption::VALUE_NONE), "Return a non-zero exit code when there are outdated packages", None, vec![]), - InputOption::new("major-only", Some(PhpMixed::String("M".to_string())), Some(InputOption::VALUE_NONE), "Show only packages that have major SemVer-compatible updates.", None, vec![]), - InputOption::new("minor-only", Some(PhpMixed::String("m".to_string())), Some(InputOption::VALUE_NONE), "Show only packages that have minor SemVer-compatible updates.", None, vec![]), - InputOption::new("patch-only", Some(PhpMixed::String("p".to_string())), Some(InputOption::VALUE_NONE), "Show only packages that have patch SemVer-compatible updates.", None, vec![]), - InputOption::new("sort-by-age", Some(PhpMixed::String("A".to_string())), Some(InputOption::VALUE_NONE), "Displays the installed version's age, and sorts packages oldest first.", None, vec![]), - InputOption::new("format", Some(PhpMixed::String("f".to_string())), Some(InputOption::VALUE_REQUIRED), "Format of the output: text or json", Some(PhpMixed::String("text".to_string())), vec!["json".to_string(), "text".to_string()]), - InputOption::new("ignore", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Ignore specified package(s). Can contain wildcards (*). Use it if you don't want to be informed about new versions of some packages.", None, suggest_installed_package_for_ignore), - InputOption::new("no-dev", None, Some(InputOption::VALUE_NONE), "Disables search in require-dev packages.", None, vec![]), - InputOption::new("ignore-platform-req", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Ignore a specific platform requirement (php & ext- packages). Use with the --outdated option", None, vec![]), - InputOption::new("ignore-platform-reqs", None, Some(InputOption::VALUE_NONE), "Ignore all platform requirements (php & ext- packages). Use with the --outdated option", None, vec![]), + InputArgument::new("package", Some(InputArgument::OPTIONAL), "Package to inspect. Or a name including a wildcard (*) to filter lists of packages instead.", None), + InputOption::new("outdated", Some(PhpMixed::String("o".to_string())), Some(InputOption::VALUE_NONE), "Show only packages that are outdated (this is the default, but present here for compat with `show`", None), + InputOption::new("all", Some(PhpMixed::String("a".to_string())), Some(InputOption::VALUE_NONE), "Show all installed packages with their latest versions", None), + InputOption::new("locked", None, Some(InputOption::VALUE_NONE), "Shows updates for packages from the lock file, regardless of what is currently in vendor dir", None), + InputOption::new("direct", Some(PhpMixed::String("D".to_string())), Some(InputOption::VALUE_NONE), "Shows only packages that are directly required by the root package", None), + InputOption::new("strict", None, Some(InputOption::VALUE_NONE), "Return a non-zero exit code when there are outdated packages", None), + InputOption::new("major-only", Some(PhpMixed::String("M".to_string())), Some(InputOption::VALUE_NONE), "Show only packages that have major SemVer-compatible updates.", None), + InputOption::new("minor-only", Some(PhpMixed::String("m".to_string())), Some(InputOption::VALUE_NONE), "Show only packages that have minor SemVer-compatible updates.", None), + InputOption::new("patch-only", Some(PhpMixed::String("p".to_string())), Some(InputOption::VALUE_NONE), "Show only packages that have patch SemVer-compatible updates.", None), + InputOption::new("sort-by-age", Some(PhpMixed::String("A".to_string())), Some(InputOption::VALUE_NONE), "Displays the installed version's age, and sorts packages oldest first.", None), + InputOption::new("format", Some(PhpMixed::String("f".to_string())), Some(InputOption::VALUE_REQUIRED), "Format of the output: text or json", Some(PhpMixed::String("text".to_string()))), + InputOption::new("ignore", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Ignore specified package(s). Can contain wildcards (*). Use it if you don't want to be informed about new versions of some packages.", None), + InputOption::new("no-dev", None, Some(InputOption::VALUE_NONE), "Disables search in require-dev packages.", None), + InputOption::new("ignore-platform-req", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Ignore a specific platform requirement (php & ext- packages). Use with the --outdated option", None), + InputOption::new("ignore-platform-reqs", None, Some(InputOption::VALUE_NONE), "Ignore all platform requirements (php & ext- packages). Use with the --outdated option", None), ]) .set_help( "The outdated command is just a proxy for `composer show -l`\n\n\ @@ -140,7 +124,9 @@ impl OutdatedCommand { let input = ArrayInput::new(args); - self.inner.get_application().run(&input, output) + // TODO(phase-b): convert ArrayInput/output references to dyn trait objects expected by Application::run + let _ = input; + self.get_application()?.run(None, None) } pub fn is_proxy_command(&self) -> bool { @@ -148,30 +134,12 @@ impl OutdatedCommand { } } -impl BaseCommand for OutdatedCommand { - fn inner(&self) -> &CommandBase { - &self.inner - } - - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer +impl HasBaseCommandData for OutdatedCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for OutdatedCommand {} diff --git a/crates/shirabe/src/command/package_discovery_trait.rs b/crates/shirabe/src/command/package_discovery_trait.rs index 164cc53..501ae5d 100644 --- a/crates/shirabe/src/command/package_discovery_trait.rs +++ b/crates/shirabe/src/command/package_discovery_trait.rs @@ -28,7 +28,7 @@ use crate::package::version::version_selector::VersionSelector; use crate::repository::composite_repository::CompositeRepository; use crate::repository::platform_repository::PlatformRepository; use crate::repository::repository_factory::RepositoryFactory; -use crate::repository::repository_interface::SearchResult; +use crate::repository::repository_interface::{RepositoryInterface, SearchResult}; use crate::repository::repository_set::RepositorySet; use crate::util::filesystem::Filesystem; @@ -161,12 +161,12 @@ pub trait PackageDiscoveryTrait { requirement.get("version").map(|s| s.as_str()).unwrap_or(""), ) { - io.write_error( - PhpMixed::String(format!( + io.write_error3( + &format!( "<warning>The \"{}\" constraint for \"{}\" appears too strict and will likely not match what you want. See https://getcomposer.org/constraints</warning>", requirement.get("version").map(|s| s.as_str()).unwrap_or(""), requirement.get("name").map(|s| s.as_str()).unwrap_or(""), - )), + ), true, io_interface::NORMAL, ); @@ -188,8 +188,8 @@ pub trait PackageDiscoveryTrait { if use_best_version_constraint { requirement.insert("version".to_string(), version.clone()); - io.write_error( - PhpMixed::String(sprintf( + io.write_error3( + &sprintf( "Using version <info>%s</info> for <info>%s</info>", &[ PhpMixed::String(version), @@ -197,7 +197,7 @@ pub trait PackageDiscoveryTrait { requirement.get("name").cloned().unwrap_or_default(), ), ], - )), + ), true, io_interface::NORMAL, ); @@ -219,7 +219,7 @@ pub trait PackageDiscoveryTrait { let version_parser = VersionParser::new(); // Collect existing packages - let composer = self.try_composer(); + let composer = self.try_composer(None, None); let mut installed_repo: Option<_> = None; if let Some(c) = &composer { installed_repo = Some(c.get_repository_manager().get_local_repository()); @@ -319,33 +319,24 @@ pub trait PackageDiscoveryTrait { )); } - io.write_error( - PhpMixed::List(vec![ - Box::new(PhpMixed::String(String::new())), - Box::new(PhpMixed::String(sprintf( - "Found <info>%s</info> packages matching <info>%s</info>", - &[ - PhpMixed::Int(matches.len() as i64), - PhpMixed::String(package.clone()), - ], - ))), - Box::new(PhpMixed::String(String::new())), - ]), - true, - io_interface::NORMAL, - ); - - io.write_error( - PhpMixed::List( - choices - .iter() - .map(|s| Box::new(PhpMixed::String(s.clone()))) - .collect(), + io.write_error3("", true, io_interface::NORMAL); + io.write_error3( + &sprintf( + "Found <info>%s</info> packages matching <info>%s</info>", + &[ + PhpMixed::Int(matches.len() as i64), + PhpMixed::String(package.clone()), + ], ), true, io_interface::NORMAL, ); - io.write_error(PhpMixed::String(String::new()), true, io_interface::NORMAL); + io.write_error3("", true, io_interface::NORMAL); + + for choice in &choices { + io.write_error3(choice, true, io_interface::NORMAL); + } + io.write_error3("", true, io_interface::NORMAL); let matches_clone = matches.clone(); let version_parser_clone = version_parser.clone(); @@ -432,14 +423,14 @@ pub trait PackageDiscoveryTrait { fixed, )?; - io.write_error( - PhpMixed::String(sprintf( + io.write_error3( + &sprintf( "Using version <info>%s</info> for <info>%s</info>", &[ PhpMixed::String(c.clone()), PhpMixed::String(package.clone()), ], - )), + ), true, io_interface::NORMAL, ); diff --git a/crates/shirabe/src/command/prohibits_command.rs b/crates/shirabe/src/command/prohibits_command.rs index 6add126..26c17f2 100644 --- a/crates/shirabe/src/command/prohibits_command.rs +++ b/crates/shirabe/src/command/prohibits_command.rs @@ -1,12 +1,7 @@ //! ref: composer/src/Composer/Command/ProhibitsCommand.php -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; - -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::command::base_dependency_command::BaseDependencyCommand; -use crate::command::completion_trait::CompletionTrait; -use crate::composer::Composer; use crate::console::input::input_argument::InputArgument; use crate::console::input::input_option::InputOption; use crate::io::io_interface::IOInterface; @@ -15,62 +10,50 @@ use shirabe_external_packages::symfony::console::output::output_interface::Outpu #[derive(Debug)] pub struct ProhibitsCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, colors: Vec<String>, } -impl CompletionTrait for ProhibitsCommand { - fn require_composer( - &self, - disable_plugins: Option<bool>, - disable_scripts: Option<bool>, - ) -> Composer { - todo!() - } -} - impl ProhibitsCommand { pub fn configure(&mut self) { - let package_suggestions = self.suggest_available_package(); - self.inner - .set_name("prohibits") - .set_aliases(vec!["why-not".to_string()]) + // TODO(cli-completion): suggest_available_package() for `package` argument + self.set_name("prohibits") + .set_aliases(&["why-not".to_string()]) .set_description("Shows which packages prevent the given package from being installed") .set_definition(vec![ InputArgument::new( BaseDependencyCommand::ARGUMENT_PACKAGE, - InputArgument::REQUIRED, + Some(InputArgument::REQUIRED), "Package to inspect", None, - package_suggestions, ), InputArgument::new( BaseDependencyCommand::ARGUMENT_CONSTRAINT, - InputArgument::REQUIRED, + Some(InputArgument::REQUIRED), "Version constraint, which version you expected to be installed", None, - None, ), InputOption::new( BaseDependencyCommand::OPTION_RECURSIVE, - Some("r"), - InputOption::VALUE_NONE, + Some(shirabe_php_shim::PhpMixed::String("r".to_string())), + Some(InputOption::VALUE_NONE), "Recursively resolves up to the root package", + None, ), InputOption::new( BaseDependencyCommand::OPTION_TREE, - Some("t"), - InputOption::VALUE_NONE, + Some(shirabe_php_shim::PhpMixed::String("t".to_string())), + Some(InputOption::VALUE_NONE), "Prints the results as a nested tree", + None, ), InputOption::new( "locked", None, - InputOption::VALUE_NONE, + Some(InputOption::VALUE_NONE), "Read dependency information from composer.lock", + None, ), ]) .set_help( @@ -81,33 +64,9 @@ impl ProhibitsCommand { } pub fn execute(&self, input: &dyn InputInterface, output: &dyn OutputInterface) -> i64 { - self.inner.do_execute(input, output, true) - } -} - -impl BaseCommand for ProhibitsCommand { - fn inner(&self) -> &CommandBase { - &self.inner - } - - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + // TODO(phase-b): wire `do_execute` from BaseDependencyCommand trait + let _ = (input, output); + todo!() } } @@ -121,4 +80,12 @@ impl BaseDependencyCommand for ProhibitsCommand { } } -impl Command for ProhibitsCommand {} +impl HasBaseCommandData for ProhibitsCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data + } + + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data + } +} diff --git a/crates/shirabe/src/command/reinstall_command.rs b/crates/shirabe/src/command/reinstall_command.rs index d16b001..7e703c4 100644 --- a/crates/shirabe/src/command/reinstall_command.rs +++ b/crates/shirabe/src/command/reinstall_command.rs @@ -4,14 +4,11 @@ use std::any::Any; use anyhow::Result; use shirabe_external_packages::composer::pcre::preg::Preg; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; use shirabe_php_shim::InvalidArgumentException; -use crate::command::base_command::BaseCommand; -use crate::command::completion_trait::CompletionTrait; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::console::input::input_argument::InputArgument; use crate::console::input::input_option::InputOption; @@ -29,43 +26,29 @@ use crate::util::platform::Platform; #[derive(Debug)] pub struct ReinstallCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, -} - -impl CompletionTrait for ReinstallCommand { - fn require_composer( - &self, - disable_plugins: Option<bool>, - disable_scripts: Option<bool>, - ) -> Composer { - todo!() - } + base_command_data: BaseCommandData, } impl ReinstallCommand { pub fn configure(&mut self) { - let suggest_prefer_install = self.suggest_prefer_install(); - let suggest_installed_package_types = self.suggest_installed_package_types(false); - let suggest_installed_package = self.suggest_installed_package(false); - self.inner + // TODO(cli-completion): suggest_prefer_install / suggest_installed_package_types / suggest_installed_package + self .set_name("reinstall") .set_description("Uninstalls and reinstalls the given package names") .set_definition(vec![ - InputOption::new("prefer-source", None, Some(InputOption::VALUE_NONE), "Forces installation from package sources when possible, including VCS information.", None, vec![]), - InputOption::new("prefer-dist", None, Some(InputOption::VALUE_NONE), "Forces installation from package dist (default behavior).", None, vec![]), - InputOption::new("prefer-install", None, Some(InputOption::VALUE_REQUIRED), "Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).", None, suggest_prefer_install), - InputOption::new("no-autoloader", None, Some(InputOption::VALUE_NONE), "Skips autoloader generation", None, vec![]), - InputOption::new("no-progress", None, Some(InputOption::VALUE_NONE), "Do not output download progress.", None, vec![]), - InputOption::new("optimize-autoloader", Some(shirabe_php_shim::PhpMixed::String("o".to_string())), Some(InputOption::VALUE_NONE), "Optimize autoloader during autoloader dump", None, vec![]), - InputOption::new("classmap-authoritative", Some(shirabe_php_shim::PhpMixed::String("a".to_string())), Some(InputOption::VALUE_NONE), "Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.", None, vec![]), - InputOption::new("apcu-autoloader", None, Some(InputOption::VALUE_NONE), "Use APCu to cache found/not-found classes.", None, vec![]), - InputOption::new("apcu-autoloader-prefix", None, Some(InputOption::VALUE_REQUIRED), "Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader", None, vec![]), - InputOption::new("ignore-platform-req", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Ignore a specific platform requirement (php & ext- packages).", None, vec![]), - InputOption::new("ignore-platform-reqs", None, Some(InputOption::VALUE_NONE), "Ignore all platform requirements (php & ext- packages).", None, vec![]), - InputOption::new("type", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Filter packages to reinstall by type(s)", None, suggest_installed_package_types), - InputArgument::new("packages", Some(InputArgument::IS_ARRAY), "List of package names to reinstall, can include a wildcard (*) to match any substring.", None, suggest_installed_package), + InputOption::new("prefer-source", None, Some(InputOption::VALUE_NONE), "Forces installation from package sources when possible, including VCS information.", None), + InputOption::new("prefer-dist", None, Some(InputOption::VALUE_NONE), "Forces installation from package dist (default behavior).", None), + InputOption::new("prefer-install", None, Some(InputOption::VALUE_REQUIRED), "Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).", None), + InputOption::new("no-autoloader", None, Some(InputOption::VALUE_NONE), "Skips autoloader generation", None), + InputOption::new("no-progress", None, Some(InputOption::VALUE_NONE), "Do not output download progress.", None), + InputOption::new("optimize-autoloader", Some(shirabe_php_shim::PhpMixed::String("o".to_string())), Some(InputOption::VALUE_NONE), "Optimize autoloader during autoloader dump", None), + InputOption::new("classmap-authoritative", Some(shirabe_php_shim::PhpMixed::String("a".to_string())), Some(InputOption::VALUE_NONE), "Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.", None), + InputOption::new("apcu-autoloader", None, Some(InputOption::VALUE_NONE), "Use APCu to cache found/not-found classes.", None), + InputOption::new("apcu-autoloader-prefix", None, Some(InputOption::VALUE_REQUIRED), "Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader", None), + InputOption::new("ignore-platform-req", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Ignore a specific platform requirement (php & ext- packages).", None), + InputOption::new("ignore-platform-reqs", None, Some(InputOption::VALUE_NONE), "Ignore all platform requirements (php & ext- packages).", None), + InputOption::new("type", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Filter packages to reinstall by type(s)", None), + InputArgument::new("packages", Some(InputArgument::IS_ARRAY), "List of package names to reinstall, can include a wildcard (*) to match any substring.", None), ]) .set_help( "The <info>reinstall</info> command looks up installed packages by name,\n\ @@ -78,9 +61,9 @@ impl ReinstallCommand { } pub fn execute(&self, input: &dyn InputInterface, output: &dyn OutputInterface) -> Result<i64> { - let io = self.inner.get_io(); + let io = self.get_io(); - let composer = self.inner.require_composer()?; + let composer = self.require_composer(None, None)?; let local_repo = composer.get_repository_manager().get_local_repository(); let mut packages_to_reinstall: Vec< @@ -198,17 +181,16 @@ impl ReinstallCommand { let command_event = CommandEvent::new( PluginEvents::COMMAND.to_string(), "reinstall".to_string(), - Box::new(input), - Box::new(output), + input, + output, vec![], vec![], ); let event_dispatcher = composer.get_event_dispatcher(); - event_dispatcher.dispatch(command_event.get_name(), &command_event); + event_dispatcher.dispatch(Some(command_event.get_name()), None); let config = composer.get_config(); - let (prefer_source, prefer_dist) = - self.inner.get_preferred_install_options(config, input)?; + let (prefer_source, prefer_dist) = self.get_preferred_install_options(config, input)?; let installation_manager = composer.get_installation_manager(); let download_manager = composer.get_download_manager(); @@ -220,13 +202,20 @@ impl ReinstallCommand { installation_manager.disable_plugins(); } - download_manager.set_prefer_source(prefer_source); - download_manager.set_prefer_dist(prefer_dist); + download_manager + .borrow_mut() + .set_prefer_source(prefer_source); + download_manager.borrow_mut().set_prefer_dist(prefer_dist); let dev_mode = local_repo.get_dev_mode().unwrap_or(true); Platform::put_env("COMPOSER_DEV_MODE", if dev_mode { "1" } else { "0" }); - event_dispatcher.dispatch_script(ScriptEvents::PRE_INSTALL_CMD, dev_mode); + event_dispatcher.dispatch_script( + ScriptEvents::PRE_INSTALL_CMD, + dev_mode, + vec![], + indexmap::IndexMap::new(), + ); installation_manager.execute(local_repo, uninstall_operations, dev_mode); installation_manager.execute(local_repo, install_operations, dev_mode); @@ -259,9 +248,7 @@ impl ReinstallCommand { let generator = composer.get_autoload_generator(); generator.set_class_map_authoritative(authoritative); generator.set_apcu(apcu, apcu_prefix.as_deref()); - generator.set_platform_requirement_filter( - self.inner.get_platform_requirement_filter(input)?, - ); + generator.set_platform_requirement_filter(self.get_platform_requirement_filter(input)?); generator.dump( config, local_repo, @@ -274,36 +261,23 @@ impl ReinstallCommand { ); } - event_dispatcher.dispatch_script(ScriptEvents::POST_INSTALL_CMD, dev_mode); + event_dispatcher.dispatch_script( + ScriptEvents::POST_INSTALL_CMD, + dev_mode, + vec![], + indexmap::IndexMap::new(), + ); Ok(0) } } -impl BaseCommand for ReinstallCommand { - fn inner(&self) -> &CommandBase { - &self.inner +impl HasBaseCommandData for ReinstallCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for ReinstallCommand {} diff --git a/crates/shirabe/src/command/remove_command.rs b/crates/shirabe/src/command/remove_command.rs index c9e967b..53bcdc9 100644 --- a/crates/shirabe/src/command/remove_command.rs +++ b/crates/shirabe/src/command/remove_command.rs @@ -2,17 +2,15 @@ use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::preg::Preg; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::component::console::exception::invalid_argument_exception::InvalidArgumentException; use shirabe_external_packages::symfony::component::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::component::console::output::output_interface::OutputInterface; use shirabe_php_shim::{PhpMixed, UnexpectedValueException, array_map, strtolower}; use crate::advisory::auditor::Auditor; -use crate::command::base_command::BaseCommand; -use crate::command::completion_trait::CompletionTrait; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; +use crate::config::config_source_interface::ConfigSourceInterface; use crate::config::json_config_source::JsonConfigSource; use crate::console::input::input_argument::InputArgument; use crate::console::input::input_option::InputOption; @@ -23,20 +21,19 @@ use crate::io::io_interface::IOInterface; use crate::json::json_file::JsonFile; use crate::package::base_package; use crate::package::base_package::BasePackage; +use crate::repository::canonical_packages_trait::CanonicalPackagesTrait; #[derive(Debug)] pub struct RemoveCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, } impl RemoveCommand { pub fn configure(&mut self) { - let suggest_root_requirement = self.suggest_root_requirement(); - self.inner + // TODO(cli-completion): suggest_root_requirement() for `packages` argument + self .set_name("remove") - .set_aliases(vec!["rm".to_string(), "uninstall".to_string()]) + .set_aliases(&["rm".to_string(), "uninstall".to_string()]) .set_description("Removes a package from the require or require-dev") .set_definition(vec![ InputArgument::new( @@ -44,7 +41,6 @@ impl RemoveCommand { Some(InputArgument::IS_ARRAY), "Packages that should be removed.", None, - suggest_root_requirement, ), InputOption::new( "dev", @@ -52,7 +48,6 @@ impl RemoveCommand { Some(InputOption::VALUE_NONE), "Removes a package from the require-dev section.", None, - vec![], ), InputOption::new( "dry-run", @@ -60,7 +55,6 @@ impl RemoveCommand { Some(InputOption::VALUE_NONE), "Outputs the operations but will not execute anything (implicitly enables --verbose).", None, - vec![], ), InputOption::new( "no-progress", @@ -68,7 +62,6 @@ impl RemoveCommand { Some(InputOption::VALUE_NONE), "Do not output download progress.", None, - vec![], ), InputOption::new( "no-update", @@ -76,7 +69,6 @@ impl RemoveCommand { Some(InputOption::VALUE_NONE), "Disables the automatic update of the dependencies (implies --no-install).", None, - vec![], ), InputOption::new( "no-install", @@ -84,7 +76,6 @@ impl RemoveCommand { Some(InputOption::VALUE_NONE), "Skip the install step after updating the composer.lock file.", None, - vec![], ), InputOption::new( "no-audit", @@ -92,7 +83,6 @@ impl RemoveCommand { Some(InputOption::VALUE_NONE), "Skip the audit step after updating the composer.lock file (can also be set via the COMPOSER_NO_AUDIT=1 env var).", None, - vec![], ), InputOption::new( "audit-format", @@ -100,7 +90,6 @@ impl RemoveCommand { Some(InputOption::VALUE_REQUIRED), "Audit output format. Must be \"table\", \"plain\", \"json\", or \"summary\".", Some(PhpMixed::String(Auditor::FORMAT_SUMMARY.to_string())), - Auditor::FORMATS.to_vec(), ), InputOption::new( "no-security-blocking", @@ -108,7 +97,6 @@ impl RemoveCommand { Some(InputOption::VALUE_NONE), "Allows installing packages with security advisories or that are abandoned (can also be set via the COMPOSER_NO_SECURITY_BLOCKING=1 env var).", None, - vec![], ), InputOption::new( "update-no-dev", @@ -116,7 +104,6 @@ impl RemoveCommand { Some(InputOption::VALUE_NONE), "Run the dependency update with the --no-dev option.", None, - vec![], ), InputOption::new( "update-with-dependencies", @@ -124,7 +111,6 @@ impl RemoveCommand { Some(InputOption::VALUE_NONE), "Allows inherited dependencies to be updated with explicit dependencies (can also be set via the COMPOSER_WITH_DEPENDENCIES=1 env var). (Deprecated, is now default behavior)", None, - vec![], ), InputOption::new( "update-with-all-dependencies", @@ -132,7 +118,6 @@ impl RemoveCommand { Some(InputOption::VALUE_NONE), "Allows all inherited dependencies to be updated, including those that are root requirements (can also be set via the COMPOSER_WITH_ALL_DEPENDENCIES=1 env var).", None, - vec![], ), InputOption::new( "with-all-dependencies", @@ -140,7 +125,6 @@ impl RemoveCommand { Some(InputOption::VALUE_NONE), "Alias for --update-with-all-dependencies", None, - vec![], ), InputOption::new( "no-update-with-dependencies", @@ -148,7 +132,6 @@ impl RemoveCommand { Some(InputOption::VALUE_NONE), "Does not allow inherited dependencies to be updated with explicit dependencies.", None, - vec![], ), InputOption::new( "minimal-changes", @@ -156,7 +139,6 @@ impl RemoveCommand { Some(InputOption::VALUE_NONE), "During an update with -w/-W, only perform absolutely necessary changes to transitive dependencies (can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var).", None, - vec![], ), InputOption::new( "unused", @@ -164,7 +146,6 @@ impl RemoveCommand { Some(InputOption::VALUE_NONE), "Remove all packages which are locked but not required by any other package.", None, - vec![], ), InputOption::new( "ignore-platform-req", @@ -172,7 +153,6 @@ impl RemoveCommand { Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Ignore a specific platform requirement (php & ext- packages).", None, - vec![], ), InputOption::new( "ignore-platform-reqs", @@ -180,7 +160,6 @@ impl RemoveCommand { Some(InputOption::VALUE_NONE), "Ignore all platform requirements (php & ext- packages).", None, - vec![], ), InputOption::new( "optimize-autoloader", @@ -188,7 +167,6 @@ impl RemoveCommand { Some(InputOption::VALUE_NONE), "Optimize autoloader during autoloader dump", None, - vec![], ), InputOption::new( "classmap-authoritative", @@ -196,7 +174,6 @@ impl RemoveCommand { Some(InputOption::VALUE_NONE), "Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.", None, - vec![], ), InputOption::new( "apcu-autoloader", @@ -204,7 +181,6 @@ impl RemoveCommand { Some(InputOption::VALUE_NONE), "Use APCu to cache found/not-found classes.", None, - vec![], ), InputOption::new( "apcu-autoloader-prefix", @@ -212,7 +188,6 @@ impl RemoveCommand { Some(InputOption::VALUE_REQUIRED), "Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader", None, - vec![], ), ]) .set_help( @@ -252,7 +227,7 @@ impl RemoveCommand { .unwrap_or_default(); if input.get_option("unused").as_bool().unwrap_or(false) { - let composer = self.require_composer()?; + let composer = self.require_composer(None, None)?; let locker = composer.get_locker(); if !locker.is_locked() { return Err(anyhow::anyhow!(UnexpectedValueException { @@ -263,7 +238,7 @@ impl RemoveCommand { })); } - let locked_packages = locker.get_locked_repository()?.get_packages(); + let locked_packages = locker.get_locked_repository(true)?.get_packages(); let mut required: IndexMap<String, bool> = IndexMap::new(); for link in composer @@ -312,13 +287,14 @@ impl RemoveCommand { } } - let file = Factory::get_composer_file(); + let file = Factory::get_composer_file()?; - let json_file = JsonFile::new(&file, None, None); + let json_file = JsonFile::new(file.clone(), None, None)?; let composer_data = json_file.read()?; let composer_backup = std::fs::read_to_string(json_file.get_path())?; - let json = JsonConfigSource::new(&json_file); + let json_file_for_source = JsonFile::new(file, None, None)?; + let json = JsonConfigSource::new(json_file_for_source, false); let r#type = if input.get_option("dev").as_bool().unwrap_or(false) { "require-dev" @@ -484,14 +460,14 @@ impl RemoveCommand { } // TODO(plugin): deactivate installed plugins - if let Some(composer_opt) = self.try_composer() { + if let Some(composer_opt) = self.try_composer(None, None) { composer_opt .get_plugin_manager() .deactivate_installed_plugins(); } self.reset_composer(); - let composer = self.require_composer()?; + let composer = self.require_composer(None, None)?; if dry_run { let root_package = composer.get_package(); @@ -680,30 +656,12 @@ impl RemoveCommand { } } -impl BaseCommand for RemoveCommand { - fn inner(&self) -> &CommandBase { - &self.inner +impl HasBaseCommandData for RemoveCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for RemoveCommand {} diff --git a/crates/shirabe/src/command/repository_command.rs b/crates/shirabe/src/command/repository_command.rs index 79b4a15..2aea141 100644 --- a/crates/shirabe/src/command/repository_command.rs +++ b/crates/shirabe/src/command/repository_command.rs @@ -2,19 +2,17 @@ use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::preg::Preg; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; -use shirabe_external_packages::symfony::console::completion::completion_input::CompletionInput; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; use shirabe_php_shim::{ InvalidArgumentException, PHP_URL_HOST, PhpMixed, RuntimeException, parse_url, strtolower, }; -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::command::base_config_command::BaseConfigCommand; use crate::composer::Composer; use crate::config::Config; +use crate::config::config_source_interface::ConfigSourceInterface; use crate::config::json_config_source::JsonConfigSource; use crate::console::input::input_argument::InputArgument; use crate::console::input::input_option::InputOption; @@ -24,9 +22,7 @@ use crate::json::json_file::JsonFile; #[derive(Debug)] pub struct RepositoryCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, config: Option<Config>, config_file: Option<JsonFile>, @@ -35,24 +31,21 @@ pub struct RepositoryCommand { impl RepositoryCommand { pub fn configure(&mut self) { - let suggest_repo_names_before = self.suggest_repo_names(); - let suggest_repo_names_after = self.suggest_repo_names(); - let suggest_repo_names_name = self.suggest_repo_names(); - let suggest_type_for_add = Self::suggest_type_for_add(); - self.inner.inner + // TODO(cli-completion): suggest_repo_names() / suggest_type_for_add() + self .set_name("repository") - .set_aliases(vec!["repo".to_string()]) + .set_aliases(&["repo".to_string()]) .set_description("Manages repositories") .set_definition(vec![ - InputOption::new("global", Some(PhpMixed::String("g".to_string())), Some(InputOption::VALUE_NONE), "Apply command to the global config file", None, vec![]), - InputOption::new("file", Some(PhpMixed::String("f".to_string())), Some(InputOption::VALUE_REQUIRED), "If you want to choose a different composer.json or config.json", None, vec![]), - InputOption::new("append", None, Some(InputOption::VALUE_NONE), "When adding a repository, append it (lower priority) instead of prepending it", None, vec![]), - InputOption::new("before", None, Some(InputOption::VALUE_REQUIRED), "When adding a repository, insert it before the given repository name", None, suggest_repo_names_before), - InputOption::new("after", None, Some(InputOption::VALUE_REQUIRED), "When adding a repository, insert it after the given repository name", None, suggest_repo_names_after), - InputArgument::new("action", Some(InputArgument::OPTIONAL), "Action to perform: list, add, remove, set-url, get-url, enable, disable", Some(PhpMixed::String("list".to_string())), vec!["list", "add", "remove", "set-url", "get-url", "enable", "disable"]), - InputArgument::new("name", Some(InputArgument::OPTIONAL), "Repository name (or special name packagist.org for enable/disable)", None, suggest_repo_names_name), - InputArgument::new("arg1", Some(InputArgument::OPTIONAL), "Type for add, or new URL for set-url, or JSON config for add", None, suggest_type_for_add), - InputArgument::new("arg2", Some(InputArgument::OPTIONAL), "URL for add (if not using JSON)", None, vec![]), + InputOption::new("global", Some(PhpMixed::String("g".to_string())), Some(InputOption::VALUE_NONE), "Apply command to the global config file", None), + InputOption::new("file", Some(PhpMixed::String("f".to_string())), Some(InputOption::VALUE_REQUIRED), "If you want to choose a different composer.json or config.json", None), + InputOption::new("append", None, Some(InputOption::VALUE_NONE), "When adding a repository, append it (lower priority) instead of prepending it", None), + InputOption::new("before", None, Some(InputOption::VALUE_REQUIRED), "When adding a repository, insert it before the given repository name", None), + InputOption::new("after", None, Some(InputOption::VALUE_REQUIRED), "When adding a repository, insert it after the given repository name", None), + InputArgument::new("action", Some(InputArgument::OPTIONAL), "Action to perform: list, add, remove, set-url, get-url, enable, disable", Some(PhpMixed::String("list".to_string()))), + InputArgument::new("name", Some(InputArgument::OPTIONAL), "Repository name (or special name packagist.org for enable/disable)", None), + InputArgument::new("arg1", Some(InputArgument::OPTIONAL), "Type for add, or new URL for set-url, or JSON config for add", None), + InputArgument::new("arg2", Some(InputArgument::OPTIONAL), "URL for add (if not using JSON)", None), ]) .set_help( "This command lets you manage repositories in your composer.json.\n\n\ @@ -98,7 +91,7 @@ impl RepositoryCommand { .as_string() .map(|s| s.to_string()); - let config_data = self.inner.config_file.as_ref().unwrap().read()?; + let config_data = self.config_file.as_ref().unwrap().read()?; let config_file_path = self .inner .config_file @@ -106,12 +99,11 @@ impl RepositoryCommand { .unwrap() .get_path() .to_string(); - self.inner - .config + self.config .as_mut() .unwrap() .merge(config_data, &config_file_path); - let repos = self.inner.config.as_ref().unwrap().get_repositories(); + let repos = self.config.as_ref().unwrap().get_repositories(); match action.as_str() { "list" | "ls" | "show" => { @@ -173,21 +165,17 @@ impl RepositoryCommand { } let reference_name = before.as_deref().or(after.as_deref()).unwrap(); let offset: i64 = if after.is_some() { 1 } else { 0 }; - self.inner - .config_source - .as_mut() - .unwrap() - .insert_repository( - name.as_deref().unwrap(), - repo_config, - reference_name, - offset, - ); + self.config_source.as_mut().unwrap().insert_repository( + name.as_deref().unwrap(), + repo_config, + reference_name, + offset, + ); return Ok(0); } let append = input.get_option("append").as_bool().unwrap_or(false); - self.inner.config_source.as_mut().unwrap().add_repository( + self.config_source.as_mut().unwrap().add_repository( name.as_deref().unwrap(), repo_config, append, @@ -202,13 +190,12 @@ impl RepositoryCommand { })); } let name_str = name.as_deref().unwrap(); - self.inner - .config_source + self.config_source .as_mut() .unwrap() .remove_repository(name_str); if ["packagist", "packagist.org"].contains(&name_str) { - self.inner.config_source.as_mut().unwrap().add_repository( + self.config_source.as_mut().unwrap().add_repository( "packagist.org", PhpMixed::Bool(false), false, @@ -223,8 +210,7 @@ impl RepositoryCommand { code: 0, })); } - self.inner - .config_source + self.config_source .as_mut() .unwrap() .set_repository_url(name.as_deref().unwrap(), arg1.as_deref().unwrap()); @@ -242,7 +228,7 @@ impl RepositoryCommand { if let PhpMixed::Array(ref repo_map) = *repo { let url = repo_map.get("url").and_then(|v| v.as_string()); if let Some(url) = url { - self.inner.inner.get_io().write(url); + self.get_io().write(url); return Ok(0); } return Err(anyhow::anyhow!(InvalidArgumentException { @@ -257,7 +243,7 @@ impl RepositoryCommand { if n == name_str { let url = repo_map.get("url").and_then(|v| v.as_string()); if let Some(url) = url { - self.inner.inner.get_io().write(url); + self.get_io().write(url); return Ok(0); } return Err(anyhow::anyhow!(InvalidArgumentException { @@ -286,7 +272,7 @@ impl RepositoryCommand { let name_str = name.as_deref().unwrap(); if ["packagist", "packagist.org"].contains(&name_str) { let append = input.get_option("append").as_bool().unwrap_or(false); - self.inner.config_source.as_mut().unwrap().add_repository( + self.config_source.as_mut().unwrap().add_repository( "packagist.org", PhpMixed::Bool(false), append, @@ -307,8 +293,7 @@ impl RepositoryCommand { } let name_str = name.as_deref().unwrap(); if ["packagist", "packagist.org"].contains(&name_str) { - self.inner - .config_source + self.config_source .as_mut() .unwrap() .remove_repository("packagist.org"); @@ -331,7 +316,7 @@ impl RepositoryCommand { } fn list_repositories(&self, mut repos: IndexMap<String, PhpMixed>) { - let io = self.inner.inner.get_io(); + let io = self.get_io(); let mut packagist_present = false; for (_key, repo) in &repos { @@ -396,84 +381,14 @@ impl RepositoryCommand { .get("url") .and_then(|v| v.as_string()) .map(|s| s.to_string()) - .unwrap_or_else(|| JsonFile::encode(repo)); + .unwrap_or_else(|| JsonFile::encode(repo, 448)); io.write(&format!("[{}] <info>{}</info> {}", name, r#type, url)); } } } - fn suggest_type_for_add() -> Box<dyn Fn(&CompletionInput) -> Vec<String>> { - Box::new(|input: &CompletionInput| { - if input.get_argument("action").as_string() == Some("add") { - vec![ - "composer".to_string(), - "vcs".to_string(), - "artifact".to_string(), - "path".to_string(), - ] - } else { - vec![] - } - }) - } - - fn suggest_repo_names(&self) -> Box<dyn Fn(&CompletionInput) -> Vec<String> + '_> { - Box::new(move |input: &CompletionInput| { - let action = input - .get_argument("action") - .as_string() - .unwrap_or("") - .to_string(); - if ["enable", "disable"].contains(&action.as_str()) { - return vec!["packagist.org".to_string()]; - } - if !["remove", "set-url", "get-url"].contains(&action.as_str()) { - return vec![]; - } - let config = Factory::create_config(None, None).unwrap(); - let config_file_path = self.inner.get_composer_config_file(input, &config); - let config_file = JsonFile::new(config_file_path, None, None); - let data = config_file.read().unwrap_or_default(); - let mut repos = vec![]; - if let Some(repositories) = data.get("repositories").and_then(|v| v.as_list()) { - for repo in repositories { - if let PhpMixed::Array(ref repo_map) = **repo { - if let Some(name) = repo_map.get("name").and_then(|v| v.as_string()) { - repos.push(name.to_string()); - } - } - } - } - repos.sort(); - repos - }) - } -} - -impl BaseCommand for RepositoryCommand { - fn inner(&self) -> &CommandBase { - &self.inner - } - - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io - } + // TODO(cli-completion): fn suggest_type_for_add() + // TODO(cli-completion): fn suggest_repo_names(&self) } impl BaseConfigCommand for RepositoryCommand { @@ -502,4 +417,12 @@ impl BaseConfigCommand for RepositoryCommand { } } -impl Command for RepositoryCommand {} +impl HasBaseCommandData for RepositoryCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data + } + + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data + } +} diff --git a/crates/shirabe/src/command/require_command.rs b/crates/shirabe/src/command/require_command.rs index 8031fbc..63efa92 100644 --- a/crates/shirabe/src/command/require_command.rs +++ b/crates/shirabe/src/command/require_command.rs @@ -5,8 +5,6 @@ use anyhow::Result; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::preg::Preg; use shirabe_external_packages::seld::signal::signal_handler::SignalHandler; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::component::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::component::console::output::output_interface::OutputInterface; use shirabe_php_shim::{ @@ -17,8 +15,7 @@ use shirabe_php_shim::{ }; use crate::advisory::auditor::Auditor; -use crate::command::base_command::BaseCommand; -use crate::command::completion_trait::CompletionTrait; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::command::package_discovery_trait::PackageDiscoveryTrait; use crate::composer::Composer; use crate::console::input::input_argument::InputArgument; @@ -49,9 +46,7 @@ use crate::util::silencer::Silencer; #[derive(Debug)] pub struct RequireCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, newly_created: bool, first_require: bool, @@ -65,16 +60,6 @@ pub struct RequireCommand { dependency_resolution_completed: bool, } -impl CompletionTrait for RequireCommand { - fn require_composer( - &self, - disable_plugins: Option<bool>, - disable_scripts: Option<bool>, - ) -> Composer { - todo!() - } -} - impl PackageDiscoveryTrait for RequireCommand { fn get_repos_mut(&mut self) -> &mut Option<CompositeRepository> { todo!() @@ -114,43 +99,41 @@ impl PackageDiscoveryTrait for RequireCommand { impl RequireCommand { pub fn configure(&mut self) { - let suggest_available_package_incl_platform = - self.suggest_available_package_incl_platform(); - let suggest_prefer_install = self.suggest_prefer_install(); - self.inner + // TODO(cli-completion): suggest_available_package_incl_platform / suggest_prefer_install + self .set_name("require") - .set_aliases(vec!["r".to_string()]) + .set_aliases(&["r".to_string()]) .set_description("Adds required packages to your composer.json and installs them") .set_definition(vec![ - InputArgument::new("packages", Some(InputArgument::IS_ARRAY | InputArgument::OPTIONAL), "Optional package name can also include a version constraint, e.g. foo/bar or foo/bar:1.0.0 or foo/bar=1.0.0 or \"foo/bar 1.0.0\"", None, suggest_available_package_incl_platform), - InputOption::new("dev", None, Some(InputOption::VALUE_NONE), "Add requirement to require-dev.", None, vec![]), - InputOption::new("dry-run", None, Some(InputOption::VALUE_NONE), "Outputs the operations but will not execute anything (implicitly enables --verbose).", None, vec![]), - InputOption::new("prefer-source", None, Some(InputOption::VALUE_NONE), "Forces installation from package sources when possible, including VCS information.", None, vec![]), - InputOption::new("prefer-dist", None, Some(InputOption::VALUE_NONE), "Forces installation from package dist (default behavior).", None, vec![]), - InputOption::new("prefer-install", None, Some(InputOption::VALUE_REQUIRED), "Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).", None, suggest_prefer_install), - InputOption::new("fixed", None, Some(InputOption::VALUE_NONE), "Write fixed version to the composer.json.", None, vec![]), - InputOption::new("no-suggest", None, Some(InputOption::VALUE_NONE), "DEPRECATED: This flag does not exist anymore.", None, vec![]), - InputOption::new("no-progress", None, Some(InputOption::VALUE_NONE), "Do not output download progress.", None, vec![]), - InputOption::new("no-update", None, Some(InputOption::VALUE_NONE), "Disables the automatic update of the dependencies (implies --no-install).", None, vec![]), - InputOption::new("no-install", None, Some(InputOption::VALUE_NONE), "Skip the install step after updating the composer.lock file.", None, vec![]), - InputOption::new("no-audit", None, Some(InputOption::VALUE_NONE), "Skip the audit step after updating the composer.lock file (can also be set via the COMPOSER_NO_AUDIT=1 env var).", None, vec![]), - InputOption::new("audit-format", None, Some(InputOption::VALUE_REQUIRED), "Audit output format. Must be \"table\", \"plain\", \"json\", or \"summary\".", Some(PhpMixed::String(Auditor::FORMAT_SUMMARY.to_string())), Auditor::FORMATS.to_vec()), - InputOption::new("no-security-blocking", None, Some(InputOption::VALUE_NONE), "Allows installing packages with security advisories or that are abandoned (can also be set via the COMPOSER_NO_SECURITY_BLOCKING=1 env var).", None, vec![]), - InputOption::new("update-no-dev", None, Some(InputOption::VALUE_NONE), "Run the dependency update with the --no-dev option.", None, vec![]), - InputOption::new("update-with-dependencies", Some(PhpMixed::String("w".to_string())), Some(InputOption::VALUE_NONE), "Allows inherited dependencies to be updated, except those that are root requirements (can also be set via the COMPOSER_WITH_DEPENDENCIES=1 env var).", None, vec![]), - InputOption::new("update-with-all-dependencies", Some(PhpMixed::String("W".to_string())), Some(InputOption::VALUE_NONE), "Allows all inherited dependencies to be updated, including those that are root requirements (can also be set via the COMPOSER_WITH_ALL_DEPENDENCIES=1 env var).", None, vec![]), - InputOption::new("with-dependencies", None, Some(InputOption::VALUE_NONE), "Alias for --update-with-dependencies", None, vec![]), - InputOption::new("with-all-dependencies", None, Some(InputOption::VALUE_NONE), "Alias for --update-with-all-dependencies", None, vec![]), - InputOption::new("ignore-platform-req", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Ignore a specific platform requirement (php & ext- packages).", None, vec![]), - InputOption::new("ignore-platform-reqs", None, Some(InputOption::VALUE_NONE), "Ignore all platform requirements (php & ext- packages).", None, vec![]), - InputOption::new("prefer-stable", None, Some(InputOption::VALUE_NONE), "Prefer stable versions of dependencies (can also be set via the COMPOSER_PREFER_STABLE=1 env var).", None, vec![]), - InputOption::new("prefer-lowest", None, Some(InputOption::VALUE_NONE), "Prefer lowest versions of dependencies (can also be set via the COMPOSER_PREFER_LOWEST=1 env var).", None, vec![]), - InputOption::new("minimal-changes", Some(PhpMixed::String("m".to_string())), Some(InputOption::VALUE_NONE), "During an update with -w/-W, only perform absolutely necessary changes to transitive dependencies (can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var).", None, vec![]), - InputOption::new("sort-packages", None, Some(InputOption::VALUE_NONE), "Sorts packages when adding/updating a new dependency", None, vec![]), - InputOption::new("optimize-autoloader", Some(PhpMixed::String("o".to_string())), Some(InputOption::VALUE_NONE), "Optimize autoloader during autoloader dump", None, vec![]), - InputOption::new("classmap-authoritative", Some(PhpMixed::String("a".to_string())), Some(InputOption::VALUE_NONE), "Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.", None, vec![]), - InputOption::new("apcu-autoloader", None, Some(InputOption::VALUE_NONE), "Use APCu to cache found/not-found classes.", None, vec![]), - InputOption::new("apcu-autoloader-prefix", None, Some(InputOption::VALUE_REQUIRED), "Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader", None, vec![]), + InputArgument::new("packages", Some(InputArgument::IS_ARRAY | InputArgument::OPTIONAL), "Optional package name can also include a version constraint, e.g. foo/bar or foo/bar:1.0.0 or foo/bar=1.0.0 or \"foo/bar 1.0.0\"", None), + InputOption::new("dev", None, Some(InputOption::VALUE_NONE), "Add requirement to require-dev.", None), + InputOption::new("dry-run", None, Some(InputOption::VALUE_NONE), "Outputs the operations but will not execute anything (implicitly enables --verbose).", None), + InputOption::new("prefer-source", None, Some(InputOption::VALUE_NONE), "Forces installation from package sources when possible, including VCS information.", None), + InputOption::new("prefer-dist", None, Some(InputOption::VALUE_NONE), "Forces installation from package dist (default behavior).", None), + InputOption::new("prefer-install", None, Some(InputOption::VALUE_REQUIRED), "Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).", None), + InputOption::new("fixed", None, Some(InputOption::VALUE_NONE), "Write fixed version to the composer.json.", None), + InputOption::new("no-suggest", None, Some(InputOption::VALUE_NONE), "DEPRECATED: This flag does not exist anymore.", None), + InputOption::new("no-progress", None, Some(InputOption::VALUE_NONE), "Do not output download progress.", None), + InputOption::new("no-update", None, Some(InputOption::VALUE_NONE), "Disables the automatic update of the dependencies (implies --no-install).", None), + InputOption::new("no-install", None, Some(InputOption::VALUE_NONE), "Skip the install step after updating the composer.lock file.", None), + InputOption::new("no-audit", None, Some(InputOption::VALUE_NONE), "Skip the audit step after updating the composer.lock file (can also be set via the COMPOSER_NO_AUDIT=1 env var).", None), + InputOption::new("audit-format", None, Some(InputOption::VALUE_REQUIRED), "Audit output format. Must be \"table\", \"plain\", \"json\", or \"summary\".", Some(PhpMixed::String(Auditor::FORMAT_SUMMARY.to_string()))), + InputOption::new("no-security-blocking", None, Some(InputOption::VALUE_NONE), "Allows installing packages with security advisories or that are abandoned (can also be set via the COMPOSER_NO_SECURITY_BLOCKING=1 env var).", None), + InputOption::new("update-no-dev", None, Some(InputOption::VALUE_NONE), "Run the dependency update with the --no-dev option.", None), + InputOption::new("update-with-dependencies", Some(PhpMixed::String("w".to_string())), Some(InputOption::VALUE_NONE), "Allows inherited dependencies to be updated, except those that are root requirements (can also be set via the COMPOSER_WITH_DEPENDENCIES=1 env var).", None), + InputOption::new("update-with-all-dependencies", Some(PhpMixed::String("W".to_string())), Some(InputOption::VALUE_NONE), "Allows all inherited dependencies to be updated, including those that are root requirements (can also be set via the COMPOSER_WITH_ALL_DEPENDENCIES=1 env var).", None), + InputOption::new("with-dependencies", None, Some(InputOption::VALUE_NONE), "Alias for --update-with-dependencies", None), + InputOption::new("with-all-dependencies", None, Some(InputOption::VALUE_NONE), "Alias for --update-with-all-dependencies", None), + InputOption::new("ignore-platform-req", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Ignore a specific platform requirement (php & ext- packages).", None), + InputOption::new("ignore-platform-reqs", None, Some(InputOption::VALUE_NONE), "Ignore all platform requirements (php & ext- packages).", None), + InputOption::new("prefer-stable", None, Some(InputOption::VALUE_NONE), "Prefer stable versions of dependencies (can also be set via the COMPOSER_PREFER_STABLE=1 env var).", None), + InputOption::new("prefer-lowest", None, Some(InputOption::VALUE_NONE), "Prefer lowest versions of dependencies (can also be set via the COMPOSER_PREFER_LOWEST=1 env var).", None), + InputOption::new("minimal-changes", Some(PhpMixed::String("m".to_string())), Some(InputOption::VALUE_NONE), "During an update with -w/-W, only perform absolutely necessary changes to transitive dependencies (can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var).", None), + InputOption::new("sort-packages", None, Some(InputOption::VALUE_NONE), "Sorts packages when adding/updating a new dependency", None), + InputOption::new("optimize-autoloader", Some(PhpMixed::String("o".to_string())), Some(InputOption::VALUE_NONE), "Optimize autoloader during autoloader dump", None), + InputOption::new("classmap-authoritative", Some(PhpMixed::String("a".to_string())), Some(InputOption::VALUE_NONE), "Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.", None), + InputOption::new("apcu-autoloader", None, Some(InputOption::VALUE_NONE), "Use APCu to cache found/not-found classes.", None), + InputOption::new("apcu-autoloader-prefix", None, Some(InputOption::VALUE_REQUIRED), "Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader", None), ]) .set_help( "The require command adds required packages to your composer.json and installs them.\n\ @@ -173,7 +156,7 @@ impl RequireCommand { output: &dyn OutputInterface, ) -> Result<i64> { self.file = Factory::get_composer_file(); - let io = self.inner.get_io(); + let io = self.get_io(); if input.get_option("no-suggest").as_bool().unwrap_or(false) { io.write_error( @@ -213,7 +196,7 @@ impl RequireCommand { file_put_contents(&self.file, "{\n}\n"); } - self.json = Some(JsonFile::new(&self.file, None, None)); + self.json = Some(JsonFile::new(self.file.clone(), None, None)?); self.lock = Factory::get_lock_file(&self.file); self.composer_backup = file_get_contents(self.json.as_ref().unwrap().get_path()).unwrap_or_default(); @@ -241,15 +224,14 @@ impl RequireCommand { // check for writability by writing to the file as is_writable can not be trusted on network-mounts // see https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926 + let file_path = self.file.clone(); + let backup_contents = self.composer_backup.clone(); if !is_writable(&self.file) - && Silencer::call( - "file_put_contents", - &[ - PhpMixed::String(self.file.clone()), - PhpMixed::String(self.composer_backup.clone()), - ], - ) - .as_bool() + && Silencer::call(|| { + shirabe_php_shim::file_put_contents(&file_path, backup_contents.as_bytes()); + Ok::<bool, anyhow::Error>(false) + }) + .ok() == Some(false) { io.write_error( @@ -300,7 +282,7 @@ impl RequireCommand { } } - let composer = self.inner.require_composer(None, None)?; + let composer = self.require_composer(None, None)?; let repos = composer.get_repository_manager().get_repositories(); let platform_overrides = composer.get_config().get("platform"); @@ -359,7 +341,7 @@ impl RequireCommand { } }; - let mut requirements = self.inner.format_requirements(requirements)?; + let mut requirements = self.format_requirements(requirements)?; if !input.get_option("dev").as_bool().unwrap_or(false) && io.is_interactive() @@ -726,8 +708,8 @@ impl RequireCommand { _remove_key: &str, ) -> Result<i64> { // Update packages - self.inner.reset_composer()?; - let composer = self.inner.require_composer(None, None)?; + self.reset_composer()?; + let composer = self.require_composer(None, None)?; self.dependency_resolution_completed = false; composer.get_event_dispatcher().add_listener( @@ -869,7 +851,7 @@ impl RequireCommand { let command_event = CommandEvent::new(PluginEvents::COMMAND, "require", input, output); composer .get_event_dispatcher() - .dispatch(command_event.get_name(), &command_event); + .dispatch(Some(command_event.get_name()), None); composer .get_installation_manager() @@ -893,13 +875,10 @@ impl RequireCommand { .set_update(true) .set_install(!input.get_option("no-install").as_bool().unwrap_or(false)) .set_update_allow_transitive_dependencies(update_allow_transitive_dependencies) - .set_platform_requirement_filter(self.inner.get_platform_requirement_filter(input)?) + .set_platform_requirement_filter(self.get_platform_requirement_filter(input)?) .set_prefer_stable(input.get_option("prefer-stable").as_bool().unwrap_or(false)) .set_prefer_lowest(input.get_option("prefer-lowest").as_bool().unwrap_or(false)) - .set_audit_config( - self.inner - .create_audit_config(composer.get_config(), input)?, - ) + .set_audit_config(self.create_audit_config(composer.get_config(), input)?) .set_minimal_update(minimal_changes); // if no lock is present, or the file is brand new, we do not do a @@ -915,7 +894,7 @@ impl RequireCommand { let status = install.run()?; if status != 0 && status != Installer::ERROR_AUDIT_FAILED { if status == Installer::ERROR_DEPENDENCY_RESOLUTION_FAILED { - for req in self.inner.normalize_requirements( + for req in self.normalize_requirements( input .get_argument("packages") .as_list() @@ -956,7 +935,7 @@ impl RequireCommand { dry_run: bool, fixed: bool, ) -> Result<i64> { - let composer = self.inner.require_composer(None, None)?; + let composer = self.require_composer(None, None)?; let locker = composer.get_locker(); let mut requirements: IndexMap<String, String> = IndexMap::new(); let version_selector = VersionSelector::new(RepositorySet::new(None, None), None); @@ -990,7 +969,7 @@ impl RequireCommand { version_selector.find_recommended_require_version(&*package), ); } - self.inner.get_io().write_error( + self.get_io().write_error( PhpMixed::String(sprintf( "Using version <info>%s</info> for <info>%s</info>", &[ @@ -1013,12 +992,12 @@ impl RequireCommand { ) .unwrap_or(false) { - self.inner.get_io().warning(format!( + self.get_io().warning(format!( "Version {} looks like it may be a feature branch which is unlikely to keep working in the long run and may be in an unstable state", requirements.get(package_name).cloned().unwrap_or_default(), )); - if self.inner.get_io().is_interactive() - && !self.inner.get_io().ask_confirmation( + if self.get_io().is_interactive() + && !self.get_io().ask_confirmation( "Are you sure you want to use this constraint (<comment>y</comment>) or would you rather abort (<comment>n</comment>) the whole operation [<comment>y,n</comment>]? " .to_string(), true, @@ -1144,7 +1123,7 @@ impl RequireCommand { pub(crate) fn interact(&self, _input: &dyn InputInterface, _output: &dyn OutputInterface) {} fn revert_composer_file(&mut self) { - let io = self.inner.get_io(); + let io = self.get_io(); if self.newly_created { io.write_error( @@ -1184,30 +1163,12 @@ impl RequireCommand { } } -impl BaseCommand for RequireCommand { - fn inner(&self) -> &CommandBase { - &self.inner - } - - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() +impl HasBaseCommandData for RequireCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for RequireCommand {} diff --git a/crates/shirabe/src/command/run_script_command.rs b/crates/shirabe/src/command/run_script_command.rs index 2b9dd1f..5e761f2 100644 --- a/crates/shirabe/src/command/run_script_command.rs +++ b/crates/shirabe/src/command/run_script_command.rs @@ -1,13 +1,11 @@ //! ref: composer/src/Composer/Command/RunScriptCommand.php use anyhow::Result; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; use shirabe_php_shim::{InvalidArgumentException, PhpMixed, RuntimeException}; -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::console::input::input_argument::InputArgument; use crate::console::input::input_option::InputOption; @@ -19,9 +17,7 @@ use crate::util::process_executor::ProcessExecutor; #[derive(Debug)] pub struct RunScriptCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, script_events: Vec<&'static str>, } @@ -48,25 +44,22 @@ impl RunScriptCommand { } pub fn configure(&mut self) { - self.inner - .set_name("run-script") - .set_aliases(vec!["run".to_string()]) + self.set_name("run-script") + .set_aliases(&["run".to_string()]) .set_description("Runs the scripts defined in composer.json") .set_definition(vec![ - // completion callback (runtime script names) is deferred to Phase B + // TODO(cli-completion): script-name completion was provided via a closure suggesting runtime script names InputArgument::new( "script", Some(InputArgument::OPTIONAL), "Script name to run.", None, - vec![], ), InputArgument::new( "args", Some(InputArgument::IS_ARRAY | InputArgument::OPTIONAL), "", None, - vec![], ), InputOption::new( "timeout", @@ -74,7 +67,6 @@ impl RunScriptCommand { Some(InputOption::VALUE_REQUIRED), "Sets script timeout in seconds, or 0 for never.", None, - vec![], ), InputOption::new( "dev", @@ -82,7 +74,6 @@ impl RunScriptCommand { Some(InputOption::VALUE_NONE), "Sets the dev mode.", None, - vec![], ), InputOption::new( "no-dev", @@ -90,7 +81,6 @@ impl RunScriptCommand { Some(InputOption::VALUE_NONE), "Disables the dev mode.", None, - vec![], ), InputOption::new( "list", @@ -98,7 +88,6 @@ impl RunScriptCommand { Some(InputOption::VALUE_NONE), "List scripts.", None, - vec![], ), ]) .set_help( @@ -129,7 +118,7 @@ impl RunScriptCommand { options.insert(script.0.clone(), script.1.clone()); } - let io = self.inner.get_io(); + let io = self.get_io(); let script = io.select( "Script to run: ".to_string(), options.keys().cloned().collect(), @@ -173,11 +162,12 @@ impl RunScriptCommand { } } - let composer = self.inner.require_composer()?; + let composer = self.require_composer(None, None)?; let dev_mode = input.get_option("dev").as_bool().unwrap_or(false) || !input.get_option("no-dev").as_bool().unwrap_or(false); - let event = ScriptEvent::new(script.clone(), &composer, self.inner.get_io(), dev_mode); - let has_listeners = composer.get_event_dispatcher().has_event_listeners(&event); + // TODO(phase-b): ScriptEvent::new takes Composer/IOInterface by value; placeholder construction. + let _ = (script.clone(), &composer, dev_mode); + let has_listeners = false; if !has_listeners { return Err(InvalidArgumentException { message: format!("Script \"{}\" is not defined in this package", script), @@ -224,20 +214,23 @@ impl RunScriptCommand { return Ok(0); } - let io = self.inner.get_io(); + let io = self.get_io(); io.write_error("<info>scripts:</info>"); let table: Vec<Vec<String>> = scripts .iter() .map(|(name, desc)| vec![format!(" {}", name), desc.clone()]) .collect(); - self.inner.render_table(table, output); + self.render_table(table, output); Ok(0) } fn get_scripts(&self) -> Result<Vec<(String, String)>> { - let scripts = self.inner.require_composer()?.get_package().get_scripts(); + let scripts = self + .require_composer(None, None)? + .get_package() + .get_scripts(); if scripts.is_empty() { return Ok(vec![]); } @@ -257,30 +250,12 @@ impl RunScriptCommand { } } -impl BaseCommand for RunScriptCommand { - fn inner(&self) -> &CommandBase { - &self.inner +impl HasBaseCommandData for RunScriptCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for RunScriptCommand {} diff --git a/crates/shirabe/src/command/script_alias_command.rs b/crates/shirabe/src/command/script_alias_command.rs index 01a420a..e077bc7 100644 --- a/crates/shirabe/src/command/script_alias_command.rs +++ b/crates/shirabe/src/command/script_alias_command.rs @@ -1,23 +1,20 @@ //! ref: composer/src/Composer/Command/ScriptAliasCommand.php +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; +use crate::composer::Composer; use crate::console::input::input_argument::InputArgument; use crate::console::input::input_option::InputOption; use crate::io::io_interface::IOInterface; use crate::util::platform::Platform; -use crate::{command::base_command::BaseCommand, composer::Composer}; use anyhow::Result; use shirabe_external_packages::composer::pcre::preg::Preg; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; use shirabe_php_shim::{InvalidArgumentException, LogicException, PhpMixed, is_string}; #[derive(Debug)] pub struct ScriptAliasCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, script: String, description: String, @@ -53,8 +50,7 @@ impl ScriptAliasCommand { } pub fn configure(&mut self) { - self.inner - .set_name(&self.script) + self.set_name(&self.script) .set_description(&self.description) .set_aliases(self.aliases.clone()) .set_definition(vec![ @@ -64,7 +60,6 @@ impl ScriptAliasCommand { Some(InputOption::VALUE_NONE), "Sets the dev mode.", None, - vec![], ), InputOption::new( "no-dev", @@ -72,14 +67,12 @@ impl ScriptAliasCommand { Some(InputOption::VALUE_NONE), "Disables the dev mode.", None, - vec![], ), InputArgument::new( "args", Some(InputArgument::IS_ARRAY | InputArgument::OPTIONAL), "", None, - vec![], ), ]) .set_help( @@ -94,7 +87,7 @@ impl ScriptAliasCommand { input: &dyn InputInterface, _output: &dyn OutputInterface, ) -> Result<i64> { - let composer = self.inner.require_composer()?; + let composer = self.require_composer(None, None)?; let args = input.get_arguments(); @@ -133,30 +126,12 @@ impl ScriptAliasCommand { } } -impl BaseCommand for ScriptAliasCommand { - fn inner(&self) -> &CommandBase { - &self.inner +impl HasBaseCommandData for ScriptAliasCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for ScriptAliasCommand {} diff --git a/crates/shirabe/src/command/search_command.rs b/crates/shirabe/src/command/search_command.rs index 40ef4fe..64727fc 100644 --- a/crates/shirabe/src/command/search_command.rs +++ b/crates/shirabe/src/command/search_command.rs @@ -1,5 +1,7 @@ //! ref: composer/src/Composer/Command/SearchCommand.php +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; +use crate::composer::Composer; use crate::console::input::input_argument::InputArgument; use crate::console::input::input_option::InputOption; use crate::io::io_interface::IOInterface; @@ -9,11 +11,8 @@ use crate::plugin::plugin_events::PluginEvents; use crate::repository::composite_repository::CompositeRepository; use crate::repository::platform_repository::PlatformRepository; use crate::repository::repository_interface::{self, RepositoryInterface}; -use crate::{command::base_command::BaseCommand, composer::Composer}; use anyhow::Result; use indexmap::IndexMap; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::console::formatter::output_formatter::OutputFormatter; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; @@ -21,22 +20,20 @@ use shirabe_php_shim::{InvalidArgumentException, PhpMixed, implode, in_array, pr #[derive(Debug)] pub struct SearchCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, } impl SearchCommand { pub fn configure(&mut self) { - self.inner + self .set_name("search") .set_description("Searches for packages") .set_definition(vec![ - InputOption::new("only-name", Some(PhpMixed::String("N".to_string())), Some(InputOption::VALUE_NONE), "Search only in package names", None, vec![]), - InputOption::new("only-vendor", Some(PhpMixed::String("O".to_string())), Some(InputOption::VALUE_NONE), "Search only for vendor / organization names, returns only \"vendor\" as result", None, vec![]), - InputOption::new("type", Some(PhpMixed::String("t".to_string())), Some(InputOption::VALUE_REQUIRED), "Search for a specific package type", None, vec![]), - InputOption::new("format", Some(PhpMixed::String("f".to_string())), Some(InputOption::VALUE_REQUIRED), "Format of the output: text or json", Some(PhpMixed::String("text".to_string())), vec!["json".to_string(), "text".to_string()]), - InputArgument::new("tokens", Some(InputArgument::IS_ARRAY | InputArgument::REQUIRED), "tokens to search for", None, vec![]), + InputOption::new("only-name", Some(PhpMixed::String("N".to_string())), Some(InputOption::VALUE_NONE), "Search only in package names", None), + InputOption::new("only-vendor", Some(PhpMixed::String("O".to_string())), Some(InputOption::VALUE_NONE), "Search only for vendor / organization names, returns only \"vendor\" as result", None), + InputOption::new("type", Some(PhpMixed::String("t".to_string())), Some(InputOption::VALUE_REQUIRED), "Search for a specific package type", None), + InputOption::new("format", Some(PhpMixed::String("f".to_string())), Some(InputOption::VALUE_REQUIRED), "Format of the output: text or json", Some(PhpMixed::String("text".to_string()))), + InputArgument::new("tokens", Some(InputArgument::IS_ARRAY | InputArgument::REQUIRED), "tokens to search for", None), ]) .set_help( "The search command searches for packages by its name\n\ @@ -51,7 +48,7 @@ impl SearchCommand { output: &dyn OutputInterface, ) -> Result<i64> { let platform_repo = PlatformRepository::new(vec![], IndexMap::new(), None, None)?; - let io = self.inner.get_io(); + let io = self.get_io(); let format = input .get_option("format") @@ -73,11 +70,10 @@ impl SearchCommand { return Ok(1); } - let composer = if let Some(c) = self.inner.try_composer() { + let composer = if let Some(c) = self.try_composer(None, None) { c } else { - self.inner - .create_composer_instance(input, self.inner.get_io(), vec![])? + self.create_composer_instance(input, self.get_io(), vec![])? }; let local_repo = composer.get_repository_manager().get_local_repository(); let installed_repo = @@ -90,14 +86,14 @@ impl SearchCommand { let command_event = CommandEvent::new( PluginEvents::COMMAND.to_string(), "search".to_string(), - Box::new(input), - Box::new(output), + input, + output, vec![], vec![], ); composer .get_event_dispatcher() - .dispatch(command_event.get_name(), &command_event); + .dispatch(Some(command_event.get_name()), None); let mut mode: i64 = repository_interface::SEARCH_FULLTEXT; if input.get_option("only-name").as_bool().unwrap_or(false) { @@ -135,7 +131,7 @@ impl SearchCommand { let results = repos.search(query, mode, r#type); if results.len() > 0 && format == "text" { - let width = self.inner.get_terminal_width(); + let width = self.get_terminal_width(); let mut name_length: i64 = 0; for result in &results { name_length = name_length.max(result.name.len() as i64); @@ -176,37 +172,19 @@ impl SearchCommand { } } } else if format == "json" { - io.write(&JsonFile::encode(&results)); + io.write(&JsonFile::encode(&results, 448)); } Ok(0) } } -impl BaseCommand for SearchCommand { - fn inner(&self) -> &CommandBase { - &self.inner +impl HasBaseCommandData for SearchCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for SearchCommand {} diff --git a/crates/shirabe/src/command/self_update_command.rs b/crates/shirabe/src/command/self_update_command.rs index c6ea1bd..7e79529 100644 --- a/crates/shirabe/src/command/self_update_command.rs +++ b/crates/shirabe/src/command/self_update_command.rs @@ -3,8 +3,6 @@ use crate::io::io_interface; use anyhow::Result; use shirabe_external_packages::composer::pcre::preg::Preg; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::component::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::component::console::output::output_interface::OutputInterface; use shirabe_external_packages::symfony::component::finder::finder::Finder; @@ -20,7 +18,7 @@ use shirabe_php_shim::{ usleep, version_compare, }; -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::config::Config; use crate::console::input::input_argument::InputArgument; @@ -35,9 +33,7 @@ use crate::util::platform::Platform; #[derive(Debug)] pub struct SelfUpdateCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, } impl SelfUpdateCommand { @@ -45,23 +41,23 @@ impl SelfUpdateCommand { const OLD_INSTALL_EXT: &'static str = "-old.phar"; pub fn configure(&mut self) { - self.inner + self .set_name("self-update") - .set_aliases(vec!["selfupdate".to_string()]) + .set_aliases(&["selfupdate".to_string()]) .set_description("Updates composer.phar to the latest version") .set_definition(vec![ - InputOption::new("rollback", Some(PhpMixed::String("r".to_string())), Some(InputOption::VALUE_NONE), "Revert to an older installation of composer", None, vec![]), - InputOption::new("clean-backups", None, Some(InputOption::VALUE_NONE), "Delete old backups during an update. This makes the current version of composer the only backup available after the update", None, vec![]), - InputArgument::new("version", Some(InputArgument::OPTIONAL), "The version to update to", None, vec![]), - InputOption::new("no-progress", None, Some(InputOption::VALUE_NONE), "Do not output download progress.", None, vec![]), - InputOption::new("update-keys", None, Some(InputOption::VALUE_NONE), "Prompt user for a key update", None, vec![]), - InputOption::new("stable", None, Some(InputOption::VALUE_NONE), "Force an update to the stable channel", None, vec![]), - InputOption::new("preview", None, Some(InputOption::VALUE_NONE), "Force an update to the preview channel", None, vec![]), - InputOption::new("snapshot", None, Some(InputOption::VALUE_NONE), "Force an update to the snapshot channel", None, vec![]), - InputOption::new("1", None, Some(InputOption::VALUE_NONE), "Force an update to the stable channel, but only use 1.x versions", None, vec![]), - InputOption::new("2", None, Some(InputOption::VALUE_NONE), "Force an update to the stable channel, but only use 2.x versions", None, vec![]), - InputOption::new("2.2", None, Some(InputOption::VALUE_NONE), "Force an update to the stable channel, but only use 2.2.x LTS versions", None, vec![]), - InputOption::new("set-channel-only", None, Some(InputOption::VALUE_NONE), "Only store the channel as the default one and then exit", None, vec![]), + InputOption::new("rollback", Some(PhpMixed::String("r".to_string())), Some(InputOption::VALUE_NONE), "Revert to an older installation of composer", None), + InputOption::new("clean-backups", None, Some(InputOption::VALUE_NONE), "Delete old backups during an update. This makes the current version of composer the only backup available after the update", None), + InputArgument::new("version", Some(InputArgument::OPTIONAL), "The version to update to", None), + InputOption::new("no-progress", None, Some(InputOption::VALUE_NONE), "Do not output download progress.", None), + InputOption::new("update-keys", None, Some(InputOption::VALUE_NONE), "Prompt user for a key update", None), + InputOption::new("stable", None, Some(InputOption::VALUE_NONE), "Force an update to the stable channel", None), + InputOption::new("preview", None, Some(InputOption::VALUE_NONE), "Force an update to the preview channel", None), + InputOption::new("snapshot", None, Some(InputOption::VALUE_NONE), "Force an update to the snapshot channel", None), + InputOption::new("1", None, Some(InputOption::VALUE_NONE), "Force an update to the stable channel, but only use 1.x versions", None), + InputOption::new("2", None, Some(InputOption::VALUE_NONE), "Force an update to the stable channel, but only use 2.x versions", None), + InputOption::new("2.2", None, Some(InputOption::VALUE_NONE), "Force an update to the stable channel, but only use 2.2.x LTS versions", None), + InputOption::new("set-channel-only", None, Some(InputOption::VALUE_NONE), "Only store the channel as the default one and then exit", None), ]) .set_help( "The <info>self-update</info> command checks getcomposer.org for newer\n\ @@ -144,7 +140,7 @@ impl SelfUpdateCommand { format!("https://{}", Self::HOMEPAGE) }; - let io = self.inner.get_io(); + let io = self.get_io(); let http_downloader = Factory::create_http_downloader(io, &config)?; let mut versions_util = Versions::new(config.clone(), http_downloader.clone()); @@ -241,11 +237,11 @@ impl SelfUpdateCommand { .unwrap_or("") .to_string(); if is_array(home_owner.clone()) && composer_user_name != home_owner_name { - io.write_error( - PhpMixed::String(format!( + io.write_error3( + &format!( "<warning>You are running Composer as \"{}\", while \"{}\" is owned by \"{}\"</warning>", composer_user_name, home, home_owner_name - )), + ), true, io_interface::NORMAL, ); @@ -308,21 +304,21 @@ impl SelfUpdateCommand { .to_string(); update_version = latest_version.clone(); - io.write_error( - PhpMixed::String(format!( + io.write_error3( + &format!( "<warning>A new stable major version of Composer is available ({}), run \"composer self-update --{}\" to update to it. See also https://getcomposer.org/{}</warning>", skipped_version, update_major_version, update_major_version - )), + ), true, io_interface::NORMAL, ); } else if version_compare(¤t_major_version, &preview_major_version, "<") { // promote next major version if available in preview - io.write_error( - PhpMixed::String(format!( + io.write_error3( + &format!( "<warning>A preview release of the next major version of Composer is available ({}), run \"composer self-update --preview\" to give it a try. See also https://github.com/composer/composer/releases for changelogs.</warning>", latest_preview.get("version").and_then(|v| v.as_string()).unwrap_or("") - )), + ), true, io_interface::NORMAL, ); @@ -342,24 +338,24 @@ impl SelfUpdateCommand { &effective_channel, ) != Some(0) { - io.write_error( - PhpMixed::String(format!( + io.write_error3( + &format!( "<warning>Warning: You forced the install of {} via --{}, but {} is the latest stable version. Updating to it via composer self-update --stable is recommended.</warning>", latest_version, effective_channel, latest_stable.get("version").and_then(|v| v.as_string()).unwrap_or("") - )), + ), true, io_interface::NORMAL, ); } if latest.contains_key("eol") { - io.write_error( - PhpMixed::String(format!( + io.write_error3( + &format!( "<warning>Warning: Version {} is EOL / End of Life. {} is the latest stable version. Updating to it via composer self-update --stable is recommended.</warning>", latest_version, latest_stable.get("version").and_then(|v| v.as_string()).unwrap_or("") - )), + ), true, io_interface::NORMAL, ); @@ -368,11 +364,8 @@ impl SelfUpdateCommand { if Preg::is_match(r"{^[0-9a-f]{40}$}", &update_version).unwrap_or(false) && update_version != latest_version { - io.write_error( - PhpMixed::String( - "<error>You can not update to a specific SHA-1 as those phars are not available for download</error>" - .to_string(), - ), + io.write_error3( + "<error>You can not update to a specific SHA-1 as those phars are not available for download</error>", true, io_interface::NORMAL, ); @@ -386,14 +379,14 @@ impl SelfUpdateCommand { } if Composer::VERSION == update_version.as_str() { - io.write_error( - PhpMixed::String(sprintf( + io.write_error3( + &sprintf( "<info>You are already using the latest available Composer version %s (%s channel).</info>", &[ PhpMixed::String(update_version.clone()), PhpMixed::String(channel_string.clone()), ], - )), + ), true, io_interface::NORMAL, ); @@ -430,14 +423,14 @@ impl SelfUpdateCommand { let updating_to_tag = !Preg::is_match(r"{^[0-9a-f]{40}$}", &update_version).unwrap_or(false); - io.write( - PhpMixed::String(sprintf( + io.write3( + &sprintf( "Upgrading to version <info>%s</info> (%s channel).", &[ PhpMixed::String(update_version.clone()), PhpMixed::String(channel_string.clone()), ], - )), + ), true, io_interface::NORMAL, ); @@ -466,20 +459,13 @@ impl SelfUpdateCommand { return Err(e.into()); } }; - io.write_error( - PhpMixed::String(" ".to_string()), - false, - io_interface::NORMAL, - ); + io.write_error3(" ", false, io_interface::NORMAL); http_downloader.copy(&remote_filename, &temp_filename)?; - io.write_error(PhpMixed::String(String::new()), true, io_interface::NORMAL); + io.write_error3("", true, io_interface::NORMAL); if !file_exists(&temp_filename) || signature.is_none() || signature.as_deref() == Some("") { - io.write_error( - PhpMixed::String( - "<error>The download of the new composer version failed for an unexpected reason</error>" - .to_string(), - ), + io.write_error3( + "<error>The download of the new composer version failed for an unexpected reason</error>", true, io_interface::NORMAL, ); @@ -490,11 +476,8 @@ impl SelfUpdateCommand { // verify phar signature if !extension_loaded("openssl") && config.get("disable-tls").as_bool() == Some(true) { - io.write_error( - PhpMixed::String( - "<warning>Skipping phar signature verification as you have disabled OpenSSL via config.disable-tls</warning>" - .to_string(), - ), + io.write_error3( + "<warning>Skipping phar signature verification as you have disabled OpenSSL via config.disable-tls</warning>", true, io_interface::NORMAL, ); @@ -640,20 +623,20 @@ RGv89BPD+2DLnJysngsvVaUCAwEAAQ==\n\ } if file_exists(&backup_file) { - io.write_error( - PhpMixed::String(sprintf( + io.write_error3( + &sprintf( "Use <info>composer self-update --rollback</info> to return to version <comment>%s</comment>", &[PhpMixed::String(Composer::VERSION.to_string())], - )), + ), true, io_interface::NORMAL, ); } else { - io.write_error( - PhpMixed::String(format!( + io.write_error3( + &format!( "<warning>A backup of the current version could not be written to {}, no rollback possible</warning>", backup_file - )), + ), true, io_interface::NORMAL, ); @@ -672,11 +655,8 @@ RGv89BPD+2DLnJysngsvVaUCAwEAAQ==\n\ .into()); } - io.write( - PhpMixed::String( - "Open <info>https://composer.github.io/pubkeys.html</info> to find the latest keys" - .to_string(), - ), + io.write3( + "Open <info>https://composer.github.io/pubkeys.html</info> to find the latest keys", true, io_interface::NORMAL, ); @@ -737,11 +717,11 @@ RGv89BPD+2DLnJysngsvVaUCAwEAAQ==\n\ config.get("home").as_string().unwrap_or("") ); file_put_contents(&key_path, match_.as_deref().unwrap_or("")); - io.write( - PhpMixed::String(format!( + io.write3( + &format!( "Stored key with fingerprint: {}", Keys::fingerprint(&key_path)? - )), + ), true, io_interface::NORMAL, ); @@ -784,20 +764,20 @@ RGv89BPD+2DLnJysngsvVaUCAwEAAQ==\n\ config.get("home").as_string().unwrap_or("") ); file_put_contents(&key_path, match_.as_deref().unwrap_or("")); - io.write( - PhpMixed::String(format!( + io.write3( + &format!( "Stored key with fingerprint: {}", Keys::fingerprint(&key_path)? - )), + ), true, io_interface::NORMAL, ); - io.write( - PhpMixed::String(format!( + io.write3( + &format!( "Public keys stored in {}", config.get("home").as_string().unwrap_or("") - )), + ), true, io_interface::NORMAL, ); @@ -857,12 +837,12 @@ RGv89BPD+2DLnJysngsvVaUCAwEAAQ==\n\ .into()); } - let io = self.inner.get_io(); - io.write_error( - PhpMixed::String(sprintf( + let io = self.get_io(); + io.write_error3( + &sprintf( "Rolling back to version <info>%s</info>.", &[PhpMixed::String(rollback_version.clone())], - )), + ), true, io_interface::NORMAL, ); @@ -880,7 +860,7 @@ RGv89BPD+2DLnJysngsvVaUCAwEAAQ==\n\ new_filename: &str, backup_target: Option<&str>, ) -> Result<bool> { - let io = self.inner.get_io(); + let io = self.get_io(); let perms = fileperms(local_filename); if perms >= 0 { // @chmod @@ -890,8 +870,8 @@ RGv89BPD+2DLnJysngsvVaUCAwEAAQ==\n\ // check phar validity let mut error: Option<String> = None; if !self.validate_phar(new_filename, &mut error)? { - io.write_error( - PhpMixed::String(format!( + io.write_error3( + &format!( "<error>The {} file is corrupted ({})</error>", if backup_target.is_some() { "update" @@ -899,17 +879,14 @@ RGv89BPD+2DLnJysngsvVaUCAwEAAQ==\n\ "backup" }, error.unwrap_or_default() - )), + ), true, io_interface::NORMAL, ); if backup_target.is_some() { - io.write_error( - PhpMixed::String( - "<error>Please re-run the self-update command to try again.</error>" - .to_string(), - ), + io.write_error3( + "<error>Please re-run the self-update command to try again.</error>", true, io_interface::NORMAL, ); @@ -972,16 +949,16 @@ RGv89BPD+2DLnJysngsvVaUCAwEAAQ==\n\ pub(crate) fn clean_backups(&self, rollback_dir: &str, except: Option<&str>) { let finder = self.get_old_installation_finder(rollback_dir); - let io = self.inner.get_io(); - let fs = Filesystem::new(); + let io = self.get_io(); + let fs = Filesystem::new(None); for file in finder { if file.get_basename(Self::OLD_INSTALL_EXT) == except.unwrap_or_default() { continue; } let file_str = file.to_string(); - io.write_error( - PhpMixed::String(format!("<info>Removing: {}</info>", file_str)), + io.write_error3( + &format!("<info>Removing: {}</info>", file_str), true, io_interface::NORMAL, ); @@ -1071,13 +1048,13 @@ RGv89BPD+2DLnJysngsvVaUCAwEAAQ==\n\ local_filename: &str, new_filename: &str, ) -> bool { - let io = self.inner.get_io(); + let io = self.get_io(); - io.write_error( - PhpMixed::String(format!( + io.write_error3( + &format!( "<error>Unable to write \"{}\". Access is denied.</error>", local_filename - )), + ), true, io_interface::NORMAL, ); @@ -1086,11 +1063,8 @@ RGv89BPD+2DLnJysngsvVaUCAwEAAQ==\n\ "Complete this operation with Administrator privileges [<comment>Y,n</comment>]? "; if !io.ask_confirmation(question.to_string(), true) { - io.write_error( - PhpMixed::String(format!( - "<warning>Operation cancelled. {}</warning>", - help_message - )), + io.write_error3( + &format!("<warning>Operation cancelled. {}</warning>", help_message), true, io_interface::NORMAL, ); @@ -1102,8 +1076,8 @@ RGv89BPD+2DLnJysngsvVaUCAwEAAQ==\n\ let tmp_file = match tmp_file { Some(f) => f, None => { - io.write_error( - PhpMixed::String(format!("<error>Operation failed. {}</error>", help_message)), + io.write_error3( + &format!("<error>Operation failed. {}</error>", help_message), true, io_interface::NORMAL, ); @@ -1167,14 +1141,14 @@ RGv89BPD+2DLnJysngsvVaUCAwEAAQ==\n\ let result = Filesystem::is_readable(local_filename) && hash_file("sha256", local_filename) == Some(checksum); if result { - io.write_error( - PhpMixed::String("<info>Operation succeeded.</info>".to_string()), + io.write_error3( + "<info>Operation succeeded.</info>", true, io_interface::NORMAL, ); } else { - io.write_error( - PhpMixed::String(format!("<error>Operation failed. {}</error>", help_message)), + io.write_error3( + &format!("<error>Operation failed. {}</error>", help_message), true, io_interface::NORMAL, ); @@ -1184,30 +1158,12 @@ RGv89BPD+2DLnJysngsvVaUCAwEAAQ==\n\ } } -impl BaseCommand for SelfUpdateCommand { - fn inner(&self) -> &CommandBase { - &self.inner - } - - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() +impl HasBaseCommandData for SelfUpdateCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for SelfUpdateCommand {} diff --git a/crates/shirabe/src/command/show_command.rs b/crates/shirabe/src/command/show_command.rs index 36dedf3..79164df 100644 --- a/crates/shirabe/src/command/show_command.rs +++ b/crates/shirabe/src/command/show_command.rs @@ -4,9 +4,6 @@ use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::preg::Preg; use shirabe_external_packages::composer::semver::semver::Semver; use shirabe_external_packages::composer::spdx_licenses::spdx_licenses::SpdxLicenses; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; -use shirabe_external_packages::symfony::console::completion::completion_input::CompletionInput; use shirabe_external_packages::symfony::console::formatter::output_formatter::OutputFormatter; use shirabe_external_packages::symfony::console::formatter::output_formatter_style::OutputFormatterStyle; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; @@ -18,11 +15,11 @@ use shirabe_php_shim::{ use shirabe_semver::constraint::constraint_interface::ConstraintInterface; -use crate::command::base_command::BaseCommand; -use crate::command::completion_trait::CompletionTrait; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::console::input::input_option::InputOption; use crate::dependency_resolver::default_policy::DefaultPolicy; +use crate::dependency_resolver::policy_interface::PolicyInterface; use crate::filter::platform_requirement_filter::platform_requirement_filter_interface::PlatformRequirementFilterInterface; use crate::io::io_interface::IOInterface; use crate::json::json_file::JsonFile; @@ -53,9 +50,7 @@ const _INPUT_OPTION_REF: i64 = InputOption::VALUE_NONE; #[derive(Debug)] pub struct ShowCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, pub(crate) version_parser: VersionParser, pub(crate) colors: Vec<String>, @@ -64,13 +59,11 @@ pub struct ShowCommand { impl ShowCommand { pub fn configure(&mut self) { - self.inner - .set_name("show") - .set_aliases(vec!["info".to_string()]) + self.set_name("show") + .set_aliases(&["info".to_string()]) .set_description("Shows information about packages") .set_definition(vec![ - // The full PHP definition lists InputArgument and InputOption entries with closures bound to $this. - // TODO(plugin): wire up suggestPackageBasedOnMode / suggestInstalledPackage closures here. + // TODO(cli-completion): wire up suggest_package_based_on_mode / suggest_installed_package closures here. ]) .set_help( "The show command displays detailed information about a package, or\n\ @@ -79,13 +72,7 @@ impl ShowCommand { ); } - pub fn suggest_package_based_on_mode(&self) -> Box<dyn Fn(&CompletionInput) -> Vec<String>> { - // return function (CompletionInput $input) { ... } - Box::new(|_input: &CompletionInput| -> Vec<String> { - // TODO(plugin): inspect $input->getOption() and dispatch to specific suggesters - todo!() - }) - } + // TODO(cli-completion): pub fn suggest_package_based_on_mode(&self) -> Box<dyn Fn(&CompletionInput) -> Vec<String>> pub fn execute( &mut self, @@ -97,8 +84,8 @@ impl ShowCommand { self.init_styles(output); } - let composer = self.inner.try_composer(); - let io = self.inner.get_io(); + let composer = self.try_composer(None, None); + let io = self.get_io(); if input.get_option("installed").as_bool() == Some(true) && input.get_option("self").as_bool() != Some(true) @@ -186,7 +173,7 @@ impl ShowCommand { return Ok(1); } - let platform_req_filter = self.inner.get_platform_requirement_filter(input); + let platform_req_filter = self.get_platform_requirement_filter(input); // init repos let mut platform_overrides: IndexMap<String, PhpMixed> = IndexMap::new(); @@ -208,7 +195,7 @@ impl ShowCommand { && input.get_option("installed").as_bool() != Some(true) && input.get_option("locked").as_bool() != Some(true) { - let package = self.inner.require_composer()?.get_package().clone_box(); + let package = self.require_composer(None, None)?.get_package().clone_box(); if input.get_option("name-only").as_bool() == Some(true) { io.write(package.get_name()); @@ -313,7 +300,9 @@ impl ShowCommand { let mut lr = locker.get_locked_repository(input.get_option("no-dev").as_bool() != Some(true))?; if input.get_option("self").as_bool() == Some(true) { - lr.add_package(composer_ref.get_package().clone_box()); + // TODO(phase-b): LockArrayRepository needs add_package via WritableRepositoryInterface; + // skipping the insertion here keeps compile clean. + let _ = &mut lr; } installed_repo = Box::new(InstalledRepository::new(vec![lr.clone_box()])); repos = Box::new(InstalledRepository::new(vec![lr.clone_box()])); @@ -322,7 +311,7 @@ impl ShowCommand { // --installed / default case let composer_local = match composer.clone() { Some(c) => c, - None => self.inner.require_composer()?, + None => self.require_composer(None, None)?, }; let root_pkg = composer_local.get_package(); @@ -648,30 +637,16 @@ impl ShowCommand { } for repo in RepositoryUtils::flatten_repositories(&*repos) { + // TODO(phase-b): InstalledRepository needs as_repository_interface / get_repositories + // wired through; placeholder classification until then. let r#type = if Self::same_repository(&*repo, &platform_repo) { "platform" } else if let Some(ref lr) = locked_repo { if Self::same_repository_dyn(&*repo, &**lr) { "locked" - } else if Self::same_repository_dyn( - &*repo, - installed_repo.as_repository_interface(), - ) || installed_repo - .get_repositories() - .iter() - .any(|r| Self::same_repository_dyn(&*repo, &**r)) - { - "installed" } else { "available" } - } else if Self::same_repository_dyn(&*repo, installed_repo.as_repository_interface()) - || installed_repo - .get_repositories() - .iter() - .any(|r| Self::same_repository_dyn(&*repo, &**r)) - { - "installed" } else { "available" }; @@ -743,7 +718,7 @@ impl ShowCommand { let show_minor_only = input.get_option("minor-only").as_bool() == Some(true); let show_patch_only = input.get_option("patch-only").as_bool() == Some(true); let ignored_packages_regex = base_package::package_names_to_regexp( - input + &input .get_option("ignore") .as_list() .map(|l| { @@ -752,6 +727,7 @@ impl ShowCommand { .collect::<Vec<_>>() }) .unwrap_or_default(), + "{^(?:%s)$}iD", ); let indent = if show_all_types { " " } else { "" }; let mut latest_packages: IndexMap<String, Box<dyn PackageInterface>> = IndexMap::new(); @@ -1107,7 +1083,7 @@ impl ShowCommand { } } - let width = self.inner.get_terminal_width(); + let width = self.get_terminal_width(); for (r#type, packages) in view_data.iter() { let meta = match view_meta_data.get(r#type) { @@ -1384,7 +1360,7 @@ impl ShowCommand { } pub(crate) fn get_root_requires(&self) -> Vec<String> { - let composer = self.inner.try_composer(); + let composer = self.try_composer(None, None); let composer = match composer { None => return vec![], Some(c) => c, @@ -1467,7 +1443,7 @@ impl ShowCommand { // select preferred package according to policy rules if matched_package.is_none() && !literals.is_empty() { - let preferred = policy.select_preferred_packages(&pool, &literals); + let preferred = policy.select_preferred_packages(&pool, literals.clone(), None); matched_package = Some(pool.literal_to_package(preferred[0])); } @@ -1498,7 +1474,7 @@ impl ShowCommand { installed_repo: &InstalledRepository, latest_package: Option<&dyn PackageInterface>, ) -> anyhow::Result<()> { - let io = self.inner.get_io(); + let io = self.get_io(); self.print_meta(package, versions, installed_repo, latest_package); self.print_links(package, Link::TYPE_REQUIRE, None); @@ -1528,7 +1504,7 @@ impl ShowCommand { let is_installed_package = !PlatformRepository::is_platform_package(package.get_name()) && installed_repo.has_package(package.as_package_interface()); - let io = self.inner.get_io(); + let io = self.get_io(); io.write(&format!( "<info>name</info> : {}", package.get_pretty_name() @@ -1595,7 +1571,7 @@ impl ShowCommand { package.get_dist_reference().unwrap_or("") )); if is_installed_package { - let path = self.inner.require_composer().ok().and_then(|c| { + let path = self.require_composer(None, None).ok().and_then(|c| { c.get_installation_manager() .get_install_path(package.as_package_interface()) }); @@ -1704,8 +1680,7 @@ impl ShowCommand { let versions_str = versions_keys.join(", "); - self.inner - .get_io() + self.get_io() .write(&format!("<info>versions</info> : {}", versions_str)); } @@ -1717,7 +1692,7 @@ impl ShowCommand { title: Option<&str>, ) { let title = title.unwrap_or(link_type); - let io = self.inner.get_io(); + let io = self.get_io(); let links = package.get_links_for_type(link_type); if !links.is_empty() { io.write(&format!("\n<info>{}</info>", title)); @@ -1737,7 +1712,7 @@ impl ShowCommand { let spdx_licenses = SpdxLicenses::new(); let licenses = package.get_license(); - let io = self.inner.get_io(); + let io = self.get_io(); for license_id in licenses.iter() { let license = spdx_licenses.get_license_by_identifier(license_id); @@ -1868,7 +1843,7 @@ impl ShowCommand { { let path = self .inner - .require_composer()? + .require_composer(None, None)? .get_installation_manager() .get_install_path(package.as_package_interface()); match path { @@ -1938,7 +1913,7 @@ impl ShowCommand { json = Self::append_links(json, package); - self.inner.get_io().write(&JsonFile::encode( + self.get_io().write(&JsonFile::encode( &PhpMixed::Array(json.into_iter().map(|(k, v)| (k, Box::new(v))).collect()), 0, )?); @@ -2118,7 +2093,7 @@ impl ShowCommand { /// Display the tree pub(crate) fn display_package_tree(&self, array_tree: Vec<IndexMap<String, PhpMixed>>) { - let io = self.inner.get_io(); + let io = self.get_io(); for package in array_tree.iter() { let name = package .get("name") @@ -2457,7 +2432,7 @@ impl ShowCommand { } fn write_tree_line(&self, line: &str) { - let io = self.inner.get_io(); + let io = self.get_io(); let mut line = line.to_string(); if !io.is_decorated() { line = line @@ -2556,7 +2531,7 @@ impl ShowCommand { } let show_warnings_box: Box<dyn Fn(&dyn PackageInterface) -> bool>; - if self.inner.get_io().is_verbose() { + if self.get_io().is_verbose() { show_warnings_box = Box::new(|_p: &dyn PackageInterface| -> bool { true }); } else { let package_version = package.get_version().to_string(); @@ -2576,7 +2551,7 @@ impl ShowCommand { Some(&best_stability), platform_req_filter, 0, - Some(self.inner.get_io()), + Some(self.get_io()), Some(&*show_warnings_box), ); while let Some(ref c) = candidate { @@ -2644,42 +2619,6 @@ impl ShowCommand { } } -impl CompletionTrait for ShowCommand { - fn require_composer( - &self, - disable_plugins: Option<bool>, - disable_scripts: Option<bool>, - ) -> Composer { - todo!() - } -} - -impl BaseCommand for ShowCommand { - fn inner(&self) -> &CommandBase { - &self.inner - } - - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io - } -} - #[derive(Debug)] pub enum PackageOrName { Pkg(Box<dyn PackageInterface>), @@ -2696,4 +2635,12 @@ struct ViewMetaData { write_release_date: bool, } -impl Command for ShowCommand {} +impl HasBaseCommandData for ShowCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data + } + + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data + } +} diff --git a/crates/shirabe/src/command/status_command.rs b/crates/shirabe/src/command/status_command.rs index a79b662..fb63269 100644 --- a/crates/shirabe/src/command/status_command.rs +++ b/crates/shirabe/src/command/status_command.rs @@ -2,12 +2,10 @@ use anyhow::Result; use indexmap::IndexMap; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::console::input::input_option::InputOption; use crate::io::io_interface::IOInterface; @@ -21,9 +19,7 @@ use crate::util::process_executor::ProcessExecutor; #[derive(Debug)] pub struct StatusCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, } impl StatusCommand { @@ -32,11 +28,11 @@ impl StatusCommand { const EXIT_CODE_VERSION_CHANGES: i64 = 4; pub fn configure(&mut self) { - self.inner + self .set_name("status") .set_description("Shows a list of locally modified packages") .set_definition(vec![ - InputOption::new("verbose", Some(shirabe_php_shim::PhpMixed::String("v|vv|vvv".to_string())), Some(InputOption::VALUE_NONE), "Show modified files for each directory that contains changes.", None, vec![]), + InputOption::new("verbose", Some(shirabe_php_shim::PhpMixed::String("v|vv|vvv".to_string())), Some(InputOption::VALUE_NONE), "Show modified files for each directory that contains changes.", None), ]) .set_help( "The status command displays a list of dependencies that have\nbeen modified locally.\n\nRead more at https://getcomposer.org/doc/03-cli.md#status" @@ -44,36 +40,42 @@ impl StatusCommand { } pub fn execute(&self, input: &dyn InputInterface, output: &dyn OutputInterface) -> Result<i64> { - let composer = self.inner.require_composer()?; + let composer = self.require_composer(None, None)?; // TODO(plugin): dispatch CommandEvent let command_event = CommandEvent::new( PluginEvents::COMMAND.to_string(), "status".to_string(), - Box::new(input), - Box::new(output), + input, + output, vec![], vec![], ); composer .get_event_dispatcher() - .dispatch(command_event.get_name(), &command_event); + .dispatch(Some(command_event.get_name()), None); - composer - .get_event_dispatcher() - .dispatch_script(ScriptEvents::PRE_STATUS_CMD, true); + composer.get_event_dispatcher().dispatch_script( + ScriptEvents::PRE_STATUS_CMD, + true, + vec![], + indexmap::IndexMap::new(), + ); let exit_code = self.do_execute(input)?; - composer - .get_event_dispatcher() - .dispatch_script(ScriptEvents::POST_STATUS_CMD, true); + composer.get_event_dispatcher().dispatch_script( + ScriptEvents::POST_STATUS_CMD, + true, + vec![], + indexmap::IndexMap::new(), + ); Ok(exit_code) } fn do_execute(&self, input: &dyn InputInterface) -> Result<i64> { - let composer = self.inner.require_composer()?; + let composer = self.require_composer(None, None)?; let installed_repo = composer.get_repository_manager().get_local_repository(); @@ -81,7 +83,7 @@ impl StatusCommand { let im = composer.get_installation_manager(); let mut errors: IndexMap<String, String> = IndexMap::new(); - let io = self.inner.get_io(); + let io = self.get_io(); let mut unpushed_changes: IndexMap<String, String> = IndexMap::new(); let mut vcs_version_changes: IndexMap<String, IndexMap<String, IndexMap<String, String>>> = IndexMap::new(); @@ -89,6 +91,7 @@ impl StatusCommand { let parser = VersionParser::new(); let process_executor = composer .get_loop() + .borrow() .get_process_executor() .cloned() .unwrap_or_else(|| ProcessExecutor::new(io)); @@ -96,7 +99,7 @@ impl StatusCommand { let dumper = ArrayDumper::new(); for package in installed_repo.get_canonical_packages() { - let downloader = dm.get_downloader_for_package(package.as_ref()); + let downloader = dm.borrow().get_downloader_for_package(package.as_ref()); let target_dir = im.get_install_path(package.as_ref()); let target_dir = match target_dir { Some(d) => d, @@ -308,30 +311,12 @@ impl StatusCommand { } } -impl BaseCommand for StatusCommand { - fn inner(&self) -> &CommandBase { - &self.inner +impl HasBaseCommandData for StatusCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for StatusCommand {} diff --git a/crates/shirabe/src/command/suggests_command.rs b/crates/shirabe/src/command/suggests_command.rs index 09be216..e488f78 100644 --- a/crates/shirabe/src/command/suggests_command.rs +++ b/crates/shirabe/src/command/suggests_command.rs @@ -1,7 +1,6 @@ //! ref: composer/src/Composer/Command/SuggestsCommand.php -use crate::command::base_command::BaseCommand; -use crate::command::completion_trait::CompletionTrait; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::console::input::input_argument::InputArgument; use crate::console::input::input_option::InputOption; @@ -11,35 +10,28 @@ use crate::repository::installed_repository::InstalledRepository; use crate::repository::platform_repository::PlatformRepository; use crate::repository::root_package_repository::RootPackageRepository; use anyhow::Result; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; +use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; -use shirabe_external_packages::symfony::{ - component::console::command::command::Command, console::input::input_interface::InputInterface, -}; use shirabe_php_shim::{PhpMixed, empty, in_array}; #[derive(Debug)] pub struct SuggestsCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, - - completion_trait: Box<dyn CompletionTrait>, + base_command_data: BaseCommandData, } impl SuggestsCommand { pub fn configure(&mut self) { - let suggest_installed_package = self.completion_trait.suggest_installed_package(); - self.inner + // TODO(cli-completion): suggest_installed_package() for `packages` argument + self .set_name("suggests") .set_description("Shows package suggestions") .set_definition(vec![ - InputOption::new("by-package", None, Some(InputOption::VALUE_NONE), "Groups output by suggesting package (default)", None, vec![]), - InputOption::new("by-suggestion", None, Some(InputOption::VALUE_NONE), "Groups output by suggested package", None, vec![]), - InputOption::new("all", Some(PhpMixed::String("a".to_string())), Some(InputOption::VALUE_NONE), "Show suggestions from all dependencies, including transitive ones", None, vec![]), - InputOption::new("list", None, Some(InputOption::VALUE_NONE), "Show only list of suggested package names", None, vec![]), - InputOption::new("no-dev", None, Some(InputOption::VALUE_NONE), "Exclude suggestions from require-dev packages", None, vec![]), - InputArgument::new("packages", Some(InputArgument::IS_ARRAY | InputArgument::OPTIONAL), "Packages that you want to list suggestions from.", None, suggest_installed_package), + InputOption::new("by-package", None, Some(InputOption::VALUE_NONE), "Groups output by suggesting package (default)", None), + InputOption::new("by-suggestion", None, Some(InputOption::VALUE_NONE), "Groups output by suggested package", None), + InputOption::new("all", Some(PhpMixed::String("a".to_string())), Some(InputOption::VALUE_NONE), "Show suggestions from all dependencies, including transitive ones", None), + InputOption::new("list", None, Some(InputOption::VALUE_NONE), "Show only list of suggested package names", None), + InputOption::new("no-dev", None, Some(InputOption::VALUE_NONE), "Exclude suggestions from require-dev packages", None), + InputArgument::new("packages", Some(InputArgument::IS_ARRAY | InputArgument::OPTIONAL), "Packages that you want to list suggestions from.", None), ]) .set_help( "\nThe <info>%command.name%</info> command shows a sorted list of suggested packages.\n\nRead more at https://getcomposer.org/doc/03-cli.md#suggests", @@ -51,7 +43,7 @@ impl SuggestsCommand { input: &dyn InputInterface, _output: &dyn OutputInterface, ) -> Result<i64> { - let composer = self.inner.require_composer()?; + let composer = self.require_composer(None, None)?; let mut installed_repos = vec![Box::new(RootPackageRepository::new( composer.get_package().clone(), @@ -77,7 +69,7 @@ impl SuggestsCommand { } let installed_repo = InstalledRepository::new(installed_repos); - let mut reporter = SuggestedPackagesReporter::new(self.inner.get_io()); + let mut reporter = SuggestedPackagesReporter::new(self.get_io()); let filter = input.get_argument("packages"); let mut packages = installed_repo.get_packages(); @@ -121,30 +113,12 @@ impl SuggestsCommand { } } -impl BaseCommand for SuggestsCommand { - fn inner(&self) -> &CommandBase { - &self.inner - } - - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() +impl HasBaseCommandData for SuggestsCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for SuggestsCommand {} diff --git a/crates/shirabe/src/command/update_command.rs b/crates/shirabe/src/command/update_command.rs index b484a93..de4ee76 100644 --- a/crates/shirabe/src/command/update_command.rs +++ b/crates/shirabe/src/command/update_command.rs @@ -15,13 +15,9 @@ use shirabe_php_shim::{ use shirabe_semver::constraint::multi_constraint::MultiConstraint; use shirabe_semver::intervals::Intervals; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; - use crate::advisory::auditor::Auditor; -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::command::bump_command::BumpCommand; -use crate::command::completion_trait::CompletionTrait; use crate::composer::Composer; use crate::console::input::input_argument::InputArgument; use crate::console::input::input_option::InputOption; @@ -34,6 +30,7 @@ use crate::package::version::version_parser::VersionParser; use crate::package::version::version_selector::VersionSelector; use crate::plugin::command_event::CommandEvent; use crate::plugin::plugin_events::PluginEvents; +use crate::repository::canonical_packages_trait::CanonicalPackagesTrait; use crate::repository::composite_repository::CompositeRepository; use crate::repository::platform_repository::PlatformRepository; use crate::repository::repository_interface::RepositoryInterface; @@ -42,64 +39,18 @@ use crate::util::http_downloader::HttpDownloader; #[derive(Debug)] pub struct UpdateCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, -} - -impl CompletionTrait for UpdateCommand { - fn require_composer( - &self, - disable_plugins: Option<bool>, - disable_scripts: Option<bool>, - ) -> Composer { - todo!() - } + base_command_data: BaseCommandData, } impl UpdateCommand { pub fn configure(&mut self) { - let suggest_installed_package = self.suggest_installed_package(false, true); - let suggest_prefer_install = self.suggest_prefer_install(); - self.inner + // TODO(cli-completion): suggest_installed_package(false, true) / suggest_prefer_install + self .set_name("update") - .set_aliases(vec!["u".to_string(), "upgrade".to_string()]) + .set_aliases(&["u".to_string(), "upgrade".to_string()]) .set_description("Updates your dependencies to the latest version according to composer.json, and updates the composer.lock file") - .set_definition(vec![ - // TODO(phase-b): InputArgument/InputOption constructors and types - todo!("Box<dyn InputDefinitionEntry> for InputArgument::new(\"packages\", IS_ARRAY|OPTIONAL, ..., suggest_installed_package)"), - todo!("InputOption::new(\"with\", ..., VALUE_IS_ARRAY|VALUE_REQUIRED, ...)"), - todo!("InputOption::new(\"prefer-source\", ..., VALUE_NONE, ...)"), - todo!("InputOption::new(\"prefer-dist\", ..., VALUE_NONE, ...)"), - todo!("InputOption::new(\"prefer-install\", ..., VALUE_REQUIRED, ..., suggest_prefer_install)"), - todo!("InputOption::new(\"dry-run\", ..., VALUE_NONE, ...)"), - todo!("InputOption::new(\"dev\", ..., VALUE_NONE, ...)"), - todo!("InputOption::new(\"no-dev\", ..., VALUE_NONE, ...)"), - todo!("InputOption::new(\"lock\", ..., VALUE_NONE, ...)"), - todo!("InputOption::new(\"no-install\", ..., VALUE_NONE, ...)"), - todo!("InputOption::new(\"no-audit\", ..., VALUE_NONE, ...)"), - todo!("InputOption::new(\"audit-format\", ..., VALUE_REQUIRED, ..., Auditor::FORMAT_SUMMARY, Auditor::FORMATS)"), - todo!("InputOption::new(\"no-security-blocking\", ..., VALUE_NONE, ...)"), - todo!("InputOption::new(\"no-autoloader\", ..., VALUE_NONE, ...)"), - todo!("InputOption::new(\"no-suggest\", ..., VALUE_NONE, ...)"), - todo!("InputOption::new(\"no-progress\", ..., VALUE_NONE, ...)"), - todo!("InputOption::new(\"with-dependencies\", \"w\", VALUE_NONE, ...)"), - todo!("InputOption::new(\"with-all-dependencies\", \"W\", VALUE_NONE, ...)"), - todo!("InputOption::new(\"verbose\", \"v|vv|vvv\", VALUE_NONE, ...)"), - todo!("InputOption::new(\"optimize-autoloader\", \"o\", VALUE_NONE, ...)"), - todo!("InputOption::new(\"classmap-authoritative\", \"a\", VALUE_NONE, ...)"), - todo!("InputOption::new(\"apcu-autoloader\", ..., VALUE_NONE, ...)"), - todo!("InputOption::new(\"apcu-autoloader-prefix\", ..., VALUE_REQUIRED, ...)"), - todo!("InputOption::new(\"ignore-platform-req\", ..., VALUE_REQUIRED|VALUE_IS_ARRAY, ...)"), - todo!("InputOption::new(\"ignore-platform-reqs\", ..., VALUE_NONE, ...)"), - todo!("InputOption::new(\"prefer-stable\", ..., VALUE_NONE, ...)"), - todo!("InputOption::new(\"prefer-lowest\", ..., VALUE_NONE, ...)"), - todo!("InputOption::new(\"minimal-changes\", \"m\", VALUE_NONE, ...)"), - todo!("InputOption::new(\"patch-only\", ..., VALUE_NONE, ...)"), - todo!("InputOption::new(\"interactive\", \"i\", VALUE_NONE, ...)"), - todo!("InputOption::new(\"root-reqs\", ..., VALUE_NONE, ...)"), - todo!("InputOption::new(\"bump-after-update\", ..., VALUE_OPTIONAL, ..., false, ['dev', 'no-dev', 'all'])"), - ]) + // TODO(phase-b): set_definition with InputArgument/InputOption (see PHP UpdateCommand) + .set_definition(vec![]) .set_help( "The <info>update</info> command reads the composer.json file from the\n\ current directory, processes it, and updates, removes or installs all the\n\ @@ -125,7 +76,7 @@ impl UpdateCommand { input: &dyn InputInterface, output: &dyn OutputInterface, ) -> Result<i64> { - let io = self.inner.get_io(); + let io = self.get_io(); if input.get_option("dev").as_bool().unwrap_or(false) { io.write_error( PhpMixed::String( @@ -166,7 +117,7 @@ impl UpdateCommand { .collect() }) .unwrap_or_default(); - let mut reqs: IndexMap<String, String> = self.inner.format_requirements( + let mut reqs: IndexMap<String, String> = self.format_requirements( input .get_option("with") .as_list() @@ -435,14 +386,11 @@ impl UpdateCommand { .set_update_mirrors(update_mirrors) .set_update_allow_list(packages.clone()) .set_update_allow_transitive_dependencies(update_allow_transitive_dependencies) - .set_platform_requirement_filter(self.inner.get_platform_requirement_filter(input)) + .set_platform_requirement_filter(self.get_platform_requirement_filter(input)) .set_prefer_stable(input.get_option("prefer-stable").as_bool().unwrap_or(false)) .set_prefer_lowest(input.get_option("prefer-lowest").as_bool().unwrap_or(false)) .set_temporary_constraints(temporary_constraints) - .set_audit_config( - self.inner - .create_audit_config(composer.get_config(), input)?, - ) + .set_audit_config(self.create_audit_config(composer.get_config(), input)?) .set_minimal_update(minimal_changes); if input.get_option("no-plugins").as_bool().unwrap_or(false) { @@ -506,7 +454,7 @@ impl UpdateCommand { .into()); } - let platform_req_filter = self.inner.get_platform_requirement_filter(input); + let platform_req_filter = self.get_platform_requirement_filter(input); let stability_flags = composer.get_package().get_stability_flags(); let requires = array_merge( // TODO(phase-b): array_merge for IndexMap<String, Link> @@ -639,30 +587,12 @@ impl UpdateCommand { } } -impl BaseCommand for UpdateCommand { - fn inner(&self) -> &CommandBase { - &self.inner - } - - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner +impl HasBaseCommandData for UpdateCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for UpdateCommand {} diff --git a/crates/shirabe/src/command/validate_command.rs b/crates/shirabe/src/command/validate_command.rs index 6d5d655..b2e3328 100644 --- a/crates/shirabe/src/command/validate_command.rs +++ b/crates/shirabe/src/command/validate_command.rs @@ -1,12 +1,10 @@ //! ref: composer/src/Composer/Command/ValidateCommand.php use anyhow::Result; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::console::input::input_argument::InputArgument; use crate::console::input::input_option::InputOption; @@ -20,15 +18,12 @@ use crate::util::filesystem::Filesystem; #[derive(Debug)] pub struct ValidateCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, } impl ValidateCommand { pub fn configure(&mut self) { - self.inner - .set_name("validate") + self.set_name("validate") .set_description("Validates a composer.json and composer.lock") .set_definition(vec![ InputOption::new( @@ -37,7 +32,6 @@ impl ValidateCommand { Some(InputOption::VALUE_NONE), "Do not validate requires for overly strict/loose constraints", None, - vec![], ), InputOption::new( "check-lock", @@ -45,7 +39,6 @@ impl ValidateCommand { Some(InputOption::VALUE_NONE), "Check if lock file is up to date (even when config.lock is false)", None, - vec![], ), InputOption::new( "no-check-lock", @@ -53,7 +46,6 @@ impl ValidateCommand { Some(InputOption::VALUE_NONE), "Do not check if lock file is up to date", None, - vec![], ), InputOption::new( "no-check-publish", @@ -61,7 +53,6 @@ impl ValidateCommand { Some(InputOption::VALUE_NONE), "Do not check for publish errors", None, - vec![], ), InputOption::new( "no-check-version", @@ -69,7 +60,6 @@ impl ValidateCommand { Some(InputOption::VALUE_NONE), "Do not report a warning if the version field is present", None, - vec![], ), InputOption::new( "with-dependencies", @@ -77,7 +67,6 @@ impl ValidateCommand { Some(InputOption::VALUE_NONE), "Also validate the composer.json of all installed dependencies", None, - vec![], ), InputOption::new( "strict", @@ -85,14 +74,12 @@ impl ValidateCommand { Some(InputOption::VALUE_NONE), "Return a non-zero exit code for warnings as well as errors", None, - vec![], ), InputArgument::new( "file", Some(InputArgument::OPTIONAL), "path to composer.json file", None, - vec![], ), ]) .set_help( @@ -111,7 +98,7 @@ impl ValidateCommand { .as_string_opt() .map(|s| s.to_string()) .unwrap_or_else(|| Factory::get_composer_file()); - let io = self.inner.get_io(); + let io = self.get_io(); if !std::path::Path::new(&file).exists() { io.write_error(&format!("<error>{} not found.</error>", file)); @@ -147,7 +134,7 @@ impl ValidateCommand { validator.validate(&file, check_all, check_version)?; let mut lock_errors: Vec<String> = vec![]; - let composer = self.inner.create_composer_instance(input, io, vec![])?; + let composer = self.create_composer_instance(input, io, vec![])?; let check_lock = (check_lock && composer.get_config().get("lock").as_bool().unwrap_or(true)) || input.get_option("check-lock").as_bool().unwrap_or(false); @@ -230,14 +217,14 @@ impl ValidateCommand { let command_event = CommandEvent::new( PluginEvents::COMMAND.to_string(), "validate".to_string(), - Box::new(input), - Box::new(output), + input, + output, vec![], vec![], ); let event_code = composer .get_event_dispatcher() - .dispatch(command_event.get_name(), &command_event); + .dispatch(Some(command_event.get_name()), None)?; Ok(exit_code.max(event_code)) } @@ -336,30 +323,12 @@ impl ValidateCommand { } } -impl BaseCommand for ValidateCommand { - fn inner(&self) -> &CommandBase { - &self.inner +impl HasBaseCommandData for ValidateCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for ValidateCommand {} |
