//! ref: composer/src/Composer/Command/GlobalCommand.php use std::path::Path; use anyhow::Result; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::Preg; use shirabe_external_packages::symfony::console::command::command::Command; use shirabe_external_packages::symfony::console::input::ArgvInput; use shirabe_external_packages::symfony::console::input::ArrayInput; use shirabe_external_packages::symfony::console::input::InputInterface; use shirabe_external_packages::symfony::console::input::StringInput; use shirabe_external_packages::symfony::console::output::OutputInterface; use shirabe_php_shim::PhpMixed; use shirabe_php_shim::{AsAny, LogicException, RuntimeException, chdir}; use std::cell::RefCell; use std::rc::Rc; use crate::advisory::AuditConfig; use crate::command::BaseCommand; use crate::command::BaseCommandData; use crate::command::base_command::base_command_initialize; use crate::composer::PartialComposerHandle; use crate::config::Config; use crate::console::input::InputArgument; use crate::factory::Factory; use crate::filter::platform_requirement_filter::PlatformRequirementFilterInterface; use crate::io::IOInterface; use crate::io::IOInterfaceImmutable; use crate::util::Filesystem; use crate::util::Platform; #[derive(Debug)] pub struct GlobalCommand { base_command_data: BaseCommandData, } impl Default for GlobalCommand { fn default() -> Self { Self::new() } } impl GlobalCommand { pub fn new() -> Self { let mut command = GlobalCommand { base_command_data: BaseCommandData::new(None), }; command .configure() .expect("GlobalCommand::configure uses static, valid metadata"); command } // TODO(cli-completion): pub fn complete(&self, input: &CompletionInput, suggestions: &mut CompletionSuggestions) // TODO remove for Symfony 6+ as it is then in the interface. // Mirrors PHP's `method_exists($input, '__toString')` guard followed by // `$input->__toString()`. `InputInterface` does not declare `__toString`, so the // concrete stringable input types are matched explicitly. fn input_to_string(input: &dyn InputInterface) -> Result { let input_any = input.as_any(); if let Some(argv_input) = input_any.downcast_ref::() { Ok(argv_input.to_string()) } else if let Some(array_input) = input_any.downcast_ref::() { Ok(array_input.to_string()) } else if input_any.is::() { // StringInput's stringification is not exposed by shirabe-external-packages // (its inner ArgvInput is pub(crate) and it defines no public __toString). todo!("StringInput::__toString is not accessible from the shirabe crate") } else { Err(LogicException { message: "Expected an Input instance that is stringable".to_string(), code: 0, } .into()) } } fn prepare_subcommand_input( &mut self, input: Rc>, quiet: bool, ) -> Result { if Platform::get_env("COMPOSER").is_some() { Platform::clear_env("COMPOSER"); } let mut config = Factory::create_config(None, None)?; let home = config.get("home").as_string().unwrap_or("").to_string(); if !Path::new(&home).is_dir() { let mut fs = Filesystem::new(None); fs.ensure_directory_exists(&home)?; if !Path::new(&home).is_dir() { return Err(RuntimeException { message: "Could not create home directory".to_string(), code: 0, } .into()); } } chdir(&home).map_err(|_e| RuntimeException { message: format!("Could not switch to home directory \"{}\"", home), code: 0, })?; if !quiet { self.get_io().borrow().write_error(&format!( "Changed current directory to {}", home )); } let new_input_str = Preg::replace4( r"{\bg(?:l(?:o(?:b(?:a(?:l)?)?)?)?)?\b}", "", &Self::input_to_string(&*input.borrow())?, 1, ); // TODO(phase-c): getApplication()->resetComposer() needs the shared shirabe Application // handle (deferred with the Application shared-ownership work). StringInput::new(&new_input_str) } } impl Command for GlobalCommand { fn configure(&mut self) -> anyhow::Result<()> { self.set_name("global")?; self.set_description("Allows running commands in the global composer dir ($COMPOSER_HOME)"); self.set_definition(&[ InputArgument::new("command-name", Some(InputArgument::REQUIRED), "", None) .unwrap() .into(), InputArgument::new( "args", Some(InputArgument::IS_ARRAY | InputArgument::OPTIONAL), "", None, ) .unwrap() .into(), ]); self.set_help( "Use this command as a wrapper to run other Composer commands\n\ within the global context of COMPOSER_HOME.\n\n\ You can use this to install CLI utilities globally, all you need\n\ is to add the COMPOSER_HOME/vendor/bin dir to your PATH env var.\n\n\ COMPOSER_HOME is c:\\Users\\\\AppData\\Roaming\\Composer on Windows\n\ and /home//.composer on unix systems.\n\n\ If your system uses freedesktop.org standards, then it will first check\n\ XDG_CONFIG_HOME or default to /home//.config/composer\n\n\ Note: This path may vary depending on customizations to bin-dir in\n\ composer.json or the environmental variable COMPOSER_BIN_DIR.\n\n\ Read more at https://getcomposer.org/doc/03-cli.md#global", ); Ok(()) } fn is_proxy_command(&self) -> bool { true } fn run( &mut self, input: Rc>, output: Rc>, ) -> anyhow::Result { let tokens = Preg::split(r"{\s+}", &Self::input_to_string(&*input.borrow())?); let mut args: Vec = vec![]; for token in &tokens { if !token.is_empty() && !token.starts_with('-') { args.push(token.clone()); if args.len() >= 2 { break; } } } if args.len() < 2 { return self.base_run(input, output); } let sub_input = self.prepare_subcommand_input(input, false)?; // TODO(phase-c): proxying to Application::run needs the shared shirabe Application handle // (deferred with the Application shared-ownership work and command registration). let _ = (sub_input, output); todo!("global command proxy run pending shared Application handle") } fn initialize( &mut self, input: Rc>, output: Rc>, ) -> anyhow::Result<()> { base_command_initialize(self, input, output) } shirabe_external_packages::delegate_command_trait_impls_to_inner!(base_command_data); } impl BaseCommand for GlobalCommand { fn command_data_mut( &mut self, ) -> &mut shirabe_external_packages::symfony::console::command::command::CommandData { self.base_command_data.command_data_mut() } crate::delegate_base_command_trait_impls_to_inner!(base_command_data); fn is_proxy_command(&self) -> bool { true } }