aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src/command/show_command.rs
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-08-07 07:26:48 +0900
committernsfisis <nsfisis@gmail.com>2026-08-07 07:26:48 +0900
commitf749a47804cd296a3059cd3f8079c62dbaa5fdc0 (patch)
treea84d5d40f6f9eea2a83355a273d0fb57214a864a /crates/shirabe/src/command/show_command.rs
parente7f83b74e8f8c12b4a1b0f9f613387b03858dbdd (diff)
downloadphp-shirabe-f749a47804cd296a3059cd3f8079c62dbaa5fdc0.tar.gz
php-shirabe-f749a47804cd296a3059cd3f8079c62dbaa5fdc0.tar.zst
php-shirabe-f749a47804cd296a3059cd3f8079c62dbaa5fdc0.zip
refactor: merge split inherent impl blocks into one per type
Enable clippy::multiple_inherent_impl and fix the 21 sites it reports. Types whose inherent methods were spread across two or three impl blocks now keep them in a single block; only the impl headers move, no method bodies change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/src/command/show_command.rs')
-rw-r--r--crates/shirabe/src/command/show_command.rs2748
1 files changed, 1373 insertions, 1375 deletions
diff --git a/crates/shirabe/src/command/show_command.rs b/crates/shirabe/src/command/show_command.rs
index fb663d86..44733531 100644
--- a/crates/shirabe/src/command/show_command.rs
+++ b/crates/shirabe/src/command/show_command.rs
@@ -81,1382 +81,7 @@ impl ShowCommand {
.expect("ShowCommand::configure uses static, valid metadata");
command
}
-}
-
-impl Command for ShowCommand {
- fn configure(&self) -> anyhow::Result<()> {
- self.set_name("show")?;
- self.set_aliases(vec!["info".to_string()])?;
- self.set_description("Shows information about packages");
- let opt_none = |name: &str, shortcut: Option<&str>, description: &str| {
- InputOption::new(
- name,
- shortcut.map(|s| PhpMixed::String(s.to_string())),
- Some(InputOption::VALUE_NONE),
- description,
- None,
- )
- .unwrap()
- .into()
- };
- self.set_definition(&[
- InputArgument::new5(
- "package",
- Some(InputArgument::OPTIONAL),
- "Package to inspect. Or a name including a wildcard (*) to filter lists of packages instead.",
- None,
- self.suggest_package_based_on_mode(),
- )
- .unwrap()
- .into(),
- InputArgument::new(
- "version",
- Some(InputArgument::OPTIONAL),
- "Version or version constraint to inspect",
- None,
- )
- .unwrap()
- .into(),
- opt_none("all", None, "List all packages"),
- opt_none("locked", None, "List all locked packages"),
- opt_none(
- "installed",
- Some("i"),
- "List installed packages only (enabled by default, only present for BC).",
- ),
- opt_none("platform", Some("p"), "List platform packages only"),
- opt_none("available", Some("a"), "List available packages only"),
- opt_none("self", Some("s"), "Show the root package information"),
- opt_none("name-only", Some("N"), "List package names only"),
- opt_none("path", Some("P"), "Show package paths"),
- opt_none("tree", Some("t"), "List the dependencies as a tree"),
- opt_none("latest", Some("l"), "Show the latest version"),
- opt_none(
- "outdated",
- Some("o"),
- "Show the latest version but only for packages that are outdated",
- ),
- InputOption::new6(
- "ignore",
- None,
- Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY),
- "Ignore specified package(s). Can contain wildcards (*). Use it with the --outdated option if you don't want to be informed about new versions of some packages.",
- None,
- self.suggest_installed_package(false, false),
- )
- .unwrap()
- .into(),
- opt_none(
- "major-only",
- Some("M"),
- "Show only packages that have major SemVer-compatible updates. Use with the --latest or --outdated option.",
- ),
- opt_none(
- "minor-only",
- Some("m"),
- "Show only packages that have minor SemVer-compatible updates. Use with the --latest or --outdated option.",
- ),
- opt_none(
- "patch-only",
- None,
- "Show only packages that have patch SemVer-compatible updates. Use with the --latest or --outdated option.",
- ),
- opt_none(
- "sort-by-age",
- Some("A"),
- "Displays the installed version's age, and sorts packages oldest first. Use with the --latest or --outdated option.",
- ),
- opt_none(
- "direct",
- Some("D"),
- "Shows only packages that are directly required by the root package",
- ),
- opt_none(
- "strict",
- None,
- "Return a non-zero exit code when there are outdated packages",
- ),
- InputOption::new6(
- "format",
- Some(PhpMixed::String("f".to_string())),
- Some(InputOption::VALUE_REQUIRED),
- "Format of the output: text or json",
- Some(PhpMixed::String("text".to_string())),
- SuggestedValues::List(vec!["json".to_string(), "text".to_string()]),
- )
- .unwrap()
- .into(),
- opt_none("no-dev", None, "Disables search in require-dev packages."),
- 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,
- )
- .unwrap()
- .into(),
- opt_none(
- "ignore-platform-reqs",
- None,
- "Ignore all platform requirements (php & ext- packages). Use with the --outdated option",
- ),
- ]);
- self.set_help(
- "The show command displays detailed information about a package, or\n\
- lists all packages available.\n\n\
- Read more at https://getcomposer.org/doc/03-cli.md#show-info",
- );
- Ok(())
- }
-
- fn execute(
- &self,
- input: std::rc::Rc<std::cell::RefCell<dyn InputInterface>>,
- output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>,
- ) -> anyhow::Result<i64> {
- *self.version_parser.borrow_mut() = VersionParser::new();
- if input.borrow().get_option("tree")?.as_bool() == Some(true) {
- self.init_styles(output.clone());
- }
-
- let composer = self.try_composer(None, None);
-
- if input.borrow().get_option("installed")?.as_bool() == Some(true)
- && input.borrow().get_option("self")?.as_bool() != Some(true)
- {
- self.get_io().write_error("<warning>You are using the deprecated option \"installed\". Only installed packages are shown by default now. The --all option can be used to show all packages.</warning>");
- }
-
- if input.borrow().get_option("outdated")?.as_bool() == Some(true) {
- input
- .borrow_mut()
- .set_option("latest", PhpMixed::Bool(true));
- } else if input
- .borrow()
- .get_option("ignore")?
- .as_list()
- .map_or(0, |l| l.len())
- > 0
- {
- self.get_io().write_error("<warning>You are using the option \"ignore\" for action other than \"outdated\", it will be ignored.</warning>");
- }
-
- if input.borrow().get_option("direct")?.as_bool() == Some(true)
- && (input.borrow().get_option("all")?.as_bool() == Some(true)
- || input.borrow().get_option("available")?.as_bool() == Some(true)
- || input.borrow().get_option("platform")?.as_bool() == Some(true))
- {
- self.get_io().write_error("The --direct (-D) option is not usable in combination with --all, --platform (-p) or --available (-a)");
-
- return Ok(1);
- }
-
- if input.borrow().get_option("tree")?.as_bool() == Some(true)
- && (input.borrow().get_option("all")?.as_bool() == Some(true)
- || input.borrow().get_option("available")?.as_bool() == Some(true))
- {
- self.get_io().write_error("The --tree (-t) option is not usable in combination with --all or --available (-a)");
-
- return Ok(1);
- }
-
- let only_count: usize = [
- input.borrow().get_option("patch-only")?.as_bool() == Some(true),
- input.borrow().get_option("minor-only")?.as_bool() == Some(true),
- input.borrow().get_option("major-only")?.as_bool() == Some(true),
- ]
- .iter()
- .filter(|b| **b)
- .count();
- if only_count > 1 {
- self.get_io().write_error(
- "Only one of --major-only, --minor-only or --patch-only can be used at once",
- );
-
- return Ok(1);
- }
-
- if input.borrow().get_option("tree")?.as_bool() == Some(true)
- && input.borrow().get_option("latest")?.as_bool() == Some(true)
- {
- self.get_io().write_error(
- "The --tree (-t) option is not usable in combination with --latest (-l)",
- );
-
- return Ok(1);
- }
-
- if input.borrow().get_option("tree")?.as_bool() == Some(true)
- && input.borrow().get_option("path")?.as_bool() == Some(true)
- {
- self.get_io().write_error(
- "The --tree (-t) option is not usable in combination with --path (-P)",
- );
-
- return Ok(1);
- }
-
- let format = input
- .borrow()
- .get_option("format")?
- .as_string()
- .unwrap_or("text")
- .to_string();
- if !in_array_loose(
- format.clone(),
- &[
- PhpMixed::String("text".to_string()),
- PhpMixed::String("json".to_string()),
- ],
- ) {
- self.get_io().write_error(&format!(
- "Unsupported format \"{}\". See help for supported formats.",
- format
- ));
-
- return Ok(1);
- }
-
- let platform_req_filter = self.get_platform_requirement_filter(input.clone())?;
-
- // init repos
- let mut platform_overrides: IndexMap<String, PhpMixed> = IndexMap::new();
- if let Some(ref composer) = composer {
- let composer = crate::composer::composer_full(composer);
- if let Some(p) = composer
- .get_config()
- .borrow()
- .get("platform")
- .as_array()
- .cloned()
- {
- platform_overrides = p.into_iter().collect();
- }
- }
- let platform_repo =
- PlatformRepositoryHandle::new(PlatformRepository::new(vec![], platform_overrides)?);
- let mut locked_repo: Option<RepositoryInterfaceHandle> = None;
-
- // The single-package $package binding from PHP gets surfaced here.
- let mut single_package: Option<crate::package::CompletePackageInterfaceHandle> = None;
- let mut versions_map: IndexMap<String, String> = IndexMap::new();
- let installed_repo: RepositoryInterfaceHandle;
- let repos: RepositoryInterfaceHandle;
-
- if input.borrow().get_option("self")?.as_bool() == Some(true)
- && input.borrow().get_option("installed")?.as_bool() != Some(true)
- && input.borrow().get_option("locked")?.as_bool() != Some(true)
- {
- let composer = self.require_composer(None, None)?;
- let package = crate::package::RootPackageInterfaceHandle::dup(
- composer.borrow_partial().get_package(),
- );
- if input.borrow().get_option("name-only")?.as_bool() == Some(true) {
- self.get_io().write(&package.get_name());
-
- return Ok(0);
- }
- if input
- .borrow()
- .get_argument("package")?
- .as_string()
- .is_some()
- {
- return Err(InvalidArgumentException {
- message: "You cannot use --self together with a package name".to_string(),
- code: 0,
- }
- .into());
- }
- installed_repo = RepositoryInterfaceHandle::new(InstalledRepository::new(vec![
- RepositoryInterfaceHandle::new(RootPackageRepository::new(package.clone())),
- ]));
- repos = RepositoryInterfaceHandle::new(InstalledRepository::new(vec![
- RepositoryInterfaceHandle::new(RootPackageRepository::new(package.clone())),
- ]));
- single_package = Some(package.into());
- } else if input.borrow().get_option("platform")?.as_bool() == Some(true) {
- installed_repo = RepositoryInterfaceHandle::new(InstalledRepository::new(vec![
- platform_repo.clone().into(),
- ]));
- repos = RepositoryInterfaceHandle::new(InstalledRepository::new(vec![
- platform_repo.clone().into(),
- ]));
- } else if input.borrow().get_option("available")?.as_bool() == Some(true) {
- let mut ir = InstalledRepository::new(vec![platform_repo.clone().into()]);
- if let Some(ref composer) = composer {
- let composer = crate::composer::composer_full(composer);
- repos = RepositoryInterfaceHandle::new(CompositeRepository::new(
- composer
- .get_repository_manager()
- .borrow()
- .get_repositories()
- .to_vec(),
- ));
- ir.add_repository(
- composer
- .get_repository_manager()
- .borrow()
- .get_local_repository(),
- );
- installed_repo = RepositoryInterfaceHandle::new(ir);
- } else {
- let default_repos =
- RepositoryFactory::default_repos_with_default_manager(self.get_io())?;
- let names: Vec<String> = default_repos.keys().cloned().collect();
- repos = RepositoryInterfaceHandle::new(CompositeRepository::new(
- default_repos.into_values().collect(),
- ));
- self.get_io().write_error(&format!(
- "No composer.json found in the current directory, showing available packages from {}",
- names.join(", ")
- ));
- installed_repo = RepositoryInterfaceHandle::new(ir);
- }
- } else if input.borrow().get_option("all")?.as_bool() == Some(true) && composer.is_some() {
- let composer_ref = crate::composer::composer_full(composer.as_ref().unwrap());
- let local_repo = composer_ref
- .get_repository_manager()
- .borrow()
- .get_local_repository();
- let locker_rc = composer_ref.get_locker().clone();
- let mut locker = locker_rc.borrow_mut();
- if locker.is_locked() {
- let lr_handle: RepositoryInterfaceHandle =
- locker.get_locked_repository(true)?.into();
- installed_repo = RepositoryInterfaceHandle::new(InstalledRepository::new(vec![
- lr_handle.clone(),
- local_repo,
- platform_repo.clone().into(),
- ]));
- locked_repo = Some(lr_handle);
- } else {
- installed_repo = RepositoryInterfaceHandle::new(InstalledRepository::new(vec![
- local_repo,
- platform_repo.clone().into(),
- ]));
- }
- let mut composite_input: Vec<RepositoryInterfaceHandle> =
- vec![RepositoryInterfaceHandle::new(FilterRepository::new(
- installed_repo.clone(),
- {
- let mut m = IndexMap::new();
- m.insert("canonical".to_string(), PhpMixed::Bool(false));
- m
- },
- )?)];
- for r in composer_ref
- .get_repository_manager()
- .borrow()
- .get_repositories()
- {
- composite_input.push(r.clone());
- }
- repos = RepositoryInterfaceHandle::new(CompositeRepository::new(composite_input));
- } else if input.borrow().get_option("all")?.as_bool() == Some(true) {
- let default_repos =
- RepositoryFactory::default_repos_with_default_manager(self.get_io())?;
- let names: Vec<String> = default_repos.keys().cloned().collect();
- self.get_io().write_error(&format!(
- "No composer.json found in the current directory, showing available packages from {}",
- names.join(", ")
- ));
- installed_repo = RepositoryInterfaceHandle::new(InstalledRepository::new(vec![
- platform_repo.clone().into(),
- ]));
- let mut composite_input: Vec<RepositoryInterfaceHandle> = vec![installed_repo.clone()];
- for (_k, v) in default_repos.into_iter() {
- composite_input.push(v);
- }
- repos = RepositoryInterfaceHandle::new(CompositeRepository::new(composite_input));
- } else if input.borrow().get_option("locked")?.as_bool() == Some(true) {
- if composer.is_none()
- || !crate::composer::composer_full(composer.as_ref().unwrap())
- .get_locker()
- .borrow_mut()
- .is_locked()
- {
- return Err(UnexpectedValueException {
- message: "A valid composer.json and composer.lock files is required to run this command with --locked".to_string(),
- code: 0,
- }
- .into());
- }
- let composer_ref = crate::composer::composer_full(composer.as_ref().unwrap());
- let locker_rc = composer_ref.get_locker().clone();
- let mut locker = locker_rc.borrow_mut();
- let lr = locker.get_locked_repository(
- input.borrow().get_option("no-dev")?.as_bool() != Some(true),
- )?;
- if input.borrow().get_option("self")?.as_bool() == Some(true) {
- lr.add_package(
- crate::package::RootPackageInterfaceHandle::dup(composer_ref.get_package())
- .into(),
- )?;
- }
- let lr_handle: RepositoryInterfaceHandle = lr.into();
- locked_repo = Some(lr_handle.clone());
- let new_repo =
- RepositoryInterfaceHandle::new(InstalledRepository::new(vec![lr_handle]));
- installed_repo = new_repo.clone();
- repos = new_repo;
- } else {
- // --installed / default case
- let composer_local_owned;
- let _guard_from_existing;
- let composer_local = match composer.as_ref() {
- Some(c) => {
- _guard_from_existing = crate::composer::composer_full(c);
- &*_guard_from_existing
- }
- None => {
- composer_local_owned = self.require_composer(None, None)?;
- _guard_from_existing = crate::composer::composer_full(&composer_local_owned);
- &*_guard_from_existing
- }
- };
- let root_pkg = composer_local.get_package();
-
- let root_repo: RepositoryInterfaceHandle =
- if input.borrow().get_option("self")?.as_bool() == Some(true) {
- RepositoryInterfaceHandle::new(RootPackageRepository::new(
- crate::package::RootPackageInterfaceHandle::dup(
- composer_local.get_package(),
- ),
- ))
- } else {
- RepositoryInterfaceHandle::new(InstalledArrayRepository::new()?)
- };
- if input.borrow().get_option("no-dev")?.as_bool() == Some(true) {
- let local_packages = composer_local
- .get_repository_manager()
- .borrow()
- .get_local_repository()
- .get_packages()?;
- let packages = RepositoryUtils::filter_required_packages(
- &local_packages,
- root_pkg.clone().into(),
- false,
- Vec::new(),
- );
- let cloned: Vec<crate::package::PackageInterfaceHandle> = packages
- .iter()
- .map(crate::package::PackageInterfaceHandle::dup)
- .collect();
- let new_repo = RepositoryInterfaceHandle::new(InstalledRepository::new(vec![
- root_repo,
- RepositoryInterfaceHandle::new(InstalledArrayRepository::new_with_packages(
- cloned,
- )?),
- ]));
- installed_repo = new_repo.clone();
- repos = new_repo;
- } else {
- let repository_manager = composer_local.get_repository_manager().clone();
- let repository_manager = repository_manager.borrow();
- let lr = repository_manager.get_local_repository();
- installed_repo = RepositoryInterfaceHandle::new(InstalledRepository::new(vec![
- root_repo.clone(),
- lr.clone(),
- ]));
- repos =
- RepositoryInterfaceHandle::new(InstalledRepository::new(vec![root_repo, lr]));
- }
-
- if installed_repo.get_packages()?.is_empty() {
- let has_non_platform_reqs = |reqs: &IndexMap<String, Link>| -> bool {
- reqs.keys()
- .any(|name| !PlatformRepository::is_platform_package(name))
- };
-
- if has_non_platform_reqs(&root_pkg.get_requires())
- || has_non_platform_reqs(&root_pkg.get_dev_requires())
- {
- // Borrow is local; release composer_local borrow first.
- let _ = root_pkg;
- self.get_io().write_error("<warning>No dependencies installed. Try running composer install or update.</warning>");
- }
- }
- }
-
- if let Some(ref composer) = composer {
- let composer = crate::composer::composer_full(composer);
- let mut command_event = CommandEvent::new6(
- PluginEvents::COMMAND,
- "show",
- input.clone(),
- output,
- vec![],
- IndexMap::new(),
- );
- let command_event_name = command_event.get_name().to_string();
- composer
- .get_event_dispatcher()
- .borrow_mut()
- .dispatch(Some(&command_event_name), Some(&mut command_event))?;
- }
-
- if input.borrow().get_option("latest")?.as_bool() == Some(true) && composer.is_none() {
- self.get_io().write_error(
- "No composer.json found in the current directory, disabling \"latest\" option",
- );
- input
- .borrow_mut()
- .set_option("latest", PhpMixed::Bool(false));
- }
-
- let package_filter: Option<String> = input
- .borrow()
- .get_argument("package")?
- .as_string()
- .map(|s| s.to_string());
-
- // show single package or single version
- if let Some(ref pkg) = single_package {
- versions_map.insert(pkg.get_pretty_version(), pkg.get_version());
- } else if let Some(ref pf) = package_filter
- && !pf.contains('*')
- {
- let (matched_package, vers) = self.get_package(
- &installed_repo,
- &repos,
- pf,
- input.borrow().get_argument("version")?,
- )?;
-
- if let Some(ref pkg) = matched_package
- && input.borrow().get_option("direct")?.as_bool() == Some(true)
- && !in_array_strict(
- pkg.get_name(),
- &self
- .get_root_requires()
- .into_iter()
- .map(PhpMixed::String)
- .collect::<Vec<_>>(),
- )
- {
- return Err(InvalidArgumentException {
- message: format!(
- "Package \"{}\" is installed but not a direct dependent of the root package.",
- pkg.get_name()
- ),
- code: 0,
- }
- .into());
- }
-
- if matched_package.is_none() {
- let options = input.borrow().get_options();
- let mut hint = String::new();
- if input.borrow().get_option("locked")?.as_bool() == Some(true) {
- hint.push_str(" in lock file");
- }
- if let Some(working_dir) = options.get("working-dir").filter(|v| !v.is_null()) {
- hint.push_str(&format!(
- " in {}/composer.json",
- working_dir.as_string().unwrap_or("")
- ));
- }
- if PlatformRepository::is_platform_package(pf)
- && input.borrow().get_option("platform")?.as_bool() != Some(true)
- {
- hint.push_str(", try using --platform (-p) to show platform packages");
- }
- if input.borrow().get_option("all")?.as_bool() != Some(true)
- && input.borrow().get_option("available")?.as_bool() != Some(true)
- {
- hint.push_str(", try using --available (-a) to show all available packages");
- }
-
- return Err(InvalidArgumentException {
- message: format!("Package \"{}\" not found{}.", pf, hint),
- code: 0,
- }
- .into());
- }
- single_package = matched_package;
- versions_map = vers;
- }
-
- if let Some(ref package) = single_package {
- // assert(isset($versions));
-
- let mut exit_code: i64 = 0;
- if input.borrow().get_option("tree")?.as_bool() == Some(true) {
- let array_tree =
- self.generate_package_tree(package.clone().into(), &installed_repo, &repos);
-
- if format == "json" {
- let mut wrapper: IndexMap<String, PhpMixed> = IndexMap::new();
- wrapper.insert(
- "installed".to_string(),
- PhpMixed::List(vec![PhpMixed::Array(array_tree.into_iter().collect())]),
- );
- self.get_io().write(&JsonFile::encode(&PhpMixed::Array(
- wrapper.into_iter().collect(),
- ))?);
- } else {
- self.display_package_tree(vec![array_tree]);
- }
-
- return Ok(exit_code);
- }
-
- let mut latest_package: Option<crate::package::PackageInterfaceHandle> = None;
- if input.borrow().get_option("latest")?.as_bool() == Some(true) {
- latest_package = self.find_latest_package(
- package.clone().into(),
- composer.as_ref().unwrap(),
- &platform_repo,
- input
- .borrow()
- .get_option("major-only")?
- .as_bool()
- .unwrap_or(false),
- input
- .borrow()
- .get_option("minor-only")?
- .as_bool()
- .unwrap_or(false),
- input
- .borrow()
- .get_option("patch-only")?
- .as_bool()
- .unwrap_or(false),
- platform_req_filter.clone(),
- )?;
- }
- if input.borrow().get_option("outdated")?.as_bool() == Some(true)
- && input.borrow().get_option("strict")?.as_bool() == Some(true)
- && latest_package.is_some()
- && latest_package
- .as_ref()
- .unwrap()
- .get_full_pretty_version(true, crate::package::DisplayMode::SourceRefIfDev)
- != package
- .get_full_pretty_version(true, crate::package::DisplayMode::SourceRefIfDev)
- && (latest_package
- .as_ref()
- .unwrap()
- .as_complete()
- .is_none_or(|c| !c.is_abandoned()))
- {
- exit_code = 1;
- }
- if input.borrow().get_option("path")?.as_bool() == Some(true) {
- self.get_io().write_no_newline(&package.get_name());
- let path = {
- let composer_ref = composer.as_ref().unwrap();
- composer_ref
- .borrow_partial()
- .get_installation_manager()
- .borrow_mut()
- .get_install_path(package.clone().into())
- };
- if let Some(path) = path {
- let real = realpath(&path).unwrap_or_default();
- let trimmed = real.split(['\r', '\n']).next().unwrap_or("");
- self.get_io().write(&format!(" {}", trimmed));
- } else {
- self.get_io().write(" null");
- }
-
- return Ok(exit_code);
- }
-
- if format == "json" {
- self.print_package_info_as_json(
- package.clone(),
- &versions_map,
- &mut *installed_repo.borrow_mut(),
- latest_package,
- )?;
- } else {
- self.print_package_info(
- package.clone(),
- &versions_map,
- &mut *installed_repo.borrow_mut(),
- latest_package,
- )?;
- }
-
- return Ok(exit_code);
- }
-
- // show tree view if requested
- if input.borrow().get_option("tree")?.as_bool() == Some(true) {
- let root_requires = self.get_root_requires();
- let mut packages = installed_repo.get_packages()?;
- packages.sort_by(|a, b| {
- let sa: String = a.to_string();
- let sb: String = b.to_string();
- sa.cmp(&sb)
- });
- let mut array_tree: Vec<IndexMap<String, PhpMixed>> = Vec::new();
- for package in packages.iter() {
- if in_array_strict(
- package.get_name(),
- &root_requires
- .iter()
- .map(|s| PhpMixed::String(s.clone()))
- .collect::<Vec<_>>(),
- ) {
- array_tree.push(self.generate_package_tree(
- package.clone(),
- &installed_repo,
- &repos,
- ));
- }
- }
-
- if format == "json" {
- let mut wrapper: IndexMap<String, PhpMixed> = IndexMap::new();
- wrapper.insert(
- "installed".to_string(),
- PhpMixed::List(
- array_tree
- .into_iter()
- .map(|m| PhpMixed::Array(m.into_iter().collect()))
- .collect(),
- ),
- );
- self.get_io().write(&JsonFile::encode(&PhpMixed::Array(
- wrapper.into_iter().collect(),
- ))?);
- } else {
- self.display_package_tree(array_tree);
- }
-
- return Ok(0);
- }
-
- // list packages
- let mut packages: IndexMap<String, IndexMap<String, PackageOrName>> = IndexMap::new();
- let mut package_filter_regex: Option<String> = None;
- if let Some(ref pf) = package_filter {
- let escaped = shirabe_php_shim::preg_quote(pf, None);
- package_filter_regex = Some(format!("{{^{}$}}i", escaped.replace("\\*", ".*?")));
- }
-
- let mut package_list_filter: Option<Vec<String>> = None;
- if input.borrow().get_option("direct")?.as_bool() == Some(true) {
- package_list_filter = Some(self.get_root_requires());
- }
-
- if input.borrow().get_option("path")?.as_bool() == Some(true) && composer.is_none() {
- self.get_io().write_error(
- "No composer.json found in the current directory, disabling \"path\" option",
- );
- input.borrow_mut().set_option("path", PhpMixed::Bool(false));
- }
-
- for repo in RepositoryUtils::flatten_repositories(repos, true) {
- let r#type = if Self::same_repository(&repo, &platform_repo) {
- "platform"
- } else if locked_repo
- .as_ref()
- .is_some_and(|lr| Self::same_repository(&repo, lr))
- {
- "locked"
- } else if Self::same_repository(&repo, &installed_repo)
- || installed_repo
- .borrow()
- .as_any()
- .downcast_ref::<InstalledRepository>()
- .is_some_and(|ir| ir.get_repositories().iter().any(|r| r.ptr_eq(&repo)))
- {
- "installed"
- } else {
- "available"
- };
- let type_owned = r#type.to_string();
- if let Some(cr_rc) = repo.downcast_rc::<ComposerRepository>() {
- let names = cr_rc
- .borrow_mut()
- .get_package_names(package_filter.as_deref())?;
- for name in names {
- packages
- .entry(type_owned.clone())
- .or_default()
- .insert(name.clone(), PackageOrName::Name(name));
- }
- } else {
- for package in repo.get_packages()? {
- let existing = packages
- .get(&type_owned)
- .and_then(|m| m.get(&package.get_name()));
- let need_replace = match existing {
- None => true,
- Some(PackageOrName::Name(_)) => true,
- Some(PackageOrName::Pkg(existing)) => {
- version_compare(&existing.get_version(), &package.get_version(), "<")
- }
- };
- if need_replace {
- let mut p: crate::package::PackageInterfaceHandle = package.clone();
- while let Some(alias) = p.as_alias() {
- p = alias.get_alias_of().into();
- }
- let matches_filter = match &package_filter_regex {
- None => true,
- Some(r) => Preg::is_match(r, &p.get_name()),
- };
- if matches_filter {
- let matches_list = match &package_list_filter {
- None => true,
- Some(list) => in_array_strict(
- p.get_name(),
- &list
- .iter()
- .map(|s| PhpMixed::String(s.clone()))
- .collect::<Vec<_>>(),
- ),
- };
- if matches_list {
- packages
- .entry(type_owned.clone())
- .or_default()
- .insert(p.get_name(), PackageOrName::Pkg(p));
- }
- }
- }
- }
- if Self::same_repository(&repo, &platform_repo) {
- for (name, p) in platform_repo.borrow().get_disabled_packages() {
- packages
- .entry(type_owned.clone())
- .or_default()
- .insert(name.clone(), PackageOrName::Pkg(p.clone().into()));
- }
- }
- }
- }
-
- let show_all_types = input.borrow().get_option("all")?.as_bool() == Some(true);
- let show_latest = input.borrow().get_option("latest")?.as_bool() == Some(true);
- let show_major_only = input.borrow().get_option("major-only")?.as_bool() == Some(true);
- let show_minor_only = input.borrow().get_option("minor-only")?.as_bool() == Some(true);
- let show_patch_only = input.borrow().get_option("patch-only")?.as_bool() == Some(true);
- let ignored_packages_regex = base_package::package_names_to_regexp(
- &input
- .borrow()
- .get_option("ignore")?
- .as_list()
- .map(|l| {
- l.iter()
- .filter_map(|v| v.as_string().map(strtolower))
- .collect::<Vec<_>>()
- })
- .unwrap_or_default(),
- "{^(?:%s)$}iD",
- );
- let indent = if show_all_types { " " } else { "" };
- let mut latest_packages: IndexMap<String, crate::package::PackageInterfaceHandle> =
- IndexMap::new();
- let mut exit_code: i64 = 0;
- let mut view_data: IndexMap<String, Vec<IndexMap<String, PhpMixed>>> = IndexMap::new();
- let mut view_meta_data: IndexMap<String, ViewMetaData> = IndexMap::new();
-
- let mut write_version = false;
- let mut write_description = false;
-
- let type_order: Vec<(&str, bool)> = vec![
- ("platform", true),
- ("locked", true),
- ("available", false),
- ("installed", true),
- ];
- for (r#type, show_version) in type_order.iter() {
- if let Some(type_packages) = packages.get_mut(*r#type) {
- type_packages.sort_keys();
-
- let mut name_length: usize = 0;
- let mut version_length: usize = 0;
- let mut latest_length: usize = 0;
- let mut release_date_length: usize = 0;
-
- if show_latest && *show_version {
- for package_or_name in type_packages.values() {
- if let PackageOrName::Pkg(package) = package_or_name
- && !Preg::is_match(&ignored_packages_regex, &package.get_pretty_name())
- {
- let latest = self.find_latest_package(
- package.clone(),
- composer.as_ref().unwrap(),
- &platform_repo,
- show_major_only,
- show_minor_only,
- show_patch_only,
- platform_req_filter.clone(),
- )?;
- if latest.is_none() {
- continue;
- }
-
- latest_packages.insert(package.get_pretty_name(), latest.unwrap());
- }
- }
- }
-
- let write_path = input.borrow().get_option("name-only")?.as_bool() != Some(true)
- && input.borrow().get_option("path")?.as_bool() == Some(true);
- write_version = input.borrow().get_option("name-only")?.as_bool() != Some(true)
- && input.borrow().get_option("path")?.as_bool() != Some(true)
- && *show_version;
- let write_latest = write_version && show_latest;
- write_description = input.borrow().get_option("name-only")?.as_bool() != Some(true)
- && input.borrow().get_option("path")?.as_bool() != Some(true);
- let write_release_date = write_latest
- && (input.borrow().get_option("sort-by-age")?.as_bool() == Some(true)
- || format == "json");
-
- let mut has_outdated_packages = false;
-
- if input.borrow().get_option("sort-by-age")?.as_bool() == Some(true) {
- type_packages.sort_by(|_ka, a, _kb, b| match (a, b) {
- (PackageOrName::Pkg(a), PackageOrName::Pkg(b)) => {
- a.get_release_date().cmp(&b.get_release_date())
- }
- _ => std::cmp::Ordering::Equal,
- });
- }
-
- let mut view_type: Vec<IndexMap<String, PhpMixed>> = Vec::new();
- for package_or_name in type_packages.values() {
- let mut package_view_data: IndexMap<String, PhpMixed> = IndexMap::new();
- if let PackageOrName::Pkg(package) = package_or_name {
- let latest_package = if show_latest
- && latest_packages.contains_key(&package.get_pretty_name())
- {
- latest_packages.get(&package.get_pretty_name())
- } else {
- None
- };
-
- // Determine if Composer is checking outdated dependencies and if current package should trigger non-default exit code
- let mut package_is_up_to_date = if let Some(latest) = latest_package {
- latest.get_full_pretty_version(
- true,
- crate::package::DisplayMode::SourceRefIfDev,
- ) == package.get_full_pretty_version(
- true,
- crate::package::DisplayMode::SourceRefIfDev,
- ) && latest.as_complete().is_none_or(|c| !c.is_abandoned())
- } else {
- false
- };
- // When using --major-only, and no bigger version than current major is found then it is considered up to date
- package_is_up_to_date =
- package_is_up_to_date || (latest_package.is_none() && show_major_only);
- let package_is_ignored =
- Preg::is_match(&ignored_packages_regex, &package.get_pretty_name());
- if input.borrow().get_option("outdated")?.as_bool() == Some(true)
- && (package_is_up_to_date || package_is_ignored)
- {
- continue;
- }
-
- if input.borrow().get_option("outdated")?.as_bool() == Some(true)
- || input.borrow().get_option("strict")?.as_bool() == Some(true)
- {
- has_outdated_packages = true;
- }
-
- package_view_data.insert(
- "name".to_string(),
- PhpMixed::String(package.get_pretty_name()),
- );
- package_view_data.insert(
- "direct-dependency".to_string(),
- PhpMixed::Bool(in_array_strict(
- package.get_name(),
- &self
- .get_root_requires()
- .into_iter()
- .map(PhpMixed::String)
- .collect::<Vec<_>>(),
- )),
- );
- if format != "json"
- || input.borrow().get_option("name-only")?.as_bool() != Some(true)
- {
- package_view_data.insert(
- "homepage".to_string(),
- match package.as_complete() {
- Some(c) => match c.get_homepage() {
- Some(h) => PhpMixed::String(h),
- None => PhpMixed::Null,
- },
- None => PhpMixed::Null,
- },
- );
- package_view_data.insert(
- "source".to_string(),
- match PackageInfo::get_view_source_url(package.clone()) {
- Some(s) => PhpMixed::String(s),
- None => PhpMixed::Null,
- },
- );
- }
- name_length = name_length.max(package.get_pretty_name().len());
- if write_version {
- let mut version_str = package.get_full_pretty_version(
- true,
- crate::package::DisplayMode::SourceRefIfDev,
- );
- if format == "text" {
- version_str = version_str.trim_start_matches('v').to_string();
- }
- version_length = version_length.max(version_str.len());
- package_view_data
- .insert("version".to_string(), PhpMixed::String(version_str));
- }
- if write_release_date {
- if let Some(release_date) = package.get_release_date() {
- let mut age = self
- .get_relative_time(&release_date)
- .replace(" ago", " old");
- if !age.contains(" old") {
- age = format!("from {}", age);
- }
- release_date_length = release_date_length.max(age.len());
- package_view_data
- .insert("release-age".to_string(), PhpMixed::String(age));
- package_view_data.insert(
- "release-date".to_string(),
- PhpMixed::String(release_date.format(DATE_ATOM).to_string()),
- );
- } else {
- package_view_data.insert(
- "release-age".to_string(),
- PhpMixed::String(String::new()),
- );
- package_view_data.insert(
- "release-date".to_string(),
- PhpMixed::String(String::new()),
- );
- }
- }
- if write_latest && let Some(latest) = latest_package {
- let mut latest_version_str = latest.get_full_pretty_version(
- true,
- crate::package::DisplayMode::SourceRefIfDev,
- );
- if format == "text" {
- latest_version_str =
- latest_version_str.trim_start_matches('v').to_string();
- }
- let update_status =
- Self::get_update_status(latest.clone(), package.clone())?;
- latest_length = latest_length.max(latest_version_str.len());
- package_view_data
- .insert("latest".to_string(), PhpMixed::String(latest_version_str));
- package_view_data.insert(
- "latest-status".to_string(),
- PhpMixed::String(update_status),
- );
-
- if let Some(rd) = latest.get_release_date() {
- package_view_data.insert(
- "latest-release-date".to_string(),
- PhpMixed::String(rd.format(DATE_ATOM).to_string()),
- );
- } else {
- package_view_data.insert(
- "latest-release-date".to_string(),
- PhpMixed::String(String::new()),
- );
- }
- } else if write_latest {
- package_view_data.insert(
- "latest".to_string(),
- PhpMixed::String("[none matched]".to_string()),
- );
- package_view_data.insert(
- "latest-status".to_string(),
- PhpMixed::String("up-to-date".to_string()),
- );
- latest_length = latest_length.max("[none matched]".len());
- }
- if write_description && let Some(c) = package.as_complete() {
- package_view_data.insert(
- "description".to_string(),
- match c.get_description() {
- Some(d) => PhpMixed::String(d),
- None => PhpMixed::Null,
- },
- );
- }
- if write_path {
- let installation_manager = composer
- .as_ref()
- .unwrap()
- .borrow_partial()
- .get_installation_manager();
- let path: Option<String> = installation_manager
- .borrow_mut()
- .get_install_path(package.clone());
- if let Some(p) = path {
- let r = realpath(&p).unwrap_or_default();
- let trimmed = r.split(['\r', '\n']).next().unwrap_or("");
- package_view_data.insert(
- "path".to_string(),
- PhpMixed::String(trimmed.to_string()),
- );
- } else {
- package_view_data.insert("path".to_string(), PhpMixed::Null);
- }
- }
-
- let mut package_is_abandoned: PhpMixed = PhpMixed::Bool(false);
- if let Some(latest) = latest_package
- && let Some(c) = latest.as_complete()
- && c.is_abandoned()
- {
- let replacement_package_name = c.get_replacement_package();
- let replacement = if let Some(ref rp) = replacement_package_name {
- format!("Use {} instead", rp)
- } else {
- "No replacement was suggested".to_string()
- };
- let package_warning = format!(
- "Package {} is abandoned, you should avoid using it. {}.",
- package.get_pretty_name(),
- replacement
- );
- package_view_data
- .insert("warning".to_string(), PhpMixed::String(package_warning));
- package_is_abandoned = match replacement_package_name {
- Some(rp) => PhpMixed::String(rp),
- None => PhpMixed::Bool(true),
- };
- }
-
- package_view_data.insert("abandoned".to_string(), package_is_abandoned);
- } else if let PackageOrName::Name(name) = package_or_name {
- package_view_data
- .insert("name".to_string(), PhpMixed::String(name.clone()));
- name_length = name_length.max(name.len());
- }
- view_type.push(package_view_data);
- }
- view_data.insert(r#type.to_string(), view_type);
- view_meta_data.insert(
- r#type.to_string(),
- ViewMetaData {
- name_length,
- version_length,
- latest_length,
- release_date_length,
- write_latest,
- write_release_date,
- },
- );
- if input.borrow().get_option("strict")?.as_bool() == Some(true)
- && has_outdated_packages
- {
- exit_code = 1;
- break;
- }
- }
- }
-
- if format == "json" {
- let mut json_map: IndexMap<String, PhpMixed> = IndexMap::new();
- for (k, v) in view_data.iter() {
- json_map.insert(
- k.clone(),
- PhpMixed::List(
- v.iter()
- .map(|m| {
- PhpMixed::Array(
- m.iter().map(|(k, v)| (k.clone(), v.clone())).collect(),
- )
- })
- .collect(),
- ),
- );
- }
- let io = self.get_io();
- io.write(&JsonFile::encode(&PhpMixed::Array(
- json_map.into_iter().collect(),
- ))?);
- } else {
- if input.borrow().get_option("latest")?.as_bool() == Some(true)
- && view_data.values().any(|v| !v.is_empty())
- {
- let io = self.get_io();
- if !io.is_decorated() {
- io.write_error("Legend:");
- io.write_error("! patch or minor release available - update recommended");
- io.write_error("~ major release available - update possible");
- if input.borrow().get_option("outdated")?.as_bool() != Some(true) {
- io.write_error("= up to date version");
- }
- } else {
- io.write_error("<info>Color legend:</info>");
- io.write_error("- <highlight>patch or minor</highlight> release available - update recommended");
- io.write_error(
- "- <comment>major</comment> release available - update possible",
- );
- if input.borrow().get_option("outdated")?.as_bool() != Some(true) {
- io.write_error("- <info>up to date</info> version");
- }
- }
- }
-
- let width = self.get_terminal_width();
-
- for (r#type, packages) in view_data.iter() {
- let meta = match view_meta_data.get(r#type) {
- Some(m) => m.clone(),
- None => continue,
- };
- let name_length = meta.name_length;
- let version_length = meta.version_length;
- let mut latest_length = meta.latest_length;
- let release_date_length = meta.release_date_length;
- let write_latest = meta.write_latest;
- let write_release_date = meta.write_release_date;
-
- let width_usize = width as usize;
- let version_fits = name_length + version_length + 3 <= width_usize;
- let latest_fits = name_length + version_length + latest_length + 3 <= width_usize;
- let release_date_fits =
- name_length + version_length + latest_length + release_date_length + 3
- <= width_usize;
- let description_fits =
- name_length + version_length + latest_length + release_date_length + 24
- <= width_usize;
-
- if latest_fits && !self.get_io().is_decorated() {
- latest_length += 2;
- }
-
- if show_all_types {
- if r#type == "available" {
- self.get_io()
- .write(&format!("<comment>{}</comment>:", r#type));
- } else {
- self.get_io().write(&format!("<info>{}</info>:", r#type));
- }
- }
-
- if write_latest && input.borrow().get_option("direct")?.as_bool() != Some(true) {
- let mut direct_deps: Vec<IndexMap<String, PhpMixed>> = Vec::new();
- let mut transitive_deps: Vec<IndexMap<String, PhpMixed>> = Vec::new();
- for pkg in packages.iter() {
- let is_direct = pkg
- .get("direct-dependency")
- .and_then(|v| v.as_bool())
- .unwrap_or(false);
- if is_direct {
- direct_deps.push(pkg.clone());
- } else {
- transitive_deps.push(pkg.clone());
- }
- }
-
- self.get_io().write_error("");
- self.get_io()
- .write_error("<info>Direct dependencies required in composer.json:</>");
- if !direct_deps.is_empty() {
- self.print_packages(
- &direct_deps,
- indent,
- write_version && version_fits,
- latest_fits,
- write_description && description_fits,
- width_usize,
- version_length,
- name_length,
- latest_length,
- write_release_date && release_date_fits,
- release_date_length,
- );
- } else {
- self.get_io().write_error("Everything up to date");
- }
- self.get_io().write_error("");
- self.get_io().write_error(
- "<info>Transitive dependencies not required in composer.json:</>",
- );
- if !transitive_deps.is_empty() {
- self.print_packages(
- &transitive_deps,
- indent,
- write_version && version_fits,
- latest_fits,
- write_description && description_fits,
- width_usize,
- version_length,
- name_length,
- latest_length,
- write_release_date && release_date_fits,
- release_date_length,
- );
- } else {
- self.get_io().write_error("Everything up to date");
- }
- } else {
- if write_latest && packages.is_empty() {
- self.get_io()
- .write_error("All your direct dependencies are up to date");
- } else {
- self.print_packages(
- packages,
- indent,
- write_version && version_fits,
- write_latest && latest_fits,
- write_description && description_fits,
- width_usize,
- version_length,
- name_length,
- latest_length,
- write_release_date && release_date_fits,
- release_date_length,
- );
- }
- }
-
- if show_all_types {
- self.get_io().write("");
- }
- }
- }
-
- Ok(exit_code)
- }
-
- fn initialize(
- &self,
- input: std::rc::Rc<std::cell::RefCell<dyn InputInterface>>,
- output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>,
- ) -> anyhow::Result<()> {
- base_command_initialize(self, input, output)
- }
-
- fn complete(
- &self,
- input: &shirabe_external_packages::symfony::console::completion::completion_input::CompletionInput,
- suggestions: &mut shirabe_external_packages::symfony::console::completion::completion_suggestions::CompletionSuggestions,
- ) -> anyhow::Result<()> {
- crate::command::base_command::base_command_complete(self, input, suggestions)
- }
-
- shirabe_external_packages::delegate_command_trait_impls_to_inner!(base_command_data);
-}
-
-impl BaseCommand for ShowCommand {
- fn base_command_data(&self) -> &crate::command::BaseCommandData {
- &self.base_command_data
- }
-
- crate::delegate_base_command_trait_impls_to_inner!(base_command_data);
-}
-impl ShowCommand {
/// PHP: protected function suggestPackageBasedOnMode(): \Closure
pub(crate) fn suggest_package_based_on_mode(&self) -> crate::console::input::SuggestedValues {
crate::console::input::SuggestedValues::Closure(Box::new(|this, input, suggestions| {
@@ -2908,6 +1533,1379 @@ impl ShowCommand {
}
}
+impl Command for ShowCommand {
+ fn configure(&self) -> anyhow::Result<()> {
+ self.set_name("show")?;
+ self.set_aliases(vec!["info".to_string()])?;
+ self.set_description("Shows information about packages");
+ let opt_none = |name: &str, shortcut: Option<&str>, description: &str| {
+ InputOption::new(
+ name,
+ shortcut.map(|s| PhpMixed::String(s.to_string())),
+ Some(InputOption::VALUE_NONE),
+ description,
+ None,
+ )
+ .unwrap()
+ .into()
+ };
+ self.set_definition(&[
+ InputArgument::new5(
+ "package",
+ Some(InputArgument::OPTIONAL),
+ "Package to inspect. Or a name including a wildcard (*) to filter lists of packages instead.",
+ None,
+ self.suggest_package_based_on_mode(),
+ )
+ .unwrap()
+ .into(),
+ InputArgument::new(
+ "version",
+ Some(InputArgument::OPTIONAL),
+ "Version or version constraint to inspect",
+ None,
+ )
+ .unwrap()
+ .into(),
+ opt_none("all", None, "List all packages"),
+ opt_none("locked", None, "List all locked packages"),
+ opt_none(
+ "installed",
+ Some("i"),
+ "List installed packages only (enabled by default, only present for BC).",
+ ),
+ opt_none("platform", Some("p"), "List platform packages only"),
+ opt_none("available", Some("a"), "List available packages only"),
+ opt_none("self", Some("s"), "Show the root package information"),
+ opt_none("name-only", Some("N"), "List package names only"),
+ opt_none("path", Some("P"), "Show package paths"),
+ opt_none("tree", Some("t"), "List the dependencies as a tree"),
+ opt_none("latest", Some("l"), "Show the latest version"),
+ opt_none(
+ "outdated",
+ Some("o"),
+ "Show the latest version but only for packages that are outdated",
+ ),
+ InputOption::new6(
+ "ignore",
+ None,
+ Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY),
+ "Ignore specified package(s). Can contain wildcards (*). Use it with the --outdated option if you don't want to be informed about new versions of some packages.",
+ None,
+ self.suggest_installed_package(false, false),
+ )
+ .unwrap()
+ .into(),
+ opt_none(
+ "major-only",
+ Some("M"),
+ "Show only packages that have major SemVer-compatible updates. Use with the --latest or --outdated option.",
+ ),
+ opt_none(
+ "minor-only",
+ Some("m"),
+ "Show only packages that have minor SemVer-compatible updates. Use with the --latest or --outdated option.",
+ ),
+ opt_none(
+ "patch-only",
+ None,
+ "Show only packages that have patch SemVer-compatible updates. Use with the --latest or --outdated option.",
+ ),
+ opt_none(
+ "sort-by-age",
+ Some("A"),
+ "Displays the installed version's age, and sorts packages oldest first. Use with the --latest or --outdated option.",
+ ),
+ opt_none(
+ "direct",
+ Some("D"),
+ "Shows only packages that are directly required by the root package",
+ ),
+ opt_none(
+ "strict",
+ None,
+ "Return a non-zero exit code when there are outdated packages",
+ ),
+ InputOption::new6(
+ "format",
+ Some(PhpMixed::String("f".to_string())),
+ Some(InputOption::VALUE_REQUIRED),
+ "Format of the output: text or json",
+ Some(PhpMixed::String("text".to_string())),
+ SuggestedValues::List(vec!["json".to_string(), "text".to_string()]),
+ )
+ .unwrap()
+ .into(),
+ opt_none("no-dev", None, "Disables search in require-dev packages."),
+ 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,
+ )
+ .unwrap()
+ .into(),
+ opt_none(
+ "ignore-platform-reqs",
+ None,
+ "Ignore all platform requirements (php & ext- packages). Use with the --outdated option",
+ ),
+ ]);
+ self.set_help(
+ "The show command displays detailed information about a package, or\n\
+ lists all packages available.\n\n\
+ Read more at https://getcomposer.org/doc/03-cli.md#show-info",
+ );
+ Ok(())
+ }
+
+ fn execute(
+ &self,
+ input: std::rc::Rc<std::cell::RefCell<dyn InputInterface>>,
+ output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>,
+ ) -> anyhow::Result<i64> {
+ *self.version_parser.borrow_mut() = VersionParser::new();
+ if input.borrow().get_option("tree")?.as_bool() == Some(true) {
+ self.init_styles(output.clone());
+ }
+
+ let composer = self.try_composer(None, None);
+
+ if input.borrow().get_option("installed")?.as_bool() == Some(true)
+ && input.borrow().get_option("self")?.as_bool() != Some(true)
+ {
+ self.get_io().write_error("<warning>You are using the deprecated option \"installed\". Only installed packages are shown by default now. The --all option can be used to show all packages.</warning>");
+ }
+
+ if input.borrow().get_option("outdated")?.as_bool() == Some(true) {
+ input
+ .borrow_mut()
+ .set_option("latest", PhpMixed::Bool(true));
+ } else if input
+ .borrow()
+ .get_option("ignore")?
+ .as_list()
+ .map_or(0, |l| l.len())
+ > 0
+ {
+ self.get_io().write_error("<warning>You are using the option \"ignore\" for action other than \"outdated\", it will be ignored.</warning>");
+ }
+
+ if input.borrow().get_option("direct")?.as_bool() == Some(true)
+ && (input.borrow().get_option("all")?.as_bool() == Some(true)
+ || input.borrow().get_option("available")?.as_bool() == Some(true)
+ || input.borrow().get_option("platform")?.as_bool() == Some(true))
+ {
+ self.get_io().write_error("The --direct (-D) option is not usable in combination with --all, --platform (-p) or --available (-a)");
+
+ return Ok(1);
+ }
+
+ if input.borrow().get_option("tree")?.as_bool() == Some(true)
+ && (input.borrow().get_option("all")?.as_bool() == Some(true)
+ || input.borrow().get_option("available")?.as_bool() == Some(true))
+ {
+ self.get_io().write_error("The --tree (-t) option is not usable in combination with --all or --available (-a)");
+
+ return Ok(1);
+ }
+
+ let only_count: usize = [
+ input.borrow().get_option("patch-only")?.as_bool() == Some(true),
+ input.borrow().get_option("minor-only")?.as_bool() == Some(true),
+ input.borrow().get_option("major-only")?.as_bool() == Some(true),
+ ]
+ .iter()
+ .filter(|b| **b)
+ .count();
+ if only_count > 1 {
+ self.get_io().write_error(
+ "Only one of --major-only, --minor-only or --patch-only can be used at once",
+ );
+
+ return Ok(1);
+ }
+
+ if input.borrow().get_option("tree")?.as_bool() == Some(true)
+ && input.borrow().get_option("latest")?.as_bool() == Some(true)
+ {
+ self.get_io().write_error(
+ "The --tree (-t) option is not usable in combination with --latest (-l)",
+ );
+
+ return Ok(1);
+ }
+
+ if input.borrow().get_option("tree")?.as_bool() == Some(true)
+ && input.borrow().get_option("path")?.as_bool() == Some(true)
+ {
+ self.get_io().write_error(
+ "The --tree (-t) option is not usable in combination with --path (-P)",
+ );
+
+ return Ok(1);
+ }
+
+ let format = input
+ .borrow()
+ .get_option("format")?
+ .as_string()
+ .unwrap_or("text")
+ .to_string();
+ if !in_array_loose(
+ format.clone(),
+ &[
+ PhpMixed::String("text".to_string()),
+ PhpMixed::String("json".to_string()),
+ ],
+ ) {
+ self.get_io().write_error(&format!(
+ "Unsupported format \"{}\". See help for supported formats.",
+ format
+ ));
+
+ return Ok(1);
+ }
+
+ let platform_req_filter = self.get_platform_requirement_filter(input.clone())?;
+
+ // init repos
+ let mut platform_overrides: IndexMap<String, PhpMixed> = IndexMap::new();
+ if let Some(ref composer) = composer {
+ let composer = crate::composer::composer_full(composer);
+ if let Some(p) = composer
+ .get_config()
+ .borrow()
+ .get("platform")
+ .as_array()
+ .cloned()
+ {
+ platform_overrides = p.into_iter().collect();
+ }
+ }
+ let platform_repo =
+ PlatformRepositoryHandle::new(PlatformRepository::new(vec![], platform_overrides)?);
+ let mut locked_repo: Option<RepositoryInterfaceHandle> = None;
+
+ // The single-package $package binding from PHP gets surfaced here.
+ let mut single_package: Option<crate::package::CompletePackageInterfaceHandle> = None;
+ let mut versions_map: IndexMap<String, String> = IndexMap::new();
+ let installed_repo: RepositoryInterfaceHandle;
+ let repos: RepositoryInterfaceHandle;
+
+ if input.borrow().get_option("self")?.as_bool() == Some(true)
+ && input.borrow().get_option("installed")?.as_bool() != Some(true)
+ && input.borrow().get_option("locked")?.as_bool() != Some(true)
+ {
+ let composer = self.require_composer(None, None)?;
+ let package = crate::package::RootPackageInterfaceHandle::dup(
+ composer.borrow_partial().get_package(),
+ );
+ if input.borrow().get_option("name-only")?.as_bool() == Some(true) {
+ self.get_io().write(&package.get_name());
+
+ return Ok(0);
+ }
+ if input
+ .borrow()
+ .get_argument("package")?
+ .as_string()
+ .is_some()
+ {
+ return Err(InvalidArgumentException {
+ message: "You cannot use --self together with a package name".to_string(),
+ code: 0,
+ }
+ .into());
+ }
+ installed_repo = RepositoryInterfaceHandle::new(InstalledRepository::new(vec![
+ RepositoryInterfaceHandle::new(RootPackageRepository::new(package.clone())),
+ ]));
+ repos = RepositoryInterfaceHandle::new(InstalledRepository::new(vec![
+ RepositoryInterfaceHandle::new(RootPackageRepository::new(package.clone())),
+ ]));
+ single_package = Some(package.into());
+ } else if input.borrow().get_option("platform")?.as_bool() == Some(true) {
+ installed_repo = RepositoryInterfaceHandle::new(InstalledRepository::new(vec![
+ platform_repo.clone().into(),
+ ]));
+ repos = RepositoryInterfaceHandle::new(InstalledRepository::new(vec![
+ platform_repo.clone().into(),
+ ]));
+ } else if input.borrow().get_option("available")?.as_bool() == Some(true) {
+ let mut ir = InstalledRepository::new(vec![platform_repo.clone().into()]);
+ if let Some(ref composer) = composer {
+ let composer = crate::composer::composer_full(composer);
+ repos = RepositoryInterfaceHandle::new(CompositeRepository::new(
+ composer
+ .get_repository_manager()
+ .borrow()
+ .get_repositories()
+ .to_vec(),
+ ));
+ ir.add_repository(
+ composer
+ .get_repository_manager()
+ .borrow()
+ .get_local_repository(),
+ );
+ installed_repo = RepositoryInterfaceHandle::new(ir);
+ } else {
+ let default_repos =
+ RepositoryFactory::default_repos_with_default_manager(self.get_io())?;
+ let names: Vec<String> = default_repos.keys().cloned().collect();
+ repos = RepositoryInterfaceHandle::new(CompositeRepository::new(
+ default_repos.into_values().collect(),
+ ));
+ self.get_io().write_error(&format!(
+ "No composer.json found in the current directory, showing available packages from {}",
+ names.join(", ")
+ ));
+ installed_repo = RepositoryInterfaceHandle::new(ir);
+ }
+ } else if input.borrow().get_option("all")?.as_bool() == Some(true) && composer.is_some() {
+ let composer_ref = crate::composer::composer_full(composer.as_ref().unwrap());
+ let local_repo = composer_ref
+ .get_repository_manager()
+ .borrow()
+ .get_local_repository();
+ let locker_rc = composer_ref.get_locker().clone();
+ let mut locker = locker_rc.borrow_mut();
+ if locker.is_locked() {
+ let lr_handle: RepositoryInterfaceHandle =
+ locker.get_locked_repository(true)?.into();
+ installed_repo = RepositoryInterfaceHandle::new(InstalledRepository::new(vec![
+ lr_handle.clone(),
+ local_repo,
+ platform_repo.clone().into(),
+ ]));
+ locked_repo = Some(lr_handle);
+ } else {
+ installed_repo = RepositoryInterfaceHandle::new(InstalledRepository::new(vec![
+ local_repo,
+ platform_repo.clone().into(),
+ ]));
+ }
+ let mut composite_input: Vec<RepositoryInterfaceHandle> =
+ vec![RepositoryInterfaceHandle::new(FilterRepository::new(
+ installed_repo.clone(),
+ {
+ let mut m = IndexMap::new();
+ m.insert("canonical".to_string(), PhpMixed::Bool(false));
+ m
+ },
+ )?)];
+ for r in composer_ref
+ .get_repository_manager()
+ .borrow()
+ .get_repositories()
+ {
+ composite_input.push(r.clone());
+ }
+ repos = RepositoryInterfaceHandle::new(CompositeRepository::new(composite_input));
+ } else if input.borrow().get_option("all")?.as_bool() == Some(true) {
+ let default_repos =
+ RepositoryFactory::default_repos_with_default_manager(self.get_io())?;
+ let names: Vec<String> = default_repos.keys().cloned().collect();
+ self.get_io().write_error(&format!(
+ "No composer.json found in the current directory, showing available packages from {}",
+ names.join(", ")
+ ));
+ installed_repo = RepositoryInterfaceHandle::new(InstalledRepository::new(vec![
+ platform_repo.clone().into(),
+ ]));
+ let mut composite_input: Vec<RepositoryInterfaceHandle> = vec![installed_repo.clone()];
+ for (_k, v) in default_repos.into_iter() {
+ composite_input.push(v);
+ }
+ repos = RepositoryInterfaceHandle::new(CompositeRepository::new(composite_input));
+ } else if input.borrow().get_option("locked")?.as_bool() == Some(true) {
+ if composer.is_none()
+ || !crate::composer::composer_full(composer.as_ref().unwrap())
+ .get_locker()
+ .borrow_mut()
+ .is_locked()
+ {
+ return Err(UnexpectedValueException {
+ message: "A valid composer.json and composer.lock files is required to run this command with --locked".to_string(),
+ code: 0,
+ }
+ .into());
+ }
+ let composer_ref = crate::composer::composer_full(composer.as_ref().unwrap());
+ let locker_rc = composer_ref.get_locker().clone();
+ let mut locker = locker_rc.borrow_mut();
+ let lr = locker.get_locked_repository(
+ input.borrow().get_option("no-dev")?.as_bool() != Some(true),
+ )?;
+ if input.borrow().get_option("self")?.as_bool() == Some(true) {
+ lr.add_package(
+ crate::package::RootPackageInterfaceHandle::dup(composer_ref.get_package())
+ .into(),
+ )?;
+ }
+ let lr_handle: RepositoryInterfaceHandle = lr.into();
+ locked_repo = Some(lr_handle.clone());
+ let new_repo =
+ RepositoryInterfaceHandle::new(InstalledRepository::new(vec![lr_handle]));
+ installed_repo = new_repo.clone();
+ repos = new_repo;
+ } else {
+ // --installed / default case
+ let composer_local_owned;
+ let _guard_from_existing;
+ let composer_local = match composer.as_ref() {
+ Some(c) => {
+ _guard_from_existing = crate::composer::composer_full(c);
+ &*_guard_from_existing
+ }
+ None => {
+ composer_local_owned = self.require_composer(None, None)?;
+ _guard_from_existing = crate::composer::composer_full(&composer_local_owned);
+ &*_guard_from_existing
+ }
+ };
+ let root_pkg = composer_local.get_package();
+
+ let root_repo: RepositoryInterfaceHandle =
+ if input.borrow().get_option("self")?.as_bool() == Some(true) {
+ RepositoryInterfaceHandle::new(RootPackageRepository::new(
+ crate::package::RootPackageInterfaceHandle::dup(
+ composer_local.get_package(),
+ ),
+ ))
+ } else {
+ RepositoryInterfaceHandle::new(InstalledArrayRepository::new()?)
+ };
+ if input.borrow().get_option("no-dev")?.as_bool() == Some(true) {
+ let local_packages = composer_local
+ .get_repository_manager()
+ .borrow()
+ .get_local_repository()
+ .get_packages()?;
+ let packages = RepositoryUtils::filter_required_packages(
+ &local_packages,
+ root_pkg.clone().into(),
+ false,
+ Vec::new(),
+ );
+ let cloned: Vec<crate::package::PackageInterfaceHandle> = packages
+ .iter()
+ .map(crate::package::PackageInterfaceHandle::dup)
+ .collect();
+ let new_repo = RepositoryInterfaceHandle::new(InstalledRepository::new(vec![
+ root_repo,
+ RepositoryInterfaceHandle::new(InstalledArrayRepository::new_with_packages(
+ cloned,
+ )?),
+ ]));
+ installed_repo = new_repo.clone();
+ repos = new_repo;
+ } else {
+ let repository_manager = composer_local.get_repository_manager().clone();
+ let repository_manager = repository_manager.borrow();
+ let lr = repository_manager.get_local_repository();
+ installed_repo = RepositoryInterfaceHandle::new(InstalledRepository::new(vec![
+ root_repo.clone(),
+ lr.clone(),
+ ]));
+ repos =
+ RepositoryInterfaceHandle::new(InstalledRepository::new(vec![root_repo, lr]));
+ }
+
+ if installed_repo.get_packages()?.is_empty() {
+ let has_non_platform_reqs = |reqs: &IndexMap<String, Link>| -> bool {
+ reqs.keys()
+ .any(|name| !PlatformRepository::is_platform_package(name))
+ };
+
+ if has_non_platform_reqs(&root_pkg.get_requires())
+ || has_non_platform_reqs(&root_pkg.get_dev_requires())
+ {
+ // Borrow is local; release composer_local borrow first.
+ let _ = root_pkg;
+ self.get_io().write_error("<warning>No dependencies installed. Try running composer install or update.</warning>");
+ }
+ }
+ }
+
+ if let Some(ref composer) = composer {
+ let composer = crate::composer::composer_full(composer);
+ let mut command_event = CommandEvent::new6(
+ PluginEvents::COMMAND,
+ "show",
+ input.clone(),
+ output,
+ vec![],
+ IndexMap::new(),
+ );
+ let command_event_name = command_event.get_name().to_string();
+ composer
+ .get_event_dispatcher()
+ .borrow_mut()
+ .dispatch(Some(&command_event_name), Some(&mut command_event))?;
+ }
+
+ if input.borrow().get_option("latest")?.as_bool() == Some(true) && composer.is_none() {
+ self.get_io().write_error(
+ "No composer.json found in the current directory, disabling \"latest\" option",
+ );
+ input
+ .borrow_mut()
+ .set_option("latest", PhpMixed::Bool(false));
+ }
+
+ let package_filter: Option<String> = input
+ .borrow()
+ .get_argument("package")?
+ .as_string()
+ .map(|s| s.to_string());
+
+ // show single package or single version
+ if let Some(ref pkg) = single_package {
+ versions_map.insert(pkg.get_pretty_version(), pkg.get_version());
+ } else if let Some(ref pf) = package_filter
+ && !pf.contains('*')
+ {
+ let (matched_package, vers) = self.get_package(
+ &installed_repo,
+ &repos,
+ pf,
+ input.borrow().get_argument("version")?,
+ )?;
+
+ if let Some(ref pkg) = matched_package
+ && input.borrow().get_option("direct")?.as_bool() == Some(true)
+ && !in_array_strict(
+ pkg.get_name(),
+ &self
+ .get_root_requires()
+ .into_iter()
+ .map(PhpMixed::String)
+ .collect::<Vec<_>>(),
+ )
+ {
+ return Err(InvalidArgumentException {
+ message: format!(
+ "Package \"{}\" is installed but not a direct dependent of the root package.",
+ pkg.get_name()
+ ),
+ code: 0,
+ }
+ .into());
+ }
+
+ if matched_package.is_none() {
+ let options = input.borrow().get_options();
+ let mut hint = String::new();
+ if input.borrow().get_option("locked")?.as_bool() == Some(true) {
+ hint.push_str(" in lock file");
+ }
+ if let Some(working_dir) = options.get("working-dir").filter(|v| !v.is_null()) {
+ hint.push_str(&format!(
+ " in {}/composer.json",
+ working_dir.as_string().unwrap_or("")
+ ));
+ }
+ if PlatformRepository::is_platform_package(pf)
+ && input.borrow().get_option("platform")?.as_bool() != Some(true)
+ {
+ hint.push_str(", try using --platform (-p) to show platform packages");
+ }
+ if input.borrow().get_option("all")?.as_bool() != Some(true)
+ && input.borrow().get_option("available")?.as_bool() != Some(true)
+ {
+ hint.push_str(", try using --available (-a) to show all available packages");
+ }
+
+ return Err(InvalidArgumentException {
+ message: format!("Package \"{}\" not found{}.", pf, hint),
+ code: 0,
+ }
+ .into());
+ }
+ single_package = matched_package;
+ versions_map = vers;
+ }
+
+ if let Some(ref package) = single_package {
+ // assert(isset($versions));
+
+ let mut exit_code: i64 = 0;
+ if input.borrow().get_option("tree")?.as_bool() == Some(true) {
+ let array_tree =
+ self.generate_package_tree(package.clone().into(), &installed_repo, &repos);
+
+ if format == "json" {
+ let mut wrapper: IndexMap<String, PhpMixed> = IndexMap::new();
+ wrapper.insert(
+ "installed".to_string(),
+ PhpMixed::List(vec![PhpMixed::Array(array_tree.into_iter().collect())]),
+ );
+ self.get_io().write(&JsonFile::encode(&PhpMixed::Array(
+ wrapper.into_iter().collect(),
+ ))?);
+ } else {
+ self.display_package_tree(vec![array_tree]);
+ }
+
+ return Ok(exit_code);
+ }
+
+ let mut latest_package: Option<crate::package::PackageInterfaceHandle> = None;
+ if input.borrow().get_option("latest")?.as_bool() == Some(true) {
+ latest_package = self.find_latest_package(
+ package.clone().into(),
+ composer.as_ref().unwrap(),
+ &platform_repo,
+ input
+ .borrow()
+ .get_option("major-only")?
+ .as_bool()
+ .unwrap_or(false),
+ input
+ .borrow()
+ .get_option("minor-only")?
+ .as_bool()
+ .unwrap_or(false),
+ input
+ .borrow()
+ .get_option("patch-only")?
+ .as_bool()
+ .unwrap_or(false),
+ platform_req_filter.clone(),
+ )?;
+ }
+ if input.borrow().get_option("outdated")?.as_bool() == Some(true)
+ && input.borrow().get_option("strict")?.as_bool() == Some(true)
+ && latest_package.is_some()
+ && latest_package
+ .as_ref()
+ .unwrap()
+ .get_full_pretty_version(true, crate::package::DisplayMode::SourceRefIfDev)
+ != package
+ .get_full_pretty_version(true, crate::package::DisplayMode::SourceRefIfDev)
+ && (latest_package
+ .as_ref()
+ .unwrap()
+ .as_complete()
+ .is_none_or(|c| !c.is_abandoned()))
+ {
+ exit_code = 1;
+ }
+ if input.borrow().get_option("path")?.as_bool() == Some(true) {
+ self.get_io().write_no_newline(&package.get_name());
+ let path = {
+ let composer_ref = composer.as_ref().unwrap();
+ composer_ref
+ .borrow_partial()
+ .get_installation_manager()
+ .borrow_mut()
+ .get_install_path(package.clone().into())
+ };
+ if let Some(path) = path {
+ let real = realpath(&path).unwrap_or_default();
+ let trimmed = real.split(['\r', '\n']).next().unwrap_or("");
+ self.get_io().write(&format!(" {}", trimmed));
+ } else {
+ self.get_io().write(" null");
+ }
+
+ return Ok(exit_code);
+ }
+
+ if format == "json" {
+ self.print_package_info_as_json(
+ package.clone(),
+ &versions_map,
+ &mut *installed_repo.borrow_mut(),
+ latest_package,
+ )?;
+ } else {
+ self.print_package_info(
+ package.clone(),
+ &versions_map,
+ &mut *installed_repo.borrow_mut(),
+ latest_package,
+ )?;
+ }
+
+ return Ok(exit_code);
+ }
+
+ // show tree view if requested
+ if input.borrow().get_option("tree")?.as_bool() == Some(true) {
+ let root_requires = self.get_root_requires();
+ let mut packages = installed_repo.get_packages()?;
+ packages.sort_by(|a, b| {
+ let sa: String = a.to_string();
+ let sb: String = b.to_string();
+ sa.cmp(&sb)
+ });
+ let mut array_tree: Vec<IndexMap<String, PhpMixed>> = Vec::new();
+ for package in packages.iter() {
+ if in_array_strict(
+ package.get_name(),
+ &root_requires
+ .iter()
+ .map(|s| PhpMixed::String(s.clone()))
+ .collect::<Vec<_>>(),
+ ) {
+ array_tree.push(self.generate_package_tree(
+ package.clone(),
+ &installed_repo,
+ &repos,
+ ));
+ }
+ }
+
+ if format == "json" {
+ let mut wrapper: IndexMap<String, PhpMixed> = IndexMap::new();
+ wrapper.insert(
+ "installed".to_string(),
+ PhpMixed::List(
+ array_tree
+ .into_iter()
+ .map(|m| PhpMixed::Array(m.into_iter().collect()))
+ .collect(),
+ ),
+ );
+ self.get_io().write(&JsonFile::encode(&PhpMixed::Array(
+ wrapper.into_iter().collect(),
+ ))?);
+ } else {
+ self.display_package_tree(array_tree);
+ }
+
+ return Ok(0);
+ }
+
+ // list packages
+ let mut packages: IndexMap<String, IndexMap<String, PackageOrName>> = IndexMap::new();
+ let mut package_filter_regex: Option<String> = None;
+ if let Some(ref pf) = package_filter {
+ let escaped = shirabe_php_shim::preg_quote(pf, None);
+ package_filter_regex = Some(format!("{{^{}$}}i", escaped.replace("\\*", ".*?")));
+ }
+
+ let mut package_list_filter: Option<Vec<String>> = None;
+ if input.borrow().get_option("direct")?.as_bool() == Some(true) {
+ package_list_filter = Some(self.get_root_requires());
+ }
+
+ if input.borrow().get_option("path")?.as_bool() == Some(true) && composer.is_none() {
+ self.get_io().write_error(
+ "No composer.json found in the current directory, disabling \"path\" option",
+ );
+ input.borrow_mut().set_option("path", PhpMixed::Bool(false));
+ }
+
+ for repo in RepositoryUtils::flatten_repositories(repos, true) {
+ let r#type = if Self::same_repository(&repo, &platform_repo) {
+ "platform"
+ } else if locked_repo
+ .as_ref()
+ .is_some_and(|lr| Self::same_repository(&repo, lr))
+ {
+ "locked"
+ } else if Self::same_repository(&repo, &installed_repo)
+ || installed_repo
+ .borrow()
+ .as_any()
+ .downcast_ref::<InstalledRepository>()
+ .is_some_and(|ir| ir.get_repositories().iter().any(|r| r.ptr_eq(&repo)))
+ {
+ "installed"
+ } else {
+ "available"
+ };
+ let type_owned = r#type.to_string();
+ if let Some(cr_rc) = repo.downcast_rc::<ComposerRepository>() {
+ let names = cr_rc
+ .borrow_mut()
+ .get_package_names(package_filter.as_deref())?;
+ for name in names {
+ packages
+ .entry(type_owned.clone())
+ .or_default()
+ .insert(name.clone(), PackageOrName::Name(name));
+ }
+ } else {
+ for package in repo.get_packages()? {
+ let existing = packages
+ .get(&type_owned)
+ .and_then(|m| m.get(&package.get_name()));
+ let need_replace = match existing {
+ None => true,
+ Some(PackageOrName::Name(_)) => true,
+ Some(PackageOrName::Pkg(existing)) => {
+ version_compare(&existing.get_version(), &package.get_version(), "<")
+ }
+ };
+ if need_replace {
+ let mut p: crate::package::PackageInterfaceHandle = package.clone();
+ while let Some(alias) = p.as_alias() {
+ p = alias.get_alias_of().into();
+ }
+ let matches_filter = match &package_filter_regex {
+ None => true,
+ Some(r) => Preg::is_match(r, &p.get_name()),
+ };
+ if matches_filter {
+ let matches_list = match &package_list_filter {
+ None => true,
+ Some(list) => in_array_strict(
+ p.get_name(),
+ &list
+ .iter()
+ .map(|s| PhpMixed::String(s.clone()))
+ .collect::<Vec<_>>(),
+ ),
+ };
+ if matches_list {
+ packages
+ .entry(type_owned.clone())
+ .or_default()
+ .insert(p.get_name(), PackageOrName::Pkg(p));
+ }
+ }
+ }
+ }
+ if Self::same_repository(&repo, &platform_repo) {
+ for (name, p) in platform_repo.borrow().get_disabled_packages() {
+ packages
+ .entry(type_owned.clone())
+ .or_default()
+ .insert(name.clone(), PackageOrName::Pkg(p.clone().into()));
+ }
+ }
+ }
+ }
+
+ let show_all_types = input.borrow().get_option("all")?.as_bool() == Some(true);
+ let show_latest = input.borrow().get_option("latest")?.as_bool() == Some(true);
+ let show_major_only = input.borrow().get_option("major-only")?.as_bool() == Some(true);
+ let show_minor_only = input.borrow().get_option("minor-only")?.as_bool() == Some(true);
+ let show_patch_only = input.borrow().get_option("patch-only")?.as_bool() == Some(true);
+ let ignored_packages_regex = base_package::package_names_to_regexp(
+ &input
+ .borrow()
+ .get_option("ignore")?
+ .as_list()
+ .map(|l| {
+ l.iter()
+ .filter_map(|v| v.as_string().map(strtolower))
+ .collect::<Vec<_>>()
+ })
+ .unwrap_or_default(),
+ "{^(?:%s)$}iD",
+ );
+ let indent = if show_all_types { " " } else { "" };
+ let mut latest_packages: IndexMap<String, crate::package::PackageInterfaceHandle> =
+ IndexMap::new();
+ let mut exit_code: i64 = 0;
+ let mut view_data: IndexMap<String, Vec<IndexMap<String, PhpMixed>>> = IndexMap::new();
+ let mut view_meta_data: IndexMap<String, ViewMetaData> = IndexMap::new();
+
+ let mut write_version = false;
+ let mut write_description = false;
+
+ let type_order: Vec<(&str, bool)> = vec![
+ ("platform", true),
+ ("locked", true),
+ ("available", false),
+ ("installed", true),
+ ];
+ for (r#type, show_version) in type_order.iter() {
+ if let Some(type_packages) = packages.get_mut(*r#type) {
+ type_packages.sort_keys();
+
+ let mut name_length: usize = 0;
+ let mut version_length: usize = 0;
+ let mut latest_length: usize = 0;
+ let mut release_date_length: usize = 0;
+
+ if show_latest && *show_version {
+ for package_or_name in type_packages.values() {
+ if let PackageOrName::Pkg(package) = package_or_name
+ && !Preg::is_match(&ignored_packages_regex, &package.get_pretty_name())
+ {
+ let latest = self.find_latest_package(
+ package.clone(),
+ composer.as_ref().unwrap(),
+ &platform_repo,
+ show_major_only,
+ show_minor_only,
+ show_patch_only,
+ platform_req_filter.clone(),
+ )?;
+ if latest.is_none() {
+ continue;
+ }
+
+ latest_packages.insert(package.get_pretty_name(), latest.unwrap());
+ }
+ }
+ }
+
+ let write_path = input.borrow().get_option("name-only")?.as_bool() != Some(true)
+ && input.borrow().get_option("path")?.as_bool() == Some(true);
+ write_version = input.borrow().get_option("name-only")?.as_bool() != Some(true)
+ && input.borrow().get_option("path")?.as_bool() != Some(true)
+ && *show_version;
+ let write_latest = write_version && show_latest;
+ write_description = input.borrow().get_option("name-only")?.as_bool() != Some(true)
+ && input.borrow().get_option("path")?.as_bool() != Some(true);
+ let write_release_date = write_latest
+ && (input.borrow().get_option("sort-by-age")?.as_bool() == Some(true)
+ || format == "json");
+
+ let mut has_outdated_packages = false;
+
+ if input.borrow().get_option("sort-by-age")?.as_bool() == Some(true) {
+ type_packages.sort_by(|_ka, a, _kb, b| match (a, b) {
+ (PackageOrName::Pkg(a), PackageOrName::Pkg(b)) => {
+ a.get_release_date().cmp(&b.get_release_date())
+ }
+ _ => std::cmp::Ordering::Equal,
+ });
+ }
+
+ let mut view_type: Vec<IndexMap<String, PhpMixed>> = Vec::new();
+ for package_or_name in type_packages.values() {
+ let mut package_view_data: IndexMap<String, PhpMixed> = IndexMap::new();
+ if let PackageOrName::Pkg(package) = package_or_name {
+ let latest_package = if show_latest
+ && latest_packages.contains_key(&package.get_pretty_name())
+ {
+ latest_packages.get(&package.get_pretty_name())
+ } else {
+ None
+ };
+
+ // Determine if Composer is checking outdated dependencies and if current package should trigger non-default exit code
+ let mut package_is_up_to_date = if let Some(latest) = latest_package {
+ latest.get_full_pretty_version(
+ true,
+ crate::package::DisplayMode::SourceRefIfDev,
+ ) == package.get_full_pretty_version(
+ true,
+ crate::package::DisplayMode::SourceRefIfDev,
+ ) && latest.as_complete().is_none_or(|c| !c.is_abandoned())
+ } else {
+ false
+ };
+ // When using --major-only, and no bigger version than current major is found then it is considered up to date
+ package_is_up_to_date =
+ package_is_up_to_date || (latest_package.is_none() && show_major_only);
+ let package_is_ignored =
+ Preg::is_match(&ignored_packages_regex, &package.get_pretty_name());
+ if input.borrow().get_option("outdated")?.as_bool() == Some(true)
+ && (package_is_up_to_date || package_is_ignored)
+ {
+ continue;
+ }
+
+ if input.borrow().get_option("outdated")?.as_bool() == Some(true)
+ || input.borrow().get_option("strict")?.as_bool() == Some(true)
+ {
+ has_outdated_packages = true;
+ }
+
+ package_view_data.insert(
+ "name".to_string(),
+ PhpMixed::String(package.get_pretty_name()),
+ );
+ package_view_data.insert(
+ "direct-dependency".to_string(),
+ PhpMixed::Bool(in_array_strict(
+ package.get_name(),
+ &self
+ .get_root_requires()
+ .into_iter()
+ .map(PhpMixed::String)
+ .collect::<Vec<_>>(),
+ )),
+ );
+ if format != "json"
+ || input.borrow().get_option("name-only")?.as_bool() != Some(true)
+ {
+ package_view_data.insert(
+ "homepage".to_string(),
+ match package.as_complete() {
+ Some(c) => match c.get_homepage() {
+ Some(h) => PhpMixed::String(h),
+ None => PhpMixed::Null,
+ },
+ None => PhpMixed::Null,
+ },
+ );
+ package_view_data.insert(
+ "source".to_string(),
+ match PackageInfo::get_view_source_url(package.clone()) {
+ Some(s) => PhpMixed::String(s),
+ None => PhpMixed::Null,
+ },
+ );
+ }
+ name_length = name_length.max(package.get_pretty_name().len());
+ if write_version {
+ let mut version_str = package.get_full_pretty_version(
+ true,
+ crate::package::DisplayMode::SourceRefIfDev,
+ );
+ if format == "text" {
+ version_str = version_str.trim_start_matches('v').to_string();
+ }
+ version_length = version_length.max(version_str.len());
+ package_view_data
+ .insert("version".to_string(), PhpMixed::String(version_str));
+ }
+ if write_release_date {
+ if let Some(release_date) = package.get_release_date() {
+ let mut age = self
+ .get_relative_time(&release_date)
+ .replace(" ago", " old");
+ if !age.contains(" old") {
+ age = format!("from {}", age);
+ }
+ release_date_length = release_date_length.max(age.len());
+ package_view_data
+ .insert("release-age".to_string(), PhpMixed::String(age));
+ package_view_data.insert(
+ "release-date".to_string(),
+ PhpMixed::String(release_date.format(DATE_ATOM).to_string()),
+ );
+ } else {
+ package_view_data.insert(
+ "release-age".to_string(),
+ PhpMixed::String(String::new()),
+ );
+ package_view_data.insert(
+ "release-date".to_string(),
+ PhpMixed::String(String::new()),
+ );
+ }
+ }
+ if write_latest && let Some(latest) = latest_package {
+ let mut latest_version_str = latest.get_full_pretty_version(
+ true,
+ crate::package::DisplayMode::SourceRefIfDev,
+ );
+ if format == "text" {
+ latest_version_str =
+ latest_version_str.trim_start_matches('v').to_string();
+ }
+ let update_status =
+ Self::get_update_status(latest.clone(), package.clone())?;
+ latest_length = latest_length.max(latest_version_str.len());
+ package_view_data
+ .insert("latest".to_string(), PhpMixed::String(latest_version_str));
+ package_view_data.insert(
+ "latest-status".to_string(),
+ PhpMixed::String(update_status),
+ );
+
+ if let Some(rd) = latest.get_release_date() {
+ package_view_data.insert(
+ "latest-release-date".to_string(),
+ PhpMixed::String(rd.format(DATE_ATOM).to_string()),
+ );
+ } else {
+ package_view_data.insert(
+ "latest-release-date".to_string(),
+ PhpMixed::String(String::new()),
+ );
+ }
+ } else if write_latest {
+ package_view_data.insert(
+ "latest".to_string(),
+ PhpMixed::String("[none matched]".to_string()),
+ );
+ package_view_data.insert(
+ "latest-status".to_string(),
+ PhpMixed::String("up-to-date".to_string()),
+ );
+ latest_length = latest_length.max("[none matched]".len());
+ }
+ if write_description && let Some(c) = package.as_complete() {
+ package_view_data.insert(
+ "description".to_string(),
+ match c.get_description() {
+ Some(d) => PhpMixed::String(d),
+ None => PhpMixed::Null,
+ },
+ );
+ }
+ if write_path {
+ let installation_manager = composer
+ .as_ref()
+ .unwrap()
+ .borrow_partial()
+ .get_installation_manager();
+ let path: Option<String> = installation_manager
+ .borrow_mut()
+ .get_install_path(package.clone());
+ if let Some(p) = path {
+ let r = realpath(&p).unwrap_or_default();
+ let trimmed = r.split(['\r', '\n']).next().unwrap_or("");
+ package_view_data.insert(
+ "path".to_string(),
+ PhpMixed::String(trimmed.to_string()),
+ );
+ } else {
+ package_view_data.insert("path".to_string(), PhpMixed::Null);
+ }
+ }
+
+ let mut package_is_abandoned: PhpMixed = PhpMixed::Bool(false);
+ if let Some(latest) = latest_package
+ && let Some(c) = latest.as_complete()
+ && c.is_abandoned()
+ {
+ let replacement_package_name = c.get_replacement_package();
+ let replacement = if let Some(ref rp) = replacement_package_name {
+ format!("Use {} instead", rp)
+ } else {
+ "No replacement was suggested".to_string()
+ };
+ let package_warning = format!(
+ "Package {} is abandoned, you should avoid using it. {}.",
+ package.get_pretty_name(),
+ replacement
+ );
+ package_view_data
+ .insert("warning".to_string(), PhpMixed::String(package_warning));
+ package_is_abandoned = match replacement_package_name {
+ Some(rp) => PhpMixed::String(rp),
+ None => PhpMixed::Bool(true),
+ };
+ }
+
+ package_view_data.insert("abandoned".to_string(), package_is_abandoned);
+ } else if let PackageOrName::Name(name) = package_or_name {
+ package_view_data
+ .insert("name".to_string(), PhpMixed::String(name.clone()));
+ name_length = name_length.max(name.len());
+ }
+ view_type.push(package_view_data);
+ }
+ view_data.insert(r#type.to_string(), view_type);
+ view_meta_data.insert(
+ r#type.to_string(),
+ ViewMetaData {
+ name_length,
+ version_length,
+ latest_length,
+ release_date_length,
+ write_latest,
+ write_release_date,
+ },
+ );
+ if input.borrow().get_option("strict")?.as_bool() == Some(true)
+ && has_outdated_packages
+ {
+ exit_code = 1;
+ break;
+ }
+ }
+ }
+
+ if format == "json" {
+ let mut json_map: IndexMap<String, PhpMixed> = IndexMap::new();
+ for (k, v) in view_data.iter() {
+ json_map.insert(
+ k.clone(),
+ PhpMixed::List(
+ v.iter()
+ .map(|m| {
+ PhpMixed::Array(
+ m.iter().map(|(k, v)| (k.clone(), v.clone())).collect(),
+ )
+ })
+ .collect(),
+ ),
+ );
+ }
+ let io = self.get_io();
+ io.write(&JsonFile::encode(&PhpMixed::Array(
+ json_map.into_iter().collect(),
+ ))?);
+ } else {
+ if input.borrow().get_option("latest")?.as_bool() == Some(true)
+ && view_data.values().any(|v| !v.is_empty())
+ {
+ let io = self.get_io();
+ if !io.is_decorated() {
+ io.write_error("Legend:");
+ io.write_error("! patch or minor release available - update recommended");
+ io.write_error("~ major release available - update possible");
+ if input.borrow().get_option("outdated")?.as_bool() != Some(true) {
+ io.write_error("= up to date version");
+ }
+ } else {
+ io.write_error("<info>Color legend:</info>");
+ io.write_error("- <highlight>patch or minor</highlight> release available - update recommended");
+ io.write_error(
+ "- <comment>major</comment> release available - update possible",
+ );
+ if input.borrow().get_option("outdated")?.as_bool() != Some(true) {
+ io.write_error("- <info>up to date</info> version");
+ }
+ }
+ }
+
+ let width = self.get_terminal_width();
+
+ for (r#type, packages) in view_data.iter() {
+ let meta = match view_meta_data.get(r#type) {
+ Some(m) => m.clone(),
+ None => continue,
+ };
+ let name_length = meta.name_length;
+ let version_length = meta.version_length;
+ let mut latest_length = meta.latest_length;
+ let release_date_length = meta.release_date_length;
+ let write_latest = meta.write_latest;
+ let write_release_date = meta.write_release_date;
+
+ let width_usize = width as usize;
+ let version_fits = name_length + version_length + 3 <= width_usize;
+ let latest_fits = name_length + version_length + latest_length + 3 <= width_usize;
+ let release_date_fits =
+ name_length + version_length + latest_length + release_date_length + 3
+ <= width_usize;
+ let description_fits =
+ name_length + version_length + latest_length + release_date_length + 24
+ <= width_usize;
+
+ if latest_fits && !self.get_io().is_decorated() {
+ latest_length += 2;
+ }
+
+ if show_all_types {
+ if r#type == "available" {
+ self.get_io()
+ .write(&format!("<comment>{}</comment>:", r#type));
+ } else {
+ self.get_io().write(&format!("<info>{}</info>:", r#type));
+ }
+ }
+
+ if write_latest && input.borrow().get_option("direct")?.as_bool() != Some(true) {
+ let mut direct_deps: Vec<IndexMap<String, PhpMixed>> = Vec::new();
+ let mut transitive_deps: Vec<IndexMap<String, PhpMixed>> = Vec::new();
+ for pkg in packages.iter() {
+ let is_direct = pkg
+ .get("direct-dependency")
+ .and_then(|v| v.as_bool())
+ .unwrap_or(false);
+ if is_direct {
+ direct_deps.push(pkg.clone());
+ } else {
+ transitive_deps.push(pkg.clone());
+ }
+ }
+
+ self.get_io().write_error("");
+ self.get_io()
+ .write_error("<info>Direct dependencies required in composer.json:</>");
+ if !direct_deps.is_empty() {
+ self.print_packages(
+ &direct_deps,
+ indent,
+ write_version && version_fits,
+ latest_fits,
+ write_description && description_fits,
+ width_usize,
+ version_length,
+ name_length,
+ latest_length,
+ write_release_date && release_date_fits,
+ release_date_length,
+ );
+ } else {
+ self.get_io().write_error("Everything up to date");
+ }
+ self.get_io().write_error("");
+ self.get_io().write_error(
+ "<info>Transitive dependencies not required in composer.json:</>",
+ );
+ if !transitive_deps.is_empty() {
+ self.print_packages(
+ &transitive_deps,
+ indent,
+ write_version && version_fits,
+ latest_fits,
+ write_description && description_fits,
+ width_usize,
+ version_length,
+ name_length,
+ latest_length,
+ write_release_date && release_date_fits,
+ release_date_length,
+ );
+ } else {
+ self.get_io().write_error("Everything up to date");
+ }
+ } else {
+ if write_latest && packages.is_empty() {
+ self.get_io()
+ .write_error("All your direct dependencies are up to date");
+ } else {
+ self.print_packages(
+ packages,
+ indent,
+ write_version && version_fits,
+ write_latest && latest_fits,
+ write_description && description_fits,
+ width_usize,
+ version_length,
+ name_length,
+ latest_length,
+ write_release_date && release_date_fits,
+ release_date_length,
+ );
+ }
+ }
+
+ if show_all_types {
+ self.get_io().write("");
+ }
+ }
+ }
+
+ Ok(exit_code)
+ }
+
+ fn initialize(
+ &self,
+ input: std::rc::Rc<std::cell::RefCell<dyn InputInterface>>,
+ output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>,
+ ) -> anyhow::Result<()> {
+ base_command_initialize(self, input, output)
+ }
+
+ fn complete(
+ &self,
+ input: &shirabe_external_packages::symfony::console::completion::completion_input::CompletionInput,
+ suggestions: &mut shirabe_external_packages::symfony::console::completion::completion_suggestions::CompletionSuggestions,
+ ) -> anyhow::Result<()> {
+ crate::command::base_command::base_command_complete(self, input, suggestions)
+ }
+
+ shirabe_external_packages::delegate_command_trait_impls_to_inner!(base_command_data);
+}
+
+impl BaseCommand for ShowCommand {
+ fn base_command_data(&self) -> &crate::command::BaseCommandData {
+ &self.base_command_data
+ }
+
+ crate::delegate_base_command_trait_impls_to_inner!(base_command_data);
+}
+
#[derive(Debug)]
pub enum PackageOrName {
Pkg(crate::package::PackageInterfaceHandle),