//! ref: composer/src/Composer/Command/HomeCommand.php use anyhow::Result; 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_var, FILTER_VALIDATE_URL}; use crate::command::base_command::BaseCommand; use crate::command::completion_trait::CompletionTrait; use crate::console::input::input_argument::InputArgument; use crate::console::input::input_option::InputOption; use crate::package::complete_package_interface::CompletePackageInterface; use crate::repository::repository_factory::RepositoryFactory; use crate::repository::repository_interface::RepositoryInterface; use crate::repository::root_package_repository::RootPackageRepository; use crate::util::platform::Platform; use crate::util::process_executor::ProcessExecutor; #[derive(Debug)] pub struct HomeCommand { inner: BaseCommand, } impl CompletionTrait for HomeCommand {} impl HomeCommand { pub fn configure(&mut self) { self.inner .set_name("browse") .set_aliases(vec!["home".to_string()]) .set_description("Opens the package's repository URL or homepage in your browser") .set_definition(vec![ InputArgument::new( "packages", Some(InputArgument::IS_ARRAY), "Package(s) to browse to.", None, self.suggest_installed_package(), ), InputOption::new("homepage", Some(shirabe_php_shim::PhpMixed::String("H".to_string())), Some(InputOption::VALUE_NONE), "Open the homepage instead of the repository URL.", None, vec![]), InputOption::new("show", Some(shirabe_php_shim::PhpMixed::String("s".to_string())), Some(InputOption::VALUE_NONE), "Only show the homepage or repository URL.", None, vec![]), ]) .set_help( "The home command opens or shows a package's repository URL or\n\ homepage in your default browser.\n\n\ To open the homepage by default, use -H or --homepage.\n\ To show instead of open the repository or homepage URL, use -s or --show.\n\n\ Read more at https://getcomposer.org/doc/03-cli.md#browse-home" ); } pub fn execute(&self, input: &dyn InputInterface, _output: &dyn OutputInterface) -> Result { let repos = self.initialize_repos()?; let io = self.inner.get_io(); let mut return_code: i64 = 0; let packages: Vec = input.get_argument("packages") .as_list() .map(|l| l.iter().filter_map(|v| v.as_string().map(|s| s.to_string())).collect()) .unwrap_or_default(); let packages = if packages.is_empty() { io.write_error("No package specified, opening homepage for the root package"); vec![self.inner.require_composer()?.get_package().get_name().to_string()] } else { packages }; let show_homepage = input.get_option("homepage").as_bool().unwrap_or(false); let show_only = input.get_option("show").as_bool().unwrap_or(false); for package_name in &packages { let mut handled = false; let mut package_exists = false; 'repos: for repo in &repos { for package in repo.find_packages(package_name) { package_exists = true; if let Some(complete_pkg) = package.as_complete_package_interface() { if self.handle_package(complete_pkg, show_homepage, show_only) { handled = true; break 'repos; } } } } if !package_exists { return_code = 1; io.write_error(&format!("Package {} not found", package_name)); } if !handled { return_code = 1; let msg = if show_homepage { "Invalid or missing homepage" } else { "Invalid or missing repository URL" }; io.write_error(&format!("{} for {}", msg, package_name)); } } Ok(return_code) } fn handle_package(&self, package: &dyn CompletePackageInterface, show_homepage: bool, show_only: bool) -> bool { let support = package.get_support(); let mut url = support.get("source").and_then(|v| v.as_string()).map(|s| s.to_string()) .or_else(|| package.get_source_url().map(|s| s.to_string())); if url.as_deref().map_or(true, |s| s.is_empty()) || show_homepage { url = package.get_homepage().map(|s| s.to_string()); } let url = match url { None => return false, Some(u) if u.is_empty() => return false, Some(u) => u, }; if !filter_var(&url, FILTER_VALIDATE_URL) { return false; } if show_only { self.inner.get_io().write(&format!("{}", url)); } else { self.open_browser(&url); } true } fn open_browser(&self, url: &str) { let io = self.inner.get_io(); let mut process = ProcessExecutor::new(io); if Platform::is_windows() { process.execute(&["start", "\"web\"", "explorer", url], None); return; } let linux = process.execute(&["which", "xdg-open"], None); let osx = process.execute(&["which", "open"], None); if linux == 0 { process.execute(&["xdg-open", url], None); } else if osx == 0 { process.execute(&["open", url], None); } else { io.write_error(&format!("No suitable browser opening command found, open yourself: {}", url)); } } fn initialize_repos(&self) -> Result>> { let composer = self.inner.try_composer(); if let Some(composer) = composer { let mut repos: Vec> = vec![]; repos.push(Box::new(RootPackageRepository::new(composer.get_package().clone_package()))); repos.push(Box::new(composer.get_repository_manager().get_local_repository())); for repo in composer.get_repository_manager().get_repositories() { repos.push(repo); } return Ok(repos); } Ok(RepositoryFactory::default_repos_with_default_manager(self.inner.get_io())) } }