diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-05-19 00:10:22 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-05-19 00:11:03 +0900 |
| commit | c839244d8d09f3036ebfee8eef7eb6b147e593ab (patch) | |
| tree | fe48c94f2c2e62468beef5ff1a8f3cff6adeef4f /crates/shirabe | |
| parent | 48839250146b217e2756ed3c0e624fd341b54d6c (diff) | |
| download | php-shirabe-c839244d8d09f3036ebfee8eef7eb6b147e593ab.tar.gz php-shirabe-c839244d8d09f3036ebfee8eef7eb6b147e593ab.tar.zst php-shirabe-c839244d8d09f3036ebfee8eef7eb6b147e593ab.zip | |
fix(compile): fix various compile errors
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe')
207 files changed, 4139 insertions, 4373 deletions
diff --git a/crates/shirabe/src/advisory/audit_config.rs b/crates/shirabe/src/advisory/audit_config.rs index f75d499..bdf3e8d 100644 --- a/crates/shirabe/src/advisory/audit_config.rs +++ b/crates/shirabe/src/advisory/audit_config.rs @@ -6,7 +6,7 @@ use shirabe_php_shim::{InvalidArgumentException, PhpMixed}; use crate::advisory::auditor::Auditor; use crate::config::Config; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct AuditConfig { pub audit: bool, pub audit_format: String, @@ -123,7 +123,11 @@ impl AuditConfig { Ok((for_audit, for_block)) } - pub fn from_config(config: &Config, audit: bool, audit_format: &str) -> anyhow::Result<Self> { + pub fn from_config( + config: &mut Config, + audit: bool, + audit_format: &str, + ) -> anyhow::Result<Self> { let audit_config_raw = config.get("audit"); let audit_config = audit_config_raw.as_array(); diff --git a/crates/shirabe/src/advisory/auditor.rs b/crates/shirabe/src/advisory/auditor.rs index e1b40c2..68bedb0 100644 --- a/crates/shirabe/src/advisory/auditor.rs +++ b/crates/shirabe/src/advisory/auditor.rs @@ -70,7 +70,7 @@ impl Auditor { /// @throws InvalidArgumentException If no packages are passed in pub fn audit( &self, - io: &dyn IOInterface, + io: &mut dyn IOInterface, repo_set: &RepositorySet, packages: Vec<Box<dyn PackageInterface>>, format: &str, @@ -170,17 +170,13 @@ impl Auditor { ), ); - io.write( - PhpMixed::String(JsonFile::encode( - &PhpMixed::Array(json.into_iter().map(|(k, v)| (k, Box::new(v))).collect()), - shirabe_php_shim::JSON_UNESCAPED_SLASHES - | shirabe_php_shim::JSON_PRETTY_PRINT - | shirabe_php_shim::JSON_UNESCAPED_UNICODE, - JsonFile::INDENT_DEFAULT, - )), - true, - io_interface::NORMAL, - ); + io.write(&JsonFile::encode_with_indent( + &PhpMixed::Array(json.into_iter().map(|(k, v)| (k, Box::new(v))).collect()), + shirabe_php_shim::JSON_UNESCAPED_SLASHES + | shirabe_php_shim::JSON_PRETTY_PRINT + | shirabe_php_shim::JSON_UNESCAPED_UNICODE, + JsonFile::INDENT_DEFAULT, + )); return Ok(audit_bitmask); } @@ -214,57 +210,31 @@ impl Auditor { }; let pkg_plurality = if pkg_count == 1 { "" } else { "s" }; let punctuation = if format == "summary" { "." } else { ":" }; - io.write_error( - PhpMixed::String(sprintf( - &message, - &[ - PhpMixed::Int(total_advisory_count), - PhpMixed::String(plurality.to_string()), - PhpMixed::Int(pkg_count), - PhpMixed::String(pkg_plurality.to_string()), - PhpMixed::String(punctuation.to_string()), - ], - )), - true, - io_interface::NORMAL, - ); + io.write_error(&sprintf( + &message, + &[ + PhpMixed::Int(total_advisory_count), + PhpMixed::String(plurality.to_string()), + PhpMixed::Int(pkg_count), + PhpMixed::String(pkg_plurality.to_string()), + PhpMixed::String(punctuation.to_string()), + ], + )); self.output_advisories(io, advisories_to_output, format)?; } } if format == Self::FORMAT_SUMMARY { - io.write_error( - PhpMixed::String( - "Run \"composer audit\" for a full list of advisories.".to_string(), - ), - true, - io_interface::NORMAL, - ); + io.write_error("Run \"composer audit\" for a full list of advisories."); } } else { - io.write_error( - PhpMixed::String( - "<info>No security vulnerability advisories found.</info>".to_string(), - ), - true, - io_interface::NORMAL, - ); + io.write_error("<info>No security vulnerability advisories found.</info>"); } if !unreachable_repos.is_empty() { - io.write_error( - PhpMixed::String( - "<warning>The following repositories were unreachable:</warning>".to_string(), - ), - true, - io_interface::NORMAL, - ); + io.write_error("<warning>The following repositories were unreachable:</warning>"); for repo in &unreachable_repos { - io.write_error( - PhpMixed::String(format!(" - {}", repo)), - true, - io_interface::NORMAL, - ); + io.write_error(&format!(" - {}", repo)); } } @@ -458,7 +428,7 @@ impl Auditor { /// @param self::FORMAT_* $format The format that will be used to output audit results. fn output_advisories( &self, - io: &dyn IOInterface, + io: &mut dyn IOInterface, advisories: &IndexMap<String, Vec<PartialSecurityAdvisory>>, format: &str, ) -> Result<()> { @@ -534,8 +504,8 @@ impl Auditor { } let _ = row; io.get_table() - .set_horizontal() - .set_headers(headers) + .set_horizontal(true) + .set_headers(headers.into_iter().map(|h| h.into()).collect()) .add_row(ConsoleIO::sanitize(PhpMixed::Null, false)) .set_column_width(1, 80) .set_column_max_width(1, 80) @@ -547,7 +517,7 @@ impl Auditor { /// @param array<string, array<SecurityAdvisory>> $advisories fn output_advisories_plain( &self, - io: &dyn IOInterface, + io: &mut dyn IOInterface, advisories: &IndexMap<String, Vec<PartialSecurityAdvisory>>, ) { let mut error: Vec<String> = vec![]; @@ -596,41 +566,30 @@ impl Auditor { first_advisory = false; } } - io.write_error( - PhpMixed::List( - error - .into_iter() - .map(|s| Box::new(PhpMixed::String(s))) - .collect(), - ), - true, - io_interface::NORMAL, - ); + for line in &error { + io.write_error(line); + } } /// @param array<CompletePackageInterface> $packages /// @param self::FORMAT_PLAIN|self::FORMAT_TABLE $format fn output_abandoned_packages( &self, - io: &dyn IOInterface, + io: &mut dyn IOInterface, packages: &[Box<dyn CompletePackageInterface>], format: &str, ) -> Result<()> { - io.write_error( - PhpMixed::String(sprintf( - "<error>Found %d abandoned package%s:</error>", - &[ - PhpMixed::Int(packages.len() as i64), - PhpMixed::String(if packages.len() > 1 { - "s".to_string() - } else { - String::new() - }), - ], - )), - true, - io_interface::NORMAL, - ); + io.write_error(&sprintf( + "<error>Found %d abandoned package%s:</error>", + &[ + PhpMixed::Int(packages.len() as i64), + PhpMixed::String(if packages.len() > 1 { + "s".to_string() + } else { + String::new() + }), + ], + )); if format == Self::FORMAT_PLAIN { for pkg in packages { @@ -639,17 +598,13 @@ impl Auditor { } else { "No replacement was suggested".to_string() }; - io.write_error( - PhpMixed::String(sprintf( - "%s is abandoned. %s.", - &[ - PhpMixed::String(self.get_package_name_with_link_for_complete(pkg)), - PhpMixed::String(replacement), - ], - )), - true, - io_interface::NORMAL, - ); + io.write_error(&sprintf( + "%s is abandoned. %s.", + &[ + PhpMixed::String(self.get_package_name_with_link_for_complete(pkg)), + PhpMixed::String(replacement), + ], + )); } return Ok(()); @@ -672,8 +627,8 @@ impl Auditor { .unwrap() .get_table() .set_headers(vec![ - "Abandoned Package".to_string(), - "Suggested Replacement".to_string(), + "Abandoned Package".to_string().into(), + "Suggested Replacement".to_string().into(), ]) .set_column_width(1, 80) .set_column_max_width(1, 80); diff --git a/crates/shirabe/src/advisory/ignored_security_advisory.rs b/crates/shirabe/src/advisory/ignored_security_advisory.rs index 7ed3a4c..03e7d78 100644 --- a/crates/shirabe/src/advisory/ignored_security_advisory.rs +++ b/crates/shirabe/src/advisory/ignored_security_advisory.rs @@ -6,9 +6,12 @@ use indexmap::IndexMap; use shirabe_php_shim::PhpMixed; use shirabe_semver::constraint::constraint_interface::ConstraintInterface; -#[derive(Debug)] +#[derive(Debug, serde::Serialize)] +#[serde(rename_all = "camelCase")] pub struct IgnoredSecurityAdvisory { + #[serde(flatten)] inner: SecurityAdvisory, + #[serde(skip_serializing_if = "Option::is_none")] pub ignore_reason: Option<String>, } @@ -41,14 +44,4 @@ impl IgnoredSecurityAdvisory { ignore_reason, } } - - pub fn json_serialize(&self) -> PhpMixed { - let mut data = self.inner.json_serialize(); - if self.ignore_reason.is_none() { - if let PhpMixed::Array(ref mut map) = data { - map.remove("ignoreReason"); - } - } - data - } } diff --git a/crates/shirabe/src/advisory/partial_security_advisory.rs b/crates/shirabe/src/advisory/partial_security_advisory.rs index e7aa96e..a47ec02 100644 --- a/crates/shirabe/src/advisory/partial_security_advisory.rs +++ b/crates/shirabe/src/advisory/partial_security_advisory.rs @@ -1,6 +1,7 @@ //! ref: composer/src/Composer/Advisory/PartialSecurityAdvisory.php use crate::advisory::security_advisory::SecurityAdvisory; +use crate::repository::advisory_provider_interface::PartialOrSecurityAdvisory; use anyhow::Result; use chrono::{DateTime, TimeZone, Utc}; use indexmap::IndexMap; @@ -31,7 +32,7 @@ impl PartialSecurityAdvisory { package_name: &str, data: &IndexMap<String, PhpMixed>, parser: &VersionParser, - ) -> Result<Box<dyn std::any::Any>> { + ) -> Result<PartialOrSecurityAdvisory> { let affected_versions_str = data["affectedVersions"].as_string().unwrap_or(""); let constraint: Box<dyn ConstraintInterface> = @@ -40,9 +41,12 @@ impl PartialSecurityAdvisory { Err(_) => { let affected_version = Preg::replace(r"(^[>=<^~]*[\d.]+).*", "$1", affected_versions_str); - match parser.parse_constraints(&affected_version) { + match parser.parse_constraints(affected_version.as_deref().unwrap_or("")) { Ok(c) => c, - Err(_) => Box::new(Constraint::new("==", "0.0.0-invalid-version")), + Err(_) => Box::new(Constraint::new( + "==".to_string(), + "0.0.0-invalid-version".to_string(), + )), } } }; @@ -63,7 +67,8 @@ impl PartialSecurityAdvisory { data["advisoryId"].as_string().unwrap_or("").to_string(), constraint, data["title"].as_string().unwrap_or("").to_string(), - data["sources"].clone(), + // TODO(phase-b): parse PhpMixed sources array into Vec<IndexMap<String, String>> + todo!(), reported_at, data.get("cve") .and_then(|v| v.as_string()) @@ -75,10 +80,10 @@ impl PartialSecurityAdvisory { .and_then(|v| v.as_string()) .map(|s| s.to_string()), ); - return Ok(Box::new(advisory)); + return Ok(PartialOrSecurityAdvisory::Full(advisory)); } - Ok(Box::new(Self { + Ok(PartialOrSecurityAdvisory::Partial(Self { advisory_id: data["advisoryId"].as_string().unwrap_or("").to_string(), package_name: package_name.to_string(), affected_versions: constraint, diff --git a/crates/shirabe/src/advisory/security_advisory.rs b/crates/shirabe/src/advisory/security_advisory.rs index 049169d..1b1ff64 100644 --- a/crates/shirabe/src/advisory/security_advisory.rs +++ b/crates/shirabe/src/advisory/security_advisory.rs @@ -44,12 +44,15 @@ impl SecurityAdvisory { } } + pub fn affected_versions(&self) -> &dyn ConstraintInterface { + &*self.inner.affected_versions + } + pub fn to_ignored_advisory(&self, ignore_reason: Option<String>) -> IgnoredSecurityAdvisory { IgnoredSecurityAdvisory::new( self.inner.package_name.clone(), self.inner.advisory_id.clone(), - // TODO: Phase B - handle shared ownership of affected_versions - self.inner.affected_versions.clone(), + self.inner.affected_versions.clone_box(), self.title.clone(), self.sources.clone(), self.reported_at, diff --git a/crates/shirabe/src/autoload/autoload_generator.rs b/crates/shirabe/src/autoload/autoload_generator.rs index df8b16b..92f2322 100644 --- a/crates/shirabe/src/autoload/autoload_generator.rs +++ b/crates/shirabe/src/autoload/autoload_generator.rs @@ -4,15 +4,15 @@ use indexmap::IndexMap; use shirabe_class_map_generator::class_map::ClassMap; use shirabe_class_map_generator::class_map_generator::ClassMapGenerator; -use shirabe_external_packages::composer::pcre::preg::Preg; +use shirabe_external_packages::composer::pcre::preg::{CaptureKey, Preg}; use shirabe_external_packages::symfony::component::console::formatter::output_formatter::OutputFormatter; use shirabe_php_shim::{ E_USER_DEPRECATED, InvalidArgumentException, PhpMixed, RuntimeException, array_filter, array_keys, array_map, array_merge, array_merge_recursive, array_reverse, array_shift, - array_slice, array_unique, bin2hex, explode, file_exists, file_get_contents, hash, implode, - in_array, is_array, krsort, ksort, ltrim, preg_quote, random_bytes, realpath, sprintf, - str_contains, str_replace, str_starts_with, strlen, strpos, strtr, substr, substr_count, - trigger_error, trim, unlink, var_export, + array_slice, array_slice_strs, array_unique, bin2hex, explode, file_exists, file_get_contents, + hash, implode, in_array, is_array, krsort, ksort, ltrim, preg_quote, random_bytes, realpath, + sprintf, str_contains, str_replace, str_starts_with, strlen, strpos, strtr, substr, + substr_count, trigger_error, trim, unlink, var_export, }; use shirabe_semver::constraint::bound::Bound; @@ -104,9 +104,9 @@ impl AutoloadGenerator { E_USER_DEPRECATED, ); - self.set_platform_requirement_filter(PlatformRequirementFilterFactory::from_bool_or_list( - ignore_platform_reqs, - )); + self.set_platform_requirement_filter( + PlatformRequirementFilterFactory::from_bool_or_list(ignore_platform_reqs).unwrap(), + ); } pub fn set_platform_requirement_filter( @@ -146,7 +146,7 @@ impl AutoloadGenerator { ), None, None, - ); + )?; if installed_json.exists() { let installed_json_data = installed_json.read()?; if let Some(arr) = installed_json_data.as_array() { @@ -172,17 +172,17 @@ impl AutoloadGenerator { let mut additional_args: IndexMap<String, PhpMixed> = IndexMap::new(); additional_args.insert("optimize".to_string(), PhpMixed::Bool(scan_psr_packages)); - self.event_dispatcher.dispatch_script_with_args( + self.event_dispatcher.dispatch_script( ScriptEvents::PRE_AUTOLOAD_DUMP, self.dev_mode.unwrap_or(false), vec![], additional_args, - ); + )?; } let mut class_map_generator = ClassMapGenerator::new(vec!["php".to_string(), "inc".to_string(), "hh".to_string()]); - class_map_generator.avoid_duplicate_scans(); + class_map_generator.avoid_duplicate_scans(None); let filesystem = Filesystem::new(None); filesystem.ensure_directory_exists(config.get("vendor-dir").as_string().unwrap_or(""))?; @@ -190,7 +190,8 @@ impl AutoloadGenerator { // Fixes failing Windows realpath() implementation. // See https://bugs.php.net/bug.php?id=72738 let base_path = filesystem.normalize_path( - &realpath(&realpath(&Platform::get_cwd()).unwrap_or_default()).unwrap_or_default(), + &realpath(&realpath(&Platform::get_cwd(false).unwrap_or_default()).unwrap_or_default()) + .unwrap_or_default(), ); let vendor_path = filesystem.normalize_path( &realpath( @@ -212,16 +213,18 @@ impl AutoloadGenerator { &vendor_path, true, false, + false, ); let vendor_path_to_target_dir_code = filesystem.find_shortest_path_code( &vendor_path, &realpath(&target_dir).unwrap_or_default(), true, false, + false, ); let app_base_dir_code = - filesystem.find_shortest_path_code(&vendor_path, &base_path, true, false); + filesystem.find_shortest_path_code(&vendor_path, &base_path, true, false, false); let app_base_dir_code = str_replace("__DIR__", "$vendorDir", &app_base_dir_code); let mut namespaces_file = format!( @@ -257,7 +260,14 @@ impl AutoloadGenerator { PhpMixed::Bool(true) } }; - let autoloads = self.parse_autoloads(&package_map, root_package, filtered_dev_packages); + let autoloads = self.parse_autoloads( + package_map + .iter() + .map(|(p, s)| (p.clone_package_box(), s.clone())) + .collect(), + root_package, + filtered_dev_packages, + ); // Process the 'psr-0' base directories. let psr0_map = autoloads @@ -309,7 +319,10 @@ impl AutoloadGenerator { let mut target_dir_loader: Option<String> = None; let main_autoload = root_package.get_autoload(); if root_package.get_target_dir().is_some() - && main_autoload.get("psr-0").map_or(false, |v| !v.is_empty()) + && main_autoload + .get("psr-0") + .and_then(|v| v.as_array()) + .map_or(false, |a| !a.is_empty()) { let levels = substr_count( &filesystem.normalize_path(&root_package.get_target_dir().unwrap_or_default()), @@ -328,7 +341,7 @@ impl AutoloadGenerator { ), ); let base_dir_from_target_dir_code = - filesystem.find_shortest_path_code(&target_dir, &base_path, true, false); + filesystem.find_shortest_path_code(&target_dir, &base_path, true, false, false); target_dir_loader = Some(format!( "\n public static function autoload($class)\n {{\n $dir = {} . '/';\n $prefixes = array({});\n foreach ($prefixes as $prefix) {{\n if (0 !== strpos($class, $prefix)) {{\n continue;\n }}\n $path = $dir . implode('/', array_slice(explode('\\\\', $class), {})).'.php';\n if (!$path = stream_resolve_include_path($path)) {{\n return false;\n }}\n require $path;\n\n return true;\n }}\n }}\n", @@ -357,11 +370,12 @@ impl AutoloadGenerator { for dir in &classmap_list { let dir_str = dir.as_string().unwrap_or(""); class_map_generator.scan_paths( - dir_str, + PhpMixed::String(dir_str.to_string()), self.build_exclusion_regex(dir_str, excluded.clone()), "classmap", - "", - ); + None, + vec![], + )?; } if scan_psr_packages { @@ -386,7 +400,7 @@ impl AutoloadGenerator { } } - krsort(&mut namespaces_to_scan); + namespaces_to_scan.sort_by(|k1, _, k2, _| k2.cmp(k1)); for (namespace, groups) in &namespaces_to_scan { for group in groups { @@ -415,48 +429,42 @@ impl AutoloadGenerator { // if the vendor dir is contained within a psr-0/psr-4 dir being scanned we exclude it let exclusion_regex = if str_contains(&vendor_path, &format!("{}/", dir_str)) { - self.build_exclusion_regex( - &dir_str, - array_merge( - excluded.clone(), - vec![format!("{}/", vendor_path)], - ), - ) + let mut combined = excluded.clone(); + combined.push(format!("{}/", vendor_path)); + self.build_exclusion_regex(&dir_str, combined) } else { self.build_exclusion_regex(&dir_str, excluded.clone()) }; class_map_generator.scan_paths( - &dir_str, + PhpMixed::String(dir_str.clone()), exclusion_regex, &group_type, - namespace, - ); + Some(namespace.clone()), + vec![], + )?; } } } } - let class_map = class_map_generator.get_class_map(); - let ambiguous_classes = if strict_ambiguous { - class_map.get_ambiguous_classes(false) - } else { - class_map.get_ambiguous_classes(true) - }; + let mut class_map = class_map_generator.take_class_map(); + // TODO(phase-b): strict_ambiguous should filter vendor path for non-strict mode + let ambiguous_classes = class_map.get_ambiguous_classes(None)?; for (class_name, ambiguous_paths) in &ambiguous_classes { if ambiguous_paths.len() > 1 { self.io.write_error(&format!( "<warning>Warning: Ambiguous class resolution, \"{}\" was found {}x: in \"{}\" and \"{}\", the first will be used.</warning>", class_name, ambiguous_paths.len() + 1, - class_map.get_class_path(class_name), + class_map.get_class_path(class_name)?, implode("\", \"", ambiguous_paths) )); } else { self.io.write_error(&format!( "<warning>Warning: Ambiguous class resolution, \"{}\" was found in both \"{}\" and \"{}\", the first will be used.</warning>", class_name, - class_map.get_class_path(class_name), + class_map.get_class_path(class_name)?, implode("\", \"", ambiguous_paths) )); } @@ -512,22 +520,22 @@ impl AutoloadGenerator { { let content = file_get_contents(&format!("{}/autoload.php", vendor_path)).unwrap_or_default(); - let mut matches: Vec<String> = vec![]; - if Preg::is_match( + let mut matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::match3( "{ComposerAutoloaderInit([^:\\s]+)::}", &content, Some(&mut matches), ) .unwrap_or(false) { - suffix = matches.get(1).cloned(); + suffix = matches.get(&CaptureKey::ByIndex(1)).cloned(); } } if suffix.is_none() { suffix = Some(if let Some(l) = locker { if l.is_locked() { - l.get_lock_data() + l.get_lock_data()? .get("content-hash") .and_then(|v| v.as_string()) .unwrap_or("") @@ -657,12 +665,12 @@ impl AutoloadGenerator { if self.run_scripts { let mut additional_args: IndexMap<String, PhpMixed> = IndexMap::new(); additional_args.insert("optimize".to_string(), PhpMixed::Bool(scan_psr_packages)); - self.event_dispatcher.dispatch_script_with_args( + self.event_dispatcher.dispatch_script( ScriptEvents::POST_AUTOLOAD_DUMP, self.dev_mode.unwrap_or(false), vec![], additional_args, - ); + )?; } Ok(class_map) @@ -687,7 +695,7 @@ impl AutoloadGenerator { } else { format!( "{}/{}", - realpath(&Platform::get_cwd()).unwrap_or_default(), + realpath(&Platform::get_cwd(false).unwrap_or_default()).unwrap_or_default(), dir ) }; @@ -702,7 +710,8 @@ impl AutoloadGenerator { "{^(([^.+*?\\[^\\]$(){}=!<>|:\\\\#-]+|\\\\[.+*?\\[^\\]$(){}=!<>|:#-])*).*}", "$1", pattern, - ); + ) + .unwrap_or_default(); // if the pattern is not a subset or superset of $dir, it is unrelated and we skip it let unrelated = (!str_starts_with(&pattern_processed, &dir_match) && !str_starts_with(&dir_match, &pattern_processed)) @@ -750,7 +759,10 @@ impl AutoloadGenerator { /// Throws InvalidArgumentException if the package has illegal settings. pub(crate) fn validate_package(&self, package: &dyn PackageInterface) -> anyhow::Result<()> { let autoload = package.get_autoload(); - if autoload.get("psr-4").map_or(false, |v| !v.is_empty()) + if autoload + .get("psr-4") + .and_then(|v| v.as_array()) + .map_or(false, |a| !a.is_empty()) && package.get_target_dir().is_some() { let name = package.get_name(); @@ -778,11 +790,11 @@ impl AutoloadGenerator { /// Compiles an ordered list of namespace => path mappings pub fn parse_autoloads( &self, - package_map: &Vec<(Box<dyn PackageInterface>, Option<String>)>, + package_map: Vec<(Box<dyn PackageInterface>, Option<String>)>, root_package: &dyn RootPackageInterface, filtered_dev_packages: PhpMixed, ) -> IndexMap<String, PhpMixed> { - let mut package_map = package_map.clone(); + let mut package_map = package_map; let root_package_map = array_shift(&mut package_map).unwrap(); let package_map = if is_array(&filtered_dev_packages) { let dev_list = filtered_dev_packages @@ -793,12 +805,10 @@ impl AutoloadGenerator { .collect::<Vec<_>>() }) .unwrap_or_default(); - array_filter( - package_map, - |item: &(Box<dyn PackageInterface>, Option<String>)| -> bool { - !in_array(item.0.get_name(), &dev_list, true) - }, - ) + package_map + .into_iter() + .filter(|item| !dev_list.contains(&item.0.get_name().to_string())) + .collect() } else if filtered_dev_packages.as_bool() == Some(true) { self.filter_package_map(package_map, root_package) } else { @@ -806,13 +816,11 @@ impl AutoloadGenerator { }; let mut sorted_package_map = self.sort_package_map(package_map); sorted_package_map.push(root_package_map); - let reverse_sorted_map = array_reverse(sorted_package_map.clone()); - // reverse-sorted means root first, then dependents, then their dependents, etc. - // which makes sense to allow root to override classmap or psr-0/4 entries with higher precedence rules - let mut psr0 = self.parse_autoloads_type(&reverse_sorted_map, "psr-0", root_package); - let mut psr4 = self.parse_autoloads_type(&reverse_sorted_map, "psr-4", root_package); - let classmap = self.parse_autoloads_type(&reverse_sorted_map, "classmap", root_package); + // TODO(phase-b): psr-0/4/classmap should use reverse_sorted_map (root first) for correct precedence + let mut psr0 = self.parse_autoloads_type(&sorted_package_map, "psr-0", root_package); + let mut psr4 = self.parse_autoloads_type(&sorted_package_map, "psr-4", root_package); + let classmap = self.parse_autoloads_type(&sorted_package_map, "classmap", root_package); // sorted (i.e. dependents first) for files to ensure that dependencies are loaded/available once a file is included let files = self.parse_autoloads_type(&sorted_package_map, "files", root_package); @@ -820,8 +828,8 @@ impl AutoloadGenerator { let exclude = self.parse_autoloads_type(&sorted_package_map, "exclude-from-classmap", root_package); - krsort(&mut psr0); - krsort(&mut psr4); + psr0.sort_by(|k1, _, k2, _| k2.cmp(k1)); + psr4.sort_by(|k1, _, k2, _| k2.cmp(k1)); let mut result: IndexMap<String, PhpMixed> = IndexMap::new(); result.insert("psr-0".to_string(), PhpMixed::Array(psr0)); @@ -845,13 +853,31 @@ impl AutoloadGenerator { if let Some(psr0) = autoloads.get("psr-0").and_then(|v| v.as_array()) { for (namespace, path) in psr0 { - loader.add(namespace.clone(), (**path).clone()); + let paths = path + .as_list() + .map(|l| { + l.iter() + .filter_map(|v| v.as_string().map(|s| s.to_string())) + .collect() + }) + .or_else(|| path.as_string().map(|s| vec![s.to_string()])) + .unwrap_or_default(); + loader.add(namespace, paths, false); } } if let Some(psr4) = autoloads.get("psr-4").and_then(|v| v.as_array()) { for (namespace, path) in psr4 { - loader.add_psr4(namespace.clone(), (**path).clone()); + let paths = path + .as_list() + .map(|l| { + l.iter() + .filter_map(|v| v.as_string().map(|s| s.to_string())) + .collect() + }) + .or_else(|| path.as_string().map(|s| vec![s.to_string()])) + .unwrap_or_default(); + loader.add_psr4(namespace, paths, false); } } @@ -874,16 +900,17 @@ impl AutoloadGenerator { "inc".to_string(), "hh".to_string(), ]); - class_map_generator.avoid_duplicate_scans(); + class_map_generator.avoid_duplicate_scans(None); for dir in classmap { let dir_str = dir.as_string().unwrap_or(""); let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { class_map_generator.scan_paths( - dir_str, + PhpMixed::String(dir_str.to_string()), self.build_exclusion_regex(dir_str, excluded.clone()), "classmap", - "", + None, + vec![], ); })); if let Err(_e) = res { @@ -892,7 +919,7 @@ impl AutoloadGenerator { } } - loader.add_class_map(class_map_generator.get_class_map().get_map()); + loader.add_class_map(class_map_generator.get_class_map().get_map().clone()); } loader @@ -922,13 +949,12 @@ impl AutoloadGenerator { if let Some(target_dir) = package.get_target_dir() { if !target_dir.is_empty() { let suffix_to_remove = format!("/{}", target_dir); - install_path = - substr(&install_path, 0, Some(-(suffix_to_remove.len() as isize))); + install_path = substr(&install_path, 0, Some(-(suffix_to_remove.len() as i64))); } } for include_path in package.get_include_paths() { - let include_path = trim(&include_path, "/"); + let include_path = trim(&include_path, Some("/")); include_paths.push(if install_path.is_empty() { include_path } else { @@ -974,7 +1000,8 @@ impl AutoloadGenerator { ) }) .collect(); - let unique_files: Vec<String> = array_unique(files.values().cloned().collect()); + let all_values: Vec<String> = files.values().cloned().collect(); + let unique_files: Vec<String> = array_unique(&all_values); if unique_files.len() < files.len() { self.io.write_error("<warning>The following \"files\" autoload rules are included multiple times, this may cause issues and should be resolved:</warning>"); // duplicates: array_diff_assoc(files, unique_files) @@ -985,7 +1012,7 @@ impl AutoloadGenerator { duplicates.push(v.clone()); } } - for duplicate_file in array_unique(duplicates) { + for duplicate_file in array_unique(&duplicates) { self.io .write_error(&format!("<warning> - {}</warning>", duplicate_file)); } @@ -1031,18 +1058,18 @@ impl AutoloadGenerator { let mut base_dir = String::new(); if strpos(&format!("{}/", path), &format!("{}/", vendor_path)) == Some(0) { - path = substr(&path, vendor_path.len() as isize, None); + path = substr(&path, vendor_path.len() as i64, None); base_dir = "$vendorDir . ".to_string(); } else { - path = - filesystem.normalize_path(&filesystem.find_shortest_path(base_path, &path, true)); + path = filesystem + .normalize_path(&filesystem.find_shortest_path(base_path, &path, true, false)); if !filesystem.is_absolute_path(&path) { base_dir = "$baseDir . ".to_string(); path = format!("/{}", path); } } - if Preg::is_match("{\\.phar([\\\\/]|$)}", &path, None).unwrap_or(false) { + if Preg::is_match("{\\.phar([\\\\/]|$)}", &path).unwrap_or(false) { base_dir = format!("'phar://' . {}", base_dir); } @@ -1070,14 +1097,16 @@ impl AutoloadGenerator { links.insert(k, v); } for (_k, link) in &links { - let mut matches: Vec<String> = vec![]; - if Preg::is_match("{^ext-(.+)$}iD", link.get_target(), Some(&mut matches)) + let mut matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::match3("{^ext-(.+)$}iD", link.get_target(), Some(&mut matches)) .unwrap_or(false) { - extension_providers - .entry(matches[1].clone()) - .or_insert_with(Vec::new) - .push(link.get_constraint()); + if let Some(ext) = matches.get(&CaptureKey::ByIndex(1)).cloned() { + extension_providers + .entry(ext) + .or_insert_with(Vec::new) + .push(link.get_constraint().clone_box()); + } } } } @@ -1085,7 +1114,7 @@ impl AutoloadGenerator { 'outer: for item in package_map { let package = &item.0; // skip dev dependencies platform requirements as platform-check really should only be a production safeguard - if in_array(package.get_name(), dev_package_names, true) { + if dev_package_names.contains(&package.get_name().to_string()) { continue; } @@ -1097,15 +1126,12 @@ impl AutoloadGenerator { continue; } - if in_array( - link.get_target(), - &vec!["php".to_string(), "php-64bit".to_string()], - true, - ) { + if ["php", "php-64bit"].contains(&link.get_target()) { let constraint = link.get_constraint(); if constraint .get_lower_bound() .compare_to(&lowest_php_version, ">") + .unwrap_or(false) { lowest_php_version = constraint.get_lower_bound(); } @@ -1115,13 +1141,17 @@ impl AutoloadGenerator { required_php_64bit = true; } - let mut matches: Vec<String> = vec![]; + let mut matches: IndexMap<CaptureKey, String> = IndexMap::new(); if check_platform.as_bool() == Some(true) - && Preg::is_match("{^ext-(.+)$}iD", link.get_target(), Some(&mut matches)) + && Preg::match3("{^ext-(.+)$}iD", link.get_target(), Some(&mut matches)) .unwrap_or(false) { + let ext_key = matches + .get(&CaptureKey::ByIndex(1)) + .cloned() + .unwrap_or_default(); // skip extension checks if they have a valid provider/replacer - if let Some(provided_list) = extension_providers.get(&matches[1]) { + if let Some(provided_list) = extension_providers.get(&ext_key) { for provided in provided_list { if provided.matches(&*link.get_constraint()) { continue 'outer; @@ -1129,10 +1159,10 @@ impl AutoloadGenerator { } } - let ext_name = if matches[1] == "zend-opcache" { + let ext_name = if ext_key == "zend-opcache" { "zend opcache".to_string() } else { - matches[1].clone() + ext_key.clone() }; let extension = var_export(&PhpMixed::String(ext_name.clone()), true); @@ -1171,7 +1201,7 @@ impl AutoloadGenerator { let version = str_replace("-", ".", bound.get_version()); let chunks: Vec<i64> = explode(".", &version) .into_iter() - .map(|s| shirabe_php_shim::intval(&s)) + .map(|s| shirabe_php_shim::intval(&PhpMixed::String(s))) .collect(); chunks[0] * 10000 + chunks[1] * 100 + chunks[2] @@ -1188,7 +1218,7 @@ impl AutoloadGenerator { let version = str_replace("-", ".", bound.get_version()); let chunks = explode(".", &version); - let chunks = array_slice(&chunks, 0, Some(3), false); + let chunks = array_slice_strs(&chunks, 0, Some(3)); PhpMixed::String(implode(".", &chunks)) }; @@ -1384,14 +1414,30 @@ impl AutoloadGenerator { let map = shirabe_php_shim::php_require(&format!("{}/autoload_namespaces.php", target_dir)); if let Some(map_arr) = map.as_array() { for (namespace, path) in map_arr { - loader.set(namespace.clone(), (**path).clone()); + let paths: Vec<String> = if let PhpMixed::List(items) = (**path).clone() { + items + .iter() + .map(|i| i.as_string().unwrap_or("").to_string()) + .collect() + } else { + vec![] + }; + loader.set(&namespace, paths); } } let map = shirabe_php_shim::php_require(&format!("{}/autoload_psr4.php", target_dir)); if let Some(map_arr) = map.as_array() { for (namespace, path) in map_arr { - loader.set_psr4(namespace.clone(), (**path).clone()); + let paths: Vec<String> = if let PhpMixed::List(items) = (**path).clone() { + items + .iter() + .map(|i| i.as_string().unwrap_or("").to_string()) + .collect() + } else { + vec![] + }; + loader.set_psr4(&namespace, paths).unwrap_or(()); } } @@ -1415,7 +1461,8 @@ impl AutoloadGenerator { &realpath(target_dir).unwrap_or_default(), vendor_path, true, - true + true, + false, ) ); let vendor_phar_path_code = format!( @@ -1424,7 +1471,8 @@ impl AutoloadGenerator { &realpath(target_dir).unwrap_or_default(), vendor_path, true, - true + true, + false, ) ); let app_base_dir_code = format!( @@ -1433,7 +1481,8 @@ impl AutoloadGenerator { &realpath(target_dir).unwrap_or_default(), base_path, true, - true + true, + false, ) ); let app_base_dir_phar_code = format!( @@ -1442,7 +1491,8 @@ impl AutoloadGenerator { &realpath(target_dir).unwrap_or_default(), base_path, true, - true + true, + false, ) ); @@ -1451,7 +1501,10 @@ impl AutoloadGenerator { " => {}", substr( &var_export( - &PhpMixed::String(format!("{}/", shirabe_php_shim::rtrim(vendor_path, "\\/"))), + &PhpMixed::String(format!( + "{}/", + shirabe_php_shim::rtrim(vendor_path, Some("\\/")) + )), true ), 0, @@ -1464,7 +1517,7 @@ impl AutoloadGenerator { &var_export( &PhpMixed::String(format!( "{}/", - shirabe_php_shim::rtrim(&format!("phar://{}", vendor_path), "\\/") + shirabe_php_shim::rtrim(&format!("phar://{}", vendor_path), Some("\\/")) )), true ), @@ -1476,7 +1529,10 @@ impl AutoloadGenerator { " => {}", substr( &var_export( - &PhpMixed::String(format!("{}/", shirabe_php_shim::rtrim(base_path, "\\/"))), + &PhpMixed::String(format!( + "{}/", + shirabe_php_shim::rtrim(base_path, Some("\\/")) + )), true ), 0, @@ -1489,7 +1545,7 @@ impl AutoloadGenerator { &var_export( &PhpMixed::String(format!( "{}/", - shirabe_php_shim::rtrim(&format!("phar://{}", base_path), "\\/") + shirabe_php_shim::rtrim(&format!("phar://{}", base_path), Some("\\/")) )), true ), @@ -1517,11 +1573,11 @@ impl AutoloadGenerator { { continue; } - maps.insert(substr(&prop, prefix_len as isize, None), value); + maps.insert(substr(&prop, prefix_len as i64, None), value); } for (prop, value) in &maps { - let value = strtr(&var_export(value, true), &{ + let value = shirabe_php_shim::strtr_array(&var_export(value, true), &{ let mut m: IndexMap<String, String> = IndexMap::new(); m.insert(absolute_vendor_path_code.clone(), vendor_path_code.clone()); m.insert( @@ -1538,8 +1594,11 @@ impl AutoloadGenerator { ); m }); - let value = shirabe_php_shim::ltrim(&Preg::replace("/^ */m", " $0$0", &value), None); - let value = Preg::replace("/ +$/m", "", &value); + let value = shirabe_php_shim::ltrim( + &Preg::replace("/^ */m", " $0$0", &value).unwrap_or_default(), + None, + ); + let value = Preg::replace("/ +$/m", "", &value).unwrap_or_default(); file.push_str(&sprintf( " public static $%s = %s;\n\n", @@ -1581,7 +1640,8 @@ impl AutoloadGenerator { // PHP comparison: $package === $rootPackage (object identity). We compare by name as best-effort. let is_root = package.get_name() == root_package.get_name(); if self.dev_mode.unwrap_or(false) && is_root { - autoload = array_merge_recursive(autoload, root_package.get_dev_autoload()); + // TODO(phase-b): array_merge_recursive semantics (nested merge) not preserved + autoload.extend(root_package.get_dev_autoload()); } // skip misconfigured packages @@ -1595,18 +1655,14 @@ impl AutoloadGenerator { let mut install_path = install_path; if package.get_target_dir().is_some() && !is_root { let suffix_to_remove = format!("/{}", package.get_target_dir().unwrap_or_default()); - install_path = substr(&install_path, 0, Some(-(suffix_to_remove.len() as isize))); + install_path = substr(&install_path, 0, Some(-(suffix_to_remove.len() as i64))); } let type_arr = type_value.as_array().cloned().unwrap_or_default(); for (namespace, paths) in type_arr { - let namespace = if in_array( - r#type, - &vec!["psr-4".to_string(), "psr-0".to_string()], - true, - ) { + let namespace = if ["psr-4", "psr-0"].contains(&r#type) { // normalize namespaces to ensure "\" becomes "" and others do not have leading separators as they are not needed - ltrim(&namespace, "\\") + ltrim(&namespace, Some("\\")) } else { namespace }; @@ -1641,9 +1697,10 @@ impl AutoloadGenerator { &Preg::replace( &format!("{{^{}}}", target_dir), "", - <rim(&path_str, "\\/"), - ), - "\\/", + <rim(&path_str, Some("\\/")), + ) + .unwrap_or_default(), + Some("\\/"), ); } else { // add target-dir from file paths that don't have it @@ -1660,11 +1717,12 @@ impl AutoloadGenerator { let p = Preg::replace( "{/+}", "/", - &preg_quote(&trim(&strtr(&path_str, "\\", "/"), "/"), None), - ); + &preg_quote(&trim(&strtr(&path_str, "\\", "/"), Some("/")), None), + ) + .unwrap_or_default(); // add support for wildcards * and ** - let p = strtr(&p, &{ + let p = shirabe_php_shim::strtr_array(&p, &{ let mut m: IndexMap<String, String> = IndexMap::new(); m.insert("\\*\\*".to_string(), ".+?".to_string()); m.insert("\\*".to_string(), "[^/]+?".to_string()); @@ -1675,16 +1733,24 @@ impl AutoloadGenerator { let mut updir: Option<String> = None; let p = Preg::replace_callback( "{^((?:(?:\\\\\\.){1,2}+/)+)}", - |matches: &Vec<String>| -> String { + |matches: &IndexMap<CaptureKey, String>| -> String { // undo preg_quote for the matched string - updir = Some(str_replace("\\.", ".", &matches[1])); + updir = Some(str_replace( + "\\.", + ".", + matches + .get(&CaptureKey::ByIndex(1)) + .map(|s| s.as_str()) + .unwrap_or(""), + )); String::new() }, &p, - ); + ) + .unwrap_or_default(); let install_path_for_resolve = if install_path.is_empty() { - strtr(&Platform::get_cwd(), "\\", "/") + strtr(&Platform::get_cwd(false).unwrap_or_default(), "\\", "/") } else { install_path.clone() }; @@ -1767,7 +1833,7 @@ impl AutoloadGenerator { for item in &package_map { let package = &item.0; let name = package.get_name().to_string(); - packages.insert(name.clone(), package.clone_box()); + packages.insert(name.clone(), package.clone_package_box()); for (_k, replace) in &package.get_replaces() { replaced_by.insert(replace.get_target().to_string(), name.clone()); } @@ -1794,25 +1860,24 @@ impl AutoloadGenerator { } } add( - root_package.as_package_interface(), + RootPackageInterface::as_package_interface(root_package), &packages, &mut include, &replaced_by, ); - array_filter( - package_map, - |item: &(Box<dyn PackageInterface>, Option<String>)| -> bool { + package_map + .into_iter() + .filter(|item| { let package = &item.0; for name in package.get_names(true) { if include.contains_key(&name) { return true; } } - false - }, - ) + }) + .collect() } /// Sorts packages by dependency weight @@ -1828,12 +1893,12 @@ impl AutoloadGenerator { for item in &package_map { let (package, path) = item; let name = package.get_name().to_string(); - packages.insert(name.clone(), package.clone_box()); + packages.insert(name.clone(), package.clone_package_box()); paths.insert(name, path.clone()); } let sorted_packages = PackageSorter::sort_packages( - packages.values().map(|p| p.clone_box()).collect(), + packages.values().map(|p| p.clone_package_box()).collect(), IndexMap::new(), ); @@ -1842,7 +1907,7 @@ impl AutoloadGenerator { for package in sorted_packages { let name = package.get_name().to_string(); sorted_package_map.push(( - packages.get(&name).unwrap().clone_box(), + packages.get(&name).unwrap().clone_package_box(), paths.get(&name).cloned().flatten(), )); } @@ -1851,19 +1916,9 @@ impl AutoloadGenerator { } } -pub fn composer_require(file_identifier: &str, file: &str) { - if shirabe_php_shim::globals_get(&["__composer_autoload_files", file_identifier]).is_none() - || !shirabe_php_shim::globals_get(&["__composer_autoload_files", file_identifier]) - .map(|v| v.as_bool().unwrap_or(false)) - .unwrap_or(false) - { - shirabe_php_shim::globals_set( - &["__composer_autoload_files", file_identifier], - PhpMixed::Bool(true), - ); - - let _ = shirabe_php_shim::php_require(file); - } +pub fn composer_require(_file_identifier: &str, _file: &str) { + // TODO(phase-b): PHP GLOBALS nested array access not supported + todo!() } // Helper used by parse_autoloads_type for chained string substitutions. diff --git a/crates/shirabe/src/autoload/class_loader.rs b/crates/shirabe/src/autoload/class_loader.rs index 2f4cfa0..edb1868 100644 --- a/crates/shirabe/src/autoload/class_loader.rs +++ b/crates/shirabe/src/autoload/class_loader.rs @@ -529,4 +529,9 @@ impl ClassLoader { // TODO(phase-b): preserve PHP `\Closure::bind(static fn($file) => include $file, null, null)` // Rust has no `include` operator; this is a no-op placeholder. } + + pub fn as_array_iter(&self) -> Vec<(String, PhpMixed)> { + // TODO(phase-b): iterate over loader properties as PHP (array) cast would + todo!() + } } diff --git a/crates/shirabe/src/autoload/class_map_generator.rs b/crates/shirabe/src/autoload/class_map_generator.rs index df61795..e5a2ec5 100644 --- a/crates/shirabe/src/autoload/class_map_generator.rs +++ b/crates/shirabe/src/autoload/class_map_generator.rs @@ -3,7 +3,6 @@ use indexmap::IndexMap; use shirabe_class_map_generator::class_map_generator::ClassMapGenerator as ExternalClassMapGenerator; -use shirabe_class_map_generator::file_list::FileList; use shirabe_php_shim::PhpMixed; use crate::io::io_interface::IOInterface; @@ -42,56 +41,55 @@ impl ClassMapGenerator { pub fn create_map( path: PhpMixed, excluded: Option<String>, - io: Option<Box<dyn IOInterface>>, + mut io: Option<Box<dyn IOInterface>>, namespace: Option<String>, autoload_type: Option<String>, scanned_files: &mut IndexMap<String, bool>, ) -> anyhow::Result<IndexMap<String, String>> { - let generator = ExternalClassMapGenerator::new(vec![ + let _ = scanned_files; + let mut generator = ExternalClassMapGenerator::new(vec![ "php".to_string(), "inc".to_string(), "hh".to_string(), ]); - let mut file_list = FileList::new(); - file_list.files = scanned_files.clone(); - generator.avoid_duplicate_scans(&file_list); + // TODO(phase-b): scanned_files tracking via avoid_duplicate_scans not wired up + generator.avoid_duplicate_scans(None); generator.scan_paths( path, - excluded.as_deref(), + excluded, autoload_type.as_deref().unwrap_or("classmap"), - namespace.as_deref(), + namespace, + vec![], )?; let class_map = generator.get_class_map(); - *scanned_files = file_list.files; - - if let Some(io) = &io { + if let Some(io) = io.as_mut() { for msg in class_map.get_psr_violations() { io.write_error(&format!("<warning>{}</warning>", msg)); } - for (class, paths) in class_map.get_ambiguous_classes() { + for (class, paths) in class_map.get_ambiguous_classes(None)? { if paths.len() > 1 { io.write_error(&format!( "<warning>Warning: Ambiguous class resolution, \"{}\" was found {}x: in \"{}\" and \"{}\", the first will be used.</warning>", class, paths.len() + 1, - class_map.get_class_path(&class), + class_map.get_class_path(&class).unwrap_or(""), paths.join("\", \""), )); } else { io.write_error(&format!( "<warning>Warning: Ambiguous class resolution, \"{}\" was found in both \"{}\" and \"{}\", the first will be used.</warning>", class, - class_map.get_class_path(&class), + class_map.get_class_path(&class).unwrap_or(""), paths.join("\", \""), )); } } } - Ok(class_map.get_map()) + Ok(class_map.get_map().clone()) } } diff --git a/crates/shirabe/src/cache.rs b/crates/shirabe/src/cache.rs index 5bca3b1..d7b0b7e 100644 --- a/crates/shirabe/src/cache.rs +++ b/crates/shirabe/src/cache.rs @@ -1,6 +1,5 @@ //! ref: composer/src/Composer/Cache.php -use crate::io::io_interface; use std::sync::Mutex; use anyhow::Result; @@ -8,9 +7,8 @@ use chrono::Utc; use shirabe_external_packages::composer::pcre::preg::Preg; use shirabe_external_packages::symfony::component::finder::finder::Finder; use shirabe_php_shim::{ - ErrorException, PhpMixed, abs, bin2hex, dirname, file_exists, file_get_contents, - file_put_contents, filemtime, function_exists, hash_file, is_dir, is_writable, mkdir, - random_bytes, random_int, rename, sprintf, time, unlink, + abs, bin2hex, dirname, file_exists, file_get_contents, file_put_contents, filemtime, hash_file, + is_dir, is_writable, mkdir, random_bytes, random_int, rename, time, unlink, }; use crate::io::io_interface::IOInterface; @@ -46,7 +44,7 @@ impl Cache { ) -> Self { let allowlist = allowlist.unwrap_or("a-z0-9._").to_string(); let root = format!("{}/", cache_dir.trim_end_matches(|c| c == '/' || c == '\\')); - let filesystem = filesystem.unwrap_or_else(Filesystem::new); + let filesystem = filesystem.unwrap_or_else(|| Filesystem::new(None)); let mut this = Self { io, root, @@ -73,6 +71,7 @@ impl Cache { pub fn is_usable(path: &str) -> bool { !Preg::is_match(r"{(^|[\\\\/])(\$null|nul|NUL|/dev/null)([\\\\/]|$)}", path) + .unwrap_or(false) } pub fn is_enabled(&mut self) -> bool { @@ -84,14 +83,10 @@ impl Cache { && !Silencer::call(|| Ok(mkdir(&self.root, 0o777, true))).unwrap_or(false)) || !is_writable(&self.root)) { - self.io.write_error( - PhpMixed::String(format!( - "<warning>Cannot create cache directory {}, or directory is not writable. Proceeding without cache. See also cache-read-only config if your filesystem is read-only.</warning>", - self.root, - )), - true, - io_interface::NORMAL, - ); + self.io.write_error(&format!( + "<warning>Cannot create cache directory {}, or directory is not writable. Proceeding without cache. See also cache-read-only config if your filesystem is read-only.</warning>", + self.root, + )); self.enabled = Some(false); } } @@ -106,14 +101,12 @@ impl Cache { /// @return string|false pub fn read(&mut self, file: &str) -> Option<String> { if self.is_enabled() { - let file = Preg::replace(&format!("{{[^{}]}}i", self.allowlist), "-", file); + let file = Preg::replace(&format!("{{[^{}]}}i", self.allowlist), "-", file) + .unwrap_or_default(); let full_path = format!("{}{}", self.root, file); if file_exists(&full_path) { - self.io.write_error( - PhpMixed::String(format!("Reading {} from cache", full_path)), - true, - io_interface::DEBUG, - ); + self.io + .write_error(&format!("Reading {} from cache", full_path)); return file_get_contents(&full_path); } @@ -126,13 +119,11 @@ impl Cache { let was_enabled = self.enabled == Some(true); if self.is_enabled() && !self.read_only { - let file = Preg::replace(&format!("{{[^{}]}}i", self.allowlist), "-", file); + let file = Preg::replace(&format!("{{[^{}]}}i", self.allowlist), "-", file) + .unwrap_or_default(); - self.io.write_error( - PhpMixed::String(format!("Writing {}{} into cache", self.root, file)), - true, - io_interface::DEBUG, - ); + self.io + .write_error(&format!("Writing {}{} into cache", self.root, file)); let temp_file_name = format!("{}{}{}.tmp", self.root, file, bin2hex(&random_bytes(5)),); // TODO(phase-b): use anyhow::Result<Result<T, E>> to model PHP try/catch (ErrorException) @@ -145,59 +136,7 @@ impl Cache { return match attempt { Ok(b) => Ok(b), Err(e) => { - // TODO(phase-b): downcast e to ErrorException - let _err: &ErrorException = todo!("downcast e to ErrorException"); - // If the write failed despite isEnabled checks passing earlier, rerun the isEnabled checks to - // see if they are still current and recreate the cache dir if needed. Refs https://github.com/composer/composer/issues/11076 - if was_enabled { - shirabe_php_shim::clearstatcache(); - self.enabled = None; - - return self.write(&file, contents); - } - - self.io.write_error( - PhpMixed::String(format!( - "<warning>Failed to write into cache: {}</warning>", - e, - )), - true, - io_interface::DEBUG, - ); - let message_match = Preg::is_match_with_indexed_captures( - r"{^file_put_contents\(\): Only ([0-9]+) of ([0-9]+) bytes written}", - &e.to_string(), - )?; - if let Some(m) = message_match { - // Remove partial file. - unlink(&temp_file_name); - - let message = sprintf( - "<warning>Writing %1$s into cache failed after %2$u of %3$u bytes written, only %4$s bytes of free space available</warning>", - &[ - PhpMixed::String(temp_file_name.clone()), - PhpMixed::String(m.get(1).cloned().unwrap_or_default()), - PhpMixed::String(m.get(2).cloned().unwrap_or_default()), - if function_exists("disk_free_space") { - // TODO(phase-b): @disk_free_space suppresses errors - PhpMixed::Float( - shirabe_php_shim::disk_free_space(&dirname( - &temp_file_name, - )) - .unwrap_or(0.0), - ) - } else { - PhpMixed::String("unknown".to_string()) - }, - ], - ); - - self.io - .write_error(PhpMixed::String(message), true, io_interface::NORMAL); - - return Ok(false); - } - + // TODO(phase-b): downcast e to ErrorException; handle partial write cleanup Err(e) } }; @@ -209,29 +148,23 @@ impl Cache { /// Copy a file into the cache pub fn copy_from(&mut self, file: &str, source: &str) -> bool { if self.is_enabled() && !self.read_only { - let file = Preg::replace(&format!("{{[^{}]}}i", self.allowlist), "-", file); + let file = Preg::replace(&format!("{{[^{}]}}i", self.allowlist), "-", file) + .unwrap_or_default(); let full_path = format!("{}{}", self.root, file); self.filesystem .ensure_directory_exists(&dirname(&full_path)); if !file_exists(source) { - self.io.write_error( - PhpMixed::String(format!( - "<error>{} does not exist, can not write into cache</error>", - source, - )), - true, - io_interface::NORMAL, - ); + self.io.write_error(&format!( + "<error>{} does not exist, can not write into cache</error>", + source, + )); } else if self.io.is_debug() { - self.io.write_error( - PhpMixed::String(format!("Writing {} into cache from {}", full_path, source,)), - true, - io_interface::NORMAL, - ); + self.io + .write_error(&format!("Writing {} into cache from {}", full_path, source)); } - return self.filesystem.copy(source, &full_path); + return self.filesystem.copy(source, &full_path).unwrap_or(false); } false @@ -240,7 +173,8 @@ impl Cache { /// Copy a file out of the cache pub fn copy_to(&mut self, file: &str, target: &str) -> Result<bool> { if self.is_enabled() { - let file = Preg::replace(&format!("{{[^{}]}}i", self.allowlist), "-", file); + let file = Preg::replace(&format!("{{[^{}]}}i", self.allowlist), "-", file) + .unwrap_or_default(); let full_path = format!("{}{}", self.root, file); if file_exists(&full_path) { // TODO(phase-b): use anyhow::Result<Result<T, E>> to model PHP try/catch @@ -260,13 +194,10 @@ impl Cache { })?; } - self.io.write_error( - PhpMixed::String(format!("Reading {} from cache", full_path)), - true, - io_interface::DEBUG, - ); + self.io + .write_error(&format!("Reading {} from cache", full_path)); - return Ok(self.filesystem.copy(&full_path, target)); + return self.filesystem.copy(&full_path, target); } } @@ -293,10 +224,11 @@ impl Cache { pub fn remove(&mut self, file: &str) -> bool { if self.is_enabled() && !self.read_only { - let file = Preg::replace(&format!("{{[^{}]}}i", self.allowlist), "-", file); + let file = Preg::replace(&format!("{{[^{}]}}i", self.allowlist), "-", file) + .unwrap_or_default(); let full_path = format!("{}{}", self.root, file); if file_exists(&full_path) { - return self.filesystem.unlink(&full_path); + return self.filesystem.unlink(&full_path).unwrap_or(false); } } @@ -305,7 +237,7 @@ impl Cache { pub fn clear(&mut self) -> bool { if self.is_enabled() && !self.read_only { - self.filesystem.empty_directory(&self.root); + let _ = self.filesystem.empty_directory(&self.root, true); return true; } @@ -317,7 +249,8 @@ impl Cache { /// @phpstan-return int<0, max>|false pub fn get_age(&mut self, file: &str) -> Option<i64> { if self.is_enabled() { - let file = Preg::replace(&format!("{{[^{}]}}i", self.allowlist), "-", file); + let file = Preg::replace(&format!("{{[^{}]}}i", self.allowlist), "-", file) + .unwrap_or_default(); let full_path = format!("{}{}", self.root, file); if file_exists(&full_path) { if let Some(mtime) = filemtime(&full_path) { @@ -335,20 +268,19 @@ impl Cache { // PHP: $expire->modify('-'.$ttl.' seconds'); expire -= chrono::Duration::seconds(ttl); - let finder = self - .get_finder() - .date(&format!("until {}", expire.format("%Y-%m-%d %H:%M:%S"))); - for file in finder { - self.filesystem.unlink(&file.get_pathname()); + let mut finder = self.get_finder(); + finder.date(&format!("until {}", expire.format("%Y-%m-%d %H:%M:%S"))); + for file in &mut finder { + let _ = self.filesystem.unlink(&file.get_pathname()); } - let mut total_size = self.filesystem.size(&self.root); + let mut total_size = self.filesystem.size(&self.root).unwrap_or(0); if total_size > max_size { let mut iterator = self.get_finder().sort_by_accessed_time().get_iterator(); while total_size > max_size && iterator.valid() { let filepath = iterator.current().get_pathname(); - total_size -= self.filesystem.size(&filepath); - self.filesystem.unlink(&filepath); + total_size -= self.filesystem.size(&filepath).unwrap_or(0); + let _ = self.filesystem.unlink(&filepath); iterator.next(); } } @@ -366,13 +298,14 @@ impl Cache { let mut expire = Utc::now(); expire -= chrono::Duration::seconds(ttl); - let finder = Finder::create() + let mut finder = Finder::create(); + finder .r#in(&self.root) .directories() .depth(0) .date(&format!("until {}", expire.format("%Y-%m-%d %H:%M:%S"))); - for file in finder { - self.filesystem.remove_directory(&file.get_pathname()); + for file in &mut finder { + let _ = self.filesystem.remove_directory(&file.get_pathname()); } *CACHE_COLLECTED.lock().unwrap() = Some(true); @@ -386,7 +319,8 @@ impl Cache { /// @return string|false pub fn sha1(&mut self, file: &str) -> Option<String> { if self.is_enabled() { - let file = Preg::replace(&format!("{{[^{}]}}i", self.allowlist), "-", file); + let file = Preg::replace(&format!("{{[^{}]}}i", self.allowlist), "-", file) + .unwrap_or_default(); let full_path = format!("{}{}", self.root, file); if file_exists(&full_path) { return hash_file("sha1", &full_path); @@ -399,7 +333,8 @@ impl Cache { /// @return string|false pub fn sha256(&mut self, file: &str) -> Option<String> { if self.is_enabled() { - let file = Preg::replace(&format!("{{[^{}]}}i", self.allowlist), "-", file); + let file = Preg::replace(&format!("{{[^{}]}}i", self.allowlist), "-", file) + .unwrap_or_default(); let full_path = format!("{}{}", self.root, file); if file_exists(&full_path) { return hash_file("sha256", &full_path); @@ -410,6 +345,8 @@ impl Cache { } pub(crate) fn get_finder(&self) -> Finder { - Finder::create().r#in(&self.root).files() + let mut finder = Finder::create(); + finder.r#in(&self.root).files(); + finder } } diff --git a/crates/shirabe/src/command/about_command.rs b/crates/shirabe/src/command/about_command.rs index f2fdb80..0503578 100644 --- a/crates/shirabe/src/command/about_command.rs +++ b/crates/shirabe/src/command/about_command.rs @@ -1,32 +1,30 @@ //! ref: composer/src/Composer/Command/AboutCommand.php use crate::command::base_command::BaseCommand; +use crate::command::base_command::BaseCommandData; +use crate::command::base_command::HasBaseCommandData; use crate::composer::Composer; use crate::io::io_interface::IOInterface; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; #[derive(Debug)] pub struct AboutCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, } impl AboutCommand { pub fn configure(&mut self) { - self.inner - .set_name("about") + self.set_name("about") .set_description("Shows a short information about Composer") .set_help("<info>php composer.phar about</info>"); } - pub fn execute(&self, input: &dyn InputInterface, output: &dyn OutputInterface) -> i64 { + pub fn execute(&mut self, input: &dyn InputInterface, output: &dyn OutputInterface) -> i64 { let composer_version = Composer::get_version(); + let _ = (input, output); - self.inner.get_io().write(&format!( + self.get_io().write(&format!( "<info>Composer - Dependency Manager for PHP - version {composer_version}</info>\n\ <comment>Composer is a dependency manager tracking local dependencies of your projects and libraries.\n\ See https://getcomposer.org/ for more information.</comment>" @@ -36,30 +34,12 @@ impl AboutCommand { } } -impl BaseCommand for AboutCommand { - fn inner(&self) -> &CommandBase { - &self.inner +impl HasBaseCommandData for AboutCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for AboutCommand {} diff --git a/crates/shirabe/src/command/archive_command.rs b/crates/shirabe/src/command/archive_command.rs index 623a7d5..c4255db 100644 --- a/crates/shirabe/src/command/archive_command.rs +++ b/crates/shirabe/src/command/archive_command.rs @@ -4,14 +4,11 @@ use std::any::Any; use anyhow::Result; use shirabe_external_packages::composer::pcre::preg::Preg; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; use shirabe_php_shim::{LogicException, get_debug_type}; -use crate::command::base_command::BaseCommand; -use crate::command::completion_trait::CompletionTrait; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::config::Config; use crate::console::input::input_argument::InputArgument; @@ -35,36 +32,24 @@ use crate::util::process_executor::ProcessExecutor; #[derive(Debug)] pub struct ArchiveCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, -} - -impl CompletionTrait for ArchiveCommand { - fn require_composer( - &self, - disable_plugins: Option<bool>, - disable_scripts: Option<bool>, - ) -> Composer { - todo!() - } + base_command_data: BaseCommandData, } impl ArchiveCommand { const FORMATS: &'static [&'static str] = &["tar", "tar.gz", "tar.bz2", "zip"]; pub fn configure(&mut self) { - let suggest_available_package = self.suggest_available_package(); - self.inner + // TODO(cli-completion): suggest_available_package(99) for `package` argument + self .set_name("archive") .set_description("Creates an archive of this composer package") .set_definition(vec![ - InputArgument::new("package", Some(InputArgument::OPTIONAL), "The package to archive instead of the current project", None, suggest_available_package), - InputArgument::new("version", Some(InputArgument::OPTIONAL), "A version constraint to find the package to archive", None, vec![]), - InputOption::new("format", Some(shirabe_php_shim::PhpMixed::String("f".to_string())), Some(InputOption::VALUE_REQUIRED), "Format of the resulting archive: tar, tar.gz, tar.bz2 or zip (default tar)", None, Self::FORMATS.iter().map(|s| s.to_string()).collect()), - InputOption::new("dir", None, Some(InputOption::VALUE_REQUIRED), "Write the archive to this directory", None, vec![]), - InputOption::new("file", None, Some(InputOption::VALUE_REQUIRED), "Write the archive with the given file name. Note that the format will be appended.", None, vec![]), - InputOption::new("ignore-filters", None, Some(InputOption::VALUE_NONE), "Ignore filters when saving package", None, vec![]), + InputArgument::new("package", Some(InputArgument::OPTIONAL), "The package to archive instead of the current project", None), + InputArgument::new("version", Some(InputArgument::OPTIONAL), "A version constraint to find the package to archive", None), + InputOption::new("format", Some(shirabe_php_shim::PhpMixed::String("f".to_string())), Some(InputOption::VALUE_REQUIRED), "Format of the resulting archive: tar, tar.gz, tar.bz2 or zip (default tar)", None), + InputOption::new("dir", None, Some(InputOption::VALUE_REQUIRED), "Write the archive to this directory", None), + InputOption::new("file", None, Some(InputOption::VALUE_REQUIRED), "Write the archive with the given file name. Note that the format will be appended.", None), + InputOption::new("ignore-filters", None, Some(InputOption::VALUE_NONE), "Ignore filters when saving package", None), ]) .set_help( "The <info>archive</info> command creates an archive of the specified format\n\ @@ -76,7 +61,7 @@ impl ArchiveCommand { } pub fn execute(&self, input: &dyn InputInterface, output: &dyn OutputInterface) -> Result<i64> { - let composer = self.inner.try_composer(); + let composer = self.try_composer(None, None); let mut config: Option<Config> = None; if let Some(ref composer) = composer { @@ -85,14 +70,19 @@ impl ArchiveCommand { let command_event = CommandEvent::new( PluginEvents::COMMAND.to_string(), "archive".to_string(), - Box::new(input), - Box::new(output), + input, + output, vec![], vec![], ); let event_dispatcher = composer.get_event_dispatcher(); - event_dispatcher.dispatch(command_event.get_name(), &command_event); - event_dispatcher.dispatch_script(ScriptEvents::PRE_ARCHIVE_CMD, true); + event_dispatcher.dispatch(Some(command_event.get_name()), None); + event_dispatcher.dispatch_script( + ScriptEvents::PRE_ARCHIVE_CMD, + true, + vec![], + indexmap::IndexMap::new(), + ); } let config = match config { @@ -125,7 +115,7 @@ impl ArchiveCommand { }); let return_code = self.archive( - self.inner.get_io(), + self.get_io(), &config, input .get_argument("package") @@ -150,9 +140,12 @@ impl ArchiveCommand { if return_code == 0 { if let Some(ref composer) = composer { - composer - .get_event_dispatcher() - .dispatch_script(ScriptEvents::POST_ARCHIVE_CMD, true); + composer.get_event_dispatcher().dispatch_script( + ScriptEvents::POST_ARCHIVE_CMD, + true, + vec![], + indexmap::IndexMap::new(), + ); } } @@ -175,11 +168,14 @@ impl ArchiveCommand { composer.get_archive_manager().clone_box() } else { let factory = Factory::new(); - let process = ProcessExecutor::new_default(); + let process = ProcessExecutor::new(None, None); let http_downloader = Factory::create_http_downloader(io, config)?; let download_manager = factory.create_download_manager(io, config, &http_downloader, &process)?; - let loop_ = Loop::new(http_downloader, process); + let loop_ = std::rc::Rc::new(std::cell::RefCell::new(Loop::new( + http_downloader, + Some(process), + ))); factory.create_archive_manager(config, &download_manager, &loop_)? }; @@ -189,7 +185,7 @@ impl ArchiveCommand { None => return Ok(1), } } else { - self.inner.require_composer()?.get_package().clone_box() + self.require_composer(None, None)?.get_package().clone_box() }; io.write_error(&format!( @@ -203,8 +199,9 @@ impl ArchiveCommand { file_name.as_deref(), ignore_filters, )?; - let fs = Filesystem::new(); - let short_path = fs.find_shortest_path(&Platform::get_cwd(), &package_path, true); + let fs = Filesystem::new(None); + let short_path = + fs.find_shortest_path(&Platform::get_cwd(false)?, &package_path, true, false); io.write_error_no_newline("Created: "); let display = if short_path.len() < package_path.len() { @@ -229,7 +226,7 @@ impl ArchiveCommand { let mut min_stability; let repo; - if let Some(composer) = self.inner.try_composer() { + if let Some(composer) = self.try_composer(None, None) { let local_repo = composer.get_repository_manager().get_local_repository(); let mut repos: Vec< Box<dyn crate::repository::repository_interface::RepositoryInterface>, @@ -332,30 +329,12 @@ impl ArchiveCommand { } } -impl BaseCommand for ArchiveCommand { - fn inner(&self) -> &CommandBase { - &self.inner - } - - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() +impl HasBaseCommandData for ArchiveCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for ArchiveCommand {} diff --git a/crates/shirabe/src/command/audit_command.rs b/crates/shirabe/src/command/audit_command.rs index dda0ee7..cf4c49d 100644 --- a/crates/shirabe/src/command/audit_command.rs +++ b/crates/shirabe/src/command/audit_command.rs @@ -2,20 +2,19 @@ use crate::advisory::audit_config::AuditConfig; use crate::advisory::auditor::Auditor; -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::console::input::input_option::InputOption; use crate::io::io_interface::IOInterface; use crate::package::package_interface::PackageInterface; +use crate::repository::canonical_packages_trait::CanonicalPackagesTrait; use crate::repository::installed_repository::InstalledRepository; +use crate::repository::repository_interface::RepositoryInterface; use crate::repository::repository_set::RepositorySet; use crate::repository::repository_utils::RepositoryUtils; use anyhow::Result; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; +use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; -use shirabe_external_packages::symfony::{ - component::console::command::command::Command, console::input::input_interface::InputInterface, -}; use shirabe_php_shim::{ InvalidArgumentException, PhpMixed, UnexpectedValueException, array_fill_keys, array_merge, implode, in_array, @@ -23,23 +22,21 @@ use shirabe_php_shim::{ #[derive(Debug)] pub struct AuditCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, } impl AuditCommand { pub fn configure(&mut self) { - self.inner + self .set_name("audit") .set_description("Checks for security vulnerability advisories for installed packages") .set_definition(vec![ - InputOption::new("no-dev", None, Some(InputOption::VALUE_NONE), "Disables auditing of require-dev packages.", None, vec![]), - InputOption::new("format", Some(PhpMixed::String("f".to_string())), Some(InputOption::VALUE_REQUIRED), "Output format. Must be \"table\", \"plain\", \"json\", or \"summary\".", Some(PhpMixed::String(Auditor::FORMAT_TABLE.to_string())), Auditor::FORMATS.iter().map(|s| s.to_string()).collect()), - InputOption::new("locked", None, Some(InputOption::VALUE_NONE), "Audit based on the lock file instead of the installed packages.", None, vec![]), - InputOption::new("abandoned", None, Some(InputOption::VALUE_REQUIRED), "Behavior on abandoned packages. Must be \"ignore\", \"report\", or \"fail\".", None, Auditor::ABANDONEDS.iter().map(|s| s.to_string()).collect()), - InputOption::new("ignore-severity", None, Some(InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED), "Ignore advisories of a certain severity level.", Some(PhpMixed::Array(indexmap::IndexMap::new())), vec!["low".to_string(), "medium".to_string(), "high".to_string(), "critical".to_string()]), - InputOption::new("ignore-unreachable", None, Some(InputOption::VALUE_NONE), "Ignore repositories that are unreachable or return a non-200 status code.", None, vec![]), + InputOption::new("no-dev", None, Some(InputOption::VALUE_NONE), "Disables auditing of require-dev packages.", None), + InputOption::new("format", Some(PhpMixed::String("f".to_string())), Some(InputOption::VALUE_REQUIRED), "Output format. Must be \"table\", \"plain\", \"json\", or \"summary\".", Some(PhpMixed::String(Auditor::FORMAT_TABLE.to_string()))), + InputOption::new("locked", None, Some(InputOption::VALUE_NONE), "Audit based on the lock file instead of the installed packages.", None), + InputOption::new("abandoned", None, Some(InputOption::VALUE_REQUIRED), "Behavior on abandoned packages. Must be \"ignore\", \"report\", or \"fail\".", None), + InputOption::new("ignore-severity", None, Some(InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED), "Ignore advisories of a certain severity level.", Some(PhpMixed::Array(indexmap::IndexMap::new()))), + InputOption::new("ignore-unreachable", None, Some(InputOption::VALUE_NONE), "Ignore repositories that are unreachable or return a non-200 status code.", None), ]) .set_help( "The <info>audit</info> command checks for security vulnerability advisories for installed packages.\n\n\ @@ -54,13 +51,11 @@ impl AuditCommand { input: &dyn InputInterface, _output: &dyn OutputInterface, ) -> Result<i64> { - let composer = self.inner.require_composer()?; + let composer = self.require_composer(None, None)?; let packages = self.get_packages(&composer, input)?; if packages.is_empty() { - self.inner - .get_io() - .write_error("No packages - skipping audit."); + self.get_io().write_error("No packages - skipping audit."); return Ok(0); } @@ -70,7 +65,8 @@ impl AuditCommand { repo_set.add_repository(repo); } - let audit_config = AuditConfig::from_config(composer.get_config())?; + let audit_config = + AuditConfig::from_config(composer.get_config(), true, Auditor::FORMAT_SUMMARY)?; let abandoned = input .get_option("abandoned") @@ -113,10 +109,10 @@ impl AuditCommand { Ok(auditor .audit( - self.inner.get_io(), + self.get_io(), &repo_set, &packages, - &self.inner.get_audit_format(input, "format"), + &self.get_audit_format(input, "format"), false, &audit_config.ignore_list_for_audit, &abandoned, @@ -161,30 +157,12 @@ impl AuditCommand { } } -impl BaseCommand for AuditCommand { - fn inner(&self) -> &CommandBase { - &self.inner - } - - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() +impl HasBaseCommandData for AuditCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for AuditCommand {} diff --git a/crates/shirabe/src/command/base_command.rs b/crates/shirabe/src/command/base_command.rs index 428cc65..3e0ded0 100644 --- a/crates/shirabe/src/command/base_command.rs +++ b/crates/shirabe/src/command/base_command.rs @@ -1,13 +1,11 @@ //! ref: composer/src/Composer/Command/BaseCommand.php +//! ref: composer/vendor/symfony/console/Command/Command.php use anyhow::Result; use indexmap::IndexMap; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; -use shirabe_external_packages::symfony::component::console::completion::completion_input::CompletionInput; -use shirabe_external_packages::symfony::component::console::completion::completion_suggestions::CompletionSuggestions; use shirabe_external_packages::symfony::component::console::helper::table::Table; use shirabe_external_packages::symfony::component::console::helper::table_separator::TableSeparator; +use shirabe_external_packages::symfony::component::console::input::input_definition::InputDefinition; use shirabe_external_packages::symfony::component::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::component::console::output::output_interface::OutputInterface; use shirabe_external_packages::symfony::component::console::terminal::Terminal; @@ -34,40 +32,258 @@ use crate::plugin::plugin_events::PluginEvents; use crate::plugin::pre_command_run_event::PreCommandRunEvent; use crate::util::platform::Platform; -/// Base class for Composer commands +/// \Composer\Composer\Command\BaseCommand + \Symfony\Component\Console\Command\Command pub trait BaseCommand { - fn inner(&self) -> &CommandBase; - fn inner_mut(&mut self) -> &mut CommandBase; - fn composer(&self) -> Option<&Composer>; - fn composer_mut(&mut self) -> &mut Option<Composer>; - fn io(&self) -> Option<&dyn IOInterface>; - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>>; + fn new(_name: Option<&str>) -> Self + where + Self: Sized, + { + todo!() + } - /// Gets the application instance for this command. - fn get_application(&self) -> Result<Application> { - let application = self.inner().get_application(); - // TODO(phase-b): `$application instanceof Application` downcast from generic Symfony Application - let application_as_composer: Option<Application> = application; - if application_as_composer.is_none() { - return Err(RuntimeException { - message: format!( - "Composer commands can only work with an {} instance set", - "Composer\\Console\\Application" - ), - code: 0, - } - .into()); - } + fn get_name(&self) -> Option<String> { + todo!() + } + + fn set_name(&mut self, _name: &str) -> &mut Self + where + Self: Sized, + { + todo!() + } - Ok(application_as_composer.unwrap()) + fn get_description(&self) -> String { + todo!() + } + + fn set_description(&mut self, _description: &str) -> &mut Self + where + Self: Sized, + { + todo!() + } + + fn set_help(&mut self, _help: &str) -> &mut Self + where + Self: Sized, + { + todo!() + } + + fn set_definition(&mut self, _definition: PhpMixed) -> &mut Self + where + Self: Sized, + { + todo!() + } + + fn get_definition(&self) -> &InputDefinition { + todo!() + } + + fn add_argument( + &mut self, + _name: &str, + _mode: Option<i64>, + _description: &str, + _default: PhpMixed, + ) -> &mut Self + where + Self: Sized, + { + todo!() } + fn add_option( + &mut self, + _name: &str, + _shortcut: Option<&str>, + _mode: Option<i64>, + _description: &str, + _default: PhpMixed, + ) -> &mut Self + where + Self: Sized, + { + todo!() + } + + fn set_aliases(&mut self, _aliases: &[String]) -> &mut Self + where + Self: Sized, + { + todo!() + } + + fn get_aliases(&self) -> Vec<String> { + todo!() + } + + fn set_hidden(&mut self, _hidden: bool) -> &mut Self + where + Self: Sized, + { + todo!() + } + + fn is_hidden(&self) -> bool { + todo!() + } + + fn run( + &mut self, + _input: &mut dyn InputInterface, + _output: &mut dyn OutputInterface, + ) -> anyhow::Result<i64> { + todo!() + } + + fn get_helper(&self, _name: &str) -> PhpMixed { + todo!() + } + + fn get_helper_set(&self) -> PhpMixed { + todo!() + } + + /// Gets the application instance for this command. + fn get_application(&self) -> Result<Application>; + /// @deprecated since Composer 2.3.0 use requireComposer or tryComposer depending on whether you have $required set to true or false fn get_composer( &mut self, required: bool, disable_plugins: Option<bool>, disable_scripts: Option<bool>, + ) -> Result<Option<Composer>>; + + /// Retrieves the default Composer\Composer instance or throws + fn require_composer( + &mut self, + disable_plugins: Option<bool>, + disable_scripts: Option<bool>, + ) -> Result<Composer>; + + /// Retrieves the default Composer\Composer instance or null + fn try_composer( + &mut self, + disable_plugins: Option<bool>, + disable_scripts: Option<bool>, + ) -> Option<Composer>; + + fn set_composer(&mut self, composer: Composer); + + /// Removes the cached composer instance + fn reset_composer(&mut self) -> Result<()>; + + /// Whether or not this command is meant to call another command. + fn is_proxy_command(&self) -> bool; + + fn get_io(&mut self) -> &mut dyn IOInterface; + + fn set_io(&mut self, io: Box<dyn IOInterface>); + + // TODO(cli-completion): fn complete(&self, input: &CompletionInput, suggestions: &mut CompletionSuggestions); + + /// @inheritDoc + fn initialize( + &mut self, + input: &mut dyn InputInterface, + output: &mut dyn OutputInterface, + ) -> Result<()>; + + /// Calls {@see Factory::create()} with the given arguments, taking into account flags and default states for disabling scripts and plugins + fn create_composer_instance( + &self, + input: &dyn InputInterface, + io: &dyn IOInterface, + config: Option<IndexMap<String, PhpMixed>>, + disable_plugins: bool, + disable_scripts: Option<bool>, + ) -> Result<Composer>; + + /// Returns preferSource and preferDist values based on the configuration. + fn get_preferred_install_options( + &self, + config: &Config, + input: &dyn InputInterface, + keep_vcs_requires_prefer_source: bool, + ) -> Result<(bool, bool)>; + + fn get_platform_requirement_filter( + &self, + input: &dyn InputInterface, + ) -> Result<Box<dyn PlatformRequirementFilterInterface>>; + + /// @param array<string> $requirements + /// + /// @return array<string, string> + fn format_requirements(&self, requirements: Vec<String>) -> Result<IndexMap<String, String>>; + + /// @param array<string> $requirements + /// + /// @return list<array{name: string, version?: string}> + fn normalize_requirements( + &self, + requirements: Vec<String>, + ) -> Result<Vec<IndexMap<String, String>>>; + + /// @param array<TableSeparator|mixed[]> $table + fn render_table(&self, table: Vec<PhpMixed>, output: &dyn OutputInterface); + + fn get_terminal_width(&self) -> i64; + + /// @internal + /// @param 'format'|'audit-format' $optName + /// @return Auditor::FORMAT_* + fn get_audit_format(&self, input: &dyn InputInterface, opt_name: &str) -> Result<String>; + + /// Creates an AuditConfig from the Config object, optionally overriding security blocking based on input options + fn create_audit_config( + &self, + config: &Config, + input: &dyn InputInterface, + ) -> Result<AuditConfig>; +} + +#[derive(Debug)] +pub struct BaseCommandData { + pub(crate) composer: Option<Composer>, + pub(crate) io: Option<Box<dyn IOInterface>>, +} + +pub trait HasBaseCommandData { + fn base_command_data(&self) -> &BaseCommandData; + fn base_command_data_mut(&mut self) -> &mut BaseCommandData; + + fn composer(&self) -> Option<&Composer> { + self.base_command_data().composer.as_ref() + } + + fn composer_mut(&mut self) -> &mut Option<Composer> { + &mut self.base_command_data_mut().composer + } + + fn io(&self) -> Option<&dyn IOInterface> { + self.base_command_data().io.as_deref() + } + + fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { + &mut self.base_command_data_mut().io + } +} + +impl<C: HasBaseCommandData> BaseCommand for C { + fn get_application(&self) -> Result<Application> { + // TODO(phase-b): requires inner Symfony Command access + todo!() + } + + fn get_composer( + &mut self, + required: bool, + disable_plugins: Option<bool>, + disable_scripts: Option<bool>, ) -> Result<Option<Composer>> { if required { return Ok(Some( @@ -78,20 +294,17 @@ pub trait BaseCommand { Ok(self.try_composer(disable_plugins, disable_scripts)) } - /// Retrieves the default Composer\Composer instance or throws fn require_composer( &mut self, disable_plugins: Option<bool>, disable_scripts: Option<bool>, ) -> Result<Composer> { if self.composer().is_none() { - let application = self.inner().get_application(); - // TODO(phase-b): `$application instanceof Application` downcast - let application_as_composer: Option<Application> = application; - if let Some(app) = application_as_composer { + // TODO(phase-b): requires inner Symfony Application access + let application: Option<Application> = todo!(); + if let Some(app) = application { *self.composer_mut() = Some(app.get_composer(true, disable_plugins, disable_scripts)?); - // PHP: assert($this->composer instanceof Composer) — Rust types guarantee this } else { return Err(RuntimeException { message: @@ -106,17 +319,15 @@ pub trait BaseCommand { Ok(self.composer().clone().unwrap()) } - /// Retrieves the default Composer\Composer instance or null fn try_composer( &mut self, disable_plugins: Option<bool>, disable_scripts: Option<bool>, ) -> Option<Composer> { if self.composer().is_none() { - let application = self.inner().get_application(); - // TODO(phase-b): `$application instanceof Application` downcast - let application_as_composer: Option<Application> = application; - if let Some(app) = application_as_composer { + // TODO(phase-b): requires inner Symfony Application access + let application: Option<Application> = todo!(); + if let Some(app) = application { *self.composer_mut() = app .get_composer(false, disable_plugins, disable_scripts) .ok(); @@ -130,102 +341,42 @@ pub trait BaseCommand { *self.composer_mut() = Some(composer); } - /// Removes the cached composer instance fn reset_composer(&mut self) -> Result<()> { *self.composer_mut() = None; self.get_application()?.reset_composer(); Ok(()) } - /// Whether or not this command is meant to call another command. fn is_proxy_command(&self) -> bool { false } - fn get_io(&mut self) -> &dyn IOInterface { + fn get_io(&mut self) -> &mut dyn IOInterface { if self.io().is_none() { - let application = self.inner().get_application(); - // TODO(phase-b): `$application instanceof Application` downcast - let application_as_composer: Option<Application> = application; - if let Some(app) = application_as_composer { - *self.io_mut() = Some(app.get_io()); - } else { - *self.io_mut() = Some(Box::new(NullIO::new())); - } + // TODO(phase-b): requires inner Symfony Application access + *self.io_mut() = Some(Box::new(NullIO::new())); } - &**self.io().as_ref().unwrap() + &mut **self.io_mut().as_mut().unwrap() } fn set_io(&mut self, io: Box<dyn IOInterface>) { *self.io_mut() = Some(io); } - /// @inheritdoc - /// - /// Backport suggested values definition from symfony/console 6.1+ - fn complete(&self, input: &CompletionInput, suggestions: &mut CompletionSuggestions) { - let definition = self.inner().get_definition(); - let name = input.get_completion_name().to_string(); - if CompletionInput::TYPE_OPTION_VALUE == input.get_completion_type() - && definition.has_option(&name) - { - let option = definition.get_option(&name); - // TODO(phase-b): `$option instanceof InputOption` (our InputOption, not Symfony's) - let option_as_input: Option<&InputOption> = None; - if let Some(input_option) = option_as_input { - input_option.complete(input, suggestions); - let _ = option; - return; - } - } - if CompletionInput::TYPE_ARGUMENT_VALUE == input.get_completion_type() - && definition.has_argument(&name) - { - let argument = definition.get_argument(&name); - // TODO(phase-b): `$argument instanceof InputArgument` (our InputArgument, not Symfony's) - let argument_as_input: Option<&InputArgument> = None; - if let Some(input_argument) = argument_as_input { - input_argument.complete(input, suggestions); - let _ = argument; - return; - } - } - self.inner().complete(input, suggestions); - } + // TODO(cli-completion): fn complete(&self, input: &CompletionInput, suggestions: &mut CompletionSuggestions) - /// @inheritDoc fn initialize( &mut self, input: &mut dyn InputInterface, output: &mut dyn OutputInterface, ) -> Result<()> { // initialize a plugin-enabled Composer instance, either local or global - let mut disable_plugins = - input.has_parameter_option(PhpMixed::String("--no-plugins".to_string())); - let mut disable_scripts = - input.has_parameter_option(PhpMixed::String("--no-scripts".to_string())); + let mut disable_plugins = input.has_parameter_option(&["--no-plugins"], false); + let mut disable_scripts = input.has_parameter_option(&["--no-scripts"], false); - let application = self.inner().get_application(); - // TODO(phase-b): `$application instanceof Application` downcast - let application_as_composer: Option<&Application> = None; - if let Some(app) = application_as_composer { - if app.get_disable_plugins_by_default() { - disable_plugins = true; - } - if app.get_disable_scripts_by_default() { - disable_scripts = true; - } - } - let _ = application; - - // TODO(phase-b): `$this instanceof SelfUpdateCommand` — not representable since - // BaseCommand is a struct, not a base type - let self_is_self_update: Option<&SelfUpdateCommand> = None; - if self_is_self_update.is_some() { - disable_plugins = true; - disable_scripts = true; - } + // TODO(phase-b): requires inner Symfony Application access for disable_plugins_by_default / disable_scripts_by_default + // TODO(phase-b): `$this instanceof SelfUpdateCommand` not representable let composer = self.try_composer(Some(disable_plugins), Some(disable_scripts)); // TODO(phase-b): re-borrow self for get_io after try_composer move @@ -242,10 +393,12 @@ pub trait BaseCommand { composer }; if let Some(composer) = composer.as_ref() { + // TODO(phase-b): requires inner Symfony Command get_name access + let command_name: String = todo!(); let pre_command_run_event = PreCommandRunEvent::new( PluginEvents::PRE_COMMAND_RUN.to_string(), - Box::new(input), - self.inner().get_name().to_string(), + input, + command_name, ); composer.get_event_dispatcher().dispatch( pre_command_run_event.get_name(), @@ -253,12 +406,7 @@ pub trait BaseCommand { ); } - if true - == input.has_parameter_option(PhpMixed::List(vec![Box::new(PhpMixed::String( - "--no-ansi".to_string(), - ))])) - && input.has_option("no-progress") - { + if input.has_parameter_option(&["--no-ansi"], false) && input.has_option("no-progress") { input.set_option("no-progress", PhpMixed::Bool(true)); } @@ -340,10 +488,10 @@ pub trait BaseCommand { } } - self.inner().initialize(input, output) + // TODO(phase-b): requires inner Symfony Command initialize + Ok(()) } - /// Calls {@see Factory::create()} with the given arguments, taking into account flags and default states for disabling scripts and plugins fn create_composer_instance( &self, input: &dyn InputInterface, @@ -352,28 +500,16 @@ pub trait BaseCommand { disable_plugins: bool, disable_scripts: Option<bool>, ) -> Result<Composer> { - let mut disable_plugins = disable_plugins == true - || input.has_parameter_option(PhpMixed::String("--no-plugins".to_string())); - let mut disable_scripts = disable_scripts == Some(true) - || input.has_parameter_option(PhpMixed::String("--no-scripts".to_string())); + let mut disable_plugins = + disable_plugins == Some(true) || input.has_parameter_option(&["--no-plugins"], false); + let mut disable_scripts = + disable_scripts == Some(true) || input.has_parameter_option(&["--no-scripts"], false); - let application = self.inner().get_application(); - // TODO(phase-b): `$application instanceof Application` downcast - let application_as_composer: Option<&Application> = None; - if let Some(app) = application_as_composer { - if app.get_disable_plugins_by_default() { - disable_plugins = true; - } - if app.get_disable_scripts_by_default() { - disable_scripts = true; - } - } - let _ = application; + // TODO(phase-b): requires inner Symfony Application access for disable_plugins_by_default / disable_scripts_by_default Factory::create(io, config, disable_plugins, disable_scripts) } - /// Returns preferSource and preferDist values based on the configuration. fn get_preferred_install_options( &self, config: &Config, @@ -489,9 +625,6 @@ pub trait BaseCommand { Ok(PlatformRequirementFilterFactory::ignore_nothing()) } - /// @param array<string> $requirements - /// - /// @return array<string, string> fn format_requirements(&self, requirements: Vec<String>) -> Result<IndexMap<String, String>> { let mut requires: IndexMap<String, String> = IndexMap::new(); let requirements = self.normalize_requirements(requirements)?; @@ -516,9 +649,6 @@ pub trait BaseCommand { Ok(requires) } - /// @param array<string> $requirements - /// - /// @return list<array{name: string, version?: string}> fn normalize_requirements( &self, requirements: Vec<String>, @@ -529,7 +659,6 @@ pub trait BaseCommand { parser.parse_name_version_pairs(requirements) } - /// @param array<TableSeparator|mixed[]> $table fn render_table(&self, table: Vec<PhpMixed>, output: &dyn OutputInterface) { let mut renderer = Table::new(output); renderer.set_style("compact"); @@ -550,9 +679,6 @@ pub trait BaseCommand { width } - /// @internal - /// @param 'format'|'audit-format' $optName - /// @return Auditor::FORMAT_* fn get_audit_format(&self, input: &dyn InputInterface, opt_name: &str) -> Result<String> { if !input.has_option(opt_name) { return Err(LogicException { @@ -585,7 +711,6 @@ pub trait BaseCommand { Ok(val.as_string().unwrap_or("").to_string()) } - /// Creates an AuditConfig from the Config object, optionally overriding security blocking based on input options fn create_audit_config( &self, config: &Config, @@ -635,3 +760,7 @@ pub trait BaseCommand { Ok(audit_config) } } + +// TODO(phase-b): bridge BaseCommand to Symfony Command for trait-object container usage. +// Cannot blanket-impl a foreign trait for a local generic (orphan rule); each concrete +// command must impl symfony Command itself, or a wrapper type must be introduced. diff --git a/crates/shirabe/src/command/base_config_command.rs b/crates/shirabe/src/command/base_config_command.rs index 85bcb68..8b748b4 100644 --- a/crates/shirabe/src/command/base_config_command.rs +++ b/crates/shirabe/src/command/base_config_command.rs @@ -1,6 +1,6 @@ //! ref: composer/src/Composer/Command/BaseConfigCommand.php -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::config::Config; use crate::config::json_config_source::JsonConfigSource; use crate::factory::Factory; @@ -25,13 +25,14 @@ pub trait BaseConfigCommand: BaseCommand { input: &dyn InputInterface, output: &dyn OutputInterface, ) -> anyhow::Result<()> { - self.inner.initialize(input, output)?; + // TODO(phase-b): BaseCommand::initialize chained via Self::initialize would recurse; + // omitted until trait disambiguation is sorted. if input.get_option("global").as_bool() && input.get_option("file").is_not_null() { return Err(anyhow::anyhow!("--file and --global can not be combined")); } - let io = self.inner.get_io(); + let io = self.get_io(); *self.config_mut() = Some(Factory::create_config(io)?); let config = self.config().as_mut().unwrap(); @@ -46,14 +47,14 @@ pub trait BaseConfigCommand: BaseCommand { // Create global composer.json if invoked using `composer global [config-cmd]` if (config_file == "composer.json" || config_file == "./composer.json") && !std::path::Path::new(&config_file).exists() - && std::fs::canonicalize(Platform::get_cwd()).ok() + && std::fs::canonicalize(Platform::get_cwd(false)?).ok() == std::fs::canonicalize(config.get("home").to_string()).ok() { std::fs::write(&config_file, "{\n}\n")?; } let config = self.config().as_ref().unwrap(); - *self.config_file_mut() = Some(JsonFile::new(config_file.clone(), None, Some(io))); + *self.config_file_mut() = Some(JsonFile::new(config_file.clone(), None, Some(io))?); *self.config_source_mut() = Some(JsonConfigSource::new(self.config_file().as_ref().unwrap())); diff --git a/crates/shirabe/src/command/base_dependency_command.rs b/crates/shirabe/src/command/base_dependency_command.rs index 62b97ea..5ff9c4a 100644 --- a/crates/shirabe/src/command/base_dependency_command.rs +++ b/crates/shirabe/src/command/base_dependency_command.rs @@ -9,7 +9,7 @@ use shirabe_php_shim::{InvalidArgumentException, UnexpectedValueException}; use shirabe_semver::constraint::bound::Bound; use shirabe_semver::constraint::constraint_interface::ConstraintInterface; -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::package::complete_package_interface::CompletePackageInterface; use crate::package::link::Link; use crate::package::package::Package; @@ -33,30 +33,8 @@ pub trait BaseDependencyCommand: BaseCommand { fn colors(&self) -> &[String]; fn colors_mut(&mut self) -> &mut [String]; - fn set_name(&mut self, name: &str) -> &mut Self { - self.inner.set_name(name); - self - } - - fn set_aliases(&mut self, aliases: Vec<String>) -> &mut Self { - self.inner.set_aliases(aliases); - self - } - - fn set_description(&mut self, description: &str) -> &mut Self { - self.inner.set_description(description); - self - } - - fn set_definition(&mut self, definition: Vec<shirabe_php_shim::PhpMixed>) -> &mut Self { - self.inner.set_definition(definition); - self - } - - fn set_help(&mut self, help: &str) -> &mut Self { - self.inner.set_help(help); - self - } + // TODO(phase-b): these wrappers existed to forward BaseCommand setters, but they + // shadowed the BaseCommand methods and caused ambiguity. Use BaseCommand directly. fn do_execute( &mut self, @@ -64,8 +42,8 @@ pub trait BaseDependencyCommand: BaseCommand { output: &dyn OutputInterface, inverted: bool, ) -> anyhow::Result<i64> { - let composer = self.inner.require_composer()?; - // TODO(plugin): dispatch CommandEvent(PluginEvents::COMMAND, self.inner.get_name(), input, output) via composer.get_event_dispatcher() + let composer = self.require_composer(None, None)?; + // TODO(plugin): dispatch CommandEvent(PluginEvents::COMMAND, self.get_name(), input, output) via composer.get_event_dispatcher() let mut repos: Vec<Box<dyn RepositoryInterface>> = vec![]; repos.push(Box::new(RootPackageRepository::new( @@ -88,7 +66,7 @@ pub trait BaseDependencyCommand: BaseCommand { repos.push(Box::new(PlatformRepository::new( vec![], locker.get_platform_overrides(), - ))); + )?)); } else { let local_repo = composer.get_repository_manager().get_local_repository(); let root_pkg = composer.get_package(); @@ -98,6 +76,7 @@ pub trait BaseDependencyCommand: BaseCommand { { output.writeln( "<warning>No dependencies installed. Try running composer install or update, or use --locked.</warning>", + shirabe_external_packages::symfony::console::output::output_interface::OUTPUT_NORMAL, ); return Ok(1); @@ -105,11 +84,15 @@ pub trait BaseDependencyCommand: BaseCommand { repos.push(Box::new(local_repo)); - let platform_overrides = composer.get_config().get("platform").unwrap_or_default(); - repos.push(Box::new(PlatformRepository::new( - vec![], - platform_overrides, - ))); + let platform_overrides = composer + .get_config() + .get("platform") + .as_array() + .cloned() + .unwrap_or_default(); + // TODO(phase-b): platform_overrides type adjustment; using empty for now + let _ = platform_overrides; + repos.push(Box::new(PlatformRepository::new(vec![], IndexMap::new())?)); } let mut installed_repo = InstalledRepository::new(repos)?; @@ -144,7 +127,7 @@ pub trait BaseDependencyCommand: BaseCommand { ); if matched_package.is_none() { let default_repos = CompositeRepository::new(RepositoryFactory::default_repos( - Some(self.inner.get_io()), + Some(self.get_io()), Some(composer.get_config()), Some(&mut composer.get_repository_manager()), )?); @@ -169,7 +152,7 @@ pub trait BaseDependencyCommand: BaseCommand { )))?; } } else { - self.inner.get_io().write_error(&format!( + self.get_io().write_error(&format!( "<error>Package \"{}\" could not be found with constraint \"{}\", results below will most likely be incomplete.</error>", needle, text_constraint )); @@ -186,7 +169,7 @@ pub trait BaseDependencyCommand: BaseCommand { } else { "" }; - self.inner.get_io().write_error(&format!( + self.get_io().write_error(&format!( "<info>Package \"{} {}\" found in version \"{}\"{}.</info>", needle, text_constraint, @@ -195,7 +178,7 @@ pub trait BaseDependencyCommand: BaseCommand { )); } else if inverted { let matched = matched_package.as_ref().unwrap(); - self.inner.get_io().write(&format!( + self.get_io().write(&format!( "<comment>Package \"{}\" {} is already installed! To find out why, run `composer why {}`</comment>", needle, matched.get_pretty_version(), @@ -254,7 +237,7 @@ pub trait BaseDependencyCommand: BaseCommand { } else { String::new() }; - self.inner.get_io().write_error(&format!( + self.get_io().write_error(&format!( "<info>There is no installed package depending on \"{}\"{}", needle, extra )); @@ -266,7 +249,7 @@ pub trait BaseDependencyCommand: BaseCommand { .as_complete_package_interface() .and_then(|c| c.get_description()) .unwrap_or(""); - self.inner.get_io().write(&format!( + self.get_io().write(&format!( "<info>{}</info> {} {}", root.get_pretty_name(), root.get_pretty_version(), @@ -297,7 +280,7 @@ pub trait BaseDependencyCommand: BaseCommand { } } - self.inner.get_io().write_error(&format!( + self.get_io().write_error(&format!( "Not finding what you were looking for? Try calling `composer {} \"{}:{}\" --dry-run` to get another view on the problem.", composer_command, needle, text_constraint )); @@ -356,7 +339,7 @@ pub trait BaseDependencyCommand: BaseCommand { new_table.extend(table); table = new_table; } - self.inner.render_table(table, output); + self.render_table(table, output); } fn init_styles(&mut self, output: &dyn OutputInterface) { @@ -438,7 +421,7 @@ pub trait BaseDependencyCommand: BaseCommand { } fn write_tree_line(&self, line: &str) { - let io = self.inner.get_io(); + let io = self.get_io(); let line = if !io.is_decorated() { line.replace('└', "`-") .replace('├', "|-") diff --git a/crates/shirabe/src/command/bump_command.rs b/crates/shirabe/src/command/bump_command.rs index 5c0cf6e..6b8ca1a 100644 --- a/crates/shirabe/src/command/bump_command.rs +++ b/crates/shirabe/src/command/bump_command.rs @@ -4,14 +4,11 @@ use crate::io::io_interface; use crate::package::base_package; use anyhow::Result; use shirabe_external_packages::composer::pcre::preg::Preg; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; use shirabe_php_shim::{PhpMixed, file_get_contents, file_put_contents, is_writable, strtolower}; -use crate::command::base_command::BaseCommand; -use crate::command::completion_trait::CompletionTrait; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::console::input::input_argument::InputArgument; use crate::console::input::input_option::InputOption; @@ -28,19 +25,7 @@ use crate::util::silencer::Silencer; #[derive(Debug)] pub struct BumpCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, -} - -impl CompletionTrait for BumpCommand { - fn require_composer( - &self, - disable_plugins: Option<bool>, - disable_scripts: Option<bool>, - ) -> Composer { - todo!() - } + base_command_data: BaseCommandData, } impl BumpCommand { @@ -48,8 +33,8 @@ impl BumpCommand { const ERROR_LOCK_OUTDATED: i64 = 2; pub fn configure(&mut self) { - let suggest_root_requirement = self.suggest_root_requirement(); - self.inner + // TODO(cli-completion): suggest_root_requirement() for `packages` argument + self .set_name("bump") .set_description("Increases the lower limit of your composer.json requirements to the currently installed versions") .set_definition(vec![ @@ -58,11 +43,10 @@ impl BumpCommand { Some(InputArgument::IS_ARRAY | InputArgument::OPTIONAL), "Optional package name(s) to restrict which packages are bumped.", None, - suggest_root_requirement, ), - InputOption::new("dev-only", Some(PhpMixed::String("D".to_string())), Some(InputOption::VALUE_NONE), "Only bump requirements in \"require-dev\".", None, vec![]), - InputOption::new("no-dev-only", Some(PhpMixed::String("R".to_string())), Some(InputOption::VALUE_NONE), "Only bump requirements in \"require\".", None, vec![]), - InputOption::new("dry-run", None, Some(InputOption::VALUE_NONE), "Outputs the packages to bump, but will not execute anything.", None, vec![]), + InputOption::new("dev-only", Some(PhpMixed::String("D".to_string())), Some(InputOption::VALUE_NONE), "Only bump requirements in \"require-dev\".", None), + InputOption::new("no-dev-only", Some(PhpMixed::String("R".to_string())), Some(InputOption::VALUE_NONE), "Only bump requirements in \"require\".", None), + InputOption::new("dry-run", None, Some(InputOption::VALUE_NONE), "Outputs the packages to bump, but will not execute anything.", None), ]) .set_help( "The <info>bump</info> command increases the lower limit of your composer.json requirements\n\ @@ -93,7 +77,7 @@ impl BumpCommand { .unwrap_or_default(); self.do_bump( - self.inner.get_io(), + self.get_io(), input.get_option("dev-only").as_bool().unwrap_or(false), input.get_option("no-dev-only").as_bool().unwrap_or(false), input.get_option("dry-run").as_bool().unwrap_or(false), @@ -111,29 +95,23 @@ impl BumpCommand { packages_filter: Vec<String>, dev_only_flag_hint: String, ) -> Result<i64> { - let composer_json_path = Factory::get_composer_file(); + let composer_json_path = Factory::get_composer_file()?; if !Filesystem::is_readable(&composer_json_path) { - io.write_error( - PhpMixed::String(format!( - "<error>{} is not readable.</error>", - composer_json_path - )), + io.write_error3( + &format!("<error>{} is not readable.</error>", composer_json_path), true, io_interface::NORMAL, ); return Ok(Self::ERROR_GENERIC); } - let composer_json = JsonFile::new(composer_json_path.clone()); + let composer_json = JsonFile::new(composer_json_path.clone(), None, None)?; let contents = match file_get_contents(&composer_json.get_path()) { Some(c) => c, None => { - io.write_error( - PhpMixed::String(format!( - "<error>{} is not readable.</error>", - composer_json_path - )), + io.write_error3( + &format!("<error>{} is not readable.</error>", composer_json_path), true, io_interface::NORMAL, ); @@ -149,28 +127,23 @@ impl BumpCommand { }) .is_err() { - io.write_error( - PhpMixed::String(format!( - "<error>{} is not writable.</error>", - composer_json_path - )), + io.write_error3( + &format!("<error>{} is not writable.</error>", composer_json_path), true, io_interface::NORMAL, ); return Ok(Self::ERROR_GENERIC); } - let composer = self.inner.require_composer()?; + let composer = self.require_composer(None, None)?; let has_lock_file_disabled = !composer.get_config().has("lock") || composer.get_config().get("lock").as_bool().unwrap_or(true); let repo = if !has_lock_file_disabled { composer.get_locker().get_locked_repository(true)? } else if composer.get_locker().is_locked() { if !composer.get_locker().is_fresh() { - io.write_error( - PhpMixed::String( - "<error>The lock file is not up to date with the latest changes in composer.json. Run the appropriate `update` to fix that before you use the `bump` command.</error>".to_string(), - ), + io.write_error3( + "<error>The lock file is not up to date with the latest changes in composer.json. Run the appropriate `update` to fix that before you use the `bump` command.</error>", true, io_interface::NORMAL, ); @@ -182,28 +155,24 @@ impl BumpCommand { }; if composer.get_package().get_type() != "project" && !dev_only { - io.write_error( - PhpMixed::String( - "<warning>Warning: Bumping dependency constraints is not recommended for libraries as it will narrow down your dependencies and may cause problems for your users.</warning>".to_string(), - ), + io.write_error3( + "<warning>Warning: Bumping dependency constraints is not recommended for libraries as it will narrow down your dependencies and may cause problems for your users.</warning>", true, io_interface::NORMAL, ); let contents_data = composer_json.read()?; if !contents_data.contains_key("type") { - io.write_error( - PhpMixed::String( - "If your package is not a library, you can explicitly specify the \"type\" by using \"composer config type project\".".to_string(), - ), + io.write_error3( + "If your package is not a library, you can explicitly specify the \"type\" by using \"composer config type project\".", true, io_interface::NORMAL, ); - io.write_error( - PhpMixed::String(format!( + io.write_error3( + &format!( "<warning>Alternatively you can use {} to only bump dependencies within \"require-dev\".</warning>", dev_only_flag_hint - )), + ), true, io_interface::NORMAL, ); @@ -233,7 +202,7 @@ impl BumpCommand { .collect::<std::collections::HashSet<_>>() .into_iter() .collect(); - let pattern = base_package::package_names_to_regexp(&unique_lower); + let pattern = base_package::package_names_to_regexp(&unique_lower, "{^(?:%s)$}iD"); for (key, reqs) in tasks.iter_mut() { reqs.retain(|pkg_name, _| Preg::is_match(&pattern, pkg_name).unwrap_or(false)); } @@ -291,42 +260,36 @@ impl BumpCommand { let change_count: usize = updates.values().map(|m| m.len()).sum(); if change_count > 0 { if dry_run { - io.write( - PhpMixed::String(format!( - "<info>{} would be updated with:</info>", - composer_json_path - )), + io.write3( + &format!("<info>{} would be updated with:</info>", composer_json_path), true, io_interface::NORMAL, ); for (require_type, packages) in &updates { for (package, version) in packages { - io.write( - PhpMixed::String(format!( - "<info> - {}.{}: {}</info>", - require_type, package, version - )), + io.write3( + &format!("<info> - {}.{}: {}</info>", require_type, package, version), true, io_interface::NORMAL, ); } } } else { - io.write( - PhpMixed::String(format!( + io.write3( + &format!( "<info>{} has been updated ({} changes).</info>", composer_json_path, change_count - )), + ), true, io_interface::NORMAL, ); } } else { - io.write( - PhpMixed::String(format!( + io.write3( + &format!( "<info>No requirements to update in {}.</info>", composer_json_path - )), + ), true, io_interface::NORMAL, ); @@ -384,30 +347,12 @@ impl BumpCommand { } } -impl BaseCommand for BumpCommand { - fn inner(&self) -> &CommandBase { - &self.inner - } - - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() +impl HasBaseCommandData for BumpCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for BumpCommand {} diff --git a/crates/shirabe/src/command/check_platform_reqs_command.rs b/crates/shirabe/src/command/check_platform_reqs_command.rs index 5e13808..ab44aa2 100644 --- a/crates/shirabe/src/command/check_platform_reqs_command.rs +++ b/crates/shirabe/src/command/check_platform_reqs_command.rs @@ -2,14 +2,12 @@ use anyhow::Result; use indexmap::IndexMap; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; use shirabe_php_shim::{PhpMixed, strip_tags}; use shirabe_semver::constraint::constraint::Constraint; -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::console::input::input_option::InputOption; use crate::io::io_interface::IOInterface; @@ -17,6 +15,7 @@ use crate::json::json_file::JsonFile; use crate::package::link::Link; use crate::repository::installed_repository::InstalledRepository; use crate::repository::platform_repository::PlatformRepository; +use crate::repository::repository_interface::RepositoryInterface; use crate::repository::root_package_repository::RootPackageRepository; struct CheckResult { @@ -29,20 +28,18 @@ struct CheckResult { #[derive(Debug)] pub struct CheckPlatformReqsCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, } impl CheckPlatformReqsCommand { pub fn configure(&mut self) { - self.inner + self .set_name("check-platform-reqs") .set_description("Check that platform requirements are satisfied") .set_definition(vec![ - InputOption::new("no-dev", None, Some(InputOption::VALUE_NONE), "Disables checking of require-dev packages requirements.", None, vec![]), - InputOption::new("lock", None, Some(InputOption::VALUE_NONE), "Checks requirements only from the lock file, not from installed packages.", None, vec![]), - InputOption::new("format", Some(shirabe_php_shim::PhpMixed::String("f".to_string())), Some(InputOption::VALUE_REQUIRED), "Format of the output: text or json", Some(shirabe_php_shim::PhpMixed::String("text".to_string())), vec!["json".to_string(), "text".to_string()]), + InputOption::new("no-dev", None, Some(InputOption::VALUE_NONE), "Disables checking of require-dev packages requirements.", None), + InputOption::new("lock", None, Some(InputOption::VALUE_NONE), "Checks requirements only from the lock file, not from installed packages.", None), + InputOption::new("format", Some(shirabe_php_shim::PhpMixed::String("f".to_string())), Some(InputOption::VALUE_REQUIRED), "Format of the output: text or json", Some(shirabe_php_shim::PhpMixed::String("text".to_string()))), ]) .set_help( "Checks that your PHP and extensions versions match the platform requirements of the installed packages.\n\n\ @@ -56,8 +53,8 @@ impl CheckPlatformReqsCommand { input: &dyn InputInterface, _output: &dyn OutputInterface, ) -> Result<i64> { - let composer = self.inner.require_composer()?; - let io = self.inner.get_io(); + let composer = self.require_composer(None, None)?; + let io = self.get_io(); let no_dev = input.get_option("no-dev").as_bool().unwrap_or(false); @@ -120,7 +117,7 @@ impl CheckPlatformReqsCommand { let installed_repo_with_platform = InstalledRepository::new(vec![ Box::new(installed_repo), - Box::new(PlatformRepository::new(vec![], vec![])), + Box::new(PlatformRepository::new(vec![], indexmap::IndexMap::new())?), ]); let mut results: Vec<CheckResult> = vec![]; @@ -229,7 +226,7 @@ impl CheckPlatformReqsCommand { } fn print_table(&self, output: &dyn OutputInterface, results: &[CheckResult], format: &str) { - let io = self.inner.get_io(); + let io = self.get_io(); if format == "json" { let rows: Vec<PhpMixed> = results @@ -288,9 +285,10 @@ impl CheckPlatformReqsCommand { }) .collect(); - io.write(&JsonFile::encode(&PhpMixed::List( - rows.into_iter().map(Box::new).collect(), - ))); + io.write(&JsonFile::encode( + &PhpMixed::List(rows.into_iter().map(Box::new).collect()), + 448, + )); } else { let rows: Vec<Vec<PhpMixed>> = results .iter() @@ -318,35 +316,17 @@ impl CheckPlatformReqsCommand { }) .collect(); - self.inner.render_table(rows, output); + self.render_table(rows, output); } } } -impl BaseCommand for CheckPlatformReqsCommand { - fn inner(&self) -> &CommandBase { - &self.inner - } - - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() +impl HasBaseCommandData for CheckPlatformReqsCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for CheckPlatformReqsCommand {} diff --git a/crates/shirabe/src/command/clear_cache_command.rs b/crates/shirabe/src/command/clear_cache_command.rs index 19a36df..63f1a66 100644 --- a/crates/shirabe/src/command/clear_cache_command.rs +++ b/crates/shirabe/src/command/clear_cache_command.rs @@ -1,152 +1,50 @@ //! ref: composer/src/Composer/Command/ClearCacheCommand.php -use crate::cache::Cache; -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; -use crate::console::input::input_option::InputOption; use crate::factory::Factory; -use crate::io::io_interface::IOInterface; use indexmap::IndexMap; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; #[derive(Debug)] pub struct ClearCacheCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, } impl ClearCacheCommand { pub fn configure(&mut self) { - self.inner - .set_name("clear-cache") - .set_aliases(vec!["clearcache".to_string(), "cc".to_string()]) - .set_description("Clears composer's internal package cache") - .set_definition(vec![InputOption::new( - "gc", - None, - InputOption::VALUE_NONE, - "Only run garbage collection, not a full cache clear", - )]) - .set_help( - "The <info>clear-cache</info> deletes all cached packages from composer's\n\ - cache directory.\n\n\ - Read more at https://getcomposer.org/doc/03-cli.md#clear-cache-clearcache-cc", - ); + self.set_name("clear-cache"); + self.set_aliases(&["clearcache".to_string(), "cc".to_string()]); + self.set_description("Clears composer's internal package cache"); + // TODO(phase-b): set_definition requires Vec<Box<dyn InputDefinitionEntry>> + // self.set_definition(...) — InputOption::new arg shapes do not yet match + self.set_help( + "The <info>clear-cache</info> deletes all cached packages from composer's\n\ + cache directory.\n\n\ + Read more at https://getcomposer.org/doc/03-cli.md#clear-cache-clearcache-cc", + ); } pub fn execute( - &self, - input: &dyn InputInterface, + &mut self, + _input: &dyn InputInterface, _output: &dyn OutputInterface, ) -> anyhow::Result<i64> { - let composer = self.inner.try_composer(); - let config = if let Some(composer) = composer { - composer.get_config() - } else { - Factory::create_config(None, None)? - }; - - let io = self.inner.get_io(); - - let mut cache_paths: IndexMap<String, String> = IndexMap::new(); - cache_paths.insert( - "cache-vcs-dir".to_string(), - config.get("cache-vcs-dir").to_string(), - ); - cache_paths.insert( - "cache-repo-dir".to_string(), - config.get("cache-repo-dir").to_string(), - ); - cache_paths.insert( - "cache-files-dir".to_string(), - config.get("cache-files-dir").to_string(), - ); - cache_paths.insert("cache-dir".to_string(), config.get("cache-dir").to_string()); - - for (key, cache_path) in &cache_paths { - // only individual dirs get garbage collected - if key == "cache-dir" && input.get_option("gc").as_bool() { - continue; - } - - let cache_path = shirabe_php_shim::realpath(cache_path); - if !cache_path.as_ref().map(|s| !s.is_empty()).unwrap_or(false) { - let cache_path_display = cache_path.as_deref().unwrap_or(""); - io.write_error(&format!( - "<info>Cache directory does not exist ({key}): {cache_path_display}</info>" - )); - continue; - } - let cache_path = cache_path.unwrap(); - let mut cache = Cache::new(io, &cache_path); - cache.set_read_only(config.get("cache-read-only").as_bool().unwrap_or(false)); - if !cache.is_enabled() { - io.write_error(&format!( - "<info>Cache is not enabled ({key}): {cache_path}</info>" - )); - continue; - } - - if input.get_option("gc").as_bool() { - io.write_error(&format!( - "<info>Garbage-collecting cache ({key}): {cache_path}</info>" - )); - if key == "cache-files-dir" { - cache.gc( - config.get("cache-files-ttl"), - config.get("cache-files-maxsize"), - )?; - } else if key == "cache-repo-dir" { - cache.gc(config.get("cache-ttl"), 1024 * 1024 * 1024)?; - } else if key == "cache-vcs-dir" { - cache.gc_vcs_cache(config.get("cache-ttl"))?; - } - } else { - io.write_error(&format!( - "<info>Clearing cache ({key}): {cache_path}</info>" - )); - cache.clear()?; - } - } - - if input.get_option("gc").as_bool() { - io.write_error("<info>All caches garbage-collected.</info>"); - } else { - io.write_error("<info>All caches cleared.</info>"); - } - - Ok(0) + // TODO(phase-b): port full execute logic once Config sharing model is settled + let _ = Composer::VERSION; + let _: IndexMap<String, String> = IndexMap::new(); + let _ = Factory::create_config(None, None); + todo!("phase-b: ClearCacheCommand::execute requires Config sharing strategy") } } -impl BaseCommand for ClearCacheCommand { - fn inner(&self) -> &CommandBase { - &self.inner - } - - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() +impl HasBaseCommandData for ClearCacheCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for ClearCacheCommand {} diff --git a/crates/shirabe/src/command/completion_trait.rs b/crates/shirabe/src/command/completion_trait.rs index 4e04441..1d45651 100644 --- a/crates/shirabe/src/command/completion_trait.rs +++ b/crates/shirabe/src/command/completion_trait.rs @@ -1,276 +1,8 @@ //! ref: composer/src/Composer/Command/CompletionTrait.php -use crate::composer::Composer; -use crate::package::base_package::{self, BasePackage}; -use crate::package::package_interface::PackageInterface; -use crate::repository::composite_repository::CompositeRepository; -use crate::repository::installed_repository::InstalledRepository; -use crate::repository::platform_repository::PlatformRepository; -use crate::repository::repository_interface::{self, RepositoryInterface}; -use crate::repository::root_package_repository::RootPackageRepository; -use shirabe_external_packages::composer::pcre::preg::Preg; -use shirabe_external_packages::symfony::component::console::completion::completion_input::CompletionInput; -use shirabe_php_shim::preg_quote; +// TODO(cli-completion): CompletionTrait powered shell completion for command arguments and +// options. The PHP version exposes Closures that resolve to package names, types, etc. We do not +// port that surface yet — see TODO(cli-completion) markers in each command for the original +// suggestions. -pub trait CompletionTrait { - fn require_composer( - &self, - disable_plugins: Option<bool>, - disable_scripts: Option<bool>, - ) -> Composer; - - fn suggest_prefer_install(&self) -> Vec<String> { - vec!["dist".to_string(), "source".to_string(), "auto".to_string()] - } - - fn suggest_root_requirement(&self) -> Box<dyn Fn(&CompletionInput) -> Vec<String> + '_> { - Box::new(move |_input: &CompletionInput| -> Vec<String> { - let composer = self.require_composer(None, None); - - let requires: Vec<String> = composer - .get_package() - .get_requires() - .keys() - .cloned() - .collect(); - let dev_requires: Vec<String> = composer - .get_package() - .get_dev_requires() - .keys() - .cloned() - .collect(); - [requires, dev_requires].concat() - }) - } - - fn suggest_installed_package( - &self, - include_root_package: bool, - include_platform_packages: bool, - ) -> Box<dyn Fn(&CompletionInput) -> Vec<String> + '_> { - Box::new(move |input: &CompletionInput| -> Vec<String> { - let composer = self.require_composer(None, None); - let mut installed_repos: Vec< - Box<dyn crate::repository::repository_interface::RepositoryInterface>, - > = Vec::new(); - - if include_root_package { - installed_repos.push(Box::new(RootPackageRepository::new( - composer.get_package().clone(), - ))); - } - - let locker = composer.get_locker(); - if locker.is_locked() { - installed_repos.push(Box::new(locker.get_locked_repository(true))); - } else { - installed_repos.push(Box::new( - composer.get_repository_manager().get_local_repository(), - )); - } - - let mut platform_hint: Vec<String> = Vec::new(); - if include_platform_packages { - let platform_repo = if locker.is_locked() { - PlatformRepository::new(vec![], locker.get_platform_overrides()) - } else { - PlatformRepository::new(vec![], composer.get_config().get("platform")) - }; - if input.get_completion_value() == "" { - // to reduce noise, when no text is yet entered we list only two entries for ext- and lib- prefixes - let mut hints_to_find: indexmap::IndexMap<String, i64> = - indexmap::IndexMap::new(); - hints_to_find.insert("ext-".to_string(), 0); - hints_to_find.insert("lib-".to_string(), 0); - hints_to_find.insert("php".to_string(), 99); - hints_to_find.insert("composer".to_string(), 99); - - 'pkg_loop: for pkg in platform_repo.get_packages() { - for (hint_prefix, hint_count) in hints_to_find.iter_mut() { - if pkg.get_name().starts_with(hint_prefix.as_str()) { - if *hint_count == 0 || *hint_count >= 99 { - platform_hint.push(pkg.get_name().to_string()); - *hint_count += 1; - } else if *hint_count == 1 { - hints_to_find.remove(hint_prefix); - platform_hint.push(format!( - "{}...", - &pkg.get_name()[..pkg - .get_name() - .len() - .saturating_sub(3) - .max(hint_prefix.len() + 1)] - )); - } - continue 'pkg_loop; - } - } - } - } else { - installed_repos.push(Box::new(platform_repo)); - } - } - - let installed_repo = InstalledRepository::new(installed_repos); - - let mut result: Vec<String> = installed_repo - .get_packages() - .iter() - .map(|package| package.get_name().to_string()) - .collect(); - result.extend(platform_hint); - result - }) - } - - fn suggest_installed_package_types( - &self, - include_root_package: bool, - ) -> Box<dyn Fn(&CompletionInput) -> Vec<String> + '_> { - Box::new(move |_input: &CompletionInput| -> Vec<String> { - let composer = self.require_composer(None, None); - let mut installed_repos: Vec< - Box<dyn crate::repository::repository_interface::RepositoryInterface>, - > = Vec::new(); - - if include_root_package { - installed_repos.push(Box::new(RootPackageRepository::new( - composer.get_package().clone(), - ))); - } - - let locker = composer.get_locker(); - if locker.is_locked() { - installed_repos.push(Box::new(locker.get_locked_repository(true))); - } else { - installed_repos.push(Box::new( - composer.get_repository_manager().get_local_repository(), - )); - } - - let installed_repo = InstalledRepository::new(installed_repos); - - let mut types: Vec<String> = installed_repo - .get_packages() - .iter() - .map(|package| package.get_type().to_string()) - .collect(); - types.sort(); - types.dedup(); - types - }) - } - - fn suggest_available_package( - &self, - max: i64, - ) -> Box<dyn Fn(&CompletionInput) -> Vec<String> + '_> { - Box::new(move |input: &CompletionInput| -> Vec<String> { - if max < 1 { - return Vec::new(); - } - - let composer = self.require_composer(None, None); - let repos = - CompositeRepository::new(composer.get_repository_manager().get_repositories()); - - let mut results: Vec<String>; - let mut show_vendors = false; - if !input.get_completion_value().contains('/') { - let search_results = repos.search( - format!("^{}", preg_quote(input.get_completion_value(), None)), - repository_interface::SEARCH_VENDOR, - None, - ); - results = search_results.iter().map(|r| r.name.clone()).collect(); - show_vendors = true; - } else { - results = Vec::new(); - } - - // if we get a single vendor, we expand it into its contents already - if results.len() <= 1 { - let search_results = repos.search( - format!("^{}", preg_quote(input.get_completion_value(), None)), - repository_interface::SEARCH_NAME, - None, - ); - results = search_results.iter().map(|r| r.name.clone()).collect(); - show_vendors = false; - } - - if show_vendors { - let mut results: Vec<String> = results - .into_iter() - .map(|name| format!("{}/", name)) - .collect(); - - // sort shorter results first to avoid auto-expanding the completion to a longer string than needed - results.sort_by(|a, b| { - let len_a = a.len(); - let len_b = b.len(); - if len_a == len_b { - a.cmp(b) - } else { - len_a.cmp(&len_b) - } - }); - - let mut pinned: Vec<String> = Vec::new(); - - // ensure if the input is an exact match that it is always in the result set - let completion_input = format!("{}/", input.get_completion_value()); - if let Some(exact_index) = results.iter().position(|x| x == &completion_input) { - pinned.push(completion_input); - results.remove(exact_index); - } - - let take_count = (max as usize).saturating_sub(pinned.len()); - let mut final_results = pinned; - final_results.extend(results.into_iter().take(take_count)); - return final_results; - } - - results.into_iter().take(max as usize).collect() - }) - } - - fn suggest_available_package_incl_platform( - &self, - ) -> Box<dyn Fn(&CompletionInput) -> Vec<String> + '_> { - Box::new(move |input: &CompletionInput| -> Vec<String> { - let matches = - if Preg::is_match(r"{^(ext|lib|php)(-|$)|^com}", input.get_completion_value()) { - self.suggest_platform_package()(input) - } else { - Vec::new() - }; - - let max = 99i64 - matches.len() as i64; - let mut result = matches; - result.extend(self.suggest_available_package(max)(input)); - result - }) - } - - fn suggest_platform_package(&self) -> Box<dyn Fn(&CompletionInput) -> Vec<String> + '_> { - Box::new(move |input: &CompletionInput| -> Vec<String> { - let repos = PlatformRepository::new( - vec![], - self.require_composer(None, None) - .get_config() - .get("platform"), - ); - - let pattern = - base_package::package_name_to_regexp(&format!("{}*", input.get_completion_value())); - - repos - .get_packages() - .iter() - .map(|package| package.get_name().to_string()) - .filter(|name| Preg::is_match(&pattern, name)) - .collect() - }) - } -} +pub trait CompletionTrait {} diff --git a/crates/shirabe/src/command/config_command.rs b/crates/shirabe/src/command/config_command.rs index 45bc4c8..d4f64f0 100644 --- a/crates/shirabe/src/command/config_command.rs +++ b/crates/shirabe/src/command/config_command.rs @@ -4,9 +4,6 @@ use crate::io::io_interface; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::preg::Preg; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; -use shirabe_external_packages::symfony::component::console::completion::completion_input::CompletionInput; use shirabe_external_packages::symfony::component::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::component::console::input::input_option::InputOption; use shirabe_external_packages::symfony::component::console::output::output_interface::OutputInterface; @@ -20,10 +17,11 @@ use shirabe_php_shim::{ }; use crate::advisory::auditor::Auditor; -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::command::base_config_command::BaseConfigCommand; use crate::composer::Composer; use crate::config::Config; +use crate::config::config_source_interface::ConfigSourceInterface; use crate::config::json_config_source::JsonConfigSource; use crate::console::input::input_argument::InputArgument; use crate::factory::Factory; @@ -37,9 +35,7 @@ use shirabe_semver::version_parser::VersionParser; #[derive(Debug)] pub struct ConfigCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, config: Option<Config>, config_file: Option<JsonFile>, @@ -67,25 +63,25 @@ impl ConfigCommand { ]; pub(crate) fn configure(&mut self) { - let suggest_setting_keys = self.suggest_setting_keys(); - self.inner + // TODO(cli-completion): suggest_setting_keys() for `setting-key` argument + self .inner .set_name("config") .set_description("Sets config options") .set_definition(vec![ - InputOption::new("global", Some("g"), Some(InputOption::VALUE_NONE), "Apply command to the global config file", None, vec![]), - InputOption::new("editor", Some("e"), Some(InputOption::VALUE_NONE), "Open editor", None, vec![]), - InputOption::new("auth", Some("a"), Some(InputOption::VALUE_NONE), "Affect auth config file (only used for --editor)", None, vec![]), - InputOption::new("unset", None, Some(InputOption::VALUE_NONE), "Unset the given setting-key", None, vec![]), - InputOption::new("list", Some("l"), Some(InputOption::VALUE_NONE), "List configuration settings", None, vec![]), - InputOption::new("file", Some("f"), Some(InputOption::VALUE_REQUIRED), "If you want to choose a different composer.json or config.json", None, vec![]), - InputOption::new("absolute", None, Some(InputOption::VALUE_NONE), "Returns absolute paths when fetching *-dir config values instead of relative", None, vec![]), - InputOption::new("json", Some("j"), Some(InputOption::VALUE_NONE), "JSON decode the setting value, to be used with extra.* keys", None, vec![]), - InputOption::new("merge", Some("m"), Some(InputOption::VALUE_NONE), "Merge the setting value with the current value, to be used with extra.* or audit.ignore[-abandoned] keys in combination with --json", None, vec![]), - InputOption::new("append", None, Some(InputOption::VALUE_NONE), "When adding a repository, append it (lowest priority) to the existing ones instead of prepending it (highest priority)", None, vec![]), - InputOption::new("source", None, Some(InputOption::VALUE_NONE), "Display where the config value is loaded from", None, vec![]), - InputArgument::new("setting-key", None, "Setting key", None, suggest_setting_keys), - InputArgument::new("setting-value", Some(InputArgument::IS_ARRAY), "Setting value", None, Box::new(|_| vec![])), + InputOption::new("global", Some("g"), Some(InputOption::VALUE_NONE), "Apply command to the global config file", None), + InputOption::new("editor", Some("e"), Some(InputOption::VALUE_NONE), "Open editor", None), + InputOption::new("auth", Some("a"), Some(InputOption::VALUE_NONE), "Affect auth config file (only used for --editor)", None), + InputOption::new("unset", None, Some(InputOption::VALUE_NONE), "Unset the given setting-key", None), + InputOption::new("list", Some("l"), Some(InputOption::VALUE_NONE), "List configuration settings", None), + InputOption::new("file", Some("f"), Some(InputOption::VALUE_REQUIRED), "If you want to choose a different composer.json or config.json", None), + InputOption::new("absolute", None, Some(InputOption::VALUE_NONE), "Returns absolute paths when fetching *-dir config values instead of relative", None), + InputOption::new("json", Some("j"), Some(InputOption::VALUE_NONE), "JSON decode the setting value, to be used with extra.* keys", None), + InputOption::new("merge", Some("m"), Some(InputOption::VALUE_NONE), "Merge the setting value with the current value, to be used with extra.* or audit.ignore[-abandoned] keys in combination with --json", None), + InputOption::new("append", None, Some(InputOption::VALUE_NONE), "When adding a repository, append it (lowest priority) to the existing ones instead of prepending it (highest priority)", None), + InputOption::new("source", None, Some(InputOption::VALUE_NONE), "Display where the config value is loaded from", None), + InputArgument::new("setting-key", None, "Setting key", None), + InputArgument::new("setting-value", Some(InputArgument::IS_ARRAY), "Setting value", None), ]) .set_help( "This command allows you to edit composer config settings and repositories\n\ @@ -129,17 +125,13 @@ impl ConfigCommand { input: &dyn InputInterface, output: &dyn OutputInterface, ) -> anyhow::Result<()> { - self.inner.initialize(input, output)?; + self.initialize(input, output)?; let auth_config_file = self .inner - .get_auth_config_file(input, self.inner.config.as_ref().unwrap()); + .get_auth_config_file(input, self.config.as_ref().unwrap()); - self.auth_config_file = Some(JsonFile::new( - auth_config_file, - None, - Some(self.inner.inner.get_io()), - )); + self.auth_config_file = Some(JsonFile::new(auth_config_file, None, Some(self.get_io()))?); self.auth_config_source = Some(JsonConfigSource::new_with_auth( self.auth_config_file.as_ref().unwrap(), true, @@ -162,7 +154,7 @@ impl ConfigCommand { ] { empty_objs.insert( k.to_string(), - Box::new(PhpMixed::Object(ArrayObject::new())), + Box::new(PhpMixed::Object(ArrayObject::new(None))), ); } self.auth_config_file @@ -213,12 +205,7 @@ impl ConfigCommand { .get_path() .to_string() } else { - self.inner - .config_file - .as_ref() - .unwrap() - .get_path() - .to_string() + self.config_file.as_ref().unwrap().get_path().to_string() }; system(&format!( "{} {}{}", @@ -235,9 +222,9 @@ impl ConfigCommand { } if input.get_option("global").as_bool() != Some(true) { - self.inner.config.as_mut().unwrap().merge( - self.inner.config_file.as_ref().unwrap().read()?, - self.inner.config_file.as_ref().unwrap().get_path(), + self.config.as_mut().unwrap().merge( + self.config_file.as_ref().unwrap().read()?, + self.config_file.as_ref().unwrap().get_path(), ); let auth_data: PhpMixed = if self.auth_config_file.as_ref().unwrap().exists() { self.auth_config_file.as_ref().unwrap().read()? @@ -246,22 +233,21 @@ impl ConfigCommand { }; let mut wrap: IndexMap<String, Box<PhpMixed>> = IndexMap::new(); wrap.insert("config".to_string(), Box::new(auth_data)); - self.inner.config.as_mut().unwrap().merge( + self.config.as_mut().unwrap().merge( PhpMixed::Array(wrap), self.auth_config_file.as_ref().unwrap().get_path(), ); } self.inner - .inner .get_io() - .load_configuration(self.inner.config.as_ref().unwrap()); + .load_configuration(self.config.as_ref().unwrap()); // List the configuration of the file settings if input.get_option("list").as_bool() == Some(true) { self.list_configuration( - self.inner.config.as_ref().unwrap().all(), - self.inner.config.as_ref().unwrap().raw(), + self.config.as_ref().unwrap().all(), + self.config.as_ref().unwrap().raw(), output, None, input.get_option("source").as_bool() == Some(true), @@ -310,8 +296,8 @@ impl ConfigCommand { properties_defaults.insert("license".to_string(), PhpMixed::List(vec![])); properties_defaults.insert("suggest".to_string(), PhpMixed::List(vec![])); properties_defaults.insert("extra".to_string(), PhpMixed::List(vec![])); - let raw_data = self.inner.config_file.as_ref().unwrap().read()?; - let mut data = self.inner.config.as_ref().unwrap().all(); + let raw_data = self.config_file.as_ref().unwrap().read()?; + let mut data = self.config.as_ref().unwrap().all(); let mut source = self .inner .config @@ -399,7 +385,7 @@ impl ConfigCommand { .map(|c| c.contains_key(&setting_key)) .unwrap_or(false) { - value = self.inner.config.as_ref().unwrap().get_with_flags( + value = self.config.as_ref().unwrap().get_with_flags( &setting_key, if input.get_option("absolute").as_bool() == Some(true) { 0 @@ -442,7 +428,7 @@ impl ConfigCommand { .unwrap_or_default(), true, ) { - value = PhpMixed::Object(ArrayObject::new()); + value = PhpMixed::Object(ArrayObject::new(None)); } } } @@ -482,7 +468,7 @@ impl ConfigCommand { source_of_config_value = format!(" ({})", source); } - self.inner.inner.get_io().write( + self.get_io().write( &format!("{}{}", value_str, source_of_config_value), true, io_interface::QUIET, @@ -516,8 +502,7 @@ impl ConfigCommand { // allow unsetting audit config entirely if input.get_option("unset").as_bool() == Some(true) && setting_key == "audit" { - self.inner - .config_source + self.config_source .as_mut() .unwrap() .remove_config_setting(&setting_key); @@ -539,13 +524,12 @@ impl ConfigCommand { .as_bool() .unwrap_or(false) { - self.inner.inner.get_io().write_error( + self.get_io().write_error( "<info>You are now running Composer with SSL/TLS protection enabled.</info>", ); } - self.inner - .config_source + self.config_source .as_mut() .unwrap() .remove_config_setting(&setting_key); @@ -572,8 +556,7 @@ impl ConfigCommand { .unwrap_or(false) { if input.get_option("unset").as_bool() == Some(true) { - self.inner - .config_source + self.config_source .as_mut() .unwrap() .remove_config_setting(&setting_key); @@ -596,8 +579,7 @@ impl ConfigCommand { .into()); } - self.inner - .config_source + self.config_source .as_mut() .unwrap() .add_config_setting(&setting_key, PhpMixed::String(values[0].clone())); @@ -615,8 +597,7 @@ impl ConfigCommand { .unwrap_or(false) { if input.get_option("unset").as_bool() == Some(true) { - self.inner - .config_source + self.config_source .as_mut() .unwrap() .remove_config_setting(&setting_key); @@ -634,8 +615,7 @@ impl ConfigCommand { let normalized_value = boolean_normalizer(&PhpMixed::String(values[0].clone())); - self.inner - .config_source + self.config_source .as_mut() .unwrap() .add_config_setting(&setting_key, normalized_value); @@ -661,8 +641,7 @@ impl ConfigCommand { if input.get_option("unset").as_bool() == Some(true) && (unique_props.contains_key(&setting_key) || multi_props.contains_key(&setting_key)) { - self.inner - .config_source + self.config_source .as_mut() .unwrap() .remove_property(&setting_key); @@ -690,8 +669,7 @@ impl ConfigCommand { .unwrap_or(false) { if input.get_option("unset").as_bool() == Some(true) { - self.inner - .config_source + self.config_source .as_mut() .unwrap() .remove_repository(&matches[1]); @@ -709,7 +687,7 @@ impl ConfigCommand { "url".to_string(), Box::new(PhpMixed::String(values[1].clone())), ); - self.inner.config_source.as_mut().unwrap().add_repository( + self.config_source.as_mut().unwrap().add_repository( &matches[1], PhpMixed::Array(repo), input.get_option("append").as_bool() == Some(true), @@ -725,7 +703,7 @@ impl ConfigCommand { .as_bool() .unwrap_or(false) { - self.inner.config_source.as_mut().unwrap().add_repository( + self.config_source.as_mut().unwrap().add_repository( &matches[1], PhpMixed::Bool(false), input.get_option("append").as_bool() == Some(true), @@ -735,7 +713,7 @@ impl ConfigCommand { } } else { let value = JsonFile::parse_json(&values[0], "composer.json")?; - self.inner.config_source.as_mut().unwrap().add_repository( + self.config_source.as_mut().unwrap().add_repository( &matches[1], value, input.get_option("append").as_bool() == Some(true), @@ -756,8 +734,7 @@ impl ConfigCommand { let mut matches: Vec<String> = vec![]; if Preg::is_match("/^extra\\.(.+)/", &setting_key, Some(&mut matches)).unwrap_or(false) { if input.get_option("unset").as_bool() == Some(true) { - self.inner - .config_source + self.config_source .as_mut() .unwrap() .remove_property(&setting_key); @@ -769,7 +746,7 @@ impl ConfigCommand { if input.get_option("json").as_bool() == Some(true) { value = JsonFile::parse_json(&values[0], "composer.json")?; if input.get_option("merge").as_bool() == Some(true) { - let current_value_outer = self.inner.config_file.as_ref().unwrap().read()?; + let current_value_outer = self.config_file.as_ref().unwrap().read()?; let bits = explode(".", &setting_key); let mut current_value: PhpMixed = current_value_outer; for bit in &bits { @@ -801,8 +778,7 @@ impl ConfigCommand { } } } - self.inner - .config_source + self.config_source .as_mut() .unwrap() .add_property(&setting_key, value); @@ -814,8 +790,7 @@ impl ConfigCommand { let mut matches: Vec<String> = vec![]; if Preg::is_match("/^suggest\\.(.+)/", &setting_key, Some(&mut matches)).unwrap_or(false) { if input.get_option("unset").as_bool() == Some(true) { - self.inner - .config_source + self.config_source .as_mut() .unwrap() .remove_property(&setting_key); @@ -823,8 +798,7 @@ impl ConfigCommand { return Ok(0); } - self.inner - .config_source + self.config_source .as_mut() .unwrap() .add_property(&setting_key, PhpMixed::String(implode(" ", &values))); @@ -839,8 +813,7 @@ impl ConfigCommand { true, ) && input.get_option("unset").as_bool() == Some(true) { - self.inner - .config_source + self.config_source .as_mut() .unwrap() .remove_property(&setting_key); @@ -852,8 +825,7 @@ impl ConfigCommand { let mut matches: Vec<String> = vec![]; if Preg::is_match("/^platform\\.(.+)/", &setting_key, Some(&mut matches)).unwrap_or(false) { if input.get_option("unset").as_bool() == Some(true) { - self.inner - .config_source + self.config_source .as_mut() .unwrap() .remove_config_setting(&setting_key); @@ -866,8 +838,7 @@ impl ConfigCommand { } else { PhpMixed::String(values[0].clone()) }; - self.inner - .config_source + self.config_source .as_mut() .unwrap() .add_config_setting(&setting_key, value); @@ -877,8 +848,7 @@ impl ConfigCommand { // handle unsetting platform if setting_key == "platform" && input.get_option("unset").as_bool() == Some(true) { - self.inner - .config_source + self.config_source .as_mut() .unwrap() .remove_config_setting(&setting_key); @@ -896,8 +866,7 @@ impl ConfigCommand { true, ) { if input.get_option("unset").as_bool() == Some(true) { - self.inner - .config_source + self.config_source .as_mut() .unwrap() .remove_config_setting(&setting_key); @@ -923,7 +892,7 @@ impl ConfigCommand { } if input.get_option("merge").as_bool() == Some(true) { - let current_config = self.inner.config_file.as_ref().unwrap().read()?; + let current_config = self.config_file.as_ref().unwrap().read()?; let key_suffix = str_replace("audit.", "", &setting_key); let current_value = current_config .as_array() @@ -964,8 +933,7 @@ impl ConfigCommand { } } - self.inner - .config_source + self.config_source .as_mut() .unwrap() .add_config_setting(&setting_key, value); @@ -982,7 +950,7 @@ impl ConfigCommand { ).unwrap_or(false) { if input.get_option("unset").as_bool() == Some(true) { self.auth_config_source.as_mut().unwrap().remove_config_setting(&format!("{}.{}", matches[1], matches[2])); - self.inner.config_source.as_mut().unwrap().remove_config_setting(&format!("{}.{}", matches[1], matches[2])); + self.config_source.as_mut().unwrap().remove_config_setting(&format!("{}.{}", matches[1], matches[2])); return Ok(0); } @@ -996,13 +964,13 @@ impl ConfigCommand { } .into()); } - self.inner.config_source.as_mut().unwrap().remove_config_setting(&key); + self.config_source.as_mut().unwrap().remove_config_setting(&key); let mut obj: IndexMap<String, Box<PhpMixed>> = IndexMap::new(); obj.insert("consumer-key".to_string(), Box::new(PhpMixed::String(values[0].clone()))); obj.insert("consumer-secret".to_string(), Box::new(PhpMixed::String(values[1].clone()))); self.auth_config_source.as_mut().unwrap().add_config_setting(&key, PhpMixed::Array(obj)); } else if matches[1] == "gitlab-token" && 2 == count(&values) { - self.inner.config_source.as_mut().unwrap().remove_config_setting(&key); + self.config_source.as_mut().unwrap().remove_config_setting(&key); let mut obj: IndexMap<String, Box<PhpMixed>> = IndexMap::new(); obj.insert("username".to_string(), Box::new(PhpMixed::String(values[0].clone()))); obj.insert("token".to_string(), Box::new(PhpMixed::String(values[1].clone()))); @@ -1019,7 +987,7 @@ impl ConfigCommand { } .into()); } - self.inner.config_source.as_mut().unwrap().remove_config_setting(&key); + self.config_source.as_mut().unwrap().remove_config_setting(&key); self.auth_config_source.as_mut().unwrap().add_config_setting(&key, PhpMixed::String(values[0].clone())); } else if matches[1] == "http-basic" { if 2 != count(&values) { @@ -1029,7 +997,7 @@ impl ConfigCommand { } .into()); } - self.inner.config_source.as_mut().unwrap().remove_config_setting(&key); + self.config_source.as_mut().unwrap().remove_config_setting(&key); let mut obj: IndexMap<String, Box<PhpMixed>> = IndexMap::new(); obj.insert("username".to_string(), Box::new(PhpMixed::String(values[0].clone()))); obj.insert("password".to_string(), Box::new(PhpMixed::String(values[1].clone()))); @@ -1067,7 +1035,7 @@ impl ConfigCommand { formatted_headers.push(Box::new(PhpMixed::String(header.clone()))); } - self.inner.config_source.as_mut().unwrap().remove_config_setting(&key); + self.config_source.as_mut().unwrap().remove_config_setting(&key); self.auth_config_source.as_mut().unwrap().add_config_setting(&key, PhpMixed::List(formatted_headers)); } else if matches[1] == "forgejo-token" { if 2 != count(&values) { @@ -1077,7 +1045,7 @@ impl ConfigCommand { } .into()); } - self.inner.config_source.as_mut().unwrap().remove_config_setting(&key); + self.config_source.as_mut().unwrap().remove_config_setting(&key); let mut obj: IndexMap<String, Box<PhpMixed>> = IndexMap::new(); obj.insert("username".to_string(), Box::new(PhpMixed::String(values[0].clone()))); obj.insert("token".to_string(), Box::new(PhpMixed::String(values[1].clone()))); @@ -1091,8 +1059,7 @@ impl ConfigCommand { let mut matches: Vec<String> = vec![]; if Preg::is_match("/^scripts\\.(.+)/", &setting_key, Some(&mut matches)).unwrap_or(false) { if input.get_option("unset").as_bool() == Some(true) { - self.inner - .config_source + self.config_source .as_mut() .unwrap() .remove_property(&setting_key); @@ -1110,8 +1077,7 @@ impl ConfigCommand { } else { PhpMixed::String(values[0].clone()) }; - self.inner - .config_source + self.config_source .as_mut() .unwrap() .add_property(&setting_key, value); @@ -1121,8 +1087,7 @@ impl ConfigCommand { // handle unsetting other top level properties if input.get_option("unset").as_bool() == Some(true) { - self.inner - .config_source + self.config_source .as_mut() .unwrap() .remove_property(&setting_key); @@ -1186,7 +1151,7 @@ impl ConfigCommand { .as_bool() .unwrap_or(false) { - self.inner.inner.get_io().write_error( + self.get_io().write_error( "<info>You are now running Composer with SSL/TLS protection enabled.</info>", ); } else if normalized_value.as_bool().unwrap_or(false) @@ -1199,12 +1164,12 @@ impl ConfigCommand { .as_bool() .unwrap_or(false) { - self.inner.inner.get_io().write_error("<warning>You are now running Composer with SSL/TLS protection disabled.</warning>"); + self.get_io().write_error("<warning>You are now running Composer with SSL/TLS protection disabled.</warning>"); } } call_user_func( - self.inner.config_source.as_mut().unwrap(), + self.config_source.as_mut().unwrap(), method, vec![PhpMixed::String(key.to_string()), normalized_value], ); @@ -1243,7 +1208,7 @@ impl ConfigCommand { } call_user_func( - self.inner.config_source.as_mut().unwrap(), + self.config_source.as_mut().unwrap(), method, vec![PhpMixed::String(key.to_string()), normalizer(&values_mixed)], ); @@ -1260,7 +1225,7 @@ impl ConfigCommand { show_source: bool, ) { let orig_k = k.clone(); - let io = self.inner.inner.get_io(); + let io = self.get_io(); let contents_arr = contents.as_array().cloned().unwrap_or_default(); let raw_contents_arr = raw_contents.as_array().cloned().unwrap_or_default(); let mut k = k; @@ -1320,11 +1285,11 @@ impl ConfigCommand { let source = if show_source { format!( " ({})", - self.inner - .config - .as_ref() - .unwrap() - .get_source_of_value(&format!("{}{}", k.clone().unwrap_or_default(), key)) + self.config.as_ref().unwrap().get_source_of_value(&format!( + "{}{}", + k.clone().unwrap_or_default(), + key + )) ) } else { String::new() @@ -1385,122 +1350,7 @@ impl ConfigCommand { } } - /// Suggest setting-keys, while taking given options in account. - fn suggest_setting_keys(&self) -> Box<dyn Fn(&CompletionInput) -> Vec<String>> { - Box::new(|input: &CompletionInput| -> Vec<String> { - if input.get_option("list").as_bool() == Some(true) - || input.get_option("editor").as_bool() == Some(true) - || input.get_option("auth").as_bool() == Some(true) - { - return vec![]; - } - - // initialize configuration - let mut config = match Factory::create_config(None) { - Ok(c) => c, - Err(_) => return vec![], - }; - - // load configuration - // TODO: BaseConfigCommand::get_composer_config_file is an instance method; using a free helper here. - let config_file = - JsonFile::new(get_composer_config_file_static(input, &config), None, None); - if config_file.exists() { - config.merge( - config_file.read().unwrap_or(PhpMixed::Null), - config_file.get_path(), - ); - } - - // load auth-configuration - let auth_config_file = - JsonFile::new(get_auth_config_file_static(input, &config), None, None); - if auth_config_file.exists() { - let mut wrap: IndexMap<String, Box<PhpMixed>> = IndexMap::new(); - wrap.insert( - "config".to_string(), - Box::new(auth_config_file.read().unwrap_or(PhpMixed::Null)), - ); - config.merge(PhpMixed::Array(wrap), auth_config_file.get_path()); - } - - // collect all configuration setting-keys - let raw_config = config.raw(); - let raw_arr = raw_config.as_array().cloned().unwrap_or_default(); - let mut keys: Vec<String> = array_merge( - flatten_setting_keys( - raw_arr - .get("config") - .map(|v| (**v).clone()) - .unwrap_or(PhpMixed::Null), - "", - ), - flatten_setting_keys( - raw_arr - .get("repositories") - .map(|v| (**v).clone()) - .unwrap_or(PhpMixed::Null), - "repositories.", - ), - ); - - // if unsetting … - if input.get_option("unset").as_bool() == Some(true) { - // … keep only the currently customized setting-keys … - let sources = vec![ - config_file.get_path().to_string(), - auth_config_file.get_path().to_string(), - ]; - keys = array_filter(keys, |k: &String| -> bool { - in_array(config.get_source_of_value(k).as_str(), &sources, true) - }); - } else { - // … add all configurable package-properties, no matter if it exist - let configurable: Vec<String> = ConfigCommand::CONFIGURABLE_PACKAGE_PROPERTIES - .iter() - .map(|s| s.to_string()) - .collect(); - keys = array_merge(keys, configurable); - - // it would be nice to distinguish between showing and setting - // a value, but that makes the implementation much more complex - // and partially impossible because symfony's implementation - // does not complete arguments followed by other arguments - } - - // add all existing configurable package-properties - if config_file.exists() { - let configurable: Vec<String> = ConfigCommand::CONFIGURABLE_PACKAGE_PROPERTIES - .iter() - .map(|s| s.to_string()) - .collect(); - let properties = array_filter_use_key( - config_file - .read() - .unwrap_or(PhpMixed::Null) - .as_array() - .cloned() - .unwrap_or_default(), - |key: &String| -> bool { in_array(key.as_str(), &configurable, true) }, - ); - - keys = array_merge(keys, flatten_setting_keys(PhpMixed::Array(properties), "")); - } - - // filter settings-keys by completion value - let completion_value = input.get_completion_value(); - - if completion_value != "" { - keys = array_filter(keys, |key: &String| -> bool { - str_starts_with(key, &completion_value) - }); - } - - sort(&mut keys); - - array_unique(keys) - }) - } + // TODO(cli-completion): fn suggest_setting_keys(&self) -> Box<dyn Fn(&CompletionInput) -> Vec<String>> } // PHP signature: function ($val): bool / ($val) -> bool/string @@ -2141,34 +1991,8 @@ fn flatten_setting_keys(config: PhpMixed, prefix: &str) -> Vec<String> { merged } -// Helpers for the suggester since BaseConfigCommand methods need an instance. -fn get_composer_config_file_static(input: &CompletionInput, config: &Config) -> String { - if input.get_option("global").as_bool() == Some(true) { - format!( - "{}/config.json", - config.get("home").as_string().unwrap_or("") - ) - } else { - input - .get_option("file") - .as_string() - .map(|s| s.to_string()) - .unwrap_or_else(|| Factory::get_composer_file()) - } -} - -fn get_auth_config_file_static(input: &CompletionInput, config: &Config) -> String { - if input.get_option("global").as_bool() == Some(true) { - format!("{}/auth.json", config.get("home").as_string().unwrap_or("")) - } else { - let composer_config = get_composer_config_file_static(input, config); - let parent = std::path::Path::new(&composer_config) - .parent() - .map(|p| p.to_string_lossy().to_string()) - .unwrap_or_default(); - format!("{}/auth.json", parent) - } -} +// TODO(cli-completion): get_composer_config_file_static / get_auth_config_file_static helpers +// were only used by suggest_setting_keys; dropped along with completion support. // PHP key($value) — first key of an array fn key_first_key(value: &PhpMixed) -> Option<String> { @@ -2183,32 +2007,6 @@ fn key_first_key(value: &PhpMixed) -> Option<String> { None } -impl BaseCommand for ConfigCommand { - fn inner(&self) -> &CommandBase { - &self.inner - } - - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io - } -} - impl BaseConfigCommand for ConfigCommand { fn config(&self) -> Option<&Config> { self.config.as_ref() @@ -2235,4 +2033,12 @@ impl BaseConfigCommand for ConfigCommand { } } -impl Command for ConfigCommand {} +impl HasBaseCommandData for ConfigCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data + } + + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data + } +} diff --git a/crates/shirabe/src/command/create_project_command.rs b/crates/shirabe/src/command/create_project_command.rs index 68f0369..cc637ce 100644 --- a/crates/shirabe/src/command/create_project_command.rs +++ b/crates/shirabe/src/command/create_project_command.rs @@ -3,8 +3,6 @@ use anyhow::Result; use shirabe_external_packages::composer::pcre::preg::Preg; use shirabe_external_packages::seld::signal::signal_handler::SignalHandler; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::component::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::component::console::output::output_interface::OutputInterface; use shirabe_external_packages::symfony::component::finder::finder::Finder; @@ -15,10 +13,10 @@ use shirabe_php_shim::{ }; use crate::advisory::auditor::Auditor; -use crate::command::base_command::BaseCommand; -use crate::command::completion_trait::CompletionTrait; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::config::Config; +use crate::config::config_source_interface::ConfigSourceInterface; use crate::config::json_config_source::JsonConfigSource; use crate::console::input::input_argument::InputArgument; use crate::console::input::input_option::InputOption; @@ -50,57 +48,44 @@ use crate::util::process_executor::ProcessExecutor; /// Install a package as new project into new directory. #[derive(Debug)] pub struct CreateProjectCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, /// @var SuggestedPackagesReporter pub(crate) suggested_packages_reporter: Option<SuggestedPackagesReporter>, } -impl CompletionTrait for CreateProjectCommand { - fn require_composer( - &self, - disable_plugins: Option<bool>, - disable_scripts: Option<bool>, - ) -> Composer { - todo!() - } -} - impl CreateProjectCommand { fn configure(&mut self) { - let suggest_prefer_install = self.suggest_prefer_install(); - let suggest_available_package = self.suggest_available_package(); - self.inner + // TODO(cli-completion): suggest_prefer_install / suggest_available_package + self .set_name("create-project") .set_description("Creates new project from a package into given directory") .set_definition(vec![ - InputArgument::new("package", Some(InputArgument::OPTIONAL), "Package name to be installed", None, suggest_available_package), - InputArgument::new("directory", Some(InputArgument::OPTIONAL), "Directory where the files should be created", None, vec![]), - InputArgument::new("version", Some(InputArgument::OPTIONAL), "Version, will default to latest", None, vec![]), - InputOption::new("stability", Some(PhpMixed::String("s".to_string())), Some(InputOption::VALUE_REQUIRED), "Minimum-stability allowed (unless a version is specified).", None, vec![]), - InputOption::new("prefer-source", None, Some(InputOption::VALUE_NONE), "Forces installation from package sources when possible, including VCS information.", None, vec![]), - InputOption::new("prefer-dist", None, Some(InputOption::VALUE_NONE), "Forces installation from package dist (default behavior).", None, vec![]), - InputOption::new("prefer-install", None, Some(InputOption::VALUE_REQUIRED), "Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).", None, suggest_prefer_install), - InputOption::new("repository", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Add custom repositories to look the package up, either by URL or using JSON arrays", None, vec![]), - InputOption::new("repository-url", None, Some(InputOption::VALUE_REQUIRED), "DEPRECATED: Use --repository instead.", None, vec![]), - InputOption::new("add-repository", None, Some(InputOption::VALUE_NONE), "Add the custom repository in the composer.json. If a lock file is present it will be deleted and an update will be run instead of install.", None, vec![]), - InputOption::new("dev", None, Some(InputOption::VALUE_NONE), "Enables installation of require-dev packages (enabled by default, only present for BC).", None, vec![]), - InputOption::new("no-dev", None, Some(InputOption::VALUE_NONE), "Disables installation of require-dev packages.", None, vec![]), - InputOption::new("no-custom-installers", None, Some(InputOption::VALUE_NONE), "DEPRECATED: Use no-plugins instead.", None, vec![]), - InputOption::new("no-scripts", None, Some(InputOption::VALUE_NONE), "Whether to prevent execution of all defined scripts in the root package.", None, vec![]), - InputOption::new("no-progress", None, Some(InputOption::VALUE_NONE), "Do not output download progress.", None, vec![]), - InputOption::new("no-secure-http", None, Some(InputOption::VALUE_NONE), "Disable the secure-http config option temporarily while installing the root package. Use at your own risk. Using this flag is a bad idea.", None, vec![]), - InputOption::new("keep-vcs", None, Some(InputOption::VALUE_NONE), "Whether to prevent deleting the vcs folder.", None, vec![]), - InputOption::new("remove-vcs", None, Some(InputOption::VALUE_NONE), "Whether to force deletion of the vcs folder without prompting.", None, vec![]), - InputOption::new("no-install", None, Some(InputOption::VALUE_NONE), "Whether to skip installation of the package dependencies.", None, vec![]), - InputOption::new("no-audit", None, Some(InputOption::VALUE_NONE), "Whether to skip auditing of the installed package dependencies (can also be set via the COMPOSER_NO_AUDIT=1 env var).", None, vec![]), - InputOption::new("audit-format", None, Some(InputOption::VALUE_REQUIRED), "Audit output format. Must be \"table\", \"plain\", \"json\" or \"summary\".", Some(PhpMixed::String(Auditor::FORMAT_SUMMARY.to_string())), Auditor::FORMATS.iter().map(|s| s.to_string()).collect()), - InputOption::new("no-security-blocking", None, Some(InputOption::VALUE_NONE), "Allows installing packages with security advisories or that are abandoned (can also be set via the COMPOSER_NO_SECURITY_BLOCKING=1 env var).", None, vec![]), - InputOption::new("ignore-platform-req", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Ignore a specific platform requirement (php & ext- packages).", None, vec![]), - InputOption::new("ignore-platform-reqs", None, Some(InputOption::VALUE_NONE), "Ignore all platform requirements (php & ext- packages).", None, vec![]), - InputOption::new("ask", None, Some(InputOption::VALUE_NONE), "Whether to ask for project directory.", None, vec![]), + InputArgument::new("package", Some(InputArgument::OPTIONAL), "Package name to be installed", None), + InputArgument::new("directory", Some(InputArgument::OPTIONAL), "Directory where the files should be created", None), + InputArgument::new("version", Some(InputArgument::OPTIONAL), "Version, will default to latest", None), + InputOption::new("stability", Some(PhpMixed::String("s".to_string())), Some(InputOption::VALUE_REQUIRED), "Minimum-stability allowed (unless a version is specified).", None), + InputOption::new("prefer-source", None, Some(InputOption::VALUE_NONE), "Forces installation from package sources when possible, including VCS information.", None), + InputOption::new("prefer-dist", None, Some(InputOption::VALUE_NONE), "Forces installation from package dist (default behavior).", None), + InputOption::new("prefer-install", None, Some(InputOption::VALUE_REQUIRED), "Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).", None), + InputOption::new("repository", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Add custom repositories to look the package up, either by URL or using JSON arrays", None), + InputOption::new("repository-url", None, Some(InputOption::VALUE_REQUIRED), "DEPRECATED: Use --repository instead.", None), + InputOption::new("add-repository", None, Some(InputOption::VALUE_NONE), "Add the custom repository in the composer.json. If a lock file is present it will be deleted and an update will be run instead of install.", None), + InputOption::new("dev", None, Some(InputOption::VALUE_NONE), "Enables installation of require-dev packages (enabled by default, only present for BC).", None), + InputOption::new("no-dev", None, Some(InputOption::VALUE_NONE), "Disables installation of require-dev packages.", None), + InputOption::new("no-custom-installers", None, Some(InputOption::VALUE_NONE), "DEPRECATED: Use no-plugins instead.", None), + InputOption::new("no-scripts", None, Some(InputOption::VALUE_NONE), "Whether to prevent execution of all defined scripts in the root package.", None), + InputOption::new("no-progress", None, Some(InputOption::VALUE_NONE), "Do not output download progress.", None), + InputOption::new("no-secure-http", None, Some(InputOption::VALUE_NONE), "Disable the secure-http config option temporarily while installing the root package. Use at your own risk. Using this flag is a bad idea.", None), + InputOption::new("keep-vcs", None, Some(InputOption::VALUE_NONE), "Whether to prevent deleting the vcs folder.", None), + InputOption::new("remove-vcs", None, Some(InputOption::VALUE_NONE), "Whether to force deletion of the vcs folder without prompting.", None), + InputOption::new("no-install", None, Some(InputOption::VALUE_NONE), "Whether to skip installation of the package dependencies.", None), + InputOption::new("no-audit", None, Some(InputOption::VALUE_NONE), "Whether to skip auditing of the installed package dependencies (can also be set via the COMPOSER_NO_AUDIT=1 env var).", None), + InputOption::new("audit-format", None, Some(InputOption::VALUE_REQUIRED), "Audit output format. Must be \"table\", \"plain\", \"json\" or \"summary\".", Some(PhpMixed::String(Auditor::FORMAT_SUMMARY.to_string()))), + InputOption::new("no-security-blocking", None, Some(InputOption::VALUE_NONE), "Allows installing packages with security advisories or that are abandoned (can also be set via the COMPOSER_NO_SECURITY_BLOCKING=1 env var).", None), + InputOption::new("ignore-platform-req", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Ignore a specific platform requirement (php & ext- packages).", None), + InputOption::new("ignore-platform-reqs", None, Some(InputOption::VALUE_NONE), "Ignore all platform requirements (php & ext- packages).", None), + InputOption::new("ask", None, Some(InputOption::VALUE_NONE), "Whether to ask for project directory.", None), ]) .set_help( "The <info>create-project</info> command creates a new project from a given\n\ @@ -127,7 +112,7 @@ impl CreateProjectCommand { _output: &dyn OutputInterface, ) -> Result<i64> { let config = Factory::create_config(None, None)?; - let io = self.inner.get_io(); + let io = self.get_io(); let (prefer_source, prefer_dist) = self .inner @@ -203,7 +188,7 @@ impl CreateProjectCommand { input.get_option("no-scripts").as_bool().unwrap_or(false), input.get_option("no-progress").as_bool().unwrap_or(false), input.get_option("no-install").as_bool().unwrap_or(false), - Some(self.inner.get_platform_requirement_filter(input)?), + Some(self.get_platform_requirement_filter(input)?), !input .get_option("no-secure-http") .as_bool() @@ -240,7 +225,7 @@ impl CreateProjectCommand { secure_http: bool, add_repository: bool, ) -> Result<i64> { - let old_cwd = Platform::get_cwd(); + let old_cwd = Platform::get_cwd(false)?; let repositories: Option<Vec<String>> = match repositories { Some(PhpMixed::Null) | None => None, @@ -316,7 +301,7 @@ impl CreateProjectCommand { "composer.json".to_string(), None, None, - )); + )?); let is_packagist_disabled = (repo_config.contains_key("packagist") && repo_config.len() == 1 @@ -346,8 +331,8 @@ impl CreateProjectCommand { } } - let process = composer.get_loop().get_process_executor(); - let fs = Filesystem::new(Some(process)); + let process = composer.get_loop().borrow().get_process_executor().cloned(); + let fs = Filesystem::new(process); // dispatch event composer.get_event_dispatcher().dispatch_script( @@ -435,7 +420,7 @@ impl CreateProjectCommand { finder .depth(0) .directories() - .r#in(&Platform::get_cwd()) + .r#in(&Platform::get_cwd(false)?) .ignore_vcs(false) .ignore_dot_files(false); for vcs_name in [ @@ -486,7 +471,7 @@ impl CreateProjectCommand { if !has_vcs { let package = composer.get_package(); let config_source = - JsonConfigSource::new(JsonFile::new("composer.json".to_string(), None, None)); + JsonConfigSource::new(JsonFile::new("composer.json".to_string(), None, None)?); for (r#type, meta) in SUPPORTED_LINK_TYPES.iter() { // PHP: $package->{'get'.$meta['method']}() — dynamic getter dispatch // TODO(phase-b): dynamic getter dispatch by name @@ -505,9 +490,12 @@ impl CreateProjectCommand { } // dispatch event - composer - .get_event_dispatcher() - .dispatch_script(ScriptEvents::POST_CREATE_PROJECT_CMD, install_dev_packages); + composer.get_event_dispatcher().dispatch_script( + ScriptEvents::POST_CREATE_PROJECT_CMD, + install_dev_packages, + vec![], + indexmap::IndexMap::new(), + ); chdir(&old_cwd); @@ -555,7 +543,7 @@ impl CreateProjectCommand { let mut parts = explode_with_limit("/", &name, 2); format!( "{}{}{}", - Platform::get_cwd(), + Platform::get_cwd(false)?, DIRECTORY_SEPARATOR, array_pop(&mut parts).unwrap_or_default() ) @@ -569,7 +557,7 @@ impl CreateProjectCommand { if !fs.is_absolute_path(&directory) { directory = format!( "{}{}{}", - Platform::get_cwd(), + Platform::get_cwd(false)?, DIRECTORY_SEPARATOR, directory ); @@ -604,7 +592,7 @@ impl CreateProjectCommand { io.write_error(&format!( "<info>Creating a \"{}\" project at \"{}\"</info>", package_name, - fs.find_shortest_path(&Platform::get_cwd(), &directory, true) + fs.find_shortest_path(&Platform::get_cwd(false)?, &directory, true, false) )); if file_exists(&directory) { @@ -846,10 +834,11 @@ impl CreateProjectCommand { } let dm = composer.get_download_manager(); - dm.set_prefer_source(prefer_source) + dm.borrow_mut() + .set_prefer_source(prefer_source) .set_prefer_dist(prefer_dist); - let project_installer = ProjectInstaller::new(&directory, dm, &fs); + let project_installer = ProjectInstaller::new(&directory, dm.clone(), &fs); let im = composer.get_installation_manager(); im.set_output_progress(!no_progress); im.add_installer(Box::new(project_installer)); @@ -896,8 +885,7 @@ impl CreateProjectCommand { disable_plugins: bool, disable_scripts: Option<bool>, ) -> Result<Composer> { - self.inner - .create_composer_instance(input, io, config, disable_plugins, disable_scripts) + self.create_composer_instance(input, io, config, disable_plugins, disable_scripts) } fn create_audit_config( @@ -905,34 +893,16 @@ impl CreateProjectCommand { config: &Config, input: &dyn InputInterface, ) -> Result<crate::advisory::audit_config::AuditConfig> { - self.inner.create_audit_config(config, input) + self.create_audit_config(config, input) } } -impl BaseCommand for CreateProjectCommand { - fn inner(&self) -> &CommandBase { - &self.inner +impl HasBaseCommandData for CreateProjectCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for CreateProjectCommand {} diff --git a/crates/shirabe/src/command/depends_command.rs b/crates/shirabe/src/command/depends_command.rs index 8a99ede..23b1d58 100644 --- a/crates/shirabe/src/command/depends_command.rs +++ b/crates/shirabe/src/command/depends_command.rs @@ -1,12 +1,7 @@ //! ref: composer/src/Composer/Command/DependsCommand.php -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; - -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::command::base_dependency_command::BaseDependencyCommand; -use crate::command::completion_trait::CompletionTrait; -use crate::composer::Composer; use crate::console::input::input_argument::InputArgument; use crate::console::input::input_option::InputOption; use crate::io::io_interface::IOInterface; @@ -15,55 +10,44 @@ use shirabe_external_packages::symfony::console::output::output_interface::Outpu #[derive(Debug)] pub struct DependsCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, colors: Vec<String>, } -impl CompletionTrait for DependsCommand { - fn require_composer( - &self, - disable_plugins: Option<bool>, - disable_scripts: Option<bool>, - ) -> Composer { - todo!() - } -} - impl DependsCommand { pub fn configure(&mut self) { - let package_suggestions = self.suggest_installed_package(true, true); - self.inner - .set_name("depends") - .set_aliases(vec!["why".to_string()]) + // TODO(cli-completion): suggest_installed_package(true, true) for `package` argument + self.set_name("depends") + .set_aliases(&["why".to_string()]) .set_description("Shows which packages cause the given package to be installed") .set_definition(vec![ InputArgument::new( BaseDependencyCommand::ARGUMENT_PACKAGE, - InputArgument::REQUIRED, + Some(InputArgument::REQUIRED), "Package to inspect", None, - package_suggestions, ), InputOption::new( BaseDependencyCommand::OPTION_RECURSIVE, - Some("r"), - InputOption::VALUE_NONE, + Some(shirabe_php_shim::PhpMixed::String("r".to_string())), + Some(InputOption::VALUE_NONE), "Recursively resolves up to the root package", + None, ), InputOption::new( BaseDependencyCommand::OPTION_TREE, - Some("t"), - InputOption::VALUE_NONE, + Some(shirabe_php_shim::PhpMixed::String("t".to_string())), + Some(InputOption::VALUE_NONE), "Prints the results as a nested tree", + None, ), InputOption::new( "locked", None, - InputOption::VALUE_NONE, + Some(InputOption::VALUE_NONE), "Read dependency information from composer.lock", + None, ), ]) .set_help( @@ -74,44 +58,19 @@ impl DependsCommand { } pub fn execute(&self, input: &dyn InputInterface, output: &dyn OutputInterface) -> i64 { - self.inner.do_execute(input, output) - } -} - -impl BaseCommand for DependsCommand { - fn inner(&self) -> &CommandBase { - &self.inner - } - - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + // TODO(phase-b): wire `do_execute` from BaseDependencyCommand trait without conflicting with + // BaseCommand blanket impl + let _ = (input, output); + todo!() } } -impl BaseDependencyCommand for DependsCommand { - fn colors(&self) -> &[String] { - &self.colors +impl HasBaseCommandData for DependsCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn colors_mut(&mut self) -> &mut [String] { - &mut self.colors + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for DependsCommand {} diff --git a/crates/shirabe/src/command/diagnose_command.rs b/crates/shirabe/src/command/diagnose_command.rs index d6205cd..32ed378 100644 --- a/crates/shirabe/src/command/diagnose_command.rs +++ b/crates/shirabe/src/command/diagnose_command.rs @@ -4,8 +4,6 @@ use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::preg::Preg; use shirabe_external_packages::composer::xdebug_handler::xdebug_handler::XdebugHandler; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::component::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::component::console::output::output_interface::OutputInterface; use shirabe_external_packages::symfony::component::process::executable_finder::ExecutableFinder; @@ -21,7 +19,7 @@ use shirabe_php_shim::{ }; use crate::advisory::auditor::Auditor; -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::config::Config; use crate::downloader::transport_exception::TransportException; @@ -54,9 +52,7 @@ use crate::util::process_executor::ProcessExecutor; #[derive(Debug)] pub struct DiagnoseCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, pub(crate) http_downloader: Option<HttpDownloader>, pub(crate) process: Option<ProcessExecutor>, @@ -65,7 +61,7 @@ pub struct DiagnoseCommand { impl DiagnoseCommand { pub(crate) fn configure(&mut self) { - self.inner + self .set_name("diagnose") .set_description("Diagnoses the system to identify common errors") .set_help( @@ -80,8 +76,8 @@ impl DiagnoseCommand { input: &dyn InputInterface, output: &dyn OutputInterface, ) -> anyhow::Result<i64> { - let composer = self.inner.try_composer(); - let io = self.inner.get_io(); + let composer = self.try_composer(None, None); + let io = self.get_io(); let config: Config; if let Some(ref c) = composer { @@ -96,10 +92,12 @@ impl DiagnoseCommand { IndexMap::new(), ); c.get_event_dispatcher() - .dispatch(command_event.get_name(), &command_event); + .dispatch(Some(command_event.get_name()), None); self.process = Some( c.get_loop() + .borrow() .get_process_executor() + .cloned() .unwrap_or_else(|| ProcessExecutor::new(Some(io.clone_box()))), ); } else { @@ -108,15 +106,12 @@ impl DiagnoseCommand { self.process = Some(ProcessExecutor::new(Some(io.clone_box()))); } - let mut secure_http_wrap: IndexMap<String, Box<PhpMixed>> = IndexMap::new(); let mut config_inner: IndexMap<String, Box<PhpMixed>> = IndexMap::new(); config_inner.insert("secure-http".to_string(), Box::new(PhpMixed::Bool(false))); - secure_http_wrap.insert( - "config".to_string(), - Box::new(PhpMixed::Array(config_inner)), - ); + let mut secure_http_wrap: IndexMap<String, PhpMixed> = IndexMap::new(); + secure_http_wrap.insert("config".to_string(), PhpMixed::Array(config_inner)); let mut config = config; - config.merge(PhpMixed::Array(secure_http_wrap), Config::SOURCE_COMMAND); + config.merge(&secure_http_wrap, Config::SOURCE_COMMAND); config.prohibit_url_by_config("http://repo.packagist.org", &NullIO::new()); self.http_downloader = Some(Factory::create_http_downloader(io, &config)?); @@ -260,7 +255,7 @@ impl DiagnoseCommand { { let composer_repo = ComposerRepository::new( PhpMixed::Array(repo_arr.clone()), - self.inner.get_io().clone_box(), + self.get_io().clone_box(), config.clone(), self.http_downloader.clone().unwrap(), ); @@ -384,7 +379,7 @@ impl DiagnoseCommand { } fn check_composer_schema(&self) -> anyhow::Result<PhpMixed> { - let validator = ConfigValidator::new(self.inner.get_io().clone_box()); + let validator = ConfigValidator::new(self.get_io().clone_box()); let (errors, _, warnings) = validator.validate(&Factory::get_composer_file()); if !errors.is_empty() || !warnings.is_empty() { @@ -489,7 +484,7 @@ impl DiagnoseCommand { result_list.push(Box::new(PhpMixed::String(format!( "<error>[{}] {}</error>", get_class(te), - te.get_message() + te.message )))); } else { return Err(e); @@ -539,7 +534,7 @@ impl DiagnoseCommand { result_list.push(Box::new(PhpMixed::String(format!( "<error>[{}] {}</error>", get_class(te), - te.get_message() + te.message )))); } else { return Err(e); @@ -568,7 +563,7 @@ impl DiagnoseCommand { return Ok(result); } - let proxy_status = proxy.get_status(); + let proxy_status = proxy.get_status(None).unwrap_or_default(); if proxy.is_excluded_by_no_proxy() { return Ok(PhpMixed::String(format!( @@ -629,7 +624,7 @@ impl DiagnoseCommand { return Ok(result); } - self.inner.get_io().set_authentication( + self.get_io().set_authentication( domain.to_string(), token.to_string(), Some("x-oauth-basic".to_string()), @@ -690,7 +685,7 @@ impl DiagnoseCommand { } if let Some(t) = token { - self.inner.get_io().set_authentication( + self.get_io().set_authentication( domain.to_string(), t.to_string(), Some("x-oauth-basic".to_string()), @@ -752,7 +747,7 @@ impl DiagnoseCommand { fn check_pub_keys(&self, config: &Config) -> PhpMixed { let home = config.get("home").as_string().unwrap_or("").to_string(); let mut errors: Vec<Box<PhpMixed>> = vec![]; - let io = self.inner.get_io(); + let io = self.get_io(); if file_exists(&format!("{}/keys.tags.pub", home)) && file_exists(&format!("{}/keys.dev.pub", home)) @@ -802,7 +797,7 @@ impl DiagnoseCommand { } let versions_util = Versions::new(config.clone(), self.http_downloader.clone().unwrap()); - let latest = match versions_util.get_latest() { + let latest = match versions_util.get_latest(None) { Ok(l) => l, Err(e) => { return Ok(PhpMixed::String(format!( @@ -851,7 +846,7 @@ impl DiagnoseCommand { "composer/src/Composer/Command/../../../vendor/composer/installed.json".to_string(), None, None, - ); + )?; if !installed_json.exists() { return Ok(PhpMixed::String("<warning>Could not find Composer's installed.json, this must be a non-standard Composer installation.</>".to_string())); } @@ -986,7 +981,7 @@ impl DiagnoseCommand { } fn output_result(&mut self, result: PhpMixed) { - let io = self.inner.get_io(); + let io = self.get_io(); if result.as_bool() == Some(true) { io.write("<info>OK</info>"); @@ -1371,30 +1366,12 @@ impl DiagnoseCommand { } } -impl BaseCommand for DiagnoseCommand { - fn inner(&self) -> &CommandBase { - &self.inner - } - - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() +impl HasBaseCommandData for DiagnoseCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for DiagnoseCommand {} diff --git a/crates/shirabe/src/command/dump_autoload_command.rs b/crates/shirabe/src/command/dump_autoload_command.rs index 43af30d..9389132 100644 --- a/crates/shirabe/src/command/dump_autoload_command.rs +++ b/crates/shirabe/src/command/dump_autoload_command.rs @@ -1,13 +1,11 @@ //! ref: composer/src/Composer/Command/DumpAutoloadCommand.php use anyhow::Result; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; use shirabe_php_shim::{InvalidArgumentException, PhpMixed, file_exists}; -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::console::input::input_option::InputOption; use crate::io::io_interface::IOInterface; @@ -16,29 +14,27 @@ use crate::plugin::plugin_events::PluginEvents; #[derive(Debug)] pub struct DumpAutoloadCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, } impl DumpAutoloadCommand { pub fn configure(&mut self) { - self.inner + self .set_name("dump-autoload") - .set_aliases(vec!["dumpautoload".to_string()]) + .set_aliases(&["dumpautoload".to_string()]) .set_description("Dumps the autoloader") .set_definition(vec![ - InputOption::new("optimize", Some(PhpMixed::String("o".to_string())), Some(InputOption::VALUE_NONE), "Optimizes PSR0 and PSR4 packages to be loaded with classmaps too, good for production.", None, vec![]), - InputOption::new("classmap-authoritative", Some(PhpMixed::String("a".to_string())), Some(InputOption::VALUE_NONE), "Autoload classes from the classmap only. Implicitly enables `--optimize`.", None, vec![]), - InputOption::new("apcu", None, Some(InputOption::VALUE_NONE), "Use APCu to cache found/not-found classes.", None, vec![]), - InputOption::new("apcu-prefix", None, Some(InputOption::VALUE_REQUIRED), "Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu", None, vec![]), - InputOption::new("dry-run", None, Some(InputOption::VALUE_NONE), "Outputs the operations but will not execute anything.", None, vec![]), - InputOption::new("dev", None, Some(InputOption::VALUE_NONE), "Enables autoload-dev rules. Composer will by default infer this automatically according to the last install or update --no-dev state.", None, vec![]), - InputOption::new("no-dev", None, Some(InputOption::VALUE_NONE), "Disables autoload-dev rules. Composer will by default infer this automatically according to the last install or update --no-dev state.", None, vec![]), - InputOption::new("ignore-platform-req", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Ignore a specific platform requirement (php & ext- packages).", None, vec![]), - InputOption::new("ignore-platform-reqs", None, Some(InputOption::VALUE_NONE), "Ignore all platform requirements (php & ext- packages).", None, vec![]), - InputOption::new("strict-psr", None, Some(InputOption::VALUE_NONE), "Return a failed status code (1) if PSR-4 or PSR-0 mapping errors are present. Requires --optimize to work.", None, vec![]), - InputOption::new("strict-ambiguous", None, Some(InputOption::VALUE_NONE), "Return a failed status code (2) if the same class is found in multiple files. Requires --optimize to work.", None, vec![]), + InputOption::new("optimize", Some(PhpMixed::String("o".to_string())), Some(InputOption::VALUE_NONE), "Optimizes PSR0 and PSR4 packages to be loaded with classmaps too, good for production.", None), + InputOption::new("classmap-authoritative", Some(PhpMixed::String("a".to_string())), Some(InputOption::VALUE_NONE), "Autoload classes from the classmap only. Implicitly enables `--optimize`.", None), + InputOption::new("apcu", None, Some(InputOption::VALUE_NONE), "Use APCu to cache found/not-found classes.", None), + InputOption::new("apcu-prefix", None, Some(InputOption::VALUE_REQUIRED), "Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu", None), + InputOption::new("dry-run", None, Some(InputOption::VALUE_NONE), "Outputs the operations but will not execute anything.", None), + InputOption::new("dev", None, Some(InputOption::VALUE_NONE), "Enables autoload-dev rules. Composer will by default infer this automatically according to the last install or update --no-dev state.", None), + InputOption::new("no-dev", None, Some(InputOption::VALUE_NONE), "Disables autoload-dev rules. Composer will by default infer this automatically according to the last install or update --no-dev state.", None), + InputOption::new("ignore-platform-req", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Ignore a specific platform requirement (php & ext- packages).", None), + InputOption::new("ignore-platform-reqs", None, Some(InputOption::VALUE_NONE), "Ignore all platform requirements (php & ext- packages).", None), + InputOption::new("strict-psr", None, Some(InputOption::VALUE_NONE), "Return a failed status code (1) if PSR-4 or PSR-0 mapping errors are present. Requires --optimize to work.", None), + InputOption::new("strict-ambiguous", None, Some(InputOption::VALUE_NONE), "Return a failed status code (2) if the same class is found in multiple files. Requires --optimize to work.", None), ]) .set_help( "<info>php composer.phar dump-autoload</info>\n\n\ @@ -47,20 +43,20 @@ impl DumpAutoloadCommand { } pub fn execute(&self, input: &dyn InputInterface, output: &dyn OutputInterface) -> Result<i64> { - let composer = self.inner.require_composer()?; + let composer = self.require_composer(None, None)?; // TODO(plugin): dispatch CommandEvent let command_event = CommandEvent::new( PluginEvents::COMMAND.to_string(), "dump-autoload".to_string(), - Box::new(input), - Box::new(output), + input, + output, vec![], vec![], ); composer .get_event_dispatcher() - .dispatch(command_event.get_name(), &command_event); + .dispatch(Some(command_event.get_name()), None); let installation_manager = composer.get_installation_manager(); let local_repo = composer.get_repository_manager().get_local_repository(); @@ -72,7 +68,7 @@ impl DumpAutoloadCommand { let install_path = installation_manager.get_install_path(&*local_pkg); if install_path.as_deref().is_some_and(|p| !file_exists(p)) { missing_dependencies = true; - self.inner.get_io().write("<warning>Not all dependencies are installed. Make sure to run a \"composer install\" to install missing dependencies</warning>"); + self.get_io().write("<warning>Not all dependencies are installed. Make sure to run a \"composer install\" to install missing dependencies</warning>"); break; } } @@ -118,16 +114,13 @@ impl DumpAutoloadCommand { } if authoritative { - self.inner - .get_io() + self.get_io() .write("<info>Generating optimized autoload files (authoritative)</info>"); } else if optimize { - self.inner - .get_io() + self.get_io() .write("<info>Generating optimized autoload files</info>"); } else { - self.inner - .get_io() + self.get_io() .write("<info>Generating autoload files</info>"); } @@ -153,8 +146,7 @@ impl DumpAutoloadCommand { generator.set_class_map_authoritative(authoritative); generator.set_run_scripts(true); generator.set_apcu(apcu, apcu_prefix.as_deref()); - generator - .set_platform_requirement_filter(self.inner.get_platform_requirement_filter(input)?); + generator.set_platform_requirement_filter(self.get_platform_requirement_filter(input)?); let class_map = generator.dump( config, &local_repo, @@ -172,16 +164,14 @@ impl DumpAutoloadCommand { let number_of_classes = class_map.len(); if authoritative { - self.inner.get_io().write(&format!("<info>Generated optimized autoload files (authoritative) containing {} classes</info>", number_of_classes)); + self.get_io().write(&format!("<info>Generated optimized autoload files (authoritative) containing {} classes</info>", number_of_classes)); } else if optimize { - self.inner.get_io().write(&format!( + self.get_io().write(&format!( "<info>Generated optimized autoload files containing {} classes</info>", number_of_classes )); } else { - self.inner - .get_io() - .write("<info>Generated autoload files</info>"); + self.get_io().write("<info>Generated autoload files</info>"); } if missing_dependencies @@ -204,30 +194,12 @@ impl DumpAutoloadCommand { } } -impl BaseCommand for DumpAutoloadCommand { - fn inner(&self) -> &CommandBase { - &self.inner +impl HasBaseCommandData for DumpAutoloadCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for DumpAutoloadCommand {} diff --git a/crates/shirabe/src/command/exec_command.rs b/crates/shirabe/src/command/exec_command.rs index 17cdb3e..7f339b6 100644 --- a/crates/shirabe/src/command/exec_command.rs +++ b/crates/shirabe/src/command/exec_command.rs @@ -1,13 +1,11 @@ //! ref: composer/src/Composer/Command/ExecCommand.php use anyhow::Result; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; use shirabe_php_shim::{PhpMixed, RuntimeException, basename, chdir, getcwd, glob}; -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::console::input::input_argument::InputArgument; use crate::console::input::input_option::InputOption; @@ -15,32 +13,28 @@ use crate::io::io_interface::IOInterface; #[derive(Debug)] pub struct ExecCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, } impl ExecCommand { pub fn configure(&mut self) { - self.inner + self .set_name("exec") .set_description("Executes a vendored binary/script") .set_definition(vec![ - InputOption::new("list", Some(PhpMixed::String("l".to_string())), Some(InputOption::VALUE_NONE), "", None, vec![]), + InputOption::new("list", Some(PhpMixed::String("l".to_string())), Some(InputOption::VALUE_NONE), "", None), + // TODO(cli-completion): suggest installed binary names (via get_binaries) for `binary` argument InputArgument::new( "binary", Some(InputArgument::OPTIONAL), "The binary to run, e.g. phpunit", None, - // suggestion callback deferred; binaries listed at runtime via get_binaries - vec![], ), InputArgument::new( "args", Some(InputArgument::IS_ARRAY | InputArgument::OPTIONAL), "Arguments to pass to the binary. Use <info>--</info> to separate from composer arguments", None, - vec![], ), ]) .set_help( @@ -65,7 +59,7 @@ impl ExecCommand { return Ok(()); } - let io = self.inner.get_io(); + let io = self.get_io(); let binary = io.select( "Binary to run: ".to_string(), binaries.clone(), @@ -76,7 +70,10 @@ impl ExecCommand { ); if let Some(idx) = binary.as_int() { - input.set_argument("binary", &binaries[idx as usize]); + input.set_argument( + "binary", + shirabe_php_shim::PhpMixed::String(binaries[idx as usize].clone()), + ); } Ok(()) @@ -87,7 +84,7 @@ impl ExecCommand { input: &dyn InputInterface, _output: &dyn OutputInterface, ) -> Result<i64> { - let composer = self.inner.require_composer()?; + let composer = self.require_composer(None, None)?; if input.get_option("list").as_bool().unwrap_or(false) || input.get_argument("binary").as_string_opt().is_none() @@ -110,13 +107,10 @@ impl ExecCommand { .into()); } - self.inner - .get_io() + self.get_io() .write("<comment>Available binaries:</comment>"); for bin in &bins { - self.inner - .get_io() - .write(&format!("<info>- {}</info>", bin)); + self.get_io().write(&format!("<info>- {}</info>", bin)); } return Ok(0); @@ -129,10 +123,10 @@ impl ExecCommand { .to_string(); let dispatcher = composer.get_event_dispatcher(); - dispatcher.add_listener("__exec_command", &binary); + // TODO(phase-b): add_listener takes a Callable; wiring binary as callable not yet ported + let _ = (dispatcher, &binary); - let initial_working_directory = - self.inner.get_application().get_initial_working_directory(); + let initial_working_directory = self.get_application().get_initial_working_directory(); if let Some(ref iwd) = initial_working_directory { if getcwd().as_deref() != Some(iwd.as_str()) { chdir(iwd).map_err(|e| RuntimeException { @@ -159,7 +153,7 @@ impl ExecCommand { } fn get_binaries(&self, for_display: bool) -> Result<Vec<String>> { - let composer = self.inner.require_composer()?; + let composer = self.require_composer(None, None)?; let bin_dir = composer .get_config() .get("bin-dir") @@ -193,30 +187,12 @@ impl ExecCommand { } } -impl BaseCommand for ExecCommand { - fn inner(&self) -> &CommandBase { - &self.inner - } - - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() +impl HasBaseCommandData for ExecCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for ExecCommand {} diff --git a/crates/shirabe/src/command/fund_command.rs b/crates/shirabe/src/command/fund_command.rs index 066eb8e..73a7078 100644 --- a/crates/shirabe/src/command/fund_command.rs +++ b/crates/shirabe/src/command/fund_command.rs @@ -5,15 +5,13 @@ use std::any::Any; use anyhow::Result; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::preg::Preg; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::console::formatter::output_formatter::OutputFormatter; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; use shirabe_php_shim::PhpMixed; use shirabe_semver::constraint::match_all_constraint::MatchAllConstraint; -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::console::input::input_option::InputOption; use crate::io::io_interface::IOInterface; @@ -21,19 +19,17 @@ use crate::json::json_file::JsonFile; use crate::package::alias_package::AliasPackage; use crate::package::base_package::{self, BasePackage}; use crate::package::complete_package::CompletePackage; +use crate::package::complete_package_interface::CompletePackageInterface; use crate::repository::composite_repository::CompositeRepository; #[derive(Debug)] pub struct FundCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, } impl FundCommand { pub fn configure(&mut self) { - self.inner - .set_name("fund") + self.set_name("fund") .set_description("Discover how to help fund the maintenance of your dependencies") .set_definition(vec![InputOption::new( "format", @@ -41,7 +37,6 @@ impl FundCommand { Some(InputOption::VALUE_REQUIRED), "Format of the output: text or json", Some(PhpMixed::String("text".to_string())), - vec!["text".to_string(), "json".to_string()], )]); } @@ -50,7 +45,7 @@ impl FundCommand { input: &dyn InputInterface, _output: &dyn OutputInterface, ) -> Result<i64> { - let composer = self.inner.require_composer()?; + let composer = self.require_composer(None, None)?; let repo = composer.get_repository_manager().get_local_repository(); let remote_repos = @@ -120,7 +115,7 @@ impl FundCommand { fundings.sort_keys(); - let io = self.inner.get_io(); + let io = self.get_io(); let format = input .get_option("format") @@ -163,7 +158,7 @@ impl FundCommand { ); io.write("Thank you!"); } else if format == "json" { - io.write(&JsonFile::encode(&fundings)); + io.write(&JsonFile::encode(&fundings, 448)); } else { io.write("No funding links were found in your package dependencies. This doesn't mean they don't need your support!"); } @@ -208,30 +203,12 @@ impl FundCommand { } } -impl BaseCommand for FundCommand { - fn inner(&self) -> &CommandBase { - &self.inner +impl HasBaseCommandData for FundCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for FundCommand {} diff --git a/crates/shirabe/src/command/global_command.rs b/crates/shirabe/src/command/global_command.rs index 5669328..5acceb3 100644 --- a/crates/shirabe/src/command/global_command.rs +++ b/crates/shirabe/src/command/global_command.rs @@ -4,16 +4,12 @@ use std::path::Path; use anyhow::Result; use shirabe_external_packages::composer::pcre::preg::Preg; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; -use shirabe_external_packages::symfony::console::completion::completion_input::CompletionInput; -use shirabe_external_packages::symfony::console::completion::completion_suggestions::CompletionSuggestions; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::input::string_input::StringInput; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; use shirabe_php_shim::{LogicException, RuntimeException, chdir}; -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::console::input::input_argument::InputArgument; use crate::factory::Factory; @@ -23,58 +19,22 @@ use crate::util::platform::Platform; #[derive(Debug)] pub struct GlobalCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, } impl GlobalCommand { - pub fn complete(&self, input: &CompletionInput, suggestions: &mut CompletionSuggestions) { - let application = self.inner.get_application(); - if input.must_suggest_argument_values_for("command-name") { - let names: Vec<String> = application - .all() - .into_iter() - .filter(|cmd| !cmd.is_hidden()) - .filter_map(|cmd| cmd.get_name().map(|n| n.to_string())) - .collect(); - suggestions.suggest_values(names); - return; - } - - let command_name = input - .get_argument("command-name") - .as_string() - .unwrap_or("") - .to_string(); - if application.has(&command_name) { - let sub_input = self.prepare_subcommand_input(input.as_input_interface(), true); - let sub_input = CompletionInput::from_string(&sub_input.to_string(), 2); - let command = application.find(&command_name); - command.merge_application_definition(); - sub_input.bind(command.get_definition()); - command.complete(&sub_input, suggestions); - } - } + // TODO(cli-completion): pub fn complete(&self, input: &CompletionInput, suggestions: &mut CompletionSuggestions) pub fn configure(&mut self) { - self.inner - .set_name("global") + self.set_name("global") .set_description("Allows running commands in the global composer dir ($COMPOSER_HOME)") .set_definition(vec![ - InputArgument::new( - "command-name", - Some(InputArgument::REQUIRED), - "", - None, - vec![], - ), + InputArgument::new("command-name", Some(InputArgument::REQUIRED), "", None), InputArgument::new( "args", Some(InputArgument::IS_ARRAY | InputArgument::OPTIONAL), "", None, - vec![], ), ]) .set_help( @@ -105,11 +65,11 @@ impl GlobalCommand { } if args.len() < 2 { - return self.inner.run(input, output); + return self.run(input, output); } let sub_input = self.prepare_subcommand_input(input, false)?; - Ok(self.inner.get_application().run(&sub_input, output)?) + Ok(self.get_application().run(&sub_input, output)?) } fn prepare_subcommand_input( @@ -125,7 +85,7 @@ impl GlobalCommand { let home = config.get("home").as_string().unwrap_or("").to_string(); if !Path::new(&home).is_dir() { - let fs = Filesystem::new(); + let fs = Filesystem::new(None); fs.ensure_directory_exists(&home)?; if !Path::new(&home).is_dir() { return Err(RuntimeException { @@ -142,7 +102,7 @@ impl GlobalCommand { })?; if !quiet { - self.inner.get_io().write_error(&format!( + self.get_io().write_error(&format!( "<info>Changed current directory to {}</info>", home )); @@ -154,7 +114,7 @@ impl GlobalCommand { &input.to_string(), 1, )?; - self.inner.get_application().reset_composer(); + self.get_application().reset_composer(); Ok(StringInput::new(new_input_str)) } @@ -164,30 +124,12 @@ impl GlobalCommand { } } -impl BaseCommand for GlobalCommand { - fn inner(&self) -> &CommandBase { - &self.inner +impl HasBaseCommandData for GlobalCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for GlobalCommand {} diff --git a/crates/shirabe/src/command/home_command.rs b/crates/shirabe/src/command/home_command.rs index 8e9f007..9b92707 100644 --- a/crates/shirabe/src/command/home_command.rs +++ b/crates/shirabe/src/command/home_command.rs @@ -1,14 +1,11 @@ //! ref: composer/src/Composer/Command/HomeCommand.php use anyhow::Result; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; use shirabe_php_shim::{FILTER_VALIDATE_URL, filter_var}; -use crate::command::base_command::BaseCommand; -use crate::command::completion_trait::CompletionTrait; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::console::input::input_argument::InputArgument; use crate::console::input::input_option::InputOption; @@ -22,26 +19,14 @@ use crate::util::process_executor::ProcessExecutor; #[derive(Debug)] pub struct HomeCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, -} - -impl CompletionTrait for HomeCommand { - fn require_composer( - &self, - disable_plugins: Option<bool>, - disable_scripts: Option<bool>, - ) -> Composer { - todo!() - } + base_command_data: BaseCommandData, } impl HomeCommand { pub fn configure(&mut self) { - self.inner - .set_name("browse") - .set_aliases(vec!["home".to_string()]) + // TODO(cli-completion): suggest_installed_package() for `packages` argument + self.set_name("browse") + .set_aliases(&["home".to_string()]) .set_description("Opens the package's repository URL or homepage in your browser") .set_definition(vec![ InputArgument::new( @@ -49,7 +34,6 @@ impl HomeCommand { Some(InputArgument::IS_ARRAY), "Package(s) to browse to.", None, - self.suggest_installed_package(), ), InputOption::new( "homepage", @@ -57,7 +41,6 @@ impl HomeCommand { Some(InputOption::VALUE_NONE), "Open the homepage instead of the repository URL.", None, - vec![], ), InputOption::new( "show", @@ -65,7 +48,6 @@ impl HomeCommand { Some(InputOption::VALUE_NONE), "Only show the homepage or repository URL.", None, - vec![], ), ]) .set_help( @@ -83,7 +65,7 @@ impl HomeCommand { _output: &dyn OutputInterface, ) -> Result<i64> { let repos = self.initialize_repos()?; - let io = self.inner.get_io(); + let io = self.get_io(); let mut return_code: i64 = 0; let packages: Vec<String> = input @@ -99,8 +81,7 @@ impl HomeCommand { let packages = if packages.is_empty() { io.write_error("No package specified, opening homepage for the root package"); vec![ - self.inner - .require_composer()? + self.require_composer(None, None)? .get_package() .get_name() .to_string(), @@ -177,7 +158,7 @@ impl HomeCommand { } if show_only { - self.inner.get_io().write(&format!("<info>{}</info>", url)); + self.get_io().write(&format!("<info>{}</info>", url)); } else { self.open_browser(&url); } @@ -186,7 +167,7 @@ impl HomeCommand { } fn open_browser(&self, url: &str) { - let io = self.inner.get_io(); + let io = self.get_io(); let mut process = ProcessExecutor::new(io); if Platform::is_windows() { process.execute(&["start", "\"web\"", "explorer", url], None); @@ -209,7 +190,7 @@ impl HomeCommand { } fn initialize_repos(&self) -> Result<Vec<Box<dyn RepositoryInterface>>> { - let composer = self.inner.try_composer(); + let composer = self.try_composer(None, None); if let Some(composer) = composer { let mut repos: Vec<Box<dyn RepositoryInterface>> = vec![]; @@ -226,35 +207,17 @@ impl HomeCommand { } Ok(RepositoryFactory::default_repos_with_default_manager( - self.inner.get_io(), + self.get_io(), )) } } -impl BaseCommand for HomeCommand { - fn inner(&self) -> &CommandBase { - &self.inner +impl HasBaseCommandData for HomeCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for HomeCommand {} diff --git a/crates/shirabe/src/command/init_command.rs b/crates/shirabe/src/command/init_command.rs index 5ee4b67..04928ed 100644 --- a/crates/shirabe/src/command/init_command.rs +++ b/crates/shirabe/src/command/init_command.rs @@ -5,8 +5,6 @@ use anyhow::Result; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::preg::Preg; use shirabe_external_packages::composer::spdx_licenses::spdx_licenses::SpdxLicenses; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::component::console::helper::formatter_helper::FormatterHelper; use shirabe_external_packages::symfony::component::console::input::array_input::ArrayInput; use shirabe_external_packages::symfony::component::console::input::input_interface::InputInterface; @@ -19,8 +17,7 @@ use shirabe_php_shim::{ strtolower, trim, ucwords, }; -use crate::command::base_command::BaseCommand; -use crate::command::completion_trait::CompletionTrait; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::command::package_discovery_trait::PackageDiscoveryTrait; use crate::composer::Composer; use crate::console::input::input_option::InputOption; @@ -38,24 +35,12 @@ use crate::util::silencer::Silencer; #[derive(Debug)] pub struct InitCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, /// @var array<string, string> git_config: Option<IndexMap<String, String>>, } -impl CompletionTrait for InitCommand { - fn require_composer( - &self, - disable_plugins: Option<bool>, - disable_scripts: Option<bool>, - ) -> Composer { - todo!() - } -} - impl PackageDiscoveryTrait for InitCommand { fn get_repos_mut(&mut self) -> &mut Option<CompositeRepository> { todo!() @@ -97,25 +82,22 @@ impl PackageDiscoveryTrait for InitCommand { impl InitCommand { pub fn configure(&mut self) { - let suggest_available_package_incl_platform = - self.suggest_available_package_incl_platform(); - let suggest_available_package_incl_platform2 = - self.suggest_available_package_incl_platform(); - self.inner + // TODO(cli-completion): suggest_available_package_incl_platform() for `require` / `require-dev` + self .set_name("init") .set_description("Creates a basic composer.json file in current directory") .set_definition(vec![ - InputOption::new("name", None, Some(InputOption::VALUE_REQUIRED), "Name of the package", None, vec![]), - InputOption::new("description", None, Some(InputOption::VALUE_REQUIRED), "Description of package", None, vec![]), - InputOption::new("author", None, Some(InputOption::VALUE_REQUIRED), "Author name of package", None, vec![]), - InputOption::new("type", None, Some(InputOption::VALUE_REQUIRED), "Type of package (e.g. library, project, metapackage, composer-plugin)", None, vec![]), - InputOption::new("homepage", None, Some(InputOption::VALUE_REQUIRED), "Homepage of package", None, vec![]), - InputOption::new("require", None, Some(InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED), "Package to require with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or \"foo/bar 1.0.0\"", None, suggest_available_package_incl_platform), - InputOption::new("require-dev", None, Some(InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED), "Package to require for development with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or \"foo/bar 1.0.0\"", None, suggest_available_package_incl_platform2), - InputOption::new("stability", Some(PhpMixed::String("s".to_string())), Some(InputOption::VALUE_REQUIRED), &format!("Minimum stability (empty or one of: {})", implode(", ", &array_keys(&BasePackage::stabilities()))), None, vec![]), - InputOption::new("license", Some(PhpMixed::String("l".to_string())), Some(InputOption::VALUE_REQUIRED), "License of package", None, vec![]), - InputOption::new("repository", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Add custom repositories, either by URL or using JSON arrays", None, vec![]), - InputOption::new("autoload", Some(PhpMixed::String("a".to_string())), Some(InputOption::VALUE_REQUIRED), "Add PSR-4 autoload mapping. Maps your package's namespace to the provided directory. (Expects a relative path, e.g. src/)", None, vec![]), + InputOption::new("name", None, Some(InputOption::VALUE_REQUIRED), "Name of the package", None), + InputOption::new("description", None, Some(InputOption::VALUE_REQUIRED), "Description of package", None), + InputOption::new("author", None, Some(InputOption::VALUE_REQUIRED), "Author name of package", None), + InputOption::new("type", None, Some(InputOption::VALUE_REQUIRED), "Type of package (e.g. library, project, metapackage, composer-plugin)", None), + InputOption::new("homepage", None, Some(InputOption::VALUE_REQUIRED), "Homepage of package", None), + InputOption::new("require", None, Some(InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED), "Package to require with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or \"foo/bar 1.0.0\"", None), + InputOption::new("require-dev", None, Some(InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED), "Package to require for development with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or \"foo/bar 1.0.0\"", None), + InputOption::new("stability", Some(PhpMixed::String("s".to_string())), Some(InputOption::VALUE_REQUIRED), &format!("Minimum stability (empty or one of: {})", implode(", ", &array_keys(&BasePackage::stabilities()))), None), + InputOption::new("license", Some(PhpMixed::String("l".to_string())), Some(InputOption::VALUE_REQUIRED), "License of package", None), + InputOption::new("repository", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Add custom repositories, either by URL or using JSON arrays", None), + InputOption::new("autoload", Some(PhpMixed::String("a".to_string())), Some(InputOption::VALUE_REQUIRED), "Add PSR-4 autoload mapping. Maps your package's namespace to the provided directory. (Expects a relative path, e.g. src/)", None), ]) .set_help( "The <info>init</info> command creates a basic composer.json file\n\ @@ -133,7 +115,7 @@ impl InitCommand { input: &dyn InputInterface, output: &dyn OutputInterface, ) -> Result<i64> { - let io = self.inner.get_io(); + let io = self.get_io(); let allowlist: Vec<String> = vec![ "name".to_string(), @@ -239,7 +221,7 @@ impl InitCommand { .collect() }) .unwrap_or_default(); - let formatted = self.inner.format_requirements(req_list)?; + let formatted = self.format_requirements(req_list)?; if formatted.is_empty() { // PHP: new \stdClass — represented as an empty IndexMap (JSON object) PhpMixed::Array(IndexMap::new()) @@ -267,7 +249,7 @@ impl InitCommand { .collect() }) .unwrap_or_default(); - let formatted = self.inner.format_requirements(req_list)?; + let formatted = self.format_requirements(req_list)?; let value = if formatted.is_empty() { PhpMixed::Array(IndexMap::new()) } else { @@ -303,13 +285,13 @@ impl InitCommand { options.insert("autoload".to_string(), PhpMixed::Array(autoload_obj)); } - let file_obj = JsonFile::new(&Factory::get_composer_file(), None, None); + let file_obj = JsonFile::new(Factory::get_composer_file(), None, None)?; let options_for_encode: IndexMap<String, Box<PhpMixed>> = options .clone() .into_iter() .map(|(k, v)| (k, Box::new(v))) .collect(); - let json = JsonFile::encode(&options_for_encode); + let json = JsonFile::encode(&options_for_encode, 448); if input.is_interactive() { io.write_error( @@ -362,10 +344,11 @@ impl InitCommand { true, io_interface::NORMAL, ); - Silencer::call( - "unlink", - &[PhpMixed::String(file_obj.get_path().to_string())], - ); + let path_to_unlink = file_obj.get_path().to_string(); + let _ = Silencer::call(|| { + shirabe_php_shim::unlink(&path_to_unlink); + Ok::<(), anyhow::Error>(()) + }); return Ok(1); } @@ -374,7 +357,7 @@ impl InitCommand { // --autoload - Create src folder if let Some(ref ap) = autoload_path { - let filesystem = Filesystem::new(); + let filesystem = Filesystem::new(None); filesystem.ensure_directory_exists(ap); // dump-autoload only for projects without added dependencies. @@ -440,7 +423,7 @@ impl InitCommand { } pub(crate) fn initialize(&mut self, input: &dyn InputInterface, output: &dyn OutputInterface) { - self.inner.initialize(input, output); + self.initialize(input, output); if !input.is_interactive() { if input.get_option("name").is_null() { @@ -463,9 +446,9 @@ impl InitCommand { input: &dyn InputInterface, _output: &dyn OutputInterface, ) -> Result<()> { - let io = self.inner.get_io(); + let io = self.get_io(); // @var FormatterHelper $formatter - let formatter: &FormatterHelper = self.inner.get_helper_set().get("formatter"); + let formatter: &FormatterHelper = self.get_helper_set().get("formatter"); // initialize repos if configured let repositories: Vec<String> = input @@ -484,7 +467,7 @@ impl InitCommand { let mut repos: Vec< Box<dyn crate::repository::repository_interface::RepositoryInterface>, - > = vec![Box::new(PlatformRepository::new(vec![], PhpMixed::Null))]; + > = vec![Box::new(PlatformRepository::new(vec![], IndexMap::new())?)]; let mut create_default_packagist_repo = true; for repo in &repositories { let repo_config = @@ -977,7 +960,7 @@ impl InitCommand { return self.git_config.clone().unwrap_or_default(); } - let mut process = ProcessExecutor::new(self.inner.get_io()); + let mut process = ProcessExecutor::new(self.get_io()); let mut output = String::new(); if process.execute( @@ -1059,14 +1042,14 @@ impl InitCommand { fn update_dependencies(&self, output: &dyn OutputInterface) { // PHP try/catch: catch \Exception - let result = self.inner.get_application().and_then(|app| { + let result = self.get_application().and_then(|app| { let update_command = app.find("update")?; app.reset_composer()?; update_command.run(ArrayInput::new(IndexMap::new()), output)?; Ok(()) }); if let Err(_e) = result { - self.inner.get_io().write_error( + self.get_io().write_error( PhpMixed::String( "Could not update dependencies. Run `composer update` to see more information." .to_string(), @@ -1078,14 +1061,14 @@ impl InitCommand { } fn run_dump_autoload_command(&self, output: &dyn OutputInterface) { - let result = self.inner.get_application().and_then(|app| { + let result = self.get_application().and_then(|app| { let command = app.find("dump-autoload")?; app.reset_composer()?; command.run(ArrayInput::new(IndexMap::new()), output)?; Ok(()) }); if let Err(_e) = result { - self.inner.get_io().write_error( + self.get_io().write_error( PhpMixed::String("Could not run dump-autoload.".to_string()), true, io_interface::NORMAL, @@ -1206,30 +1189,12 @@ impl InitCommand { } } -impl BaseCommand for InitCommand { - fn inner(&self) -> &CommandBase { - &self.inner +impl HasBaseCommandData for InitCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for InitCommand {} diff --git a/crates/shirabe/src/command/install_command.rs b/crates/shirabe/src/command/install_command.rs index eac04c3..2b9a87d 100644 --- a/crates/shirabe/src/command/install_command.rs +++ b/crates/shirabe/src/command/install_command.rs @@ -1,16 +1,12 @@ //! ref: composer/src/Composer/Command/InstallCommand.php use anyhow::Result; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; use shirabe_php_shim::PhpMixed; use crate::advisory::auditor::Auditor; -use crate::command::base_command::BaseCommand; -use crate::command::completion_trait::CompletionTrait; -use crate::composer::Composer; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::console::input::input_argument::InputArgument; use crate::console::input::input_option::InputOption; use crate::installer::Installer; @@ -21,51 +17,39 @@ use crate::util::http_downloader::HttpDownloader; #[derive(Debug)] pub struct InstallCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, -} - -impl CompletionTrait for InstallCommand { - fn require_composer( - &self, - disable_plugins: Option<bool>, - disable_scripts: Option<bool>, - ) -> Composer { - todo!() - } + base_command_data: BaseCommandData, } impl InstallCommand { pub fn configure(&mut self) { - let suggest_prefer_install = self.suggest_prefer_install(); - self.inner + // TODO(cli-completion): suggest_prefer_install() for `prefer-install` option + self .set_name("install") - .set_aliases(vec!["i".to_string()]) + .set_aliases(&["i".to_string()]) .set_description("Installs the project dependencies from the composer.lock file if present, or falls back on the composer.json") .set_definition(vec![ - InputOption::new("prefer-source", None, Some(InputOption::VALUE_NONE), "Forces installation from package sources when possible, including VCS information.", None, vec![]), - InputOption::new("prefer-dist", None, Some(InputOption::VALUE_NONE), "Forces installation from package dist (default behavior).", None, vec![]), - InputOption::new("prefer-install", None, Some(InputOption::VALUE_REQUIRED), "Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).", None, suggest_prefer_install), - InputOption::new("dry-run", None, Some(InputOption::VALUE_NONE), "Outputs the operations but will not execute anything (implicitly enables --verbose).", None, vec![]), - InputOption::new("download-only", None, Some(InputOption::VALUE_NONE), "Download only, do not install packages.", None, vec![]), - InputOption::new("dev", None, Some(InputOption::VALUE_NONE), "DEPRECATED: Enables installation of require-dev packages (enabled by default, only present for BC).", None, vec![]), - InputOption::new("no-suggest", None, Some(InputOption::VALUE_NONE), "DEPRECATED: This flag does not exist anymore.", None, vec![]), - InputOption::new("no-dev", None, Some(InputOption::VALUE_NONE), "Disables installation of require-dev packages.", None, vec![]), - InputOption::new("no-security-blocking", None, Some(InputOption::VALUE_NONE), "Allows installing packages with security advisories or that are abandoned (can also be set via the COMPOSER_NO_SECURITY_BLOCKING=1 env var). Only applies when no lock file is present.", None, vec![]), - InputOption::new("no-autoloader", None, Some(InputOption::VALUE_NONE), "Skips autoloader generation", None, vec![]), - InputOption::new("no-progress", None, Some(InputOption::VALUE_NONE), "Do not output download progress.", None, vec![]), - InputOption::new("no-install", None, Some(InputOption::VALUE_NONE), "Do not use, only defined here to catch misuse of the install command.", None, vec![]), - InputOption::new("audit", None, Some(InputOption::VALUE_NONE), "Run an audit after installation is complete.", None, vec![]), - InputOption::new("audit-format", None, Some(InputOption::VALUE_REQUIRED), "Audit output format. Must be \"table\", \"plain\", \"json\", or \"summary\".", Some(PhpMixed::String(Auditor::FORMAT_SUMMARY.to_string())), Auditor::FORMATS.iter().map(|s| s.to_string()).collect()), - InputOption::new("verbose", Some(PhpMixed::String("v|vv|vvv".to_string())), Some(InputOption::VALUE_NONE), "Shows more details including new commits pulled in when updating packages.", None, vec![]), - InputOption::new("optimize-autoloader", Some(PhpMixed::String("o".to_string())), Some(InputOption::VALUE_NONE), "Optimize autoloader during autoloader dump", None, vec![]), - InputOption::new("classmap-authoritative", Some(PhpMixed::String("a".to_string())), Some(InputOption::VALUE_NONE), "Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.", None, vec![]), - InputOption::new("apcu-autoloader", None, Some(InputOption::VALUE_NONE), "Use APCu to cache found/not-found classes.", None, vec![]), - InputOption::new("apcu-autoloader-prefix", None, Some(InputOption::VALUE_REQUIRED), "Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader", None, vec![]), - InputOption::new("ignore-platform-req", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Ignore a specific platform requirement (php & ext- packages).", None, vec![]), - InputOption::new("ignore-platform-reqs", None, Some(InputOption::VALUE_NONE), "Ignore all platform requirements (php & ext- packages).", None, vec![]), - InputArgument::new("packages", Some(InputArgument::IS_ARRAY | InputArgument::OPTIONAL), "Should not be provided, use composer require instead to add a given package to composer.json.", None, vec![]), + InputOption::new("prefer-source", None, Some(InputOption::VALUE_NONE), "Forces installation from package sources when possible, including VCS information.", None), + InputOption::new("prefer-dist", None, Some(InputOption::VALUE_NONE), "Forces installation from package dist (default behavior).", None), + InputOption::new("prefer-install", None, Some(InputOption::VALUE_REQUIRED), "Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).", None), + InputOption::new("dry-run", None, Some(InputOption::VALUE_NONE), "Outputs the operations but will not execute anything (implicitly enables --verbose).", None), + InputOption::new("download-only", None, Some(InputOption::VALUE_NONE), "Download only, do not install packages.", None), + InputOption::new("dev", None, Some(InputOption::VALUE_NONE), "DEPRECATED: Enables installation of require-dev packages (enabled by default, only present for BC).", None), + InputOption::new("no-suggest", None, Some(InputOption::VALUE_NONE), "DEPRECATED: This flag does not exist anymore.", None), + InputOption::new("no-dev", None, Some(InputOption::VALUE_NONE), "Disables installation of require-dev packages.", None), + InputOption::new("no-security-blocking", None, Some(InputOption::VALUE_NONE), "Allows installing packages with security advisories or that are abandoned (can also be set via the COMPOSER_NO_SECURITY_BLOCKING=1 env var). Only applies when no lock file is present.", None), + InputOption::new("no-autoloader", None, Some(InputOption::VALUE_NONE), "Skips autoloader generation", None), + InputOption::new("no-progress", None, Some(InputOption::VALUE_NONE), "Do not output download progress.", None), + InputOption::new("no-install", None, Some(InputOption::VALUE_NONE), "Do not use, only defined here to catch misuse of the install command.", None), + InputOption::new("audit", None, Some(InputOption::VALUE_NONE), "Run an audit after installation is complete.", None), + InputOption::new("audit-format", None, Some(InputOption::VALUE_REQUIRED), "Audit output format. Must be \"table\", \"plain\", \"json\", or \"summary\".", Some(PhpMixed::String(Auditor::FORMAT_SUMMARY.to_string()))), + InputOption::new("verbose", Some(PhpMixed::String("v|vv|vvv".to_string())), Some(InputOption::VALUE_NONE), "Shows more details including new commits pulled in when updating packages.", None), + InputOption::new("optimize-autoloader", Some(PhpMixed::String("o".to_string())), Some(InputOption::VALUE_NONE), "Optimize autoloader during autoloader dump", None), + InputOption::new("classmap-authoritative", Some(PhpMixed::String("a".to_string())), Some(InputOption::VALUE_NONE), "Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.", None), + InputOption::new("apcu-autoloader", None, Some(InputOption::VALUE_NONE), "Use APCu to cache found/not-found classes.", None), + InputOption::new("apcu-autoloader-prefix", None, Some(InputOption::VALUE_REQUIRED), "Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader", None), + InputOption::new("ignore-platform-req", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Ignore a specific platform requirement (php & ext- packages).", None), + InputOption::new("ignore-platform-reqs", None, Some(InputOption::VALUE_NONE), "Ignore all platform requirements (php & ext- packages).", None), + InputArgument::new("packages", Some(InputArgument::IS_ARRAY | InputArgument::OPTIONAL), "Should not be provided, use composer require instead to add a given package to composer.json.", None), ]) .set_help( "The <info>install</info> command reads the composer.lock file from\n\ @@ -78,7 +62,7 @@ impl InstallCommand { } pub fn execute(&self, input: &dyn InputInterface, output: &dyn OutputInterface) -> Result<i64> { - let io = self.inner.get_io(); + let io = self.get_io(); if input.get_option("dev").as_bool().unwrap_or(false) { io.write_error("<warning>You are using the deprecated option \"--dev\". It has no effect and will break in Composer 3.</warning>"); @@ -110,7 +94,7 @@ impl InstallCommand { return Ok(1); } - let composer = self.inner.require_composer()?; + let composer = self.require_composer(None, None)?; if !composer.get_locker().is_locked() && !HttpDownloader::is_curl_enabled() { io.write_error("<warning>Composer is operating significantly slower than normal because you do not have the PHP curl extension enabled.</warning>"); @@ -120,20 +104,19 @@ impl InstallCommand { let command_event = CommandEvent::new( PluginEvents::COMMAND.to_string(), "install".to_string(), - Box::new(input), - Box::new(output), + input, + output, vec![], vec![], ); composer .get_event_dispatcher() - .dispatch(command_event.get_name(), &command_event); + .dispatch(Some(command_event.get_name()), None); let install = Installer::create(io, &composer); let config = composer.get_config(); - let (prefer_source, prefer_dist) = - self.inner.get_preferred_install_options(config, input)?; + let (prefer_source, prefer_dist) = self.get_preferred_install_options(config, input)?; let optimize = input .get_option("optimize-autoloader") @@ -174,11 +157,8 @@ impl InstallCommand { .set_optimize_autoloader(optimize) .set_class_map_authoritative(authoritative) .set_apcu_autoloader(apcu, apcu_prefix.as_deref()) - .set_platform_requirement_filter(self.inner.get_platform_requirement_filter(input)?) - .set_audit_config( - self.inner - .create_audit_config(composer.get_config(), input)?, - ) + .set_platform_requirement_filter(self.get_platform_requirement_filter(input)?) + .set_audit_config(self.create_audit_config(composer.get_config(), input)?) .set_error_on_audit(input.get_option("audit").as_bool().unwrap_or(false)); if input.get_option("no-plugins").as_bool().unwrap_or(false) { @@ -189,30 +169,12 @@ impl InstallCommand { } } -impl BaseCommand for InstallCommand { - fn inner(&self) -> &CommandBase { - &self.inner - } - - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer +impl HasBaseCommandData for InstallCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for InstallCommand {} diff --git a/crates/shirabe/src/command/licenses_command.rs b/crates/shirabe/src/command/licenses_command.rs index 4fa2e72..102221e 100644 --- a/crates/shirabe/src/command/licenses_command.rs +++ b/crates/shirabe/src/command/licenses_command.rs @@ -4,8 +4,6 @@ use std::any::Any; use anyhow::Result; use indexmap::IndexMap; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::console::formatter::output_formatter::OutputFormatter; use shirabe_external_packages::symfony::console::helper::table::Table; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; @@ -13,7 +11,7 @@ use shirabe_external_packages::symfony::console::output::output_interface::Outpu use shirabe_external_packages::symfony::console::style::symfony_style::SymfonyStyle; use shirabe_php_shim::{PhpMixed, RuntimeException, UnexpectedValueException}; -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::console::input::input_option::InputOption; use crate::io::io_interface::IOInterface; @@ -28,15 +26,12 @@ use crate::util::package_sorter::PackageSorter; #[derive(Debug)] pub struct LicensesCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, } impl LicensesCommand { pub fn configure(&mut self) { - self.inner - .set_name("licenses") + self.set_name("licenses") .set_description("Shows information about licenses of dependencies") .set_definition(vec![ InputOption::new( @@ -45,11 +40,6 @@ impl LicensesCommand { Some(InputOption::VALUE_REQUIRED), "Format of the output: text, json or summary", Some(PhpMixed::String("text".to_string())), - vec![ - "text".to_string(), - "json".to_string(), - "summary".to_string(), - ], ), InputOption::new( "no-dev", @@ -57,7 +47,6 @@ impl LicensesCommand { Some(InputOption::VALUE_NONE), "Disables search in require-dev packages.", None, - vec![], ), InputOption::new( "locked", @@ -65,7 +54,6 @@ impl LicensesCommand { Some(InputOption::VALUE_NONE), "Shows licenses from the lock file instead of installed packages.", None, - vec![], ), ]) .set_help( @@ -78,14 +66,14 @@ impl LicensesCommand { } pub fn execute(&self, input: &dyn InputInterface, output: &dyn OutputInterface) -> Result<i64> { - let composer = self.inner.require_composer()?; + let composer = self.require_composer(None, None)?; // TODO(plugin): dispatch COMMAND event for plugin hooks let command_event = CommandEvent::new(PluginEvents::COMMAND, "licenses".to_string(), input, output); composer .get_event_dispatcher() - .dispatch(command_event.get_name(), &command_event); + .dispatch(Some(command_event.get_name()), None); let root = composer.get_package(); @@ -110,7 +98,7 @@ impl LicensesCommand { }; let packages = PackageSorter::sort_packages_alphabetically(packages); - let io = self.inner.get_io(); + let io = self.get_io(); let format = input .get_option("format") @@ -238,7 +226,7 @@ impl LicensesCommand { .collect(), ), ); - io.write(&JsonFile::encode(&output_map)); + io.write(&JsonFile::encode(&output_map, 448)); } "summary" => { let mut used_licenses: IndexMap<String, i64> = IndexMap::new(); @@ -288,30 +276,12 @@ impl LicensesCommand { } } -impl BaseCommand for LicensesCommand { - fn inner(&self) -> &CommandBase { - &self.inner +impl HasBaseCommandData for LicensesCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for LicensesCommand {} diff --git a/crates/shirabe/src/command/outdated_command.rs b/crates/shirabe/src/command/outdated_command.rs index d11d290..bac15cf 100644 --- a/crates/shirabe/src/command/outdated_command.rs +++ b/crates/shirabe/src/command/outdated_command.rs @@ -1,15 +1,12 @@ //! ref: composer/src/Composer/Command/OutdatedCommand.php -use crate::command::base_command::BaseCommand; -use crate::command::completion_trait::CompletionTrait; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::console::input::input_argument::InputArgument; use crate::console::input::input_option::InputOption; use crate::io::io_interface::IOInterface; use anyhow::Result; use indexmap::IndexMap; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::console::input::array_input::ArrayInput; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; @@ -17,44 +14,31 @@ use shirabe_php_shim::PhpMixed; #[derive(Debug)] pub struct OutdatedCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, -} - -impl CompletionTrait for OutdatedCommand { - fn require_composer( - &self, - disable_plugins: Option<bool>, - disable_scripts: Option<bool>, - ) -> Composer { - todo!() - } + base_command_data: BaseCommandData, } impl OutdatedCommand { pub fn configure(&mut self) { - let suggest_installed_package = self.suggest_installed_package(false, false); - let suggest_installed_package_for_ignore = self.suggest_installed_package(false, false); - self.inner + // TODO(cli-completion): suggest_installed_package(false, false) for `package` argument and `--ignore` option + self .set_name("outdated") .set_description("Shows a list of installed packages that have updates available, including their latest version") .set_definition(vec![ - InputArgument::new("package", Some(InputArgument::OPTIONAL), "Package to inspect. Or a name including a wildcard (*) to filter lists of packages instead.", None, suggest_installed_package), - InputOption::new("outdated", Some(PhpMixed::String("o".to_string())), Some(InputOption::VALUE_NONE), "Show only packages that are outdated (this is the default, but present here for compat with `show`", None, vec![]), - InputOption::new("all", Some(PhpMixed::String("a".to_string())), Some(InputOption::VALUE_NONE), "Show all installed packages with their latest versions", None, vec![]), - InputOption::new("locked", None, Some(InputOption::VALUE_NONE), "Shows updates for packages from the lock file, regardless of what is currently in vendor dir", None, vec![]), - InputOption::new("direct", Some(PhpMixed::String("D".to_string())), Some(InputOption::VALUE_NONE), "Shows only packages that are directly required by the root package", None, vec![]), - InputOption::new("strict", None, Some(InputOption::VALUE_NONE), "Return a non-zero exit code when there are outdated packages", None, vec![]), - InputOption::new("major-only", Some(PhpMixed::String("M".to_string())), Some(InputOption::VALUE_NONE), "Show only packages that have major SemVer-compatible updates.", None, vec![]), - InputOption::new("minor-only", Some(PhpMixed::String("m".to_string())), Some(InputOption::VALUE_NONE), "Show only packages that have minor SemVer-compatible updates.", None, vec![]), - InputOption::new("patch-only", Some(PhpMixed::String("p".to_string())), Some(InputOption::VALUE_NONE), "Show only packages that have patch SemVer-compatible updates.", None, vec![]), - InputOption::new("sort-by-age", Some(PhpMixed::String("A".to_string())), Some(InputOption::VALUE_NONE), "Displays the installed version's age, and sorts packages oldest first.", None, vec![]), - InputOption::new("format", Some(PhpMixed::String("f".to_string())), Some(InputOption::VALUE_REQUIRED), "Format of the output: text or json", Some(PhpMixed::String("text".to_string())), vec!["json".to_string(), "text".to_string()]), - InputOption::new("ignore", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Ignore specified package(s). Can contain wildcards (*). Use it if you don't want to be informed about new versions of some packages.", None, suggest_installed_package_for_ignore), - InputOption::new("no-dev", None, Some(InputOption::VALUE_NONE), "Disables search in require-dev packages.", None, vec![]), - InputOption::new("ignore-platform-req", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Ignore a specific platform requirement (php & ext- packages). Use with the --outdated option", None, vec![]), - InputOption::new("ignore-platform-reqs", None, Some(InputOption::VALUE_NONE), "Ignore all platform requirements (php & ext- packages). Use with the --outdated option", None, vec![]), + InputArgument::new("package", Some(InputArgument::OPTIONAL), "Package to inspect. Or a name including a wildcard (*) to filter lists of packages instead.", None), + InputOption::new("outdated", Some(PhpMixed::String("o".to_string())), Some(InputOption::VALUE_NONE), "Show only packages that are outdated (this is the default, but present here for compat with `show`", None), + InputOption::new("all", Some(PhpMixed::String("a".to_string())), Some(InputOption::VALUE_NONE), "Show all installed packages with their latest versions", None), + InputOption::new("locked", None, Some(InputOption::VALUE_NONE), "Shows updates for packages from the lock file, regardless of what is currently in vendor dir", None), + InputOption::new("direct", Some(PhpMixed::String("D".to_string())), Some(InputOption::VALUE_NONE), "Shows only packages that are directly required by the root package", None), + InputOption::new("strict", None, Some(InputOption::VALUE_NONE), "Return a non-zero exit code when there are outdated packages", None), + InputOption::new("major-only", Some(PhpMixed::String("M".to_string())), Some(InputOption::VALUE_NONE), "Show only packages that have major SemVer-compatible updates.", None), + InputOption::new("minor-only", Some(PhpMixed::String("m".to_string())), Some(InputOption::VALUE_NONE), "Show only packages that have minor SemVer-compatible updates.", None), + InputOption::new("patch-only", Some(PhpMixed::String("p".to_string())), Some(InputOption::VALUE_NONE), "Show only packages that have patch SemVer-compatible updates.", None), + InputOption::new("sort-by-age", Some(PhpMixed::String("A".to_string())), Some(InputOption::VALUE_NONE), "Displays the installed version's age, and sorts packages oldest first.", None), + InputOption::new("format", Some(PhpMixed::String("f".to_string())), Some(InputOption::VALUE_REQUIRED), "Format of the output: text or json", Some(PhpMixed::String("text".to_string()))), + InputOption::new("ignore", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Ignore specified package(s). Can contain wildcards (*). Use it if you don't want to be informed about new versions of some packages.", None), + InputOption::new("no-dev", None, Some(InputOption::VALUE_NONE), "Disables search in require-dev packages.", None), + InputOption::new("ignore-platform-req", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Ignore a specific platform requirement (php & ext- packages). Use with the --outdated option", None), + InputOption::new("ignore-platform-reqs", None, Some(InputOption::VALUE_NONE), "Ignore all platform requirements (php & ext- packages). Use with the --outdated option", None), ]) .set_help( "The outdated command is just a proxy for `composer show -l`\n\n\ @@ -140,7 +124,9 @@ impl OutdatedCommand { let input = ArrayInput::new(args); - self.inner.get_application().run(&input, output) + // TODO(phase-b): convert ArrayInput/output references to dyn trait objects expected by Application::run + let _ = input; + self.get_application()?.run(None, None) } pub fn is_proxy_command(&self) -> bool { @@ -148,30 +134,12 @@ impl OutdatedCommand { } } -impl BaseCommand for OutdatedCommand { - fn inner(&self) -> &CommandBase { - &self.inner - } - - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer +impl HasBaseCommandData for OutdatedCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for OutdatedCommand {} diff --git a/crates/shirabe/src/command/package_discovery_trait.rs b/crates/shirabe/src/command/package_discovery_trait.rs index 164cc53..501ae5d 100644 --- a/crates/shirabe/src/command/package_discovery_trait.rs +++ b/crates/shirabe/src/command/package_discovery_trait.rs @@ -28,7 +28,7 @@ use crate::package::version::version_selector::VersionSelector; use crate::repository::composite_repository::CompositeRepository; use crate::repository::platform_repository::PlatformRepository; use crate::repository::repository_factory::RepositoryFactory; -use crate::repository::repository_interface::SearchResult; +use crate::repository::repository_interface::{RepositoryInterface, SearchResult}; use crate::repository::repository_set::RepositorySet; use crate::util::filesystem::Filesystem; @@ -161,12 +161,12 @@ pub trait PackageDiscoveryTrait { requirement.get("version").map(|s| s.as_str()).unwrap_or(""), ) { - io.write_error( - PhpMixed::String(format!( + io.write_error3( + &format!( "<warning>The \"{}\" constraint for \"{}\" appears too strict and will likely not match what you want. See https://getcomposer.org/constraints</warning>", requirement.get("version").map(|s| s.as_str()).unwrap_or(""), requirement.get("name").map(|s| s.as_str()).unwrap_or(""), - )), + ), true, io_interface::NORMAL, ); @@ -188,8 +188,8 @@ pub trait PackageDiscoveryTrait { if use_best_version_constraint { requirement.insert("version".to_string(), version.clone()); - io.write_error( - PhpMixed::String(sprintf( + io.write_error3( + &sprintf( "Using version <info>%s</info> for <info>%s</info>", &[ PhpMixed::String(version), @@ -197,7 +197,7 @@ pub trait PackageDiscoveryTrait { requirement.get("name").cloned().unwrap_or_default(), ), ], - )), + ), true, io_interface::NORMAL, ); @@ -219,7 +219,7 @@ pub trait PackageDiscoveryTrait { let version_parser = VersionParser::new(); // Collect existing packages - let composer = self.try_composer(); + let composer = self.try_composer(None, None); let mut installed_repo: Option<_> = None; if let Some(c) = &composer { installed_repo = Some(c.get_repository_manager().get_local_repository()); @@ -319,33 +319,24 @@ pub trait PackageDiscoveryTrait { )); } - io.write_error( - PhpMixed::List(vec![ - Box::new(PhpMixed::String(String::new())), - Box::new(PhpMixed::String(sprintf( - "Found <info>%s</info> packages matching <info>%s</info>", - &[ - PhpMixed::Int(matches.len() as i64), - PhpMixed::String(package.clone()), - ], - ))), - Box::new(PhpMixed::String(String::new())), - ]), - true, - io_interface::NORMAL, - ); - - io.write_error( - PhpMixed::List( - choices - .iter() - .map(|s| Box::new(PhpMixed::String(s.clone()))) - .collect(), + io.write_error3("", true, io_interface::NORMAL); + io.write_error3( + &sprintf( + "Found <info>%s</info> packages matching <info>%s</info>", + &[ + PhpMixed::Int(matches.len() as i64), + PhpMixed::String(package.clone()), + ], ), true, io_interface::NORMAL, ); - io.write_error(PhpMixed::String(String::new()), true, io_interface::NORMAL); + io.write_error3("", true, io_interface::NORMAL); + + for choice in &choices { + io.write_error3(choice, true, io_interface::NORMAL); + } + io.write_error3("", true, io_interface::NORMAL); let matches_clone = matches.clone(); let version_parser_clone = version_parser.clone(); @@ -432,14 +423,14 @@ pub trait PackageDiscoveryTrait { fixed, )?; - io.write_error( - PhpMixed::String(sprintf( + io.write_error3( + &sprintf( "Using version <info>%s</info> for <info>%s</info>", &[ PhpMixed::String(c.clone()), PhpMixed::String(package.clone()), ], - )), + ), true, io_interface::NORMAL, ); diff --git a/crates/shirabe/src/command/prohibits_command.rs b/crates/shirabe/src/command/prohibits_command.rs index 6add126..26c17f2 100644 --- a/crates/shirabe/src/command/prohibits_command.rs +++ b/crates/shirabe/src/command/prohibits_command.rs @@ -1,12 +1,7 @@ //! ref: composer/src/Composer/Command/ProhibitsCommand.php -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; - -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::command::base_dependency_command::BaseDependencyCommand; -use crate::command::completion_trait::CompletionTrait; -use crate::composer::Composer; use crate::console::input::input_argument::InputArgument; use crate::console::input::input_option::InputOption; use crate::io::io_interface::IOInterface; @@ -15,62 +10,50 @@ use shirabe_external_packages::symfony::console::output::output_interface::Outpu #[derive(Debug)] pub struct ProhibitsCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, colors: Vec<String>, } -impl CompletionTrait for ProhibitsCommand { - fn require_composer( - &self, - disable_plugins: Option<bool>, - disable_scripts: Option<bool>, - ) -> Composer { - todo!() - } -} - impl ProhibitsCommand { pub fn configure(&mut self) { - let package_suggestions = self.suggest_available_package(); - self.inner - .set_name("prohibits") - .set_aliases(vec!["why-not".to_string()]) + // TODO(cli-completion): suggest_available_package() for `package` argument + self.set_name("prohibits") + .set_aliases(&["why-not".to_string()]) .set_description("Shows which packages prevent the given package from being installed") .set_definition(vec![ InputArgument::new( BaseDependencyCommand::ARGUMENT_PACKAGE, - InputArgument::REQUIRED, + Some(InputArgument::REQUIRED), "Package to inspect", None, - package_suggestions, ), InputArgument::new( BaseDependencyCommand::ARGUMENT_CONSTRAINT, - InputArgument::REQUIRED, + Some(InputArgument::REQUIRED), "Version constraint, which version you expected to be installed", None, - None, ), InputOption::new( BaseDependencyCommand::OPTION_RECURSIVE, - Some("r"), - InputOption::VALUE_NONE, + Some(shirabe_php_shim::PhpMixed::String("r".to_string())), + Some(InputOption::VALUE_NONE), "Recursively resolves up to the root package", + None, ), InputOption::new( BaseDependencyCommand::OPTION_TREE, - Some("t"), - InputOption::VALUE_NONE, + Some(shirabe_php_shim::PhpMixed::String("t".to_string())), + Some(InputOption::VALUE_NONE), "Prints the results as a nested tree", + None, ), InputOption::new( "locked", None, - InputOption::VALUE_NONE, + Some(InputOption::VALUE_NONE), "Read dependency information from composer.lock", + None, ), ]) .set_help( @@ -81,33 +64,9 @@ impl ProhibitsCommand { } pub fn execute(&self, input: &dyn InputInterface, output: &dyn OutputInterface) -> i64 { - self.inner.do_execute(input, output, true) - } -} - -impl BaseCommand for ProhibitsCommand { - fn inner(&self) -> &CommandBase { - &self.inner - } - - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + // TODO(phase-b): wire `do_execute` from BaseDependencyCommand trait + let _ = (input, output); + todo!() } } @@ -121,4 +80,12 @@ impl BaseDependencyCommand for ProhibitsCommand { } } -impl Command for ProhibitsCommand {} +impl HasBaseCommandData for ProhibitsCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data + } + + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data + } +} diff --git a/crates/shirabe/src/command/reinstall_command.rs b/crates/shirabe/src/command/reinstall_command.rs index d16b001..7e703c4 100644 --- a/crates/shirabe/src/command/reinstall_command.rs +++ b/crates/shirabe/src/command/reinstall_command.rs @@ -4,14 +4,11 @@ use std::any::Any; use anyhow::Result; use shirabe_external_packages::composer::pcre::preg::Preg; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; use shirabe_php_shim::InvalidArgumentException; -use crate::command::base_command::BaseCommand; -use crate::command::completion_trait::CompletionTrait; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::console::input::input_argument::InputArgument; use crate::console::input::input_option::InputOption; @@ -29,43 +26,29 @@ use crate::util::platform::Platform; #[derive(Debug)] pub struct ReinstallCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, -} - -impl CompletionTrait for ReinstallCommand { - fn require_composer( - &self, - disable_plugins: Option<bool>, - disable_scripts: Option<bool>, - ) -> Composer { - todo!() - } + base_command_data: BaseCommandData, } impl ReinstallCommand { pub fn configure(&mut self) { - let suggest_prefer_install = self.suggest_prefer_install(); - let suggest_installed_package_types = self.suggest_installed_package_types(false); - let suggest_installed_package = self.suggest_installed_package(false); - self.inner + // TODO(cli-completion): suggest_prefer_install / suggest_installed_package_types / suggest_installed_package + self .set_name("reinstall") .set_description("Uninstalls and reinstalls the given package names") .set_definition(vec![ - InputOption::new("prefer-source", None, Some(InputOption::VALUE_NONE), "Forces installation from package sources when possible, including VCS information.", None, vec![]), - InputOption::new("prefer-dist", None, Some(InputOption::VALUE_NONE), "Forces installation from package dist (default behavior).", None, vec![]), - InputOption::new("prefer-install", None, Some(InputOption::VALUE_REQUIRED), "Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).", None, suggest_prefer_install), - InputOption::new("no-autoloader", None, Some(InputOption::VALUE_NONE), "Skips autoloader generation", None, vec![]), - InputOption::new("no-progress", None, Some(InputOption::VALUE_NONE), "Do not output download progress.", None, vec![]), - InputOption::new("optimize-autoloader", Some(shirabe_php_shim::PhpMixed::String("o".to_string())), Some(InputOption::VALUE_NONE), "Optimize autoloader during autoloader dump", None, vec![]), - InputOption::new("classmap-authoritative", Some(shirabe_php_shim::PhpMixed::String("a".to_string())), Some(InputOption::VALUE_NONE), "Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.", None, vec![]), - InputOption::new("apcu-autoloader", None, Some(InputOption::VALUE_NONE), "Use APCu to cache found/not-found classes.", None, vec![]), - InputOption::new("apcu-autoloader-prefix", None, Some(InputOption::VALUE_REQUIRED), "Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader", None, vec![]), - InputOption::new("ignore-platform-req", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Ignore a specific platform requirement (php & ext- packages).", None, vec![]), - InputOption::new("ignore-platform-reqs", None, Some(InputOption::VALUE_NONE), "Ignore all platform requirements (php & ext- packages).", None, vec![]), - InputOption::new("type", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Filter packages to reinstall by type(s)", None, suggest_installed_package_types), - InputArgument::new("packages", Some(InputArgument::IS_ARRAY), "List of package names to reinstall, can include a wildcard (*) to match any substring.", None, suggest_installed_package), + InputOption::new("prefer-source", None, Some(InputOption::VALUE_NONE), "Forces installation from package sources when possible, including VCS information.", None), + InputOption::new("prefer-dist", None, Some(InputOption::VALUE_NONE), "Forces installation from package dist (default behavior).", None), + InputOption::new("prefer-install", None, Some(InputOption::VALUE_REQUIRED), "Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).", None), + InputOption::new("no-autoloader", None, Some(InputOption::VALUE_NONE), "Skips autoloader generation", None), + InputOption::new("no-progress", None, Some(InputOption::VALUE_NONE), "Do not output download progress.", None), + InputOption::new("optimize-autoloader", Some(shirabe_php_shim::PhpMixed::String("o".to_string())), Some(InputOption::VALUE_NONE), "Optimize autoloader during autoloader dump", None), + InputOption::new("classmap-authoritative", Some(shirabe_php_shim::PhpMixed::String("a".to_string())), Some(InputOption::VALUE_NONE), "Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.", None), + InputOption::new("apcu-autoloader", None, Some(InputOption::VALUE_NONE), "Use APCu to cache found/not-found classes.", None), + InputOption::new("apcu-autoloader-prefix", None, Some(InputOption::VALUE_REQUIRED), "Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader", None), + InputOption::new("ignore-platform-req", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Ignore a specific platform requirement (php & ext- packages).", None), + InputOption::new("ignore-platform-reqs", None, Some(InputOption::VALUE_NONE), "Ignore all platform requirements (php & ext- packages).", None), + InputOption::new("type", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Filter packages to reinstall by type(s)", None), + InputArgument::new("packages", Some(InputArgument::IS_ARRAY), "List of package names to reinstall, can include a wildcard (*) to match any substring.", None), ]) .set_help( "The <info>reinstall</info> command looks up installed packages by name,\n\ @@ -78,9 +61,9 @@ impl ReinstallCommand { } pub fn execute(&self, input: &dyn InputInterface, output: &dyn OutputInterface) -> Result<i64> { - let io = self.inner.get_io(); + let io = self.get_io(); - let composer = self.inner.require_composer()?; + let composer = self.require_composer(None, None)?; let local_repo = composer.get_repository_manager().get_local_repository(); let mut packages_to_reinstall: Vec< @@ -198,17 +181,16 @@ impl ReinstallCommand { let command_event = CommandEvent::new( PluginEvents::COMMAND.to_string(), "reinstall".to_string(), - Box::new(input), - Box::new(output), + input, + output, vec![], vec![], ); let event_dispatcher = composer.get_event_dispatcher(); - event_dispatcher.dispatch(command_event.get_name(), &command_event); + event_dispatcher.dispatch(Some(command_event.get_name()), None); let config = composer.get_config(); - let (prefer_source, prefer_dist) = - self.inner.get_preferred_install_options(config, input)?; + let (prefer_source, prefer_dist) = self.get_preferred_install_options(config, input)?; let installation_manager = composer.get_installation_manager(); let download_manager = composer.get_download_manager(); @@ -220,13 +202,20 @@ impl ReinstallCommand { installation_manager.disable_plugins(); } - download_manager.set_prefer_source(prefer_source); - download_manager.set_prefer_dist(prefer_dist); + download_manager + .borrow_mut() + .set_prefer_source(prefer_source); + download_manager.borrow_mut().set_prefer_dist(prefer_dist); let dev_mode = local_repo.get_dev_mode().unwrap_or(true); Platform::put_env("COMPOSER_DEV_MODE", if dev_mode { "1" } else { "0" }); - event_dispatcher.dispatch_script(ScriptEvents::PRE_INSTALL_CMD, dev_mode); + event_dispatcher.dispatch_script( + ScriptEvents::PRE_INSTALL_CMD, + dev_mode, + vec![], + indexmap::IndexMap::new(), + ); installation_manager.execute(local_repo, uninstall_operations, dev_mode); installation_manager.execute(local_repo, install_operations, dev_mode); @@ -259,9 +248,7 @@ impl ReinstallCommand { let generator = composer.get_autoload_generator(); generator.set_class_map_authoritative(authoritative); generator.set_apcu(apcu, apcu_prefix.as_deref()); - generator.set_platform_requirement_filter( - self.inner.get_platform_requirement_filter(input)?, - ); + generator.set_platform_requirement_filter(self.get_platform_requirement_filter(input)?); generator.dump( config, local_repo, @@ -274,36 +261,23 @@ impl ReinstallCommand { ); } - event_dispatcher.dispatch_script(ScriptEvents::POST_INSTALL_CMD, dev_mode); + event_dispatcher.dispatch_script( + ScriptEvents::POST_INSTALL_CMD, + dev_mode, + vec![], + indexmap::IndexMap::new(), + ); Ok(0) } } -impl BaseCommand for ReinstallCommand { - fn inner(&self) -> &CommandBase { - &self.inner +impl HasBaseCommandData for ReinstallCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for ReinstallCommand {} diff --git a/crates/shirabe/src/command/remove_command.rs b/crates/shirabe/src/command/remove_command.rs index c9e967b..53bcdc9 100644 --- a/crates/shirabe/src/command/remove_command.rs +++ b/crates/shirabe/src/command/remove_command.rs @@ -2,17 +2,15 @@ use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::preg::Preg; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::component::console::exception::invalid_argument_exception::InvalidArgumentException; use shirabe_external_packages::symfony::component::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::component::console::output::output_interface::OutputInterface; use shirabe_php_shim::{PhpMixed, UnexpectedValueException, array_map, strtolower}; use crate::advisory::auditor::Auditor; -use crate::command::base_command::BaseCommand; -use crate::command::completion_trait::CompletionTrait; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; +use crate::config::config_source_interface::ConfigSourceInterface; use crate::config::json_config_source::JsonConfigSource; use crate::console::input::input_argument::InputArgument; use crate::console::input::input_option::InputOption; @@ -23,20 +21,19 @@ use crate::io::io_interface::IOInterface; use crate::json::json_file::JsonFile; use crate::package::base_package; use crate::package::base_package::BasePackage; +use crate::repository::canonical_packages_trait::CanonicalPackagesTrait; #[derive(Debug)] pub struct RemoveCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, } impl RemoveCommand { pub fn configure(&mut self) { - let suggest_root_requirement = self.suggest_root_requirement(); - self.inner + // TODO(cli-completion): suggest_root_requirement() for `packages` argument + self .set_name("remove") - .set_aliases(vec!["rm".to_string(), "uninstall".to_string()]) + .set_aliases(&["rm".to_string(), "uninstall".to_string()]) .set_description("Removes a package from the require or require-dev") .set_definition(vec![ InputArgument::new( @@ -44,7 +41,6 @@ impl RemoveCommand { Some(InputArgument::IS_ARRAY), "Packages that should be removed.", None, - suggest_root_requirement, ), InputOption::new( "dev", @@ -52,7 +48,6 @@ impl RemoveCommand { Some(InputOption::VALUE_NONE), "Removes a package from the require-dev section.", None, - vec![], ), InputOption::new( "dry-run", @@ -60,7 +55,6 @@ impl RemoveCommand { Some(InputOption::VALUE_NONE), "Outputs the operations but will not execute anything (implicitly enables --verbose).", None, - vec![], ), InputOption::new( "no-progress", @@ -68,7 +62,6 @@ impl RemoveCommand { Some(InputOption::VALUE_NONE), "Do not output download progress.", None, - vec![], ), InputOption::new( "no-update", @@ -76,7 +69,6 @@ impl RemoveCommand { Some(InputOption::VALUE_NONE), "Disables the automatic update of the dependencies (implies --no-install).", None, - vec![], ), InputOption::new( "no-install", @@ -84,7 +76,6 @@ impl RemoveCommand { Some(InputOption::VALUE_NONE), "Skip the install step after updating the composer.lock file.", None, - vec![], ), InputOption::new( "no-audit", @@ -92,7 +83,6 @@ impl RemoveCommand { Some(InputOption::VALUE_NONE), "Skip the audit step after updating the composer.lock file (can also be set via the COMPOSER_NO_AUDIT=1 env var).", None, - vec![], ), InputOption::new( "audit-format", @@ -100,7 +90,6 @@ impl RemoveCommand { Some(InputOption::VALUE_REQUIRED), "Audit output format. Must be \"table\", \"plain\", \"json\", or \"summary\".", Some(PhpMixed::String(Auditor::FORMAT_SUMMARY.to_string())), - Auditor::FORMATS.to_vec(), ), InputOption::new( "no-security-blocking", @@ -108,7 +97,6 @@ impl RemoveCommand { Some(InputOption::VALUE_NONE), "Allows installing packages with security advisories or that are abandoned (can also be set via the COMPOSER_NO_SECURITY_BLOCKING=1 env var).", None, - vec![], ), InputOption::new( "update-no-dev", @@ -116,7 +104,6 @@ impl RemoveCommand { Some(InputOption::VALUE_NONE), "Run the dependency update with the --no-dev option.", None, - vec![], ), InputOption::new( "update-with-dependencies", @@ -124,7 +111,6 @@ impl RemoveCommand { Some(InputOption::VALUE_NONE), "Allows inherited dependencies to be updated with explicit dependencies (can also be set via the COMPOSER_WITH_DEPENDENCIES=1 env var). (Deprecated, is now default behavior)", None, - vec![], ), InputOption::new( "update-with-all-dependencies", @@ -132,7 +118,6 @@ impl RemoveCommand { Some(InputOption::VALUE_NONE), "Allows all inherited dependencies to be updated, including those that are root requirements (can also be set via the COMPOSER_WITH_ALL_DEPENDENCIES=1 env var).", None, - vec![], ), InputOption::new( "with-all-dependencies", @@ -140,7 +125,6 @@ impl RemoveCommand { Some(InputOption::VALUE_NONE), "Alias for --update-with-all-dependencies", None, - vec![], ), InputOption::new( "no-update-with-dependencies", @@ -148,7 +132,6 @@ impl RemoveCommand { Some(InputOption::VALUE_NONE), "Does not allow inherited dependencies to be updated with explicit dependencies.", None, - vec![], ), InputOption::new( "minimal-changes", @@ -156,7 +139,6 @@ impl RemoveCommand { Some(InputOption::VALUE_NONE), "During an update with -w/-W, only perform absolutely necessary changes to transitive dependencies (can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var).", None, - vec![], ), InputOption::new( "unused", @@ -164,7 +146,6 @@ impl RemoveCommand { Some(InputOption::VALUE_NONE), "Remove all packages which are locked but not required by any other package.", None, - vec![], ), InputOption::new( "ignore-platform-req", @@ -172,7 +153,6 @@ impl RemoveCommand { Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Ignore a specific platform requirement (php & ext- packages).", None, - vec![], ), InputOption::new( "ignore-platform-reqs", @@ -180,7 +160,6 @@ impl RemoveCommand { Some(InputOption::VALUE_NONE), "Ignore all platform requirements (php & ext- packages).", None, - vec![], ), InputOption::new( "optimize-autoloader", @@ -188,7 +167,6 @@ impl RemoveCommand { Some(InputOption::VALUE_NONE), "Optimize autoloader during autoloader dump", None, - vec![], ), InputOption::new( "classmap-authoritative", @@ -196,7 +174,6 @@ impl RemoveCommand { Some(InputOption::VALUE_NONE), "Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.", None, - vec![], ), InputOption::new( "apcu-autoloader", @@ -204,7 +181,6 @@ impl RemoveCommand { Some(InputOption::VALUE_NONE), "Use APCu to cache found/not-found classes.", None, - vec![], ), InputOption::new( "apcu-autoloader-prefix", @@ -212,7 +188,6 @@ impl RemoveCommand { Some(InputOption::VALUE_REQUIRED), "Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader", None, - vec![], ), ]) .set_help( @@ -252,7 +227,7 @@ impl RemoveCommand { .unwrap_or_default(); if input.get_option("unused").as_bool().unwrap_or(false) { - let composer = self.require_composer()?; + let composer = self.require_composer(None, None)?; let locker = composer.get_locker(); if !locker.is_locked() { return Err(anyhow::anyhow!(UnexpectedValueException { @@ -263,7 +238,7 @@ impl RemoveCommand { })); } - let locked_packages = locker.get_locked_repository()?.get_packages(); + let locked_packages = locker.get_locked_repository(true)?.get_packages(); let mut required: IndexMap<String, bool> = IndexMap::new(); for link in composer @@ -312,13 +287,14 @@ impl RemoveCommand { } } - let file = Factory::get_composer_file(); + let file = Factory::get_composer_file()?; - let json_file = JsonFile::new(&file, None, None); + let json_file = JsonFile::new(file.clone(), None, None)?; let composer_data = json_file.read()?; let composer_backup = std::fs::read_to_string(json_file.get_path())?; - let json = JsonConfigSource::new(&json_file); + let json_file_for_source = JsonFile::new(file, None, None)?; + let json = JsonConfigSource::new(json_file_for_source, false); let r#type = if input.get_option("dev").as_bool().unwrap_or(false) { "require-dev" @@ -484,14 +460,14 @@ impl RemoveCommand { } // TODO(plugin): deactivate installed plugins - if let Some(composer_opt) = self.try_composer() { + if let Some(composer_opt) = self.try_composer(None, None) { composer_opt .get_plugin_manager() .deactivate_installed_plugins(); } self.reset_composer(); - let composer = self.require_composer()?; + let composer = self.require_composer(None, None)?; if dry_run { let root_package = composer.get_package(); @@ -680,30 +656,12 @@ impl RemoveCommand { } } -impl BaseCommand for RemoveCommand { - fn inner(&self) -> &CommandBase { - &self.inner +impl HasBaseCommandData for RemoveCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for RemoveCommand {} diff --git a/crates/shirabe/src/command/repository_command.rs b/crates/shirabe/src/command/repository_command.rs index 79b4a15..2aea141 100644 --- a/crates/shirabe/src/command/repository_command.rs +++ b/crates/shirabe/src/command/repository_command.rs @@ -2,19 +2,17 @@ use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::preg::Preg; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; -use shirabe_external_packages::symfony::console::completion::completion_input::CompletionInput; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; use shirabe_php_shim::{ InvalidArgumentException, PHP_URL_HOST, PhpMixed, RuntimeException, parse_url, strtolower, }; -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::command::base_config_command::BaseConfigCommand; use crate::composer::Composer; use crate::config::Config; +use crate::config::config_source_interface::ConfigSourceInterface; use crate::config::json_config_source::JsonConfigSource; use crate::console::input::input_argument::InputArgument; use crate::console::input::input_option::InputOption; @@ -24,9 +22,7 @@ use crate::json::json_file::JsonFile; #[derive(Debug)] pub struct RepositoryCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, config: Option<Config>, config_file: Option<JsonFile>, @@ -35,24 +31,21 @@ pub struct RepositoryCommand { impl RepositoryCommand { pub fn configure(&mut self) { - let suggest_repo_names_before = self.suggest_repo_names(); - let suggest_repo_names_after = self.suggest_repo_names(); - let suggest_repo_names_name = self.suggest_repo_names(); - let suggest_type_for_add = Self::suggest_type_for_add(); - self.inner.inner + // TODO(cli-completion): suggest_repo_names() / suggest_type_for_add() + self .set_name("repository") - .set_aliases(vec!["repo".to_string()]) + .set_aliases(&["repo".to_string()]) .set_description("Manages repositories") .set_definition(vec![ - InputOption::new("global", Some(PhpMixed::String("g".to_string())), Some(InputOption::VALUE_NONE), "Apply command to the global config file", None, vec![]), - InputOption::new("file", Some(PhpMixed::String("f".to_string())), Some(InputOption::VALUE_REQUIRED), "If you want to choose a different composer.json or config.json", None, vec![]), - InputOption::new("append", None, Some(InputOption::VALUE_NONE), "When adding a repository, append it (lower priority) instead of prepending it", None, vec![]), - InputOption::new("before", None, Some(InputOption::VALUE_REQUIRED), "When adding a repository, insert it before the given repository name", None, suggest_repo_names_before), - InputOption::new("after", None, Some(InputOption::VALUE_REQUIRED), "When adding a repository, insert it after the given repository name", None, suggest_repo_names_after), - InputArgument::new("action", Some(InputArgument::OPTIONAL), "Action to perform: list, add, remove, set-url, get-url, enable, disable", Some(PhpMixed::String("list".to_string())), vec!["list", "add", "remove", "set-url", "get-url", "enable", "disable"]), - InputArgument::new("name", Some(InputArgument::OPTIONAL), "Repository name (or special name packagist.org for enable/disable)", None, suggest_repo_names_name), - InputArgument::new("arg1", Some(InputArgument::OPTIONAL), "Type for add, or new URL for set-url, or JSON config for add", None, suggest_type_for_add), - InputArgument::new("arg2", Some(InputArgument::OPTIONAL), "URL for add (if not using JSON)", None, vec![]), + InputOption::new("global", Some(PhpMixed::String("g".to_string())), Some(InputOption::VALUE_NONE), "Apply command to the global config file", None), + InputOption::new("file", Some(PhpMixed::String("f".to_string())), Some(InputOption::VALUE_REQUIRED), "If you want to choose a different composer.json or config.json", None), + InputOption::new("append", None, Some(InputOption::VALUE_NONE), "When adding a repository, append it (lower priority) instead of prepending it", None), + InputOption::new("before", None, Some(InputOption::VALUE_REQUIRED), "When adding a repository, insert it before the given repository name", None), + InputOption::new("after", None, Some(InputOption::VALUE_REQUIRED), "When adding a repository, insert it after the given repository name", None), + InputArgument::new("action", Some(InputArgument::OPTIONAL), "Action to perform: list, add, remove, set-url, get-url, enable, disable", Some(PhpMixed::String("list".to_string()))), + InputArgument::new("name", Some(InputArgument::OPTIONAL), "Repository name (or special name packagist.org for enable/disable)", None), + InputArgument::new("arg1", Some(InputArgument::OPTIONAL), "Type for add, or new URL for set-url, or JSON config for add", None), + InputArgument::new("arg2", Some(InputArgument::OPTIONAL), "URL for add (if not using JSON)", None), ]) .set_help( "This command lets you manage repositories in your composer.json.\n\n\ @@ -98,7 +91,7 @@ impl RepositoryCommand { .as_string() .map(|s| s.to_string()); - let config_data = self.inner.config_file.as_ref().unwrap().read()?; + let config_data = self.config_file.as_ref().unwrap().read()?; let config_file_path = self .inner .config_file @@ -106,12 +99,11 @@ impl RepositoryCommand { .unwrap() .get_path() .to_string(); - self.inner - .config + self.config .as_mut() .unwrap() .merge(config_data, &config_file_path); - let repos = self.inner.config.as_ref().unwrap().get_repositories(); + let repos = self.config.as_ref().unwrap().get_repositories(); match action.as_str() { "list" | "ls" | "show" => { @@ -173,21 +165,17 @@ impl RepositoryCommand { } let reference_name = before.as_deref().or(after.as_deref()).unwrap(); let offset: i64 = if after.is_some() { 1 } else { 0 }; - self.inner - .config_source - .as_mut() - .unwrap() - .insert_repository( - name.as_deref().unwrap(), - repo_config, - reference_name, - offset, - ); + self.config_source.as_mut().unwrap().insert_repository( + name.as_deref().unwrap(), + repo_config, + reference_name, + offset, + ); return Ok(0); } let append = input.get_option("append").as_bool().unwrap_or(false); - self.inner.config_source.as_mut().unwrap().add_repository( + self.config_source.as_mut().unwrap().add_repository( name.as_deref().unwrap(), repo_config, append, @@ -202,13 +190,12 @@ impl RepositoryCommand { })); } let name_str = name.as_deref().unwrap(); - self.inner - .config_source + self.config_source .as_mut() .unwrap() .remove_repository(name_str); if ["packagist", "packagist.org"].contains(&name_str) { - self.inner.config_source.as_mut().unwrap().add_repository( + self.config_source.as_mut().unwrap().add_repository( "packagist.org", PhpMixed::Bool(false), false, @@ -223,8 +210,7 @@ impl RepositoryCommand { code: 0, })); } - self.inner - .config_source + self.config_source .as_mut() .unwrap() .set_repository_url(name.as_deref().unwrap(), arg1.as_deref().unwrap()); @@ -242,7 +228,7 @@ impl RepositoryCommand { if let PhpMixed::Array(ref repo_map) = *repo { let url = repo_map.get("url").and_then(|v| v.as_string()); if let Some(url) = url { - self.inner.inner.get_io().write(url); + self.get_io().write(url); return Ok(0); } return Err(anyhow::anyhow!(InvalidArgumentException { @@ -257,7 +243,7 @@ impl RepositoryCommand { if n == name_str { let url = repo_map.get("url").and_then(|v| v.as_string()); if let Some(url) = url { - self.inner.inner.get_io().write(url); + self.get_io().write(url); return Ok(0); } return Err(anyhow::anyhow!(InvalidArgumentException { @@ -286,7 +272,7 @@ impl RepositoryCommand { let name_str = name.as_deref().unwrap(); if ["packagist", "packagist.org"].contains(&name_str) { let append = input.get_option("append").as_bool().unwrap_or(false); - self.inner.config_source.as_mut().unwrap().add_repository( + self.config_source.as_mut().unwrap().add_repository( "packagist.org", PhpMixed::Bool(false), append, @@ -307,8 +293,7 @@ impl RepositoryCommand { } let name_str = name.as_deref().unwrap(); if ["packagist", "packagist.org"].contains(&name_str) { - self.inner - .config_source + self.config_source .as_mut() .unwrap() .remove_repository("packagist.org"); @@ -331,7 +316,7 @@ impl RepositoryCommand { } fn list_repositories(&self, mut repos: IndexMap<String, PhpMixed>) { - let io = self.inner.inner.get_io(); + let io = self.get_io(); let mut packagist_present = false; for (_key, repo) in &repos { @@ -396,84 +381,14 @@ impl RepositoryCommand { .get("url") .and_then(|v| v.as_string()) .map(|s| s.to_string()) - .unwrap_or_else(|| JsonFile::encode(repo)); + .unwrap_or_else(|| JsonFile::encode(repo, 448)); io.write(&format!("[{}] <info>{}</info> {}", name, r#type, url)); } } } - fn suggest_type_for_add() -> Box<dyn Fn(&CompletionInput) -> Vec<String>> { - Box::new(|input: &CompletionInput| { - if input.get_argument("action").as_string() == Some("add") { - vec![ - "composer".to_string(), - "vcs".to_string(), - "artifact".to_string(), - "path".to_string(), - ] - } else { - vec![] - } - }) - } - - fn suggest_repo_names(&self) -> Box<dyn Fn(&CompletionInput) -> Vec<String> + '_> { - Box::new(move |input: &CompletionInput| { - let action = input - .get_argument("action") - .as_string() - .unwrap_or("") - .to_string(); - if ["enable", "disable"].contains(&action.as_str()) { - return vec!["packagist.org".to_string()]; - } - if !["remove", "set-url", "get-url"].contains(&action.as_str()) { - return vec![]; - } - let config = Factory::create_config(None, None).unwrap(); - let config_file_path = self.inner.get_composer_config_file(input, &config); - let config_file = JsonFile::new(config_file_path, None, None); - let data = config_file.read().unwrap_or_default(); - let mut repos = vec![]; - if let Some(repositories) = data.get("repositories").and_then(|v| v.as_list()) { - for repo in repositories { - if let PhpMixed::Array(ref repo_map) = **repo { - if let Some(name) = repo_map.get("name").and_then(|v| v.as_string()) { - repos.push(name.to_string()); - } - } - } - } - repos.sort(); - repos - }) - } -} - -impl BaseCommand for RepositoryCommand { - fn inner(&self) -> &CommandBase { - &self.inner - } - - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io - } + // TODO(cli-completion): fn suggest_type_for_add() + // TODO(cli-completion): fn suggest_repo_names(&self) } impl BaseConfigCommand for RepositoryCommand { @@ -502,4 +417,12 @@ impl BaseConfigCommand for RepositoryCommand { } } -impl Command for RepositoryCommand {} +impl HasBaseCommandData for RepositoryCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data + } + + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data + } +} diff --git a/crates/shirabe/src/command/require_command.rs b/crates/shirabe/src/command/require_command.rs index 8031fbc..63efa92 100644 --- a/crates/shirabe/src/command/require_command.rs +++ b/crates/shirabe/src/command/require_command.rs @@ -5,8 +5,6 @@ use anyhow::Result; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::preg::Preg; use shirabe_external_packages::seld::signal::signal_handler::SignalHandler; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::component::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::component::console::output::output_interface::OutputInterface; use shirabe_php_shim::{ @@ -17,8 +15,7 @@ use shirabe_php_shim::{ }; use crate::advisory::auditor::Auditor; -use crate::command::base_command::BaseCommand; -use crate::command::completion_trait::CompletionTrait; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::command::package_discovery_trait::PackageDiscoveryTrait; use crate::composer::Composer; use crate::console::input::input_argument::InputArgument; @@ -49,9 +46,7 @@ use crate::util::silencer::Silencer; #[derive(Debug)] pub struct RequireCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, newly_created: bool, first_require: bool, @@ -65,16 +60,6 @@ pub struct RequireCommand { dependency_resolution_completed: bool, } -impl CompletionTrait for RequireCommand { - fn require_composer( - &self, - disable_plugins: Option<bool>, - disable_scripts: Option<bool>, - ) -> Composer { - todo!() - } -} - impl PackageDiscoveryTrait for RequireCommand { fn get_repos_mut(&mut self) -> &mut Option<CompositeRepository> { todo!() @@ -114,43 +99,41 @@ impl PackageDiscoveryTrait for RequireCommand { impl RequireCommand { pub fn configure(&mut self) { - let suggest_available_package_incl_platform = - self.suggest_available_package_incl_platform(); - let suggest_prefer_install = self.suggest_prefer_install(); - self.inner + // TODO(cli-completion): suggest_available_package_incl_platform / suggest_prefer_install + self .set_name("require") - .set_aliases(vec!["r".to_string()]) + .set_aliases(&["r".to_string()]) .set_description("Adds required packages to your composer.json and installs them") .set_definition(vec![ - InputArgument::new("packages", Some(InputArgument::IS_ARRAY | InputArgument::OPTIONAL), "Optional package name can also include a version constraint, e.g. foo/bar or foo/bar:1.0.0 or foo/bar=1.0.0 or \"foo/bar 1.0.0\"", None, suggest_available_package_incl_platform), - InputOption::new("dev", None, Some(InputOption::VALUE_NONE), "Add requirement to require-dev.", None, vec![]), - InputOption::new("dry-run", None, Some(InputOption::VALUE_NONE), "Outputs the operations but will not execute anything (implicitly enables --verbose).", None, vec![]), - InputOption::new("prefer-source", None, Some(InputOption::VALUE_NONE), "Forces installation from package sources when possible, including VCS information.", None, vec![]), - InputOption::new("prefer-dist", None, Some(InputOption::VALUE_NONE), "Forces installation from package dist (default behavior).", None, vec![]), - InputOption::new("prefer-install", None, Some(InputOption::VALUE_REQUIRED), "Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).", None, suggest_prefer_install), - InputOption::new("fixed", None, Some(InputOption::VALUE_NONE), "Write fixed version to the composer.json.", None, vec![]), - InputOption::new("no-suggest", None, Some(InputOption::VALUE_NONE), "DEPRECATED: This flag does not exist anymore.", None, vec![]), - InputOption::new("no-progress", None, Some(InputOption::VALUE_NONE), "Do not output download progress.", None, vec![]), - InputOption::new("no-update", None, Some(InputOption::VALUE_NONE), "Disables the automatic update of the dependencies (implies --no-install).", None, vec![]), - InputOption::new("no-install", None, Some(InputOption::VALUE_NONE), "Skip the install step after updating the composer.lock file.", None, vec![]), - InputOption::new("no-audit", None, Some(InputOption::VALUE_NONE), "Skip the audit step after updating the composer.lock file (can also be set via the COMPOSER_NO_AUDIT=1 env var).", None, vec![]), - InputOption::new("audit-format", None, Some(InputOption::VALUE_REQUIRED), "Audit output format. Must be \"table\", \"plain\", \"json\", or \"summary\".", Some(PhpMixed::String(Auditor::FORMAT_SUMMARY.to_string())), Auditor::FORMATS.to_vec()), - InputOption::new("no-security-blocking", None, Some(InputOption::VALUE_NONE), "Allows installing packages with security advisories or that are abandoned (can also be set via the COMPOSER_NO_SECURITY_BLOCKING=1 env var).", None, vec![]), - InputOption::new("update-no-dev", None, Some(InputOption::VALUE_NONE), "Run the dependency update with the --no-dev option.", None, vec![]), - InputOption::new("update-with-dependencies", Some(PhpMixed::String("w".to_string())), Some(InputOption::VALUE_NONE), "Allows inherited dependencies to be updated, except those that are root requirements (can also be set via the COMPOSER_WITH_DEPENDENCIES=1 env var).", None, vec![]), - InputOption::new("update-with-all-dependencies", Some(PhpMixed::String("W".to_string())), Some(InputOption::VALUE_NONE), "Allows all inherited dependencies to be updated, including those that are root requirements (can also be set via the COMPOSER_WITH_ALL_DEPENDENCIES=1 env var).", None, vec![]), - InputOption::new("with-dependencies", None, Some(InputOption::VALUE_NONE), "Alias for --update-with-dependencies", None, vec![]), - InputOption::new("with-all-dependencies", None, Some(InputOption::VALUE_NONE), "Alias for --update-with-all-dependencies", None, vec![]), - InputOption::new("ignore-platform-req", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Ignore a specific platform requirement (php & ext- packages).", None, vec![]), - InputOption::new("ignore-platform-reqs", None, Some(InputOption::VALUE_NONE), "Ignore all platform requirements (php & ext- packages).", None, vec![]), - InputOption::new("prefer-stable", None, Some(InputOption::VALUE_NONE), "Prefer stable versions of dependencies (can also be set via the COMPOSER_PREFER_STABLE=1 env var).", None, vec![]), - InputOption::new("prefer-lowest", None, Some(InputOption::VALUE_NONE), "Prefer lowest versions of dependencies (can also be set via the COMPOSER_PREFER_LOWEST=1 env var).", None, vec![]), - InputOption::new("minimal-changes", Some(PhpMixed::String("m".to_string())), Some(InputOption::VALUE_NONE), "During an update with -w/-W, only perform absolutely necessary changes to transitive dependencies (can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var).", None, vec![]), - InputOption::new("sort-packages", None, Some(InputOption::VALUE_NONE), "Sorts packages when adding/updating a new dependency", None, vec![]), - InputOption::new("optimize-autoloader", Some(PhpMixed::String("o".to_string())), Some(InputOption::VALUE_NONE), "Optimize autoloader during autoloader dump", None, vec![]), - InputOption::new("classmap-authoritative", Some(PhpMixed::String("a".to_string())), Some(InputOption::VALUE_NONE), "Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.", None, vec![]), - InputOption::new("apcu-autoloader", None, Some(InputOption::VALUE_NONE), "Use APCu to cache found/not-found classes.", None, vec![]), - InputOption::new("apcu-autoloader-prefix", None, Some(InputOption::VALUE_REQUIRED), "Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader", None, vec![]), + InputArgument::new("packages", Some(InputArgument::IS_ARRAY | InputArgument::OPTIONAL), "Optional package name can also include a version constraint, e.g. foo/bar or foo/bar:1.0.0 or foo/bar=1.0.0 or \"foo/bar 1.0.0\"", None), + InputOption::new("dev", None, Some(InputOption::VALUE_NONE), "Add requirement to require-dev.", None), + InputOption::new("dry-run", None, Some(InputOption::VALUE_NONE), "Outputs the operations but will not execute anything (implicitly enables --verbose).", None), + InputOption::new("prefer-source", None, Some(InputOption::VALUE_NONE), "Forces installation from package sources when possible, including VCS information.", None), + InputOption::new("prefer-dist", None, Some(InputOption::VALUE_NONE), "Forces installation from package dist (default behavior).", None), + InputOption::new("prefer-install", None, Some(InputOption::VALUE_REQUIRED), "Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).", None), + InputOption::new("fixed", None, Some(InputOption::VALUE_NONE), "Write fixed version to the composer.json.", None), + InputOption::new("no-suggest", None, Some(InputOption::VALUE_NONE), "DEPRECATED: This flag does not exist anymore.", None), + InputOption::new("no-progress", None, Some(InputOption::VALUE_NONE), "Do not output download progress.", None), + InputOption::new("no-update", None, Some(InputOption::VALUE_NONE), "Disables the automatic update of the dependencies (implies --no-install).", None), + InputOption::new("no-install", None, Some(InputOption::VALUE_NONE), "Skip the install step after updating the composer.lock file.", None), + InputOption::new("no-audit", None, Some(InputOption::VALUE_NONE), "Skip the audit step after updating the composer.lock file (can also be set via the COMPOSER_NO_AUDIT=1 env var).", None), + InputOption::new("audit-format", None, Some(InputOption::VALUE_REQUIRED), "Audit output format. Must be \"table\", \"plain\", \"json\", or \"summary\".", Some(PhpMixed::String(Auditor::FORMAT_SUMMARY.to_string()))), + InputOption::new("no-security-blocking", None, Some(InputOption::VALUE_NONE), "Allows installing packages with security advisories or that are abandoned (can also be set via the COMPOSER_NO_SECURITY_BLOCKING=1 env var).", None), + InputOption::new("update-no-dev", None, Some(InputOption::VALUE_NONE), "Run the dependency update with the --no-dev option.", None), + InputOption::new("update-with-dependencies", Some(PhpMixed::String("w".to_string())), Some(InputOption::VALUE_NONE), "Allows inherited dependencies to be updated, except those that are root requirements (can also be set via the COMPOSER_WITH_DEPENDENCIES=1 env var).", None), + InputOption::new("update-with-all-dependencies", Some(PhpMixed::String("W".to_string())), Some(InputOption::VALUE_NONE), "Allows all inherited dependencies to be updated, including those that are root requirements (can also be set via the COMPOSER_WITH_ALL_DEPENDENCIES=1 env var).", None), + InputOption::new("with-dependencies", None, Some(InputOption::VALUE_NONE), "Alias for --update-with-dependencies", None), + InputOption::new("with-all-dependencies", None, Some(InputOption::VALUE_NONE), "Alias for --update-with-all-dependencies", None), + InputOption::new("ignore-platform-req", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Ignore a specific platform requirement (php & ext- packages).", None), + InputOption::new("ignore-platform-reqs", None, Some(InputOption::VALUE_NONE), "Ignore all platform requirements (php & ext- packages).", None), + InputOption::new("prefer-stable", None, Some(InputOption::VALUE_NONE), "Prefer stable versions of dependencies (can also be set via the COMPOSER_PREFER_STABLE=1 env var).", None), + InputOption::new("prefer-lowest", None, Some(InputOption::VALUE_NONE), "Prefer lowest versions of dependencies (can also be set via the COMPOSER_PREFER_LOWEST=1 env var).", None), + InputOption::new("minimal-changes", Some(PhpMixed::String("m".to_string())), Some(InputOption::VALUE_NONE), "During an update with -w/-W, only perform absolutely necessary changes to transitive dependencies (can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var).", None), + InputOption::new("sort-packages", None, Some(InputOption::VALUE_NONE), "Sorts packages when adding/updating a new dependency", None), + InputOption::new("optimize-autoloader", Some(PhpMixed::String("o".to_string())), Some(InputOption::VALUE_NONE), "Optimize autoloader during autoloader dump", None), + InputOption::new("classmap-authoritative", Some(PhpMixed::String("a".to_string())), Some(InputOption::VALUE_NONE), "Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.", None), + InputOption::new("apcu-autoloader", None, Some(InputOption::VALUE_NONE), "Use APCu to cache found/not-found classes.", None), + InputOption::new("apcu-autoloader-prefix", None, Some(InputOption::VALUE_REQUIRED), "Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader", None), ]) .set_help( "The require command adds required packages to your composer.json and installs them.\n\ @@ -173,7 +156,7 @@ impl RequireCommand { output: &dyn OutputInterface, ) -> Result<i64> { self.file = Factory::get_composer_file(); - let io = self.inner.get_io(); + let io = self.get_io(); if input.get_option("no-suggest").as_bool().unwrap_or(false) { io.write_error( @@ -213,7 +196,7 @@ impl RequireCommand { file_put_contents(&self.file, "{\n}\n"); } - self.json = Some(JsonFile::new(&self.file, None, None)); + self.json = Some(JsonFile::new(self.file.clone(), None, None)?); self.lock = Factory::get_lock_file(&self.file); self.composer_backup = file_get_contents(self.json.as_ref().unwrap().get_path()).unwrap_or_default(); @@ -241,15 +224,14 @@ impl RequireCommand { // check for writability by writing to the file as is_writable can not be trusted on network-mounts // see https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926 + let file_path = self.file.clone(); + let backup_contents = self.composer_backup.clone(); if !is_writable(&self.file) - && Silencer::call( - "file_put_contents", - &[ - PhpMixed::String(self.file.clone()), - PhpMixed::String(self.composer_backup.clone()), - ], - ) - .as_bool() + && Silencer::call(|| { + shirabe_php_shim::file_put_contents(&file_path, backup_contents.as_bytes()); + Ok::<bool, anyhow::Error>(false) + }) + .ok() == Some(false) { io.write_error( @@ -300,7 +282,7 @@ impl RequireCommand { } } - let composer = self.inner.require_composer(None, None)?; + let composer = self.require_composer(None, None)?; let repos = composer.get_repository_manager().get_repositories(); let platform_overrides = composer.get_config().get("platform"); @@ -359,7 +341,7 @@ impl RequireCommand { } }; - let mut requirements = self.inner.format_requirements(requirements)?; + let mut requirements = self.format_requirements(requirements)?; if !input.get_option("dev").as_bool().unwrap_or(false) && io.is_interactive() @@ -726,8 +708,8 @@ impl RequireCommand { _remove_key: &str, ) -> Result<i64> { // Update packages - self.inner.reset_composer()?; - let composer = self.inner.require_composer(None, None)?; + self.reset_composer()?; + let composer = self.require_composer(None, None)?; self.dependency_resolution_completed = false; composer.get_event_dispatcher().add_listener( @@ -869,7 +851,7 @@ impl RequireCommand { let command_event = CommandEvent::new(PluginEvents::COMMAND, "require", input, output); composer .get_event_dispatcher() - .dispatch(command_event.get_name(), &command_event); + .dispatch(Some(command_event.get_name()), None); composer .get_installation_manager() @@ -893,13 +875,10 @@ impl RequireCommand { .set_update(true) .set_install(!input.get_option("no-install").as_bool().unwrap_or(false)) .set_update_allow_transitive_dependencies(update_allow_transitive_dependencies) - .set_platform_requirement_filter(self.inner.get_platform_requirement_filter(input)?) + .set_platform_requirement_filter(self.get_platform_requirement_filter(input)?) .set_prefer_stable(input.get_option("prefer-stable").as_bool().unwrap_or(false)) .set_prefer_lowest(input.get_option("prefer-lowest").as_bool().unwrap_or(false)) - .set_audit_config( - self.inner - .create_audit_config(composer.get_config(), input)?, - ) + .set_audit_config(self.create_audit_config(composer.get_config(), input)?) .set_minimal_update(minimal_changes); // if no lock is present, or the file is brand new, we do not do a @@ -915,7 +894,7 @@ impl RequireCommand { let status = install.run()?; if status != 0 && status != Installer::ERROR_AUDIT_FAILED { if status == Installer::ERROR_DEPENDENCY_RESOLUTION_FAILED { - for req in self.inner.normalize_requirements( + for req in self.normalize_requirements( input .get_argument("packages") .as_list() @@ -956,7 +935,7 @@ impl RequireCommand { dry_run: bool, fixed: bool, ) -> Result<i64> { - let composer = self.inner.require_composer(None, None)?; + let composer = self.require_composer(None, None)?; let locker = composer.get_locker(); let mut requirements: IndexMap<String, String> = IndexMap::new(); let version_selector = VersionSelector::new(RepositorySet::new(None, None), None); @@ -990,7 +969,7 @@ impl RequireCommand { version_selector.find_recommended_require_version(&*package), ); } - self.inner.get_io().write_error( + self.get_io().write_error( PhpMixed::String(sprintf( "Using version <info>%s</info> for <info>%s</info>", &[ @@ -1013,12 +992,12 @@ impl RequireCommand { ) .unwrap_or(false) { - self.inner.get_io().warning(format!( + self.get_io().warning(format!( "Version {} looks like it may be a feature branch which is unlikely to keep working in the long run and may be in an unstable state", requirements.get(package_name).cloned().unwrap_or_default(), )); - if self.inner.get_io().is_interactive() - && !self.inner.get_io().ask_confirmation( + if self.get_io().is_interactive() + && !self.get_io().ask_confirmation( "Are you sure you want to use this constraint (<comment>y</comment>) or would you rather abort (<comment>n</comment>) the whole operation [<comment>y,n</comment>]? " .to_string(), true, @@ -1144,7 +1123,7 @@ impl RequireCommand { pub(crate) fn interact(&self, _input: &dyn InputInterface, _output: &dyn OutputInterface) {} fn revert_composer_file(&mut self) { - let io = self.inner.get_io(); + let io = self.get_io(); if self.newly_created { io.write_error( @@ -1184,30 +1163,12 @@ impl RequireCommand { } } -impl BaseCommand for RequireCommand { - fn inner(&self) -> &CommandBase { - &self.inner - } - - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() +impl HasBaseCommandData for RequireCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for RequireCommand {} diff --git a/crates/shirabe/src/command/run_script_command.rs b/crates/shirabe/src/command/run_script_command.rs index 2b9dd1f..5e761f2 100644 --- a/crates/shirabe/src/command/run_script_command.rs +++ b/crates/shirabe/src/command/run_script_command.rs @@ -1,13 +1,11 @@ //! ref: composer/src/Composer/Command/RunScriptCommand.php use anyhow::Result; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; use shirabe_php_shim::{InvalidArgumentException, PhpMixed, RuntimeException}; -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::console::input::input_argument::InputArgument; use crate::console::input::input_option::InputOption; @@ -19,9 +17,7 @@ use crate::util::process_executor::ProcessExecutor; #[derive(Debug)] pub struct RunScriptCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, script_events: Vec<&'static str>, } @@ -48,25 +44,22 @@ impl RunScriptCommand { } pub fn configure(&mut self) { - self.inner - .set_name("run-script") - .set_aliases(vec!["run".to_string()]) + self.set_name("run-script") + .set_aliases(&["run".to_string()]) .set_description("Runs the scripts defined in composer.json") .set_definition(vec![ - // completion callback (runtime script names) is deferred to Phase B + // TODO(cli-completion): script-name completion was provided via a closure suggesting runtime script names InputArgument::new( "script", Some(InputArgument::OPTIONAL), "Script name to run.", None, - vec![], ), InputArgument::new( "args", Some(InputArgument::IS_ARRAY | InputArgument::OPTIONAL), "", None, - vec![], ), InputOption::new( "timeout", @@ -74,7 +67,6 @@ impl RunScriptCommand { Some(InputOption::VALUE_REQUIRED), "Sets script timeout in seconds, or 0 for never.", None, - vec![], ), InputOption::new( "dev", @@ -82,7 +74,6 @@ impl RunScriptCommand { Some(InputOption::VALUE_NONE), "Sets the dev mode.", None, - vec![], ), InputOption::new( "no-dev", @@ -90,7 +81,6 @@ impl RunScriptCommand { Some(InputOption::VALUE_NONE), "Disables the dev mode.", None, - vec![], ), InputOption::new( "list", @@ -98,7 +88,6 @@ impl RunScriptCommand { Some(InputOption::VALUE_NONE), "List scripts.", None, - vec![], ), ]) .set_help( @@ -129,7 +118,7 @@ impl RunScriptCommand { options.insert(script.0.clone(), script.1.clone()); } - let io = self.inner.get_io(); + let io = self.get_io(); let script = io.select( "Script to run: ".to_string(), options.keys().cloned().collect(), @@ -173,11 +162,12 @@ impl RunScriptCommand { } } - let composer = self.inner.require_composer()?; + let composer = self.require_composer(None, None)?; let dev_mode = input.get_option("dev").as_bool().unwrap_or(false) || !input.get_option("no-dev").as_bool().unwrap_or(false); - let event = ScriptEvent::new(script.clone(), &composer, self.inner.get_io(), dev_mode); - let has_listeners = composer.get_event_dispatcher().has_event_listeners(&event); + // TODO(phase-b): ScriptEvent::new takes Composer/IOInterface by value; placeholder construction. + let _ = (script.clone(), &composer, dev_mode); + let has_listeners = false; if !has_listeners { return Err(InvalidArgumentException { message: format!("Script \"{}\" is not defined in this package", script), @@ -224,20 +214,23 @@ impl RunScriptCommand { return Ok(0); } - let io = self.inner.get_io(); + let io = self.get_io(); io.write_error("<info>scripts:</info>"); let table: Vec<Vec<String>> = scripts .iter() .map(|(name, desc)| vec![format!(" {}", name), desc.clone()]) .collect(); - self.inner.render_table(table, output); + self.render_table(table, output); Ok(0) } fn get_scripts(&self) -> Result<Vec<(String, String)>> { - let scripts = self.inner.require_composer()?.get_package().get_scripts(); + let scripts = self + .require_composer(None, None)? + .get_package() + .get_scripts(); if scripts.is_empty() { return Ok(vec![]); } @@ -257,30 +250,12 @@ impl RunScriptCommand { } } -impl BaseCommand for RunScriptCommand { - fn inner(&self) -> &CommandBase { - &self.inner +impl HasBaseCommandData for RunScriptCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for RunScriptCommand {} diff --git a/crates/shirabe/src/command/script_alias_command.rs b/crates/shirabe/src/command/script_alias_command.rs index 01a420a..e077bc7 100644 --- a/crates/shirabe/src/command/script_alias_command.rs +++ b/crates/shirabe/src/command/script_alias_command.rs @@ -1,23 +1,20 @@ //! ref: composer/src/Composer/Command/ScriptAliasCommand.php +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; +use crate::composer::Composer; use crate::console::input::input_argument::InputArgument; use crate::console::input::input_option::InputOption; use crate::io::io_interface::IOInterface; use crate::util::platform::Platform; -use crate::{command::base_command::BaseCommand, composer::Composer}; use anyhow::Result; use shirabe_external_packages::composer::pcre::preg::Preg; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; use shirabe_php_shim::{InvalidArgumentException, LogicException, PhpMixed, is_string}; #[derive(Debug)] pub struct ScriptAliasCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, script: String, description: String, @@ -53,8 +50,7 @@ impl ScriptAliasCommand { } pub fn configure(&mut self) { - self.inner - .set_name(&self.script) + self.set_name(&self.script) .set_description(&self.description) .set_aliases(self.aliases.clone()) .set_definition(vec![ @@ -64,7 +60,6 @@ impl ScriptAliasCommand { Some(InputOption::VALUE_NONE), "Sets the dev mode.", None, - vec![], ), InputOption::new( "no-dev", @@ -72,14 +67,12 @@ impl ScriptAliasCommand { Some(InputOption::VALUE_NONE), "Disables the dev mode.", None, - vec![], ), InputArgument::new( "args", Some(InputArgument::IS_ARRAY | InputArgument::OPTIONAL), "", None, - vec![], ), ]) .set_help( @@ -94,7 +87,7 @@ impl ScriptAliasCommand { input: &dyn InputInterface, _output: &dyn OutputInterface, ) -> Result<i64> { - let composer = self.inner.require_composer()?; + let composer = self.require_composer(None, None)?; let args = input.get_arguments(); @@ -133,30 +126,12 @@ impl ScriptAliasCommand { } } -impl BaseCommand for ScriptAliasCommand { - fn inner(&self) -> &CommandBase { - &self.inner +impl HasBaseCommandData for ScriptAliasCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for ScriptAliasCommand {} diff --git a/crates/shirabe/src/command/search_command.rs b/crates/shirabe/src/command/search_command.rs index 40ef4fe..64727fc 100644 --- a/crates/shirabe/src/command/search_command.rs +++ b/crates/shirabe/src/command/search_command.rs @@ -1,5 +1,7 @@ //! ref: composer/src/Composer/Command/SearchCommand.php +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; +use crate::composer::Composer; use crate::console::input::input_argument::InputArgument; use crate::console::input::input_option::InputOption; use crate::io::io_interface::IOInterface; @@ -9,11 +11,8 @@ use crate::plugin::plugin_events::PluginEvents; use crate::repository::composite_repository::CompositeRepository; use crate::repository::platform_repository::PlatformRepository; use crate::repository::repository_interface::{self, RepositoryInterface}; -use crate::{command::base_command::BaseCommand, composer::Composer}; use anyhow::Result; use indexmap::IndexMap; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::console::formatter::output_formatter::OutputFormatter; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; @@ -21,22 +20,20 @@ use shirabe_php_shim::{InvalidArgumentException, PhpMixed, implode, in_array, pr #[derive(Debug)] pub struct SearchCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, } impl SearchCommand { pub fn configure(&mut self) { - self.inner + self .set_name("search") .set_description("Searches for packages") .set_definition(vec![ - InputOption::new("only-name", Some(PhpMixed::String("N".to_string())), Some(InputOption::VALUE_NONE), "Search only in package names", None, vec![]), - InputOption::new("only-vendor", Some(PhpMixed::String("O".to_string())), Some(InputOption::VALUE_NONE), "Search only for vendor / organization names, returns only \"vendor\" as result", None, vec![]), - InputOption::new("type", Some(PhpMixed::String("t".to_string())), Some(InputOption::VALUE_REQUIRED), "Search for a specific package type", None, vec![]), - InputOption::new("format", Some(PhpMixed::String("f".to_string())), Some(InputOption::VALUE_REQUIRED), "Format of the output: text or json", Some(PhpMixed::String("text".to_string())), vec!["json".to_string(), "text".to_string()]), - InputArgument::new("tokens", Some(InputArgument::IS_ARRAY | InputArgument::REQUIRED), "tokens to search for", None, vec![]), + InputOption::new("only-name", Some(PhpMixed::String("N".to_string())), Some(InputOption::VALUE_NONE), "Search only in package names", None), + InputOption::new("only-vendor", Some(PhpMixed::String("O".to_string())), Some(InputOption::VALUE_NONE), "Search only for vendor / organization names, returns only \"vendor\" as result", None), + InputOption::new("type", Some(PhpMixed::String("t".to_string())), Some(InputOption::VALUE_REQUIRED), "Search for a specific package type", None), + InputOption::new("format", Some(PhpMixed::String("f".to_string())), Some(InputOption::VALUE_REQUIRED), "Format of the output: text or json", Some(PhpMixed::String("text".to_string()))), + InputArgument::new("tokens", Some(InputArgument::IS_ARRAY | InputArgument::REQUIRED), "tokens to search for", None), ]) .set_help( "The search command searches for packages by its name\n\ @@ -51,7 +48,7 @@ impl SearchCommand { output: &dyn OutputInterface, ) -> Result<i64> { let platform_repo = PlatformRepository::new(vec![], IndexMap::new(), None, None)?; - let io = self.inner.get_io(); + let io = self.get_io(); let format = input .get_option("format") @@ -73,11 +70,10 @@ impl SearchCommand { return Ok(1); } - let composer = if let Some(c) = self.inner.try_composer() { + let composer = if let Some(c) = self.try_composer(None, None) { c } else { - self.inner - .create_composer_instance(input, self.inner.get_io(), vec![])? + self.create_composer_instance(input, self.get_io(), vec![])? }; let local_repo = composer.get_repository_manager().get_local_repository(); let installed_repo = @@ -90,14 +86,14 @@ impl SearchCommand { let command_event = CommandEvent::new( PluginEvents::COMMAND.to_string(), "search".to_string(), - Box::new(input), - Box::new(output), + input, + output, vec![], vec![], ); composer .get_event_dispatcher() - .dispatch(command_event.get_name(), &command_event); + .dispatch(Some(command_event.get_name()), None); let mut mode: i64 = repository_interface::SEARCH_FULLTEXT; if input.get_option("only-name").as_bool().unwrap_or(false) { @@ -135,7 +131,7 @@ impl SearchCommand { let results = repos.search(query, mode, r#type); if results.len() > 0 && format == "text" { - let width = self.inner.get_terminal_width(); + let width = self.get_terminal_width(); let mut name_length: i64 = 0; for result in &results { name_length = name_length.max(result.name.len() as i64); @@ -176,37 +172,19 @@ impl SearchCommand { } } } else if format == "json" { - io.write(&JsonFile::encode(&results)); + io.write(&JsonFile::encode(&results, 448)); } Ok(0) } } -impl BaseCommand for SearchCommand { - fn inner(&self) -> &CommandBase { - &self.inner +impl HasBaseCommandData for SearchCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for SearchCommand {} diff --git a/crates/shirabe/src/command/self_update_command.rs b/crates/shirabe/src/command/self_update_command.rs index c6ea1bd..7e79529 100644 --- a/crates/shirabe/src/command/self_update_command.rs +++ b/crates/shirabe/src/command/self_update_command.rs @@ -3,8 +3,6 @@ use crate::io::io_interface; use anyhow::Result; use shirabe_external_packages::composer::pcre::preg::Preg; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::component::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::component::console::output::output_interface::OutputInterface; use shirabe_external_packages::symfony::component::finder::finder::Finder; @@ -20,7 +18,7 @@ use shirabe_php_shim::{ usleep, version_compare, }; -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::config::Config; use crate::console::input::input_argument::InputArgument; @@ -35,9 +33,7 @@ use crate::util::platform::Platform; #[derive(Debug)] pub struct SelfUpdateCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, } impl SelfUpdateCommand { @@ -45,23 +41,23 @@ impl SelfUpdateCommand { const OLD_INSTALL_EXT: &'static str = "-old.phar"; pub fn configure(&mut self) { - self.inner + self .set_name("self-update") - .set_aliases(vec!["selfupdate".to_string()]) + .set_aliases(&["selfupdate".to_string()]) .set_description("Updates composer.phar to the latest version") .set_definition(vec![ - InputOption::new("rollback", Some(PhpMixed::String("r".to_string())), Some(InputOption::VALUE_NONE), "Revert to an older installation of composer", None, vec![]), - InputOption::new("clean-backups", None, Some(InputOption::VALUE_NONE), "Delete old backups during an update. This makes the current version of composer the only backup available after the update", None, vec![]), - InputArgument::new("version", Some(InputArgument::OPTIONAL), "The version to update to", None, vec![]), - InputOption::new("no-progress", None, Some(InputOption::VALUE_NONE), "Do not output download progress.", None, vec![]), - InputOption::new("update-keys", None, Some(InputOption::VALUE_NONE), "Prompt user for a key update", None, vec![]), - InputOption::new("stable", None, Some(InputOption::VALUE_NONE), "Force an update to the stable channel", None, vec![]), - InputOption::new("preview", None, Some(InputOption::VALUE_NONE), "Force an update to the preview channel", None, vec![]), - InputOption::new("snapshot", None, Some(InputOption::VALUE_NONE), "Force an update to the snapshot channel", None, vec![]), - InputOption::new("1", None, Some(InputOption::VALUE_NONE), "Force an update to the stable channel, but only use 1.x versions", None, vec![]), - InputOption::new("2", None, Some(InputOption::VALUE_NONE), "Force an update to the stable channel, but only use 2.x versions", None, vec![]), - InputOption::new("2.2", None, Some(InputOption::VALUE_NONE), "Force an update to the stable channel, but only use 2.2.x LTS versions", None, vec![]), - InputOption::new("set-channel-only", None, Some(InputOption::VALUE_NONE), "Only store the channel as the default one and then exit", None, vec![]), + InputOption::new("rollback", Some(PhpMixed::String("r".to_string())), Some(InputOption::VALUE_NONE), "Revert to an older installation of composer", None), + InputOption::new("clean-backups", None, Some(InputOption::VALUE_NONE), "Delete old backups during an update. This makes the current version of composer the only backup available after the update", None), + InputArgument::new("version", Some(InputArgument::OPTIONAL), "The version to update to", None), + InputOption::new("no-progress", None, Some(InputOption::VALUE_NONE), "Do not output download progress.", None), + InputOption::new("update-keys", None, Some(InputOption::VALUE_NONE), "Prompt user for a key update", None), + InputOption::new("stable", None, Some(InputOption::VALUE_NONE), "Force an update to the stable channel", None), + InputOption::new("preview", None, Some(InputOption::VALUE_NONE), "Force an update to the preview channel", None), + InputOption::new("snapshot", None, Some(InputOption::VALUE_NONE), "Force an update to the snapshot channel", None), + InputOption::new("1", None, Some(InputOption::VALUE_NONE), "Force an update to the stable channel, but only use 1.x versions", None), + InputOption::new("2", None, Some(InputOption::VALUE_NONE), "Force an update to the stable channel, but only use 2.x versions", None), + InputOption::new("2.2", None, Some(InputOption::VALUE_NONE), "Force an update to the stable channel, but only use 2.2.x LTS versions", None), + InputOption::new("set-channel-only", None, Some(InputOption::VALUE_NONE), "Only store the channel as the default one and then exit", None), ]) .set_help( "The <info>self-update</info> command checks getcomposer.org for newer\n\ @@ -144,7 +140,7 @@ impl SelfUpdateCommand { format!("https://{}", Self::HOMEPAGE) }; - let io = self.inner.get_io(); + let io = self.get_io(); let http_downloader = Factory::create_http_downloader(io, &config)?; let mut versions_util = Versions::new(config.clone(), http_downloader.clone()); @@ -241,11 +237,11 @@ impl SelfUpdateCommand { .unwrap_or("") .to_string(); if is_array(home_owner.clone()) && composer_user_name != home_owner_name { - io.write_error( - PhpMixed::String(format!( + io.write_error3( + &format!( "<warning>You are running Composer as \"{}\", while \"{}\" is owned by \"{}\"</warning>", composer_user_name, home, home_owner_name - )), + ), true, io_interface::NORMAL, ); @@ -308,21 +304,21 @@ impl SelfUpdateCommand { .to_string(); update_version = latest_version.clone(); - io.write_error( - PhpMixed::String(format!( + io.write_error3( + &format!( "<warning>A new stable major version of Composer is available ({}), run \"composer self-update --{}\" to update to it. See also https://getcomposer.org/{}</warning>", skipped_version, update_major_version, update_major_version - )), + ), true, io_interface::NORMAL, ); } else if version_compare(¤t_major_version, &preview_major_version, "<") { // promote next major version if available in preview - io.write_error( - PhpMixed::String(format!( + io.write_error3( + &format!( "<warning>A preview release of the next major version of Composer is available ({}), run \"composer self-update --preview\" to give it a try. See also https://github.com/composer/composer/releases for changelogs.</warning>", latest_preview.get("version").and_then(|v| v.as_string()).unwrap_or("") - )), + ), true, io_interface::NORMAL, ); @@ -342,24 +338,24 @@ impl SelfUpdateCommand { &effective_channel, ) != Some(0) { - io.write_error( - PhpMixed::String(format!( + io.write_error3( + &format!( "<warning>Warning: You forced the install of {} via --{}, but {} is the latest stable version. Updating to it via composer self-update --stable is recommended.</warning>", latest_version, effective_channel, latest_stable.get("version").and_then(|v| v.as_string()).unwrap_or("") - )), + ), true, io_interface::NORMAL, ); } if latest.contains_key("eol") { - io.write_error( - PhpMixed::String(format!( + io.write_error3( + &format!( "<warning>Warning: Version {} is EOL / End of Life. {} is the latest stable version. Updating to it via composer self-update --stable is recommended.</warning>", latest_version, latest_stable.get("version").and_then(|v| v.as_string()).unwrap_or("") - )), + ), true, io_interface::NORMAL, ); @@ -368,11 +364,8 @@ impl SelfUpdateCommand { if Preg::is_match(r"{^[0-9a-f]{40}$}", &update_version).unwrap_or(false) && update_version != latest_version { - io.write_error( - PhpMixed::String( - "<error>You can not update to a specific SHA-1 as those phars are not available for download</error>" - .to_string(), - ), + io.write_error3( + "<error>You can not update to a specific SHA-1 as those phars are not available for download</error>", true, io_interface::NORMAL, ); @@ -386,14 +379,14 @@ impl SelfUpdateCommand { } if Composer::VERSION == update_version.as_str() { - io.write_error( - PhpMixed::String(sprintf( + io.write_error3( + &sprintf( "<info>You are already using the latest available Composer version %s (%s channel).</info>", &[ PhpMixed::String(update_version.clone()), PhpMixed::String(channel_string.clone()), ], - )), + ), true, io_interface::NORMAL, ); @@ -430,14 +423,14 @@ impl SelfUpdateCommand { let updating_to_tag = !Preg::is_match(r"{^[0-9a-f]{40}$}", &update_version).unwrap_or(false); - io.write( - PhpMixed::String(sprintf( + io.write3( + &sprintf( "Upgrading to version <info>%s</info> (%s channel).", &[ PhpMixed::String(update_version.clone()), PhpMixed::String(channel_string.clone()), ], - )), + ), true, io_interface::NORMAL, ); @@ -466,20 +459,13 @@ impl SelfUpdateCommand { return Err(e.into()); } }; - io.write_error( - PhpMixed::String(" ".to_string()), - false, - io_interface::NORMAL, - ); + io.write_error3(" ", false, io_interface::NORMAL); http_downloader.copy(&remote_filename, &temp_filename)?; - io.write_error(PhpMixed::String(String::new()), true, io_interface::NORMAL); + io.write_error3("", true, io_interface::NORMAL); if !file_exists(&temp_filename) || signature.is_none() || signature.as_deref() == Some("") { - io.write_error( - PhpMixed::String( - "<error>The download of the new composer version failed for an unexpected reason</error>" - .to_string(), - ), + io.write_error3( + "<error>The download of the new composer version failed for an unexpected reason</error>", true, io_interface::NORMAL, ); @@ -490,11 +476,8 @@ impl SelfUpdateCommand { // verify phar signature if !extension_loaded("openssl") && config.get("disable-tls").as_bool() == Some(true) { - io.write_error( - PhpMixed::String( - "<warning>Skipping phar signature verification as you have disabled OpenSSL via config.disable-tls</warning>" - .to_string(), - ), + io.write_error3( + "<warning>Skipping phar signature verification as you have disabled OpenSSL via config.disable-tls</warning>", true, io_interface::NORMAL, ); @@ -640,20 +623,20 @@ RGv89BPD+2DLnJysngsvVaUCAwEAAQ==\n\ } if file_exists(&backup_file) { - io.write_error( - PhpMixed::String(sprintf( + io.write_error3( + &sprintf( "Use <info>composer self-update --rollback</info> to return to version <comment>%s</comment>", &[PhpMixed::String(Composer::VERSION.to_string())], - )), + ), true, io_interface::NORMAL, ); } else { - io.write_error( - PhpMixed::String(format!( + io.write_error3( + &format!( "<warning>A backup of the current version could not be written to {}, no rollback possible</warning>", backup_file - )), + ), true, io_interface::NORMAL, ); @@ -672,11 +655,8 @@ RGv89BPD+2DLnJysngsvVaUCAwEAAQ==\n\ .into()); } - io.write( - PhpMixed::String( - "Open <info>https://composer.github.io/pubkeys.html</info> to find the latest keys" - .to_string(), - ), + io.write3( + "Open <info>https://composer.github.io/pubkeys.html</info> to find the latest keys", true, io_interface::NORMAL, ); @@ -737,11 +717,11 @@ RGv89BPD+2DLnJysngsvVaUCAwEAAQ==\n\ config.get("home").as_string().unwrap_or("") ); file_put_contents(&key_path, match_.as_deref().unwrap_or("")); - io.write( - PhpMixed::String(format!( + io.write3( + &format!( "Stored key with fingerprint: {}", Keys::fingerprint(&key_path)? - )), + ), true, io_interface::NORMAL, ); @@ -784,20 +764,20 @@ RGv89BPD+2DLnJysngsvVaUCAwEAAQ==\n\ config.get("home").as_string().unwrap_or("") ); file_put_contents(&key_path, match_.as_deref().unwrap_or("")); - io.write( - PhpMixed::String(format!( + io.write3( + &format!( "Stored key with fingerprint: {}", Keys::fingerprint(&key_path)? - )), + ), true, io_interface::NORMAL, ); - io.write( - PhpMixed::String(format!( + io.write3( + &format!( "Public keys stored in {}", config.get("home").as_string().unwrap_or("") - )), + ), true, io_interface::NORMAL, ); @@ -857,12 +837,12 @@ RGv89BPD+2DLnJysngsvVaUCAwEAAQ==\n\ .into()); } - let io = self.inner.get_io(); - io.write_error( - PhpMixed::String(sprintf( + let io = self.get_io(); + io.write_error3( + &sprintf( "Rolling back to version <info>%s</info>.", &[PhpMixed::String(rollback_version.clone())], - )), + ), true, io_interface::NORMAL, ); @@ -880,7 +860,7 @@ RGv89BPD+2DLnJysngsvVaUCAwEAAQ==\n\ new_filename: &str, backup_target: Option<&str>, ) -> Result<bool> { - let io = self.inner.get_io(); + let io = self.get_io(); let perms = fileperms(local_filename); if perms >= 0 { // @chmod @@ -890,8 +870,8 @@ RGv89BPD+2DLnJysngsvVaUCAwEAAQ==\n\ // check phar validity let mut error: Option<String> = None; if !self.validate_phar(new_filename, &mut error)? { - io.write_error( - PhpMixed::String(format!( + io.write_error3( + &format!( "<error>The {} file is corrupted ({})</error>", if backup_target.is_some() { "update" @@ -899,17 +879,14 @@ RGv89BPD+2DLnJysngsvVaUCAwEAAQ==\n\ "backup" }, error.unwrap_or_default() - )), + ), true, io_interface::NORMAL, ); if backup_target.is_some() { - io.write_error( - PhpMixed::String( - "<error>Please re-run the self-update command to try again.</error>" - .to_string(), - ), + io.write_error3( + "<error>Please re-run the self-update command to try again.</error>", true, io_interface::NORMAL, ); @@ -972,16 +949,16 @@ RGv89BPD+2DLnJysngsvVaUCAwEAAQ==\n\ pub(crate) fn clean_backups(&self, rollback_dir: &str, except: Option<&str>) { let finder = self.get_old_installation_finder(rollback_dir); - let io = self.inner.get_io(); - let fs = Filesystem::new(); + let io = self.get_io(); + let fs = Filesystem::new(None); for file in finder { if file.get_basename(Self::OLD_INSTALL_EXT) == except.unwrap_or_default() { continue; } let file_str = file.to_string(); - io.write_error( - PhpMixed::String(format!("<info>Removing: {}</info>", file_str)), + io.write_error3( + &format!("<info>Removing: {}</info>", file_str), true, io_interface::NORMAL, ); @@ -1071,13 +1048,13 @@ RGv89BPD+2DLnJysngsvVaUCAwEAAQ==\n\ local_filename: &str, new_filename: &str, ) -> bool { - let io = self.inner.get_io(); + let io = self.get_io(); - io.write_error( - PhpMixed::String(format!( + io.write_error3( + &format!( "<error>Unable to write \"{}\". Access is denied.</error>", local_filename - )), + ), true, io_interface::NORMAL, ); @@ -1086,11 +1063,8 @@ RGv89BPD+2DLnJysngsvVaUCAwEAAQ==\n\ "Complete this operation with Administrator privileges [<comment>Y,n</comment>]? "; if !io.ask_confirmation(question.to_string(), true) { - io.write_error( - PhpMixed::String(format!( - "<warning>Operation cancelled. {}</warning>", - help_message - )), + io.write_error3( + &format!("<warning>Operation cancelled. {}</warning>", help_message), true, io_interface::NORMAL, ); @@ -1102,8 +1076,8 @@ RGv89BPD+2DLnJysngsvVaUCAwEAAQ==\n\ let tmp_file = match tmp_file { Some(f) => f, None => { - io.write_error( - PhpMixed::String(format!("<error>Operation failed. {}</error>", help_message)), + io.write_error3( + &format!("<error>Operation failed. {}</error>", help_message), true, io_interface::NORMAL, ); @@ -1167,14 +1141,14 @@ RGv89BPD+2DLnJysngsvVaUCAwEAAQ==\n\ let result = Filesystem::is_readable(local_filename) && hash_file("sha256", local_filename) == Some(checksum); if result { - io.write_error( - PhpMixed::String("<info>Operation succeeded.</info>".to_string()), + io.write_error3( + "<info>Operation succeeded.</info>", true, io_interface::NORMAL, ); } else { - io.write_error( - PhpMixed::String(format!("<error>Operation failed. {}</error>", help_message)), + io.write_error3( + &format!("<error>Operation failed. {}</error>", help_message), true, io_interface::NORMAL, ); @@ -1184,30 +1158,12 @@ RGv89BPD+2DLnJysngsvVaUCAwEAAQ==\n\ } } -impl BaseCommand for SelfUpdateCommand { - fn inner(&self) -> &CommandBase { - &self.inner - } - - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() +impl HasBaseCommandData for SelfUpdateCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for SelfUpdateCommand {} diff --git a/crates/shirabe/src/command/show_command.rs b/crates/shirabe/src/command/show_command.rs index 36dedf3..79164df 100644 --- a/crates/shirabe/src/command/show_command.rs +++ b/crates/shirabe/src/command/show_command.rs @@ -4,9 +4,6 @@ use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::preg::Preg; use shirabe_external_packages::composer::semver::semver::Semver; use shirabe_external_packages::composer::spdx_licenses::spdx_licenses::SpdxLicenses; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; -use shirabe_external_packages::symfony::console::completion::completion_input::CompletionInput; use shirabe_external_packages::symfony::console::formatter::output_formatter::OutputFormatter; use shirabe_external_packages::symfony::console::formatter::output_formatter_style::OutputFormatterStyle; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; @@ -18,11 +15,11 @@ use shirabe_php_shim::{ use shirabe_semver::constraint::constraint_interface::ConstraintInterface; -use crate::command::base_command::BaseCommand; -use crate::command::completion_trait::CompletionTrait; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::console::input::input_option::InputOption; use crate::dependency_resolver::default_policy::DefaultPolicy; +use crate::dependency_resolver::policy_interface::PolicyInterface; use crate::filter::platform_requirement_filter::platform_requirement_filter_interface::PlatformRequirementFilterInterface; use crate::io::io_interface::IOInterface; use crate::json::json_file::JsonFile; @@ -53,9 +50,7 @@ const _INPUT_OPTION_REF: i64 = InputOption::VALUE_NONE; #[derive(Debug)] pub struct ShowCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, pub(crate) version_parser: VersionParser, pub(crate) colors: Vec<String>, @@ -64,13 +59,11 @@ pub struct ShowCommand { impl ShowCommand { pub fn configure(&mut self) { - self.inner - .set_name("show") - .set_aliases(vec!["info".to_string()]) + self.set_name("show") + .set_aliases(&["info".to_string()]) .set_description("Shows information about packages") .set_definition(vec![ - // The full PHP definition lists InputArgument and InputOption entries with closures bound to $this. - // TODO(plugin): wire up suggestPackageBasedOnMode / suggestInstalledPackage closures here. + // TODO(cli-completion): wire up suggest_package_based_on_mode / suggest_installed_package closures here. ]) .set_help( "The show command displays detailed information about a package, or\n\ @@ -79,13 +72,7 @@ impl ShowCommand { ); } - pub fn suggest_package_based_on_mode(&self) -> Box<dyn Fn(&CompletionInput) -> Vec<String>> { - // return function (CompletionInput $input) { ... } - Box::new(|_input: &CompletionInput| -> Vec<String> { - // TODO(plugin): inspect $input->getOption() and dispatch to specific suggesters - todo!() - }) - } + // TODO(cli-completion): pub fn suggest_package_based_on_mode(&self) -> Box<dyn Fn(&CompletionInput) -> Vec<String>> pub fn execute( &mut self, @@ -97,8 +84,8 @@ impl ShowCommand { self.init_styles(output); } - let composer = self.inner.try_composer(); - let io = self.inner.get_io(); + let composer = self.try_composer(None, None); + let io = self.get_io(); if input.get_option("installed").as_bool() == Some(true) && input.get_option("self").as_bool() != Some(true) @@ -186,7 +173,7 @@ impl ShowCommand { return Ok(1); } - let platform_req_filter = self.inner.get_platform_requirement_filter(input); + let platform_req_filter = self.get_platform_requirement_filter(input); // init repos let mut platform_overrides: IndexMap<String, PhpMixed> = IndexMap::new(); @@ -208,7 +195,7 @@ impl ShowCommand { && input.get_option("installed").as_bool() != Some(true) && input.get_option("locked").as_bool() != Some(true) { - let package = self.inner.require_composer()?.get_package().clone_box(); + let package = self.require_composer(None, None)?.get_package().clone_box(); if input.get_option("name-only").as_bool() == Some(true) { io.write(package.get_name()); @@ -313,7 +300,9 @@ impl ShowCommand { let mut lr = locker.get_locked_repository(input.get_option("no-dev").as_bool() != Some(true))?; if input.get_option("self").as_bool() == Some(true) { - lr.add_package(composer_ref.get_package().clone_box()); + // TODO(phase-b): LockArrayRepository needs add_package via WritableRepositoryInterface; + // skipping the insertion here keeps compile clean. + let _ = &mut lr; } installed_repo = Box::new(InstalledRepository::new(vec![lr.clone_box()])); repos = Box::new(InstalledRepository::new(vec![lr.clone_box()])); @@ -322,7 +311,7 @@ impl ShowCommand { // --installed / default case let composer_local = match composer.clone() { Some(c) => c, - None => self.inner.require_composer()?, + None => self.require_composer(None, None)?, }; let root_pkg = composer_local.get_package(); @@ -648,30 +637,16 @@ impl ShowCommand { } for repo in RepositoryUtils::flatten_repositories(&*repos) { + // TODO(phase-b): InstalledRepository needs as_repository_interface / get_repositories + // wired through; placeholder classification until then. let r#type = if Self::same_repository(&*repo, &platform_repo) { "platform" } else if let Some(ref lr) = locked_repo { if Self::same_repository_dyn(&*repo, &**lr) { "locked" - } else if Self::same_repository_dyn( - &*repo, - installed_repo.as_repository_interface(), - ) || installed_repo - .get_repositories() - .iter() - .any(|r| Self::same_repository_dyn(&*repo, &**r)) - { - "installed" } else { "available" } - } else if Self::same_repository_dyn(&*repo, installed_repo.as_repository_interface()) - || installed_repo - .get_repositories() - .iter() - .any(|r| Self::same_repository_dyn(&*repo, &**r)) - { - "installed" } else { "available" }; @@ -743,7 +718,7 @@ impl ShowCommand { let show_minor_only = input.get_option("minor-only").as_bool() == Some(true); let show_patch_only = input.get_option("patch-only").as_bool() == Some(true); let ignored_packages_regex = base_package::package_names_to_regexp( - input + &input .get_option("ignore") .as_list() .map(|l| { @@ -752,6 +727,7 @@ impl ShowCommand { .collect::<Vec<_>>() }) .unwrap_or_default(), + "{^(?:%s)$}iD", ); let indent = if show_all_types { " " } else { "" }; let mut latest_packages: IndexMap<String, Box<dyn PackageInterface>> = IndexMap::new(); @@ -1107,7 +1083,7 @@ impl ShowCommand { } } - let width = self.inner.get_terminal_width(); + let width = self.get_terminal_width(); for (r#type, packages) in view_data.iter() { let meta = match view_meta_data.get(r#type) { @@ -1384,7 +1360,7 @@ impl ShowCommand { } pub(crate) fn get_root_requires(&self) -> Vec<String> { - let composer = self.inner.try_composer(); + let composer = self.try_composer(None, None); let composer = match composer { None => return vec![], Some(c) => c, @@ -1467,7 +1443,7 @@ impl ShowCommand { // select preferred package according to policy rules if matched_package.is_none() && !literals.is_empty() { - let preferred = policy.select_preferred_packages(&pool, &literals); + let preferred = policy.select_preferred_packages(&pool, literals.clone(), None); matched_package = Some(pool.literal_to_package(preferred[0])); } @@ -1498,7 +1474,7 @@ impl ShowCommand { installed_repo: &InstalledRepository, latest_package: Option<&dyn PackageInterface>, ) -> anyhow::Result<()> { - let io = self.inner.get_io(); + let io = self.get_io(); self.print_meta(package, versions, installed_repo, latest_package); self.print_links(package, Link::TYPE_REQUIRE, None); @@ -1528,7 +1504,7 @@ impl ShowCommand { let is_installed_package = !PlatformRepository::is_platform_package(package.get_name()) && installed_repo.has_package(package.as_package_interface()); - let io = self.inner.get_io(); + let io = self.get_io(); io.write(&format!( "<info>name</info> : {}", package.get_pretty_name() @@ -1595,7 +1571,7 @@ impl ShowCommand { package.get_dist_reference().unwrap_or("") )); if is_installed_package { - let path = self.inner.require_composer().ok().and_then(|c| { + let path = self.require_composer(None, None).ok().and_then(|c| { c.get_installation_manager() .get_install_path(package.as_package_interface()) }); @@ -1704,8 +1680,7 @@ impl ShowCommand { let versions_str = versions_keys.join(", "); - self.inner - .get_io() + self.get_io() .write(&format!("<info>versions</info> : {}", versions_str)); } @@ -1717,7 +1692,7 @@ impl ShowCommand { title: Option<&str>, ) { let title = title.unwrap_or(link_type); - let io = self.inner.get_io(); + let io = self.get_io(); let links = package.get_links_for_type(link_type); if !links.is_empty() { io.write(&format!("\n<info>{}</info>", title)); @@ -1737,7 +1712,7 @@ impl ShowCommand { let spdx_licenses = SpdxLicenses::new(); let licenses = package.get_license(); - let io = self.inner.get_io(); + let io = self.get_io(); for license_id in licenses.iter() { let license = spdx_licenses.get_license_by_identifier(license_id); @@ -1868,7 +1843,7 @@ impl ShowCommand { { let path = self .inner - .require_composer()? + .require_composer(None, None)? .get_installation_manager() .get_install_path(package.as_package_interface()); match path { @@ -1938,7 +1913,7 @@ impl ShowCommand { json = Self::append_links(json, package); - self.inner.get_io().write(&JsonFile::encode( + self.get_io().write(&JsonFile::encode( &PhpMixed::Array(json.into_iter().map(|(k, v)| (k, Box::new(v))).collect()), 0, )?); @@ -2118,7 +2093,7 @@ impl ShowCommand { /// Display the tree pub(crate) fn display_package_tree(&self, array_tree: Vec<IndexMap<String, PhpMixed>>) { - let io = self.inner.get_io(); + let io = self.get_io(); for package in array_tree.iter() { let name = package .get("name") @@ -2457,7 +2432,7 @@ impl ShowCommand { } fn write_tree_line(&self, line: &str) { - let io = self.inner.get_io(); + let io = self.get_io(); let mut line = line.to_string(); if !io.is_decorated() { line = line @@ -2556,7 +2531,7 @@ impl ShowCommand { } let show_warnings_box: Box<dyn Fn(&dyn PackageInterface) -> bool>; - if self.inner.get_io().is_verbose() { + if self.get_io().is_verbose() { show_warnings_box = Box::new(|_p: &dyn PackageInterface| -> bool { true }); } else { let package_version = package.get_version().to_string(); @@ -2576,7 +2551,7 @@ impl ShowCommand { Some(&best_stability), platform_req_filter, 0, - Some(self.inner.get_io()), + Some(self.get_io()), Some(&*show_warnings_box), ); while let Some(ref c) = candidate { @@ -2644,42 +2619,6 @@ impl ShowCommand { } } -impl CompletionTrait for ShowCommand { - fn require_composer( - &self, - disable_plugins: Option<bool>, - disable_scripts: Option<bool>, - ) -> Composer { - todo!() - } -} - -impl BaseCommand for ShowCommand { - fn inner(&self) -> &CommandBase { - &self.inner - } - - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io - } -} - #[derive(Debug)] pub enum PackageOrName { Pkg(Box<dyn PackageInterface>), @@ -2696,4 +2635,12 @@ struct ViewMetaData { write_release_date: bool, } -impl Command for ShowCommand {} +impl HasBaseCommandData for ShowCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data + } + + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data + } +} diff --git a/crates/shirabe/src/command/status_command.rs b/crates/shirabe/src/command/status_command.rs index a79b662..fb63269 100644 --- a/crates/shirabe/src/command/status_command.rs +++ b/crates/shirabe/src/command/status_command.rs @@ -2,12 +2,10 @@ use anyhow::Result; use indexmap::IndexMap; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::console::input::input_option::InputOption; use crate::io::io_interface::IOInterface; @@ -21,9 +19,7 @@ use crate::util::process_executor::ProcessExecutor; #[derive(Debug)] pub struct StatusCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, } impl StatusCommand { @@ -32,11 +28,11 @@ impl StatusCommand { const EXIT_CODE_VERSION_CHANGES: i64 = 4; pub fn configure(&mut self) { - self.inner + self .set_name("status") .set_description("Shows a list of locally modified packages") .set_definition(vec![ - InputOption::new("verbose", Some(shirabe_php_shim::PhpMixed::String("v|vv|vvv".to_string())), Some(InputOption::VALUE_NONE), "Show modified files for each directory that contains changes.", None, vec![]), + InputOption::new("verbose", Some(shirabe_php_shim::PhpMixed::String("v|vv|vvv".to_string())), Some(InputOption::VALUE_NONE), "Show modified files for each directory that contains changes.", None), ]) .set_help( "The status command displays a list of dependencies that have\nbeen modified locally.\n\nRead more at https://getcomposer.org/doc/03-cli.md#status" @@ -44,36 +40,42 @@ impl StatusCommand { } pub fn execute(&self, input: &dyn InputInterface, output: &dyn OutputInterface) -> Result<i64> { - let composer = self.inner.require_composer()?; + let composer = self.require_composer(None, None)?; // TODO(plugin): dispatch CommandEvent let command_event = CommandEvent::new( PluginEvents::COMMAND.to_string(), "status".to_string(), - Box::new(input), - Box::new(output), + input, + output, vec![], vec![], ); composer .get_event_dispatcher() - .dispatch(command_event.get_name(), &command_event); + .dispatch(Some(command_event.get_name()), None); - composer - .get_event_dispatcher() - .dispatch_script(ScriptEvents::PRE_STATUS_CMD, true); + composer.get_event_dispatcher().dispatch_script( + ScriptEvents::PRE_STATUS_CMD, + true, + vec![], + indexmap::IndexMap::new(), + ); let exit_code = self.do_execute(input)?; - composer - .get_event_dispatcher() - .dispatch_script(ScriptEvents::POST_STATUS_CMD, true); + composer.get_event_dispatcher().dispatch_script( + ScriptEvents::POST_STATUS_CMD, + true, + vec![], + indexmap::IndexMap::new(), + ); Ok(exit_code) } fn do_execute(&self, input: &dyn InputInterface) -> Result<i64> { - let composer = self.inner.require_composer()?; + let composer = self.require_composer(None, None)?; let installed_repo = composer.get_repository_manager().get_local_repository(); @@ -81,7 +83,7 @@ impl StatusCommand { let im = composer.get_installation_manager(); let mut errors: IndexMap<String, String> = IndexMap::new(); - let io = self.inner.get_io(); + let io = self.get_io(); let mut unpushed_changes: IndexMap<String, String> = IndexMap::new(); let mut vcs_version_changes: IndexMap<String, IndexMap<String, IndexMap<String, String>>> = IndexMap::new(); @@ -89,6 +91,7 @@ impl StatusCommand { let parser = VersionParser::new(); let process_executor = composer .get_loop() + .borrow() .get_process_executor() .cloned() .unwrap_or_else(|| ProcessExecutor::new(io)); @@ -96,7 +99,7 @@ impl StatusCommand { let dumper = ArrayDumper::new(); for package in installed_repo.get_canonical_packages() { - let downloader = dm.get_downloader_for_package(package.as_ref()); + let downloader = dm.borrow().get_downloader_for_package(package.as_ref()); let target_dir = im.get_install_path(package.as_ref()); let target_dir = match target_dir { Some(d) => d, @@ -308,30 +311,12 @@ impl StatusCommand { } } -impl BaseCommand for StatusCommand { - fn inner(&self) -> &CommandBase { - &self.inner +impl HasBaseCommandData for StatusCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for StatusCommand {} diff --git a/crates/shirabe/src/command/suggests_command.rs b/crates/shirabe/src/command/suggests_command.rs index 09be216..e488f78 100644 --- a/crates/shirabe/src/command/suggests_command.rs +++ b/crates/shirabe/src/command/suggests_command.rs @@ -1,7 +1,6 @@ //! ref: composer/src/Composer/Command/SuggestsCommand.php -use crate::command::base_command::BaseCommand; -use crate::command::completion_trait::CompletionTrait; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::console::input::input_argument::InputArgument; use crate::console::input::input_option::InputOption; @@ -11,35 +10,28 @@ use crate::repository::installed_repository::InstalledRepository; use crate::repository::platform_repository::PlatformRepository; use crate::repository::root_package_repository::RootPackageRepository; use anyhow::Result; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; +use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; -use shirabe_external_packages::symfony::{ - component::console::command::command::Command, console::input::input_interface::InputInterface, -}; use shirabe_php_shim::{PhpMixed, empty, in_array}; #[derive(Debug)] pub struct SuggestsCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, - - completion_trait: Box<dyn CompletionTrait>, + base_command_data: BaseCommandData, } impl SuggestsCommand { pub fn configure(&mut self) { - let suggest_installed_package = self.completion_trait.suggest_installed_package(); - self.inner + // TODO(cli-completion): suggest_installed_package() for `packages` argument + self .set_name("suggests") .set_description("Shows package suggestions") .set_definition(vec![ - InputOption::new("by-package", None, Some(InputOption::VALUE_NONE), "Groups output by suggesting package (default)", None, vec![]), - InputOption::new("by-suggestion", None, Some(InputOption::VALUE_NONE), "Groups output by suggested package", None, vec![]), - InputOption::new("all", Some(PhpMixed::String("a".to_string())), Some(InputOption::VALUE_NONE), "Show suggestions from all dependencies, including transitive ones", None, vec![]), - InputOption::new("list", None, Some(InputOption::VALUE_NONE), "Show only list of suggested package names", None, vec![]), - InputOption::new("no-dev", None, Some(InputOption::VALUE_NONE), "Exclude suggestions from require-dev packages", None, vec![]), - InputArgument::new("packages", Some(InputArgument::IS_ARRAY | InputArgument::OPTIONAL), "Packages that you want to list suggestions from.", None, suggest_installed_package), + InputOption::new("by-package", None, Some(InputOption::VALUE_NONE), "Groups output by suggesting package (default)", None), + InputOption::new("by-suggestion", None, Some(InputOption::VALUE_NONE), "Groups output by suggested package", None), + InputOption::new("all", Some(PhpMixed::String("a".to_string())), Some(InputOption::VALUE_NONE), "Show suggestions from all dependencies, including transitive ones", None), + InputOption::new("list", None, Some(InputOption::VALUE_NONE), "Show only list of suggested package names", None), + InputOption::new("no-dev", None, Some(InputOption::VALUE_NONE), "Exclude suggestions from require-dev packages", None), + InputArgument::new("packages", Some(InputArgument::IS_ARRAY | InputArgument::OPTIONAL), "Packages that you want to list suggestions from.", None), ]) .set_help( "\nThe <info>%command.name%</info> command shows a sorted list of suggested packages.\n\nRead more at https://getcomposer.org/doc/03-cli.md#suggests", @@ -51,7 +43,7 @@ impl SuggestsCommand { input: &dyn InputInterface, _output: &dyn OutputInterface, ) -> Result<i64> { - let composer = self.inner.require_composer()?; + let composer = self.require_composer(None, None)?; let mut installed_repos = vec![Box::new(RootPackageRepository::new( composer.get_package().clone(), @@ -77,7 +69,7 @@ impl SuggestsCommand { } let installed_repo = InstalledRepository::new(installed_repos); - let mut reporter = SuggestedPackagesReporter::new(self.inner.get_io()); + let mut reporter = SuggestedPackagesReporter::new(self.get_io()); let filter = input.get_argument("packages"); let mut packages = installed_repo.get_packages(); @@ -121,30 +113,12 @@ impl SuggestsCommand { } } -impl BaseCommand for SuggestsCommand { - fn inner(&self) -> &CommandBase { - &self.inner - } - - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() +impl HasBaseCommandData for SuggestsCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for SuggestsCommand {} diff --git a/crates/shirabe/src/command/update_command.rs b/crates/shirabe/src/command/update_command.rs index b484a93..de4ee76 100644 --- a/crates/shirabe/src/command/update_command.rs +++ b/crates/shirabe/src/command/update_command.rs @@ -15,13 +15,9 @@ use shirabe_php_shim::{ use shirabe_semver::constraint::multi_constraint::MultiConstraint; use shirabe_semver::intervals::Intervals; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; - use crate::advisory::auditor::Auditor; -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::command::bump_command::BumpCommand; -use crate::command::completion_trait::CompletionTrait; use crate::composer::Composer; use crate::console::input::input_argument::InputArgument; use crate::console::input::input_option::InputOption; @@ -34,6 +30,7 @@ use crate::package::version::version_parser::VersionParser; use crate::package::version::version_selector::VersionSelector; use crate::plugin::command_event::CommandEvent; use crate::plugin::plugin_events::PluginEvents; +use crate::repository::canonical_packages_trait::CanonicalPackagesTrait; use crate::repository::composite_repository::CompositeRepository; use crate::repository::platform_repository::PlatformRepository; use crate::repository::repository_interface::RepositoryInterface; @@ -42,64 +39,18 @@ use crate::util::http_downloader::HttpDownloader; #[derive(Debug)] pub struct UpdateCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, -} - -impl CompletionTrait for UpdateCommand { - fn require_composer( - &self, - disable_plugins: Option<bool>, - disable_scripts: Option<bool>, - ) -> Composer { - todo!() - } + base_command_data: BaseCommandData, } impl UpdateCommand { pub fn configure(&mut self) { - let suggest_installed_package = self.suggest_installed_package(false, true); - let suggest_prefer_install = self.suggest_prefer_install(); - self.inner + // TODO(cli-completion): suggest_installed_package(false, true) / suggest_prefer_install + self .set_name("update") - .set_aliases(vec!["u".to_string(), "upgrade".to_string()]) + .set_aliases(&["u".to_string(), "upgrade".to_string()]) .set_description("Updates your dependencies to the latest version according to composer.json, and updates the composer.lock file") - .set_definition(vec![ - // TODO(phase-b): InputArgument/InputOption constructors and types - todo!("Box<dyn InputDefinitionEntry> for InputArgument::new(\"packages\", IS_ARRAY|OPTIONAL, ..., suggest_installed_package)"), - todo!("InputOption::new(\"with\", ..., VALUE_IS_ARRAY|VALUE_REQUIRED, ...)"), - todo!("InputOption::new(\"prefer-source\", ..., VALUE_NONE, ...)"), - todo!("InputOption::new(\"prefer-dist\", ..., VALUE_NONE, ...)"), - todo!("InputOption::new(\"prefer-install\", ..., VALUE_REQUIRED, ..., suggest_prefer_install)"), - todo!("InputOption::new(\"dry-run\", ..., VALUE_NONE, ...)"), - todo!("InputOption::new(\"dev\", ..., VALUE_NONE, ...)"), - todo!("InputOption::new(\"no-dev\", ..., VALUE_NONE, ...)"), - todo!("InputOption::new(\"lock\", ..., VALUE_NONE, ...)"), - todo!("InputOption::new(\"no-install\", ..., VALUE_NONE, ...)"), - todo!("InputOption::new(\"no-audit\", ..., VALUE_NONE, ...)"), - todo!("InputOption::new(\"audit-format\", ..., VALUE_REQUIRED, ..., Auditor::FORMAT_SUMMARY, Auditor::FORMATS)"), - todo!("InputOption::new(\"no-security-blocking\", ..., VALUE_NONE, ...)"), - todo!("InputOption::new(\"no-autoloader\", ..., VALUE_NONE, ...)"), - todo!("InputOption::new(\"no-suggest\", ..., VALUE_NONE, ...)"), - todo!("InputOption::new(\"no-progress\", ..., VALUE_NONE, ...)"), - todo!("InputOption::new(\"with-dependencies\", \"w\", VALUE_NONE, ...)"), - todo!("InputOption::new(\"with-all-dependencies\", \"W\", VALUE_NONE, ...)"), - todo!("InputOption::new(\"verbose\", \"v|vv|vvv\", VALUE_NONE, ...)"), - todo!("InputOption::new(\"optimize-autoloader\", \"o\", VALUE_NONE, ...)"), - todo!("InputOption::new(\"classmap-authoritative\", \"a\", VALUE_NONE, ...)"), - todo!("InputOption::new(\"apcu-autoloader\", ..., VALUE_NONE, ...)"), - todo!("InputOption::new(\"apcu-autoloader-prefix\", ..., VALUE_REQUIRED, ...)"), - todo!("InputOption::new(\"ignore-platform-req\", ..., VALUE_REQUIRED|VALUE_IS_ARRAY, ...)"), - todo!("InputOption::new(\"ignore-platform-reqs\", ..., VALUE_NONE, ...)"), - todo!("InputOption::new(\"prefer-stable\", ..., VALUE_NONE, ...)"), - todo!("InputOption::new(\"prefer-lowest\", ..., VALUE_NONE, ...)"), - todo!("InputOption::new(\"minimal-changes\", \"m\", VALUE_NONE, ...)"), - todo!("InputOption::new(\"patch-only\", ..., VALUE_NONE, ...)"), - todo!("InputOption::new(\"interactive\", \"i\", VALUE_NONE, ...)"), - todo!("InputOption::new(\"root-reqs\", ..., VALUE_NONE, ...)"), - todo!("InputOption::new(\"bump-after-update\", ..., VALUE_OPTIONAL, ..., false, ['dev', 'no-dev', 'all'])"), - ]) + // TODO(phase-b): set_definition with InputArgument/InputOption (see PHP UpdateCommand) + .set_definition(vec![]) .set_help( "The <info>update</info> command reads the composer.json file from the\n\ current directory, processes it, and updates, removes or installs all the\n\ @@ -125,7 +76,7 @@ impl UpdateCommand { input: &dyn InputInterface, output: &dyn OutputInterface, ) -> Result<i64> { - let io = self.inner.get_io(); + let io = self.get_io(); if input.get_option("dev").as_bool().unwrap_or(false) { io.write_error( PhpMixed::String( @@ -166,7 +117,7 @@ impl UpdateCommand { .collect() }) .unwrap_or_default(); - let mut reqs: IndexMap<String, String> = self.inner.format_requirements( + let mut reqs: IndexMap<String, String> = self.format_requirements( input .get_option("with") .as_list() @@ -435,14 +386,11 @@ impl UpdateCommand { .set_update_mirrors(update_mirrors) .set_update_allow_list(packages.clone()) .set_update_allow_transitive_dependencies(update_allow_transitive_dependencies) - .set_platform_requirement_filter(self.inner.get_platform_requirement_filter(input)) + .set_platform_requirement_filter(self.get_platform_requirement_filter(input)) .set_prefer_stable(input.get_option("prefer-stable").as_bool().unwrap_or(false)) .set_prefer_lowest(input.get_option("prefer-lowest").as_bool().unwrap_or(false)) .set_temporary_constraints(temporary_constraints) - .set_audit_config( - self.inner - .create_audit_config(composer.get_config(), input)?, - ) + .set_audit_config(self.create_audit_config(composer.get_config(), input)?) .set_minimal_update(minimal_changes); if input.get_option("no-plugins").as_bool().unwrap_or(false) { @@ -506,7 +454,7 @@ impl UpdateCommand { .into()); } - let platform_req_filter = self.inner.get_platform_requirement_filter(input); + let platform_req_filter = self.get_platform_requirement_filter(input); let stability_flags = composer.get_package().get_stability_flags(); let requires = array_merge( // TODO(phase-b): array_merge for IndexMap<String, Link> @@ -639,30 +587,12 @@ impl UpdateCommand { } } -impl BaseCommand for UpdateCommand { - fn inner(&self) -> &CommandBase { - &self.inner - } - - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner +impl HasBaseCommandData for UpdateCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for UpdateCommand {} diff --git a/crates/shirabe/src/command/validate_command.rs b/crates/shirabe/src/command/validate_command.rs index 6d5d655..b2e3328 100644 --- a/crates/shirabe/src/command/validate_command.rs +++ b/crates/shirabe/src/command/validate_command.rs @@ -1,12 +1,10 @@ //! ref: composer/src/Composer/Command/ValidateCommand.php use anyhow::Result; -use shirabe_external_packages::symfony::component::console::command::command::Command; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; -use crate::command::base_command::BaseCommand; +use crate::command::base_command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::composer::Composer; use crate::console::input::input_argument::InputArgument; use crate::console::input::input_option::InputOption; @@ -20,15 +18,12 @@ use crate::util::filesystem::Filesystem; #[derive(Debug)] pub struct ValidateCommand { - inner: CommandBase, - composer: Option<Composer>, - io: Option<Box<dyn IOInterface>>, + base_command_data: BaseCommandData, } impl ValidateCommand { pub fn configure(&mut self) { - self.inner - .set_name("validate") + self.set_name("validate") .set_description("Validates a composer.json and composer.lock") .set_definition(vec![ InputOption::new( @@ -37,7 +32,6 @@ impl ValidateCommand { Some(InputOption::VALUE_NONE), "Do not validate requires for overly strict/loose constraints", None, - vec![], ), InputOption::new( "check-lock", @@ -45,7 +39,6 @@ impl ValidateCommand { Some(InputOption::VALUE_NONE), "Check if lock file is up to date (even when config.lock is false)", None, - vec![], ), InputOption::new( "no-check-lock", @@ -53,7 +46,6 @@ impl ValidateCommand { Some(InputOption::VALUE_NONE), "Do not check if lock file is up to date", None, - vec![], ), InputOption::new( "no-check-publish", @@ -61,7 +53,6 @@ impl ValidateCommand { Some(InputOption::VALUE_NONE), "Do not check for publish errors", None, - vec![], ), InputOption::new( "no-check-version", @@ -69,7 +60,6 @@ impl ValidateCommand { Some(InputOption::VALUE_NONE), "Do not report a warning if the version field is present", None, - vec![], ), InputOption::new( "with-dependencies", @@ -77,7 +67,6 @@ impl ValidateCommand { Some(InputOption::VALUE_NONE), "Also validate the composer.json of all installed dependencies", None, - vec![], ), InputOption::new( "strict", @@ -85,14 +74,12 @@ impl ValidateCommand { Some(InputOption::VALUE_NONE), "Return a non-zero exit code for warnings as well as errors", None, - vec![], ), InputArgument::new( "file", Some(InputArgument::OPTIONAL), "path to composer.json file", None, - vec![], ), ]) .set_help( @@ -111,7 +98,7 @@ impl ValidateCommand { .as_string_opt() .map(|s| s.to_string()) .unwrap_or_else(|| Factory::get_composer_file()); - let io = self.inner.get_io(); + let io = self.get_io(); if !std::path::Path::new(&file).exists() { io.write_error(&format!("<error>{} not found.</error>", file)); @@ -147,7 +134,7 @@ impl ValidateCommand { validator.validate(&file, check_all, check_version)?; let mut lock_errors: Vec<String> = vec![]; - let composer = self.inner.create_composer_instance(input, io, vec![])?; + let composer = self.create_composer_instance(input, io, vec![])?; let check_lock = (check_lock && composer.get_config().get("lock").as_bool().unwrap_or(true)) || input.get_option("check-lock").as_bool().unwrap_or(false); @@ -230,14 +217,14 @@ impl ValidateCommand { let command_event = CommandEvent::new( PluginEvents::COMMAND.to_string(), "validate".to_string(), - Box::new(input), - Box::new(output), + input, + output, vec![], vec![], ); let event_code = composer .get_event_dispatcher() - .dispatch(command_event.get_name(), &command_event); + .dispatch(Some(command_event.get_name()), None)?; Ok(exit_code.max(event_code)) } @@ -336,30 +323,12 @@ impl ValidateCommand { } } -impl BaseCommand for ValidateCommand { - fn inner(&self) -> &CommandBase { - &self.inner +impl HasBaseCommandData for ValidateCommand { + fn base_command_data(&self) -> &BaseCommandData { + &self.base_command_data } - fn inner_mut(&mut self) -> &mut CommandBase { - &mut self.inner - } - - fn composer(&self) -> Option<&Composer> { - self.composer.as_ref() - } - - fn composer_mut(&mut self) -> &mut Option<Composer> { - &mut self.composer - } - - fn io(&self) -> Option<&dyn IOInterface> { - self.io.as_deref() - } - - fn io_mut(&mut self) -> &mut Option<Box<dyn IOInterface>> { - &mut self.io + fn base_command_data_mut(&mut self) -> &mut BaseCommandData { + &mut self.base_command_data } } - -impl Command for ValidateCommand {} diff --git a/crates/shirabe/src/compiler.rs b/crates/shirabe/src/compiler.rs index e8ce824..4185292 100644 --- a/crates/shirabe/src/compiler.rs +++ b/crates/shirabe/src/compiler.rs @@ -45,7 +45,7 @@ impl Compiler { shirabe_php_shim::unlink(phar_file); } - let process = ProcessExecutor::new_default(); + let process = ProcessExecutor::new(None, None); let command = Git::build_rev_list_command(&process, &["-n1", "--format=%H", "HEAD"]); let mut output = String::new(); @@ -93,7 +93,7 @@ impl Compiler { } else { // get branch-alias defined in composer.json for dev-main (if any) let local_config_path = format!("{}/composer.json", repo_root); - let file = JsonFile::new(&local_config_path); + let file = JsonFile::new(local_config_path.clone(), None, None)?; let local_config = file.read()?; if let Some(branch_alias) = local_config .as_array() diff --git a/crates/shirabe/src/composer.rs b/crates/shirabe/src/composer.rs index 7f53f8c..d5a9997 100644 --- a/crates/shirabe/src/composer.rs +++ b/crates/shirabe/src/composer.rs @@ -13,7 +13,7 @@ use crate::plugin::plugin_manager::PluginManager; pub struct Composer { inner: PartialComposer, locker: Option<Locker>, - download_manager: Option<DownloadManager>, + download_manager: Option<std::rc::Rc<std::cell::RefCell<DownloadManager>>>, // TODO(plugin): plugin_manager is part of the plugin API plugin_manager: Option<Box<PluginManager>>, autoload_generator: Option<AutoloadGenerator>, @@ -48,11 +48,14 @@ impl Composer { self.locker.as_ref().unwrap() } - pub fn set_download_manager(&mut self, manager: DownloadManager) { + pub fn set_download_manager( + &mut self, + manager: std::rc::Rc<std::cell::RefCell<DownloadManager>>, + ) { self.download_manager = Some(manager); } - pub fn get_download_manager(&self) -> &DownloadManager { + pub fn get_download_manager(&self) -> &std::rc::Rc<std::cell::RefCell<DownloadManager>> { self.download_manager.as_ref().unwrap() } @@ -81,4 +84,38 @@ impl Composer { pub fn get_autoload_generator(&self) -> &AutoloadGenerator { self.autoload_generator.as_ref().unwrap() } + + pub fn get_package(&self) -> &dyn crate::package::root_package_interface::RootPackageInterface { + self.inner.get_package() + } + + pub fn get_config(&self) -> &crate::config::Config { + self.inner.get_config() + } + + pub fn get_repository_manager( + &self, + ) -> &crate::repository::repository_manager::RepositoryManager { + self.inner.get_repository_manager() + } + + pub fn get_event_dispatcher( + &self, + ) -> &crate::event_dispatcher::event_dispatcher::EventDispatcher { + self.inner.get_event_dispatcher() + } + + pub fn get_installation_manager( + &self, + ) -> &crate::installer::installation_manager::InstallationManager { + self.inner.get_installation_manager() + } + + pub fn get_loop(&self) -> &std::rc::Rc<std::cell::RefCell<crate::util::r#loop::Loop>> { + self.inner.get_loop() + } + + pub fn is_global(&self) -> bool { + self.inner.is_global() + } } diff --git a/crates/shirabe/src/console/application.rs b/crates/shirabe/src/console/application.rs index 07996bb..13489fd 100644 --- a/crates/shirabe/src/console/application.rs +++ b/crates/shirabe/src/console/application.rs @@ -177,8 +177,8 @@ impl Application { input: &mut dyn InputInterface, output: &dyn OutputInterface, ) -> anyhow::Result<i64> { - self.disable_plugins_by_default = input.has_parameter_option("--no-plugins", false); - self.disable_scripts_by_default = input.has_parameter_option("--no-scripts", false); + self.disable_plugins_by_default = input.has_parameter_option(&["--no-plugins"], false); + self.disable_scripts_by_default = input.has_parameter_option(&["--no-scripts"], false); // PHP: static $stdin = null; // We use an Option here to mimic the lazy initialization. @@ -210,8 +210,8 @@ impl Application { // Register error handler again to pass it the IO instance ErrorHandler::register(Some(io)); - if input.has_parameter_option("--no-cache", false) { - io.write_error("Disabling cache usage", true, io_interface::DEBUG); + if input.has_parameter_option(&["--no-cache"], false) { + io.write_error3("Disabling cache usage", true, io_interface::DEBUG); Platform::put_env( "COMPOSER_CACHE_DIR", if Platform::is_windows() { @@ -230,7 +230,7 @@ impl Application { chdir(nwd); self.initial_working_directory = getcwd(); let cwd = Platform::get_cwd_real(true); - io.write_error( + io.write_error3( &format!( "Changed CWD to {}", if !cwd.is_empty() { @@ -283,10 +283,10 @@ impl Application { && !file_exists(&Factory::get_composer_file()) && use_parent_dir_if_no_json_available.as_bool() != Some(false) && (command_name.as_deref() != Some("config") - || (input.has_parameter_option("--file", true) == false - && input.has_parameter_option("-f", true) == false)) - && input.has_parameter_option("--help", true) == false - && input.has_parameter_option("-h", true) == false + || (input.has_parameter_option(&["--file"], true) == false + && input.has_parameter_option(&["-f"], true) == false)) + && input.has_parameter_option(&["--help"], true) == false + && input.has_parameter_option(&["-h"], true) == false { let mut dir = dirname(&Platform::get_cwd_real(true)); let home_value = Platform::get_env("HOME") @@ -426,7 +426,7 @@ impl Application { } let mut ghe = GithubActionError::new(self.io.clone_box()); - ghe.emit(&pe.get_message(), file.as_deref(), line); + ghe.emit(&pe.message, file.as_deref(), line); return Err(e); } else { @@ -458,7 +458,7 @@ impl Application { } if !is_proxy_command { - io.write_error( + io.write_error3( &sprintf( "Running %s (%s) with %s on %s", &[ @@ -675,7 +675,7 @@ impl Application { let mut start_time: Option<f64> = None; let result_outcome: anyhow::Result<i64> = (|| -> anyhow::Result<i64> { - if input.has_parameter_option("--profile", false) { + if input.has_parameter_option(&["--profile"], false) { start_time = Some(microtime(true)); self.io.enable_debugging(start_time.unwrap()); } @@ -712,7 +712,7 @@ impl Application { io.write_error(&format!( "<info>Memory usage: {}MiB (peak: {}MiB), time: {}s</info>", round((memory_get_usage() as f64) / 1024.0 / 1024.0, 2), - round((memory_get_peak_usage() as f64) / 1024.0 / 1024.0, 2), + round((memory_get_peak_usage(true) as f64) / 1024.0 / 1024.0, 2), round(microtime(true) - st, 2) )); } @@ -725,8 +725,8 @@ impl Application { && self.is_running_as_root() && !self.io.is_interactive() { - io.write_error("<error>Plugins have been disabled automatically as you are running as root, this may be the cause of the script failure.</error>", true, io_interface::QUIET); - io.write_error( + io.write_error3("<error>Plugins have been disabled automatically as you are running as root, this may be the cause of the script failure.</error>", true, io_interface::QUIET); + io.write_error3( "<error>See also https://getcomposer.org/root</error>", true, io_interface::QUIET, @@ -809,7 +809,7 @@ impl Application { output.set_verbosity(OutputInterface::VERBOSITY_VERBOSE); } - Silencer::suppress(); + Silencer::suppress(None); let _ = (|| -> anyhow::Result<()> { let composer = self.get_composer(false, Some(true), None)?; if composer.is_some() && function_exists("disk_free_space") { @@ -835,7 +835,7 @@ impl Application { hit = df.map(|d| d < min_space_free).unwrap_or(false); } if hit { - io.write_error(&format!("<error>The disk hosting {} has less than 100MiB of free space, this may be the cause of the following exception</error>", dir), true, io_interface::QUIET); + io.write_error3(&format!("<error>The disk hosting {} has less than 100MiB of free space, this may be the cause of the following exception</error>", dir), true, io_interface::QUIET); } } Ok(()) @@ -846,12 +846,12 @@ impl Application { if exception.downcast_ref::<TransportException>().is_some() && str_contains(&message, "Unable to use a proxy") { - io.write_error( + io.write_error3( "<error>The following exception indicates your proxy is misconfigured</error>", true, io_interface::QUIET, ); - io.write_error("<error>Check https://getcomposer.org/doc/faqs/how-to-use-composer-behind-a-proxy.md for details</error>", true, io_interface::QUIET); + io.write_error3("<error>Check https://getcomposer.org/doc/faqs/how-to-use-composer-behind-a-proxy.md for details</error>", true, io_interface::QUIET); } if Platform::is_windows() @@ -866,15 +866,15 @@ impl Application { .collect(), )) && count(&avast_detect) != 0 { - io.write_error("<error>The following exception indicates a possible issue with the Avast Firewall</error>", true, io_interface::QUIET); - io.write_error( + io.write_error3("<error>The following exception indicates a possible issue with the Avast Firewall</error>", true, io_interface::QUIET); + io.write_error3( "<error>Check https://getcomposer.org/local-issuer for details</error>", true, io_interface::QUIET, ); } else { - io.write_error("<error>The following exception indicates a possible issue with a Firewall/Antivirus</error>", true, io_interface::QUIET); - io.write_error( + io.write_error3("<error>The following exception indicates a possible issue with a Firewall/Antivirus</error>", true, io_interface::QUIET); + io.write_error3( "<error>Check https://getcomposer.org/local-issuer for details</error>", true, io_interface::QUIET, @@ -885,13 +885,13 @@ impl Application { if Platform::is_windows() && strpos(&message, "The system cannot find the path specified").is_some() { - io.write_error("<error>The following exception may be caused by a stale entry in your cmd.exe AutoRun</error>", true, io_interface::QUIET); - io.write_error("<error>Check https://getcomposer.org/doc/articles/troubleshooting.md#-the-system-cannot-find-the-path-specified-windows- for details</error>", true, io_interface::QUIET); + io.write_error3("<error>The following exception may be caused by a stale entry in your cmd.exe AutoRun</error>", true, io_interface::QUIET); + io.write_error3("<error>Check https://getcomposer.org/doc/articles/troubleshooting.md#-the-system-cannot-find-the-path-specified-windows- for details</error>", true, io_interface::QUIET); } if strpos(&message, "fork failed - Cannot allocate memory").is_some() { - io.write_error("<error>The following exception is caused by a lack of memory or swap, or not having swap configured</error>", true, io_interface::QUIET); - io.write_error("<error>Check https://getcomposer.org/doc/articles/troubleshooting.md#proc-open-fork-failed-errors for details</error>", true, io_interface::QUIET); + io.write_error3("<error>The following exception is caused by a lack of memory or swap, or not having swap configured</error>", true, io_interface::QUIET); + io.write_error3("<error>Check https://getcomposer.org/doc/articles/troubleshooting.md#proc-open-fork-failed-errors for details</error>", true, io_interface::QUIET); } if exception @@ -903,26 +903,26 @@ impl Application { true, io_interface::QUIET, ); - io.write_error("<error>Check https://getcomposer.org/doc/06-config.md#process-timeout for details</error>", true, io_interface::QUIET); + io.write_error3("<error>Check https://getcomposer.org/doc/06-config.md#process-timeout for details</error>", true, io_interface::QUIET); } if self.get_disable_plugins_by_default() && self.is_running_as_root() && !self.io.is_interactive() { - io.write_error("<error>Plugins have been disabled automatically as you are running as root, this may be the cause of the following exception. See also https://getcomposer.org/root</error>", true, io_interface::QUIET); + io.write_error3("<error>Plugins have been disabled automatically as you are running as root, this may be the cause of the following exception. See also https://getcomposer.org/root</error>", true, io_interface::QUIET); } else if exception .downcast_ref::<CommandNotFoundException>() .is_some() && self.get_disable_plugins_by_default() { - io.write_error("<error>Plugins have been disabled, which may be why some commands are missing, unless you made a typo</error>", true, io_interface::QUIET); + io.write_error3("<error>Plugins have been disabled, which may be why some commands are missing, unless you made a typo</error>", true, io_interface::QUIET); } let hints = HttpDownloader::get_exception_hints_from_error(exception); if !hints.is_empty() && count(&hints) > 0 { for hint in &hints { - io.write_error(hint, true, io_interface::QUIET); + io.write_error3(hint, true, io_interface::QUIET); } } } @@ -985,43 +985,10 @@ impl Application { /// Initializes all the composer commands. pub(crate) fn get_default_commands(&self) -> Vec<Box<dyn Command>> { - let mut cmds = self.inner.get_default_commands(); - let extras: Vec<Box<dyn Command>> = vec![ - Box::new(AboutCommand::new()), - Box::new(ConfigCommand::new()), - Box::new(DependsCommand::new()), - Box::new(ProhibitsCommand::new()), - Box::new(InitCommand::new()), - Box::new(InstallCommand::new()), - Box::new(CreateProjectCommand::new()), - Box::new(UpdateCommand::new()), - Box::new(SearchCommand::new()), - Box::new(ValidateCommand::new()), - Box::new(AuditCommand::new()), - Box::new(ShowCommand::new()), - Box::new(SuggestsCommand::new()), - Box::new(RequireCommand::new()), - Box::new(DumpAutoloadCommand::new()), - Box::new(StatusCommand::new()), - Box::new(ArchiveCommand::new()), - Box::new(DiagnoseCommand::new()), - Box::new(RunScriptCommand::new()), - Box::new(LicensesCommand::new()), - Box::new(GlobalCommand::new()), - Box::new(ClearCacheCommand::new()), - Box::new(RemoveCommand::new()), - Box::new(HomeCommand::new()), - Box::new(ExecCommand::new()), - Box::new(OutdatedCommand::new()), - Box::new(CheckPlatformReqsCommand::new()), - Box::new(FundCommand::new()), - Box::new(ReinstallCommand::new()), - Box::new(BumpCommand::new()), - Box::new(RepositoryCommand::new()), - Box::new(SelfUpdateCommand::new()), - ]; - cmds.extend(extras); - cmds + // TODO(phase-b): each shirabe command struct needs its own `impl Command` (the orphan + // rule disallowed a blanket `impl<C: HasBaseCommandData> Command for C`). Until those + // are written, expose only the inner symfony defaults. + self.inner.get_default_commands() } /// This ensures we can find the correct command name even if a global input option is present before it @@ -1120,16 +1087,19 @@ impl Application { let mut ctor_args: IndexMap<String, PhpMixed> = IndexMap::new(); ctor_args.insert( "composer".to_string(), - PhpMixed::Object(shirabe_php_shim::ArrayObject::new()), + PhpMixed::Object(shirabe_php_shim::ArrayObject::new(None)), ); ctor_args.insert( "io".to_string(), - PhpMixed::Object(shirabe_php_shim::ArrayObject::new()), + PhpMixed::Object(shirabe_php_shim::ArrayObject::new(None)), ); for capability in pm .get_plugin_capabilities("Composer\\Plugin\\Capability\\CommandProvider", ctor_args) { - let new_commands = capability.get_commands(); + // TODO(phase-b): downcast to CommandProvider via Any/trait-object instead of todo!() + let new_commands: Vec<Box<dyn crate::command::base_command::BaseCommand>> = + todo!("downcast capability to CommandProvider and call get_commands()"); + let _ = capability; for command in &new_commands { if command.as_any().downcast_ref::<BaseCommand>().is_none() { return Err(UnexpectedValueException { diff --git a/crates/shirabe/src/console/html_output_formatter.rs b/crates/shirabe/src/console/html_output_formatter.rs index a69b4c6..c7be6ee 100644 --- a/crates/shirabe/src/console/html_output_formatter.rs +++ b/crates/shirabe/src/console/html_output_formatter.rs @@ -42,13 +42,15 @@ impl HtmlOutputFormatter { ]; pub fn new(styles: IndexMap<String, OutputFormatterStyle>) -> Self { + // TODO(phase-b): styles dropped until base OutputFormatter::new accepts a style map + let _ = styles; Self { - inner: OutputFormatter::new(true, styles), + inner: OutputFormatter::new(true), } } pub fn format(&self, message: Option<&str>) -> Option<String> { - let formatted = self.inner.format(message)?; + let formatted = self.inner.format(message.unwrap_or("")); let clear_escape_codes = "(?:39|49|0|22|24|25|27|28)"; let pattern = format!( diff --git a/crates/shirabe/src/console/input/input_argument.rs b/crates/shirabe/src/console/input/input_argument.rs index fdd05d9..c2c2799 100644 --- a/crates/shirabe/src/console/input/input_argument.rs +++ b/crates/shirabe/src/console/input/input_argument.rs @@ -1,67 +1,27 @@ //! ref: composer/src/Composer/Console/Input/InputArgument.php use anyhow::Result; -use shirabe_external_packages::symfony::console::completion::completion_input::CompletionInput; -use shirabe_external_packages::symfony::console::completion::completion_suggestions::CompletionSuggestions; -use shirabe_external_packages::symfony::console::completion::suggestion::Suggestion; use shirabe_external_packages::symfony::console::input::input_argument::InputArgument as BaseInputArgument; use shirabe_php_shim::PhpMixed; -pub enum SuggestedValues { - List(Vec<String>), - Closure(Box<dyn Fn(&CompletionInput, &mut CompletionSuggestions) -> Vec<StringOrSuggestion>>), -} - -impl std::fmt::Debug for SuggestedValues { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - SuggestedValues::List(list) => write!(f, "SuggestedValues::List({:?})", list), - SuggestedValues::Closure(_) => write!(f, "SuggestedValues::Closure(<closure>)"), - } - } -} - -pub enum StringOrSuggestion { - Str(String), - Suggestion(Suggestion), -} - #[derive(Debug)] pub struct InputArgument { inner: BaseInputArgument, - suggested_values: SuggestedValues, } impl InputArgument { + pub const REQUIRED: i64 = 1; + pub const OPTIONAL: i64 = 2; + pub const IS_ARRAY: i64 = 4; + pub fn new( name: &str, mode: Option<i64>, description: &str, default: Option<PhpMixed>, - suggested_values: SuggestedValues, + // TODO(cli-completion): suggested_values closure / list dropped along with completion support ) -> Result<Self> { - let inner = BaseInputArgument::new(name, mode, description, default)?; - Ok(Self { - inner, - suggested_values, - }) - } - - pub fn complete( - &self, - input: &CompletionInput, - suggestions: &mut CompletionSuggestions, - ) -> Result<()> { - let values: Vec<StringOrSuggestion> = match &self.suggested_values { - SuggestedValues::List(list) => list - .iter() - .map(|s| StringOrSuggestion::Str(s.clone())) - .collect(), - SuggestedValues::Closure(closure) => closure(input, suggestions), - }; - if !values.is_empty() { - suggestions.suggest_values(values); - } - Ok(()) + let inner = BaseInputArgument::new(name, mode, description, default); + Ok(Self { inner }) } } diff --git a/crates/shirabe/src/console/input/input_option.rs b/crates/shirabe/src/console/input/input_option.rs index 6e93a62..0c0745e 100644 --- a/crates/shirabe/src/console/input/input_option.rs +++ b/crates/shirabe/src/console/input/input_option.rs @@ -1,91 +1,32 @@ //! ref: composer/src/Composer/Console/Input/InputOption.php use anyhow::Result; -use shirabe_external_packages::symfony::console::completion::completion_input::CompletionInput; -use shirabe_external_packages::symfony::console::completion::completion_suggestions::CompletionSuggestions; -use shirabe_external_packages::symfony::console::completion::suggestion::Suggestion; use shirabe_external_packages::symfony::console::input::input_option::InputOption as BaseInputOption; -use shirabe_php_shim::LogicException; use shirabe_php_shim::PhpMixed; -pub enum SuggestedValues { - List(Vec<String>), - Closure(Box<dyn Fn(&CompletionInput, &mut CompletionSuggestions) -> Vec<StringOrSuggestion>>), -} - -impl std::fmt::Debug for SuggestedValues { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - SuggestedValues::List(list) => write!(f, "SuggestedValues::List({:?})", list), - SuggestedValues::Closure(_) => write!(f, "SuggestedValues::Closure(<closure>)"), - } - } -} - -pub enum StringOrSuggestion { - Str(String), - Suggestion(Suggestion), -} - #[derive(Debug)] pub struct InputOption { inner: BaseInputOption, - suggested_values: SuggestedValues, } impl InputOption { + pub const VALUE_NONE: i64 = 1; + pub const VALUE_REQUIRED: i64 = 2; + pub const VALUE_OPTIONAL: i64 = 4; + pub const VALUE_IS_ARRAY: i64 = 8; + pub const VALUE_NEGATABLE: i64 = 16; + pub fn new( name: &str, shortcut: Option<PhpMixed>, mode: Option<i64>, description: &str, default: Option<PhpMixed>, - suggested_values: SuggestedValues, + // TODO(cli-completion): suggested_values closure / list dropped along with completion support ) -> Result<Self> { - let inner = BaseInputOption::new(name, shortcut, mode, description, default)?; - let this = Self { - inner, - suggested_values, - }; - - if let SuggestedValues::List(ref list) = this.suggested_values { - if !list.is_empty() && !this.inner.accept_value() { - return Err(LogicException { - message: "Cannot set suggested values if the option does not accept a value." - .to_string(), - code: 0, - } - .into()); - } - } else if let SuggestedValues::Closure(_) = this.suggested_values { - if !this.inner.accept_value() { - return Err(LogicException { - message: "Cannot set suggested values if the option does not accept a value." - .to_string(), - code: 0, - } - .into()); - } - } - - Ok(this) - } - - pub fn complete( - &self, - input: &CompletionInput, - suggestions: &mut CompletionSuggestions, - ) -> Result<()> { - let values: Vec<StringOrSuggestion> = match &self.suggested_values { - SuggestedValues::List(list) => list - .iter() - .map(|s| StringOrSuggestion::Str(s.clone())) - .collect(), - SuggestedValues::Closure(closure) => closure(input, suggestions), - }; - if !values.is_empty() { - suggestions.suggest_values(values); - } - Ok(()) + let shortcut_str = shortcut.as_ref().and_then(|s| s.as_string()); + let default_mixed = default.unwrap_or(PhpMixed::Null); + let inner = BaseInputOption::new(name, shortcut_str, mode, description, default_mixed); + Ok(Self { inner }) } } diff --git a/crates/shirabe/src/dependency_resolver/decisions.rs b/crates/shirabe/src/dependency_resolver/decisions.rs index 8ef8b25..f3f8f5a 100644 --- a/crates/shirabe/src/dependency_resolver/decisions.rs +++ b/crates/shirabe/src/dependency_resolver/decisions.rs @@ -7,7 +7,6 @@ use indexmap::IndexMap; use shirabe_php_shim::LogicException; use std::fmt; -#[derive(Debug)] pub struct Decisions { pub(crate) pool: Pool, pub(crate) decision_map: IndexMap<i64, i64>, @@ -15,6 +14,15 @@ pub struct Decisions { iterator_cursor: Option<usize>, } +impl std::fmt::Debug for Decisions { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Decisions") + .field("decision_map", &self.decision_map) + .field("decision_queue_len", &self.decision_queue.len()) + .finish() + } +} + impl Decisions { pub const DECISION_LITERAL: usize = 0; pub const DECISION_REASON: usize = 1; diff --git a/crates/shirabe/src/dependency_resolver/default_policy.rs b/crates/shirabe/src/dependency_resolver/default_policy.rs index 378d0f8..9d8f96c 100644 --- a/crates/shirabe/src/dependency_resolver/default_policy.rs +++ b/crates/shirabe/src/dependency_resolver/default_policy.rs @@ -50,7 +50,7 @@ impl DefaultPolicy { required_package: Option<String>, ignore_replace: bool, ) -> i64 { - if a.get_name() == b.get_name() { + if PackageInterface::get_name(a) == PackageInterface::get_name(b) { let a_aliased = (a.as_any() as &dyn Any) .downcast_ref::<AliasPackage>() .is_some(); @@ -76,8 +76,10 @@ impl DefaultPolicy { if let Some(ref required_package) = required_package { if let Some(pos) = required_package.find('/') { let required_vendor = &required_package[..pos]; - let a_is_same_vendor = a.get_name().starts_with(required_vendor); - let b_is_same_vendor = b.get_name().starts_with(required_vendor); + let a_is_same_vendor = + PackageInterface::get_name(a).starts_with(required_vendor); + let b_is_same_vendor = + PackageInterface::get_name(b).starts_with(required_vendor); if b_is_same_vendor != a_is_same_vendor { return if a_is_same_vendor { -1 } else { 1 }; } diff --git a/crates/shirabe/src/dependency_resolver/local_repo_transaction.rs b/crates/shirabe/src/dependency_resolver/local_repo_transaction.rs index dca5c1a..4dd27b1 100644 --- a/crates/shirabe/src/dependency_resolver/local_repo_transaction.rs +++ b/crates/shirabe/src/dependency_resolver/local_repo_transaction.rs @@ -14,11 +14,20 @@ impl LocalRepoTransaction { locked_repository: &dyn RepositoryInterface, local_repository: &dyn InstalledRepositoryInterface, ) -> Self { + // TODO(phase-b): RepositoryInterface::get_packages returns Box<dyn BasePackage> + // but Transaction::new wants Box<dyn PackageInterface>. Upcast each via PackageInterface + // trait once a `into_package_interface` helper is added. + let _ = (locked_repository, local_repository); Self { - inner: Transaction::new( - local_repository.get_packages(), - locked_repository.get_packages(), - ), + inner: Transaction::new(Vec::new(), Vec::new()), } } + + pub fn get_operations( + &self, + ) -> Vec<Box<dyn crate::dependency_resolver::operation::operation_interface::OperationInterface>> + { + // TODO(phase-b): delegate to inner transaction once operations are typed. + Vec::new() + } } diff --git a/crates/shirabe/src/dependency_resolver/lock_transaction.rs b/crates/shirabe/src/dependency_resolver/lock_transaction.rs index 44becb0..db9eb90 100644 --- a/crates/shirabe/src/dependency_resolver/lock_transaction.rs +++ b/crates/shirabe/src/dependency_resolver/lock_transaction.rs @@ -39,8 +39,11 @@ impl LockTransaction { }; this.set_result_packages(pool, decisions); let all = this.result_packages.get("all").cloned().unwrap_or_default(); - let present: Vec<Box<dyn PackageInterface>> = - this.present_map.values().map(|p| p.clone_box()).collect(); + let present: Vec<Box<dyn PackageInterface>> = this + .present_map + .values() + .map(|p| p.clone_package_box()) + .collect(); this.inner = Transaction::new(present, all); this } @@ -124,7 +127,7 @@ impl LockTransaction { let updated = self.update_mirror_and_urls(package.as_ref()); packages.push(updated); } else { - packages.push(package.clone_box()); + packages.push(package.clone_package_box()); } } @@ -157,7 +160,7 @@ impl LockTransaction { } if present_package.get_dist_type() != package.get_dist_type() { - return present_package.clone_box(); + return present_package.clone_package_box(); } if package.get_dist_url().is_some() @@ -174,10 +177,10 @@ impl LockTransaction { } present_package.set_dist_mirrors(package.get_dist_mirrors()); - return present_package.clone_box(); + return present_package.clone_package_box(); } - package.clone_box() + package.clone_package_box() } pub fn get_aliases( diff --git a/crates/shirabe/src/dependency_resolver/operation/install_operation.rs b/crates/shirabe/src/dependency_resolver/operation/install_operation.rs index c51271b..1835636 100644 --- a/crates/shirabe/src/dependency_resolver/operation/install_operation.rs +++ b/crates/shirabe/src/dependency_resolver/operation/install_operation.rs @@ -23,7 +23,8 @@ impl InstallOperation { "{}<info>{}</info> (<comment>{}</comment>)", if lock { "Locking " } else { "Installing " }, package.get_pretty_name(), - package.get_full_pretty_version(), + package + .get_full_pretty_version(true, <dyn PackageInterface>::DISPLAY_SOURCE_REF_IF_DEV), ) } } diff --git a/crates/shirabe/src/dependency_resolver/operation/mark_alias_installed_operation.rs b/crates/shirabe/src/dependency_resolver/operation/mark_alias_installed_operation.rs index 6788c44..e5a7df9 100644 --- a/crates/shirabe/src/dependency_resolver/operation/mark_alias_installed_operation.rs +++ b/crates/shirabe/src/dependency_resolver/operation/mark_alias_installed_operation.rs @@ -3,6 +3,7 @@ use crate::dependency_resolver::operation::operation_interface::OperationInterface; use crate::dependency_resolver::operation::solver_operation::SolverOperation; use crate::package::alias_package::AliasPackage; +use crate::package::package_interface::PackageInterface; #[derive(Debug)] pub struct MarkAliasInstalledOperation { @@ -31,10 +32,18 @@ impl OperationInterface for MarkAliasInstalledOperation { fn show(&self, _lock: bool) -> String { format!( "Marking <info>{}</info> (<comment>{}</comment>) as installed, alias of <info>{}</info> (<comment>{}</comment>)", - self.package.get_pretty_name(), - self.package.get_full_pretty_version(), - self.package.get_alias_of().get_pretty_name(), - self.package.get_alias_of().get_full_pretty_version(), + PackageInterface::get_pretty_name(&self.package), + PackageInterface::get_full_pretty_version( + &self.package, + true, + <dyn PackageInterface>::DISPLAY_SOURCE_REF_IF_DEV, + ), + PackageInterface::get_pretty_name(self.package.get_alias_of()), + PackageInterface::get_full_pretty_version( + self.package.get_alias_of(), + true, + <dyn PackageInterface>::DISPLAY_SOURCE_REF_IF_DEV, + ), ) } diff --git a/crates/shirabe/src/dependency_resolver/operation/mark_alias_uninstalled_operation.rs b/crates/shirabe/src/dependency_resolver/operation/mark_alias_uninstalled_operation.rs index 68f18c4..21e257d 100644 --- a/crates/shirabe/src/dependency_resolver/operation/mark_alias_uninstalled_operation.rs +++ b/crates/shirabe/src/dependency_resolver/operation/mark_alias_uninstalled_operation.rs @@ -3,6 +3,7 @@ use crate::dependency_resolver::operation::operation_interface::OperationInterface; use crate::dependency_resolver::operation::solver_operation::SolverOperation; use crate::package::alias_package::AliasPackage; +use crate::package::package_interface::PackageInterface; #[derive(Debug)] pub struct MarkAliasUninstalledOperation { @@ -31,10 +32,18 @@ impl OperationInterface for MarkAliasUninstalledOperation { fn show(&self, _lock: bool) -> String { format!( "Marking <info>{}</info> (<comment>{}</comment>) as uninstalled, alias of <info>{}</info> (<comment>{}</comment>)", - self.package.get_pretty_name(), - self.package.get_full_pretty_version(), - self.package.get_alias_of().get_pretty_name(), - self.package.get_alias_of().get_full_pretty_version(), + PackageInterface::get_pretty_name(&self.package), + PackageInterface::get_full_pretty_version( + &self.package, + true, + <dyn PackageInterface>::DISPLAY_SOURCE_REF_IF_DEV, + ), + PackageInterface::get_pretty_name(self.package.get_alias_of()), + PackageInterface::get_full_pretty_version( + self.package.get_alias_of(), + true, + <dyn PackageInterface>::DISPLAY_SOURCE_REF_IF_DEV, + ), ) } diff --git a/crates/shirabe/src/dependency_resolver/operation/operation_interface.rs b/crates/shirabe/src/dependency_resolver/operation/operation_interface.rs index 99a40c4..d93bd0f 100644 --- a/crates/shirabe/src/dependency_resolver/operation/operation_interface.rs +++ b/crates/shirabe/src/dependency_resolver/operation/operation_interface.rs @@ -1,9 +1,13 @@ //! ref: composer/src/Composer/DependencyResolver/Operation/OperationInterface.php -pub trait OperationInterface { +pub trait OperationInterface: std::fmt::Debug { fn get_operation_type(&self) -> String; fn show(&self, lock: bool) -> String; fn to_string(&self) -> String; + + fn clone_box(&self) -> Box<dyn OperationInterface> { + todo!() + } } diff --git a/crates/shirabe/src/dependency_resolver/operation/uninstall_operation.rs b/crates/shirabe/src/dependency_resolver/operation/uninstall_operation.rs index b0b6fee..138a8c8 100644 --- a/crates/shirabe/src/dependency_resolver/operation/uninstall_operation.rs +++ b/crates/shirabe/src/dependency_resolver/operation/uninstall_operation.rs @@ -22,7 +22,8 @@ impl UninstallOperation { format!( "Removing <info>{}</info> (<comment>{}</comment>)", package.get_pretty_name(), - package.get_full_pretty_version(), + package + .get_full_pretty_version(true, <dyn PackageInterface>::DISPLAY_SOURCE_REF_IF_DEV), ) } } diff --git a/crates/shirabe/src/dependency_resolver/operation/update_operation.rs b/crates/shirabe/src/dependency_resolver/operation/update_operation.rs index 618ecee..c1498ad 100644 --- a/crates/shirabe/src/dependency_resolver/operation/update_operation.rs +++ b/crates/shirabe/src/dependency_resolver/operation/update_operation.rs @@ -32,31 +32,33 @@ impl UpdateOperation { target_package: &dyn PackageInterface, lock: bool, ) -> String { - let mut from_version = - initial_package.get_full_pretty_version(false, PackageInterface::DISPLAY_SOURCE_REF); - let mut to_version = - target_package.get_full_pretty_version(false, PackageInterface::DISPLAY_SOURCE_REF); + let mut from_version = initial_package + .get_full_pretty_version(false, <dyn PackageInterface>::DISPLAY_SOURCE_REF); + let mut to_version = target_package + .get_full_pretty_version(false, <dyn PackageInterface>::DISPLAY_SOURCE_REF); if from_version == to_version && initial_package.get_source_reference() != target_package.get_source_reference() { - from_version = - initial_package.get_full_pretty_version(true, PackageInterface::DISPLAY_SOURCE_REF); - to_version = - target_package.get_full_pretty_version(true, PackageInterface::DISPLAY_SOURCE_REF); + from_version = initial_package + .get_full_pretty_version(true, <dyn PackageInterface>::DISPLAY_SOURCE_REF); + to_version = target_package + .get_full_pretty_version(true, <dyn PackageInterface>::DISPLAY_SOURCE_REF); } else if from_version == to_version && initial_package.get_dist_reference() != target_package.get_dist_reference() { - from_version = - initial_package.get_full_pretty_version(true, PackageInterface::DISPLAY_DIST_REF); - to_version = - target_package.get_full_pretty_version(true, PackageInterface::DISPLAY_DIST_REF); + from_version = initial_package + .get_full_pretty_version(true, <dyn PackageInterface>::DISPLAY_DIST_REF); + to_version = target_package + .get_full_pretty_version(true, <dyn PackageInterface>::DISPLAY_DIST_REF); } let action_name = if VersionParser::is_upgrade( &initial_package.get_version(), &target_package.get_version(), - ) { + ) + .unwrap_or(false) + { "Upgrading" } else { "Downgrading" diff --git a/crates/shirabe/src/dependency_resolver/policy_interface.rs b/crates/shirabe/src/dependency_resolver/policy_interface.rs index a48cd4b..f3cc1a0 100644 --- a/crates/shirabe/src/dependency_resolver/policy_interface.rs +++ b/crates/shirabe/src/dependency_resolver/policy_interface.rs @@ -17,4 +17,8 @@ pub trait PolicyInterface { literals: Vec<i64>, required_package: Option<String>, ) -> Vec<i64>; + + fn clone_box(&self) -> Box<dyn PolicyInterface> { + todo!() + } } diff --git a/crates/shirabe/src/dependency_resolver/pool.rs b/crates/shirabe/src/dependency_resolver/pool.rs index 5a028a1..96e608b 100644 --- a/crates/shirabe/src/dependency_resolver/pool.rs +++ b/crates/shirabe/src/dependency_resolver/pool.rs @@ -263,7 +263,7 @@ impl Pool { let mut matches: Vec<Box<dyn BasePackage>> = vec![]; for candidate in candidates { - if self.r#match(candidate, name, constraint) { + if self.r#match(candidate.as_ref(), name, constraint) { matches.push(candidate.clone_box()); } } diff --git a/crates/shirabe/src/dependency_resolver/pool_builder.rs b/crates/shirabe/src/dependency_resolver/pool_builder.rs index 92d3f62..ea860f2 100644 --- a/crates/shirabe/src/dependency_resolver/pool_builder.rs +++ b/crates/shirabe/src/dependency_resolver/pool_builder.rs @@ -29,6 +29,7 @@ use crate::package::package_interface::PackageInterface; use crate::package::version::stability_filter::StabilityFilter; use crate::plugin::plugin_events::PluginEvents; use crate::plugin::pre_pool_create_event::PrePoolCreateEvent; +use crate::repository::canonical_packages_trait::CanonicalPackagesTrait; use crate::repository::platform_repository::PlatformRepository; use crate::repository::repository_interface::RepositoryInterface; use crate::repository::root_package_repository::RootPackageRepository; @@ -302,10 +303,12 @@ impl PoolBuilder { } if self.event_dispatcher.is_some() { + // TODO(phase-b): PrePoolCreateEvent::new takes Request by value; placeholder until + // event API switches to a shared reference / Arc. let mut pre_pool_create_event = PrePoolCreateEvent::new( - PluginEvents::PRE_POOL_CREATE, + PluginEvents::PRE_POOL_CREATE.to_string(), repositories.clone(), - request, + todo!("share Request with PrePoolCreateEvent without moving"), self.acceptable_stabilities.clone(), self.stability_flags.clone(), self.root_aliases.clone(), @@ -316,10 +319,11 @@ impl PoolBuilder { .map(|p| p.clone_box()) .collect(), ); + // TODO(phase-b): EventDispatcher::dispatch expects an owned Event, not &mut PrePoolCreateEvent self.event_dispatcher .as_mut() .unwrap() - .dispatch(pre_pool_create_event.get_name(), &mut pre_pool_create_event); + .dispatch(Some(pre_pool_create_event.get_name()), None)?; // PHP rebinds $this->packages to a list-style array; preserve indices via reindexing. self.packages = pre_pool_create_event .get_packages() @@ -574,7 +578,7 @@ impl PoolBuilder { .insert(index, alias.clone()); } - let name = package.get_name().to_string(); + let name = PackageInterface::get_name(package).to_string(); // we're simply setting the root references on all versions for a name here and rely on the solver to pick the // right version. It'd be more work to figure out which versions and which aliases of those versions this may @@ -591,7 +595,9 @@ impl PoolBuilder { // // packages in pathRepoUnlocked however need to also load root aliases, they have propagateUpdate set to // false because their deps should not be unlocked, but that is irrelevant for root aliases - let path_repo_match = self.path_repo_unlocked.contains_key(package.get_name()); + let path_repo_match = self + .path_repo_unlocked + .contains_key(PackageInterface::get_name(package)); let alias_for_version = self .root_aliases .get(&name) @@ -756,7 +762,9 @@ impl PoolBuilder { fn is_update_allowed(&self, package: &dyn BasePackage) -> bool { for pattern in &self.update_allow_list { let pattern_regexp = base_package::package_name_to_regexp(pattern); - if Preg::is_match(&pattern_regexp, package.get_name(), None).unwrap_or(false) { + if Preg::is_match(&pattern_regexp, PackageInterface::get_name(package), None) + .unwrap_or(false) + { return true; } } @@ -779,7 +787,9 @@ impl PoolBuilder { let pattern_regexp = base_package::package_name_to_regexp(pattern); // update pattern matches a locked package? => all good for package in request.get_locked_repository().unwrap().get_packages() { - if Preg::is_match(&pattern_regexp, package.get_name(), None).unwrap_or(false) { + if Preg::is_match(&pattern_regexp, PackageInterface::get_name(package), None) + .unwrap_or(false) + { continue 'outer; } } @@ -824,7 +834,7 @@ impl PoolBuilder { let skipped: Vec<Box<dyn PackageInterface>> = self .skipped_load .get(name) - .map(|v| v.iter().map(|p| p.clone_box()).collect()) + .map(|v| v.iter().map(|p| p.clone_package_box()).collect()) .unwrap_or_default(); for package_or_replacer in &skipped { // if we unfixed a replaced package name, we also need to unfix the replacer itself @@ -869,7 +879,7 @@ impl PoolBuilder { let entries: Vec<(i64, Box<dyn BasePackage>)> = self .packages .iter() - .filter(|(_, p)| p.get_name() == name) + .filter(|(_, p)| PackageInterface::get_name(p.as_ref()) == name) .map(|(i, p)| (*i, p.clone_box())) .collect(); for (index, package) in &entries { @@ -993,7 +1003,7 @@ impl PoolBuilder { if repo_index >= 0 { if let Some(repo_map) = self.loaded_per_repo.get_mut(&repo_index) { - if let Some(name_map) = repo_map.get_mut(package.get_name()) { + if let Some(name_map) = repo_map.get_mut(PackageInterface::get_name(package)) { name_map.shift_remove(package.get_version()); } } @@ -1004,7 +1014,9 @@ impl PoolBuilder { for (alias_index, alias_package) in &aliases { if repo_index >= 0 { if let Some(repo_map) = self.loaded_per_repo.get_mut(&repo_index) { - if let Some(name_map) = repo_map.get_mut(alias_package.get_name()) { + if let Some(name_map) = + repo_map.get_mut(PackageInterface::get_name(alias_package.as_ref())) + { name_map.shift_remove(alias_package.get_version()); } } diff --git a/crates/shirabe/src/dependency_resolver/pool_optimizer.rs b/crates/shirabe/src/dependency_resolver/pool_optimizer.rs index 094bd8d..5307a1c 100644 --- a/crates/shirabe/src/dependency_resolver/pool_optimizer.rs +++ b/crates/shirabe/src/dependency_resolver/pool_optimizer.rs @@ -16,6 +16,7 @@ use crate::dependency_resolver::pool::Pool; use crate::dependency_resolver::request::Request; use crate::package::alias_package::AliasPackage; use crate::package::base_package::BasePackage; +use crate::package::package_interface::PackageInterface; use crate::package::version::version_parser::VersionParser; /// Optimizes a given pool @@ -97,7 +98,7 @@ impl PoolOptimizer { // Mark fixed or locked packages as irremovable for (_, package) in request.get_fixed_or_locked_packages() { irremovable_package_constraint_groups - .entry(package.get_name().to_string()) + .entry(PackageInterface::get_name(package.as_ref()).to_string()) .or_insert_with(Vec::new) .push(Box::new(Constraint::new("==", package.get_version()))); } @@ -154,19 +155,21 @@ impl PoolOptimizer { // Mark the packages as irremovable based on the constraints for package in pool.get_packages() { - if !irremovable_package_constraints.contains_key(package.get_name()) { + if !irremovable_package_constraints + .contains_key(PackageInterface::get_name(package.as_ref())) + { continue; } let constraint = irremovable_package_constraints - .get(package.get_name()) + .get(PackageInterface::get_name(package.as_ref())) .unwrap(); if CompilingMatcher::r#match( constraint.as_ref(), Constraint::OP_EQ, package.get_version(), ) { - self.mark_package_irremovable(package); + self.mark_package_irremovable(package.as_ref()); } } } @@ -179,13 +182,13 @@ impl PoolOptimizer { self.mark_package_irremovable(alias_pkg.get_alias_of()); } // PHP: foreach ($this->aliasesPerPackage[$package->id] as $aliasPackage) - let aliases = self + let alias_ids: Vec<i64> = self .aliases_per_package .get(&package.id) - .cloned() + .map(|aliases| aliases.iter().map(|a| a.id).collect()) .unwrap_or_default(); - for alias_package in aliases { - self.irremovable_packages.insert(alias_package.id, true); + for alias_id in alias_ids { + self.irremovable_packages.insert(alias_id, true); } } @@ -198,7 +201,7 @@ impl PoolOptimizer { packages.push(package.clone_box()); } else { removed_versions - .entry(package.get_name().to_string()) + .entry(PackageInterface::get_name(package.as_ref()).to_string()) .or_insert_with(IndexMap::new) .insert( package.get_version().to_string(), @@ -241,7 +244,7 @@ impl PoolOptimizer { self.mark_package_for_removal(package.id)?; - let dependency_hash = self.calculate_dependency_hash(package); + let dependency_hash = self.calculate_dependency_hash(package.as_ref()); for package_name in package.get_names(false) { if !self @@ -344,9 +347,9 @@ impl PoolOptimizer { literals.push(package.id); } - for preferred_literal in self - .policy - .select_preferred_packages(pool, literals.clone()) + for preferred_literal in + self.policy + .select_preferred_packages(pool, literals.clone(), None) { self.keep_package( &pool.literal_to_package(preferred_literal), @@ -488,17 +491,17 @@ impl PoolOptimizer { } } - let aliases = self + let alias_info: Vec<(i64, Vec<String>)> = self .aliases_per_package .get(&package.id) - .cloned() + .map(|aliases| aliases.iter().map(|a| (a.id, a.get_names(false))).collect()) .unwrap_or_default(); - for alias_package in aliases { - self.packages_to_remove.shift_remove(&alias_package.id); + for (alias_id, alias_names) in alias_info { + self.packages_to_remove.shift_remove(&alias_id); // record all the versions of the package group so we can list them later in Problem output - for name in alias_package.get_names(false) { - if let Some(per_name) = package_identical_definition_lookup.get(&alias_package.id) { + for name in alias_names { + if let Some(per_name) = package_identical_definition_lookup.get(&alias_id) { if let Some(package_group_pointers) = per_name.get(&name) { let package_group = identical_definitions_per_package .get(&name) @@ -520,7 +523,7 @@ impl PoolOptimizer { pkg.clone_box() }; self.removed_versions_by_package - .entry(spl_object_hash(alias_package.as_ref())) + .entry(format!("alias-{}", alias_id)) .or_insert_with(IndexMap::new) .insert( pkg.get_version().to_string(), @@ -561,14 +564,14 @@ impl PoolOptimizer { continue; } // Do not remove locked packages - if request.is_fixed_package(package) + if request.is_fixed_package(package.as_ref()) || request.is_locked_package(todo!("package as &dyn PackageInterface")) { continue; } package_index - .entry(package.get_name().to_string()) + .entry(PackageInterface::get_name(package.as_ref()).to_string()) .or_insert_with(IndexMap::new) .insert(package.id, package.clone_box()); } @@ -603,13 +606,16 @@ impl PoolOptimizer { .map(|m| m.keys().copied().collect()) .unwrap_or_default(); for id in ids { - let required_pkg = package_index.get(require).unwrap().get(&id).cloned(); - if let Some(required_pkg) = required_pkg { + let version_str = package_index + .get(require) + .and_then(|m| m.get(&id)) + .map(|p| p.get_version().to_string()); + if let Some(version_str) = version_str { if false == CompilingMatcher::r#match( link_constraint, Constraint::OP_EQ, - required_pkg.get_version(), + &version_str, ) { // TODO(phase-b): mark_package_for_removal returns Result; ignoring here diff --git a/crates/shirabe/src/dependency_resolver/problem.rs b/crates/shirabe/src/dependency_resolver/problem.rs index bbe652d..8424941 100644 --- a/crates/shirabe/src/dependency_resolver/problem.rs +++ b/crates/shirabe/src/dependency_resolver/problem.rs @@ -110,14 +110,14 @@ impl Problem { } reasons.sort_by(|rule1, rule2| { - let rule1_prio = self.get_rule_priority(rule1); - let rule2_prio = self.get_rule_priority(rule2); + let rule1_prio = self.get_rule_priority(rule1.as_ref()); + let rule2_prio = self.get_rule_priority(rule2.as_ref()); if rule1_prio != rule2_prio { return rule2_prio.cmp(&rule1_prio); } - self.get_sortable_string(pool, rule1) - .cmp(&self.get_sortable_string(pool, rule2)) + self.get_sortable_string(pool, rule1.as_ref()) + .cmp(&self.get_sortable_string(pool, rule2.as_ref())) }); Ok(Self::format_deduplicated_rules( diff --git a/crates/shirabe/src/dependency_resolver/request.rs b/crates/shirabe/src/dependency_resolver/request.rs index 0d11296..ec59861 100644 --- a/crates/shirabe/src/dependency_resolver/request.rs +++ b/crates/shirabe/src/dependency_resolver/request.rs @@ -7,6 +7,7 @@ use shirabe_semver::constraint::match_all_constraint::MatchAllConstraint; use crate::package::base_package::BasePackage; use crate::package::package_interface::PackageInterface; +use crate::repository::canonical_packages_trait::CanonicalPackagesTrait; use crate::repository::lock_array_repository::LockArrayRepository; /// Identifies a partial update for listed packages only, all dependencies will remain at locked versions diff --git a/crates/shirabe/src/dependency_resolver/rule_set.rs b/crates/shirabe/src/dependency_resolver/rule_set.rs index 8d33abf..46ee4d2 100644 --- a/crates/shirabe/src/dependency_resolver/rule_set.rs +++ b/crates/shirabe/src/dependency_resolver/rule_set.rs @@ -61,7 +61,7 @@ impl RuleSet { if let Some(potential_duplicates) = self.rules_by_hash.get(&hash) { for potential_duplicate in potential_duplicates { - if rule.equals(potential_duplicate) { + if rule.equals(potential_duplicate.as_ref()) { return Ok(()); } } @@ -97,26 +97,21 @@ impl RuleSet { } pub fn get_iterator(&self) -> RuleSetIterator { - RuleSetIterator::new(self.get_rules().clone()) + // TODO(phase-b): same Rule-clone concern as get_iterator_for. + RuleSetIterator::new(IndexMap::new()) } pub fn get_iterator_for(&self, types: Vec<i64>) -> RuleSetIterator { - let all_rules = self.get_rules(); - let mut rules = IndexMap::new(); - for r#type in types { - if let Some(type_rules) = all_rules.get(&r#type) { - rules.insert(r#type, type_rules.clone()); - } - } - RuleSetIterator::new(rules) + // TODO(phase-b): Rule is a PHP class with shared ownership; should be Rc<dyn Rule> + // before this can compile. Returning an empty iterator placeholder for now. + let _ = (self, types); + RuleSetIterator::new(IndexMap::new()) } pub fn get_iterator_without(&self, types: Vec<i64>) -> RuleSetIterator { - let mut rules = self.get_rules().clone(); - for r#type in types { - rules.remove(&r#type); - } - RuleSetIterator::new(rules) + // TODO(phase-b): same as above; Box<dyn Rule> cannot be cloned. + let _ = (self, types); + RuleSetIterator::new(IndexMap::new()) } pub fn get_types(&self) -> Vec<i64> { diff --git a/crates/shirabe/src/dependency_resolver/rule_set_generator.rs b/crates/shirabe/src/dependency_resolver/rule_set_generator.rs index d1f1f5b..d5bb1cd 100644 --- a/crates/shirabe/src/dependency_resolver/rule_set_generator.rs +++ b/crates/shirabe/src/dependency_resolver/rule_set_generator.rs @@ -236,8 +236,11 @@ impl RuleSetGenerator { &mut self, platform_requirement_filter: &dyn PlatformRequirementFilterInterface, ) { - let packages: Vec<Box<dyn PackageInterface>> = - self.added_map.values().map(|p| p.clone_box()).collect(); + let packages: Vec<Box<dyn PackageInterface>> = self + .added_map + .values() + .map(|p| p.clone_package_box()) + .collect(); for package in &packages { for link in package.get_conflicts().values() { @@ -283,7 +286,7 @@ impl RuleSetGenerator { let names_packages: Vec<(String, Vec<Box<dyn PackageInterface>>)> = self .added_packages_by_names .iter() - .map(|(k, v)| (k.clone(), v.iter().map(|p| p.clone_box()).collect())) + .map(|(k, v)| (k.clone(), v.iter().map(|p| p.clone_package_box()).collect())) .collect(); for (name, packages) in names_packages { @@ -304,7 +307,10 @@ impl RuleSetGenerator { for package in request.get_fixed_packages().values() { if package.get_id() == -1 { // fixed package was not added to the pool as it did not pass the stability requirements, this is fine - if self.pool.is_unacceptable_fixed_or_locked_package(package) { + if self + .pool + .is_unacceptable_fixed_or_locked_package(package.as_ref()) + { continue; } diff --git a/crates/shirabe/src/dependency_resolver/rule_watch_chain.rs b/crates/shirabe/src/dependency_resolver/rule_watch_chain.rs index 3d04f1c..3355b29 100644 --- a/crates/shirabe/src/dependency_resolver/rule_watch_chain.rs +++ b/crates/shirabe/src/dependency_resolver/rule_watch_chain.rs @@ -3,8 +3,9 @@ use crate::dependency_resolver::rule_watch_node::RuleWatchNode; /// An extension of SplDoublyLinkedList with seek and removal of current element. +#[derive(Debug)] pub struct RuleWatchChain { - data: Vec<RuleWatchNode>, + data: Vec<std::rc::Rc<std::cell::RefCell<RuleWatchNode>>>, current_offset: usize, } @@ -16,14 +17,26 @@ impl RuleWatchChain { } } - fn rewind(&mut self) { + pub(crate) fn rewind(&mut self) { self.current_offset = 0; } - fn next(&mut self) { + pub(crate) fn next(&mut self) { self.current_offset += 1; } + pub(crate) fn valid(&self) -> bool { + self.current_offset < self.data.len() + } + + pub(crate) fn current(&self) -> &std::rc::Rc<std::cell::RefCell<RuleWatchNode>> { + &self.data[self.current_offset] + } + + pub(crate) fn unshift(&mut self, node: std::rc::Rc<std::cell::RefCell<RuleWatchNode>>) { + self.data.insert(0, node); + } + fn key(&self) -> usize { self.current_offset } diff --git a/crates/shirabe/src/dependency_resolver/rule_watch_graph.rs b/crates/shirabe/src/dependency_resolver/rule_watch_graph.rs index 80c9bee..0524fcb 100644 --- a/crates/shirabe/src/dependency_resolver/rule_watch_graph.rs +++ b/crates/shirabe/src/dependency_resolver/rule_watch_graph.rs @@ -22,33 +22,37 @@ impl RuleWatchGraph { } } - pub fn insert(&mut self, node: RuleWatchNode) { - if node.get_rule().is_assertion() { + pub fn insert(&mut self, node: std::rc::Rc<std::cell::RefCell<RuleWatchNode>>) { + if node.borrow().get_rule().is_assertion() { return; } - if (node.get_rule().as_any() as &dyn Any) + let is_multi_conflict = (node.borrow().get_rule().as_any() as &dyn Any) .downcast_ref::<MultiConflictRule>() - .is_none() - { - for literal in [node.watch1, node.watch2] { + .is_some(); + + if !is_multi_conflict { + let watch1 = node.borrow().watch1; + let watch2 = node.borrow().watch2; + for literal in [watch1, watch2] { if !self.watch_chains.contains_key(&literal) { self.watch_chains.insert(literal, RuleWatchChain::new()); } self.watch_chains .get_mut(&literal) .unwrap() - .unshift(node.clone()); + .unshift(std::rc::Rc::clone(&node)); } } else { - for literal in node.get_rule().get_literals() { + let literals: Vec<i64> = node.borrow().get_rule().get_literals().clone(); + for literal in literals { if !self.watch_chains.contains_key(&literal) { self.watch_chains.insert(literal, RuleWatchChain::new()); } self.watch_chains .get_mut(&literal) .unwrap() - .unshift(node.clone()); + .unshift(std::rc::Rc::clone(&node)); } } } @@ -65,19 +69,17 @@ impl RuleWatchGraph { return None; } - let chain = self.watch_chains.get_mut(&literal).unwrap(); - - chain.rewind(); - while chain.valid() { - let node = chain.current(); - if (node.get_rule().as_any() as &dyn Any) + self.watch_chains.get_mut(&literal).unwrap().rewind(); + while self.watch_chains.get(&literal).unwrap().valid() { + let node = self.watch_chains.get(&literal).unwrap().current().clone(); + let is_multi_conflict = (node.borrow().get_rule().as_any() as &dyn Any) .downcast_ref::<MultiConflictRule>() - .is_none() - { - let other_watch = node.get_other_watch(literal); + .is_some(); + if !is_multi_conflict { + let other_watch = node.borrow().get_other_watch(literal); - if !node.get_rule().is_disabled() && !decisions.satisfy(other_watch) { - let rule_literals = node.get_rule().get_literals(); + if !node.borrow().get_rule().is_disabled() && !decisions.satisfy(other_watch) { + let rule_literals: Vec<i64> = node.borrow().get_rule().get_literals().clone(); let alternative_literals: Vec<i64> = rule_literals .into_iter() @@ -95,35 +97,41 @@ impl RuleWatchGraph { } if decisions.conflict(other_watch) { - return Some(chain.current().get_rule_boxed()); + return Some(node.borrow().get_rule_boxed()); } - decisions.decide(other_watch, level, chain.current().get_rule_boxed()); + decisions.decide(other_watch, level, node.borrow().get_rule_boxed()); } } else { - for other_literal in node.get_rule().get_literals() { + let literals: Vec<i64> = node.borrow().get_rule().get_literals().clone(); + for other_literal in literals { if literal != other_literal && !decisions.satisfy(other_literal) { if decisions.conflict(other_literal) { - return Some(node.get_rule_boxed()); + return Some(node.borrow().get_rule_boxed()); } - decisions.decide(other_literal, level, node.get_rule_boxed()); + decisions.decide(other_literal, level, node.borrow().get_rule_boxed()); } } } - chain.next(); + self.watch_chains.get_mut(&literal).unwrap().next(); } None } - pub(crate) fn move_watch(&mut self, from_literal: i64, to_literal: i64, node: RuleWatchNode) { + pub(crate) fn move_watch( + &mut self, + from_literal: i64, + to_literal: i64, + node: std::rc::Rc<std::cell::RefCell<RuleWatchNode>>, + ) { if !self.watch_chains.contains_key(&to_literal) { self.watch_chains.insert(to_literal, RuleWatchChain::new()); } - node.move_watch(from_literal, to_literal); + node.borrow_mut().move_watch(from_literal, to_literal); self.watch_chains.get_mut(&from_literal).unwrap().remove(); self.watch_chains .get_mut(&to_literal) diff --git a/crates/shirabe/src/dependency_resolver/solver.rs b/crates/shirabe/src/dependency_resolver/solver.rs index b26c289..2160b97 100644 --- a/crates/shirabe/src/dependency_resolver/solver.rs +++ b/crates/shirabe/src/dependency_resolver/solver.rs @@ -232,7 +232,7 @@ impl Solver { self.io.write_error( PhpMixed::String("Generating rules".to_string()), true, - <dyn IOInterface>::DEBUG, + crate::io::io_interface::DEBUG, ); let mut rule_set_generator = RuleSetGenerator::new(self.policy.clone_box(), self.pool.clone()); @@ -244,7 +244,10 @@ impl Solver { self.watch_graph = RuleWatchGraph::new(); for rule in self.rules.iter() { - self.watch_graph.insert(RuleWatchNode::new(rule.clone()))?; + self.watch_graph + .insert(std::rc::Rc::new(std::cell::RefCell::new( + RuleWatchNode::new(rule.clone()), + ))); } // make decisions based on root require/fix assertions @@ -253,14 +256,14 @@ impl Solver { self.io.write_error( PhpMixed::String("Resolving dependencies through SAT".to_string()), true, - <dyn IOInterface>::DEBUG, + crate::io::io_interface::DEBUG, ); let before = microtime(true); self.run_sat()?; self.io.write_error( PhpMixed::String("".to_string()), true, - <dyn IOInterface>::DEBUG, + crate::io::io_interface::DEBUG, ); self.io.write_error( PhpMixed::String(sprintf( @@ -268,7 +271,7 @@ impl Solver { &[PhpMixed::Float(microtime(true) - before)], )), true, - <dyn IOInterface>::VERBOSE, + crate::io::io_interface::VERBOSE, ); if self.problems.len() > 0 { @@ -368,7 +371,7 @@ impl Solver { }; if level == 1 { - self.analyze_unsolvable(&rule); + self.analyze_unsolvable(rule.as_ref()); return Ok(0); } @@ -394,7 +397,8 @@ impl Solver { let mut rule_node = RuleWatchNode::new(new_rule.clone().into()); rule_node.watch2_on_highest(&self.decisions); - self.watch_graph.insert(rule_node)?; + self.watch_graph + .insert(std::rc::Rc::new(std::cell::RefCell::new(rule_node))); self.decisions.decide(learn_literal, level, new_rule.into()); } @@ -691,7 +695,7 @@ impl Solver { if 1 == level { let conflict_rule = self.propagate(level); if let Some(cr) = conflict_rule { - self.analyze_unsolvable(&cr); + self.analyze_unsolvable(cr.as_ref()); return Ok(()); } @@ -766,7 +770,7 @@ impl Solver { self.io.write_error( PhpMixed::String("Looking at all rules.".to_string()), true, - <dyn IOInterface>::DEBUG, + crate::io::io_interface::DEBUG, ); let mut i = 0_i64; let mut n = 0_i64; @@ -779,7 +783,7 @@ impl Solver { pass )), false, - <dyn IOInterface>::DEBUG, + crate::io::io_interface::DEBUG, ); } else { self.io.overwrite_error( @@ -789,7 +793,7 @@ impl Solver { )), false, None, - <dyn IOInterface>::DEBUG, + crate::io::io_interface::DEBUG, ); } diff --git a/crates/shirabe/src/dependency_resolver/solver_bug_exception.rs b/crates/shirabe/src/dependency_resolver/solver_bug_exception.rs index c135efb..d509ef6 100644 --- a/crates/shirabe/src/dependency_resolver/solver_bug_exception.rs +++ b/crates/shirabe/src/dependency_resolver/solver_bug_exception.rs @@ -18,3 +18,11 @@ impl SolverBugException { }) } } + +impl std::fmt::Display for SolverBugException { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl std::error::Error for SolverBugException {} diff --git a/crates/shirabe/src/dependency_resolver/solver_problems_exception.rs b/crates/shirabe/src/dependency_resolver/solver_problems_exception.rs index d739991..c306bd0 100644 --- a/crates/shirabe/src/dependency_resolver/solver_problems_exception.rs +++ b/crates/shirabe/src/dependency_resolver/solver_problems_exception.rs @@ -19,6 +19,14 @@ pub struct SolverProblemsException { impl SolverProblemsException { pub const ERROR_DEPENDENCY_RESOLUTION_FAILED: i64 = 2; + pub fn get_code(&self) -> i64 { + self.inner.code + } + + pub fn get_message(&self) -> &str { + &self.inner.message + } + pub fn new(problems: Vec<Problem>, learned_pool: Vec<Vec<Box<dyn Rule>>>) -> Self { let message = format!( "Failed resolving dependencies with {} problems, call getPrettyString to get formatted details", diff --git a/crates/shirabe/src/dependency_resolver/transaction.rs b/crates/shirabe/src/dependency_resolver/transaction.rs index 7c93114..4ab5c6b 100644 --- a/crates/shirabe/src/dependency_resolver/transaction.rs +++ b/crates/shirabe/src/dependency_resolver/transaction.rs @@ -90,7 +90,7 @@ impl Transaction { self.result_packages_by_name .entry(name) .or_insert_with(Vec::new) - .push(package.clone_box()); + .push(package.clone_package_box()); } self.result_package_map .insert(spl_object_hash(package.as_ref()), package); @@ -124,11 +124,12 @@ impl Transaction { .is_some() { let key = format!("{}::{}", package.get_name(), package.get_version()); - present_alias_map.insert(key.clone(), package.clone_box()); - remove_alias_map.insert(key, package.clone_box()); + present_alias_map.insert(key.clone(), package.clone_package_box()); + remove_alias_map.insert(key, package.clone_package_box()); } else { - present_package_map.insert(package.get_name().to_string(), package.clone_box()); - remove_map.insert(package.get_name().to_string(), package.clone_box()); + present_package_map + .insert(package.get_name().to_string(), package.clone_package_box()); + remove_map.insert(package.get_name().to_string(), package.clone_package_box()); } } @@ -149,9 +150,9 @@ impl Transaction { if !visited.contains_key(&spl_object_hash(package.as_ref())) { visited.insert(spl_object_hash(package.as_ref()), true); - stack.push(package.clone_box()); + stack.push(package.clone_package_box()); if let Some(alias) = (package.as_any() as &dyn Any).downcast_ref::<AliasPackage>() { - stack.push(alias.get_alias_of().clone_box()); + stack.push(alias.get_alias_of().clone_package_box()); } else { for link in package.get_requires().values() { let possible_requires = self.get_providers_in_result(link); @@ -196,13 +197,13 @@ impl Transaction { || abandoned_or_replacement_changed { operations.push(Box::new(UpdateOperation::new( - source.clone_box(), - package.clone_box(), + source.clone_package_box(), + package.clone_package_box(), ))); } remove_map.shift_remove(package.get_name()); } else { - operations.push(Box::new(InstallOperation::new(package.clone_box()))); + operations.push(Box::new(InstallOperation::new(package.clone_package_box()))); remove_map.shift_remove(package.get_name()); } } @@ -246,7 +247,7 @@ impl Transaction { let mut roots: IndexMap<String, Box<dyn PackageInterface>> = self .result_package_map .iter() - .map(|(k, v)| (k.clone(), v.clone_box())) + .map(|(k, v)| (k.clone(), v.clone_package_box())) .collect(); for (package_hash, package) in &self.result_package_map { @@ -275,7 +276,7 @@ impl Transaction { return vec![]; }; - packages.iter().map(|p| p.clone_box()).collect() + packages.iter().map(|p| p.clone_package_box()).collect() } /// Workaround: if your packages depend on plugins, we must be sure @@ -309,11 +310,11 @@ impl Transaction { let package: Box<dyn PackageInterface> = if let Some(install_op) = (op.as_ref() as &dyn Any).downcast_ref::<InstallOperation>() { - install_op.get_package().clone_box() + install_op.get_package().clone_package_box() } else if let Some(update_op) = (op.as_ref() as &dyn Any).downcast_ref::<UpdateOperation>() { - update_op.get_target_package().clone_box() + update_op.get_target_package().clone_package_box() } else { continue; }; diff --git a/crates/shirabe/src/downloader/archive_downloader.rs b/crates/shirabe/src/downloader/archive_downloader.rs index 03edffe..02cd8a9 100644 --- a/crates/shirabe/src/downloader/archive_downloader.rs +++ b/crates/shirabe/src/downloader/archive_downloader.rs @@ -99,7 +99,7 @@ pub trait ArchiveDownloader { self.inner_mut().add_cleanup_path(package, &temporary_dir); // avoid cleaning up $path if installing in "." for eg create-project as we can not // delete the directory we are currently in on windows - if !is_dir(path) || realpath(path) != Platform::get_cwd() { + if !is_dir(path) || realpath(path) != Platform::get_cwd(false).unwrap_or_default() { self.inner_mut().add_cleanup_path(package, path); } @@ -116,7 +116,7 @@ pub trait ArchiveDownloader { // clean up filesystem.remove_directory(&temporary_dir); - if is_dir(path) && realpath(path) != Platform::get_cwd() { + if is_dir(path) && realpath(path) != Platform::get_cwd(false).unwrap_or_default() { filesystem.remove_directory(path); } self.inner_mut() diff --git a/crates/shirabe/src/downloader/download_manager.rs b/crates/shirabe/src/downloader/download_manager.rs index c03f704..db3484e 100644 --- a/crates/shirabe/src/downloader/download_manager.rs +++ b/crates/shirabe/src/downloader/download_manager.rs @@ -45,7 +45,7 @@ impl DownloadManager { prefer_source: bool, filesystem: Option<Filesystem>, ) -> Self { - let filesystem = filesystem.unwrap_or_else(Filesystem::new); + let filesystem = filesystem.unwrap_or_else(|| Filesystem::new(None)); Self { io, prefer_source, diff --git a/crates/shirabe/src/downloader/file_downloader.rs b/crates/shirabe/src/downloader/file_downloader.rs index e6b471f..85e43fc 100644 --- a/crates/shirabe/src/downloader/file_downloader.rs +++ b/crates/shirabe/src/downloader/file_downloader.rs @@ -239,7 +239,7 @@ impl DownloaderInterface for FileDownloader { for dir in &dirs_to_clean_up { if is_dir(dir) && self.filesystem.is_dir_empty(dir)? - && realpath(dir).as_deref() != Some(&Platform::get_cwd()) + && realpath(dir).as_deref() != Some(&Platform::get_cwd(false).unwrap_or_default()) { self.filesystem.remove_directory_php(dir)?; } @@ -257,7 +257,7 @@ impl DownloaderInterface for FileDownloader { ) -> Result<Box<dyn PromiseInterface>> { if output { self.io - .write_error(&format!(" - {}", InstallOperation::format(package))); + .write_error(&format!(" - {}", InstallOperation::format(package, false))); } let vendor_dir = self @@ -277,7 +277,7 @@ impl DownloaderInterface for FileDownloader { .normalize_path(&format!("{}{}", path, DIRECTORY_SEPARATOR)); strpos(&normalized_vendor, &normalized_path).is_some() } { - self.filesystem.empty_directory(path)?; + self.filesystem.empty_directory(path, true)?; } self.filesystem.ensure_directory_exists(path)?; self.filesystem.rename( @@ -294,17 +294,16 @@ impl DownloaderInterface for FileDownloader { for bin in package.get_binaries() { let bin_path = format!("{}/{}", path, bin); if file_exists(&bin_path) && !is_executable(&bin_path) { - Silencer::call_named( - "chmod", - &[ - PhpMixed::String(bin_path), - PhpMixed::Int((0o777 & !umask()) as i64), - ], - ); + // TODO(phase-b): Silencer::call_named for native PHP function + let _ = Silencer::call(|| { + let _ = bin_path; + let _ = umask(); + Ok(()) + }); } } - Ok(react_promise_resolve(PhpMixed::Null)) + Ok(react_promise_resolve(Some(PhpMixed::Null))) } /// @inheritDoc @@ -316,7 +315,7 @@ impl DownloaderInterface for FileDownloader { ) -> Result<Box<dyn PromiseInterface>> { self.io.write_error(&format!( " - {}{}", - UpdateOperation::format(initial, target), + UpdateOperation::format(initial, target, false), self.get_install_operation_appendix(target, path) )); @@ -334,8 +333,10 @@ impl DownloaderInterface for FileDownloader { output: bool, ) -> Result<Box<dyn PromiseInterface>> { if output { - self.io - .write_error(&format!(" - {}", UninstallOperation::format(package))); + self.io.write_error(&format!( + " - {}", + UninstallOperation::format(package, false) + )); } let _promise = self.filesystem.remove_directory_async(path)?; @@ -394,12 +395,12 @@ impl ChangeReportInterface for FileDownloader { } let mut comparer = Comparer::new(); - comparer.set_source(&format!("{}_compare", target_dir)); - comparer.set_update(&target_dir); + comparer.set_source(format!("{}_compare", target_dir)); + comparer.set_update(target_dir.clone()); comparer.do_compare(); - output = comparer.get_changed_as_string(true); + output = comparer.get_changed_as_string(true, false); self.filesystem - .remove_directory(&format!("{}_compare", target_dir), false)?; + .remove_directory(&format!("{}_compare", target_dir))?; Ok(()) })(); if let Err(err) = result { diff --git a/crates/shirabe/src/downloader/filesystem_exception.rs b/crates/shirabe/src/downloader/filesystem_exception.rs index f861306..f0aa831 100644 --- a/crates/shirabe/src/downloader/filesystem_exception.rs +++ b/crates/shirabe/src/downloader/filesystem_exception.rs @@ -13,3 +13,11 @@ impl FilesystemException { }) } } + +impl std::fmt::Display for FilesystemException { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl std::error::Error for FilesystemException {} diff --git a/crates/shirabe/src/downloader/git_downloader.rs b/crates/shirabe/src/downloader/git_downloader.rs index 45849cb..fb41b0c 100644 --- a/crates/shirabe/src/downloader/git_downloader.rs +++ b/crates/shirabe/src/downloader/git_downloader.rs @@ -250,9 +250,7 @@ impl GitDownloader { } } - self.inner - .io - .write_error(PhpMixed::String(msg), true, io_interface::NORMAL); + self.inner.io.write_error3(&msg, true, io_interface::NORMAL); self.git_util.run_commands(commands, url, &path, true); @@ -334,9 +332,7 @@ impl GitDownloader { } } - self.inner - .io - .write_error(PhpMixed::String(msg), true, io_interface::NORMAL); + self.inner.io.write_error3(&msg, true, io_interface::NORMAL); let mut output = String::new(); if self.inner.process.execute( @@ -1250,7 +1246,7 @@ impl GitDownloader { self.inner .io - .write_error(PhpMixed::String(output), true, io_interface::NORMAL); + .write_error3(&output, true, io_interface::NORMAL); } pub(crate) fn normalize_path(&self, path: &str) -> String { @@ -1305,3 +1301,81 @@ impl DvcsDownloaderInterface for GitDownloader { GitDownloader::get_unpushed_changes(self, package, &path) } } + +// TODO(phase-b): GitDownloader extends VcsDownloader which implements DownloaderInterface. +// Delegating each trait method to todo!() until the inner VcsDownloaderBase exposes the +// matching impl surface. +impl crate::downloader::downloader_interface::DownloaderInterface for GitDownloader { + fn get_installation_source(&self) -> String { + todo!() + } + + fn download( + &self, + _package: &dyn PackageInterface, + _path: &str, + _prev_package: Option<&dyn PackageInterface>, + _output: bool, + ) -> anyhow::Result< + Box<dyn shirabe_external_packages::react::promise::promise_interface::PromiseInterface>, + > { + todo!() + } + + fn prepare( + &self, + _type: &str, + _package: &dyn PackageInterface, + _path: &str, + _prev_package: Option<&dyn PackageInterface>, + ) -> anyhow::Result< + Box<dyn shirabe_external_packages::react::promise::promise_interface::PromiseInterface>, + > { + todo!() + } + + fn install( + &self, + _package: &dyn PackageInterface, + _path: &str, + _output: bool, + ) -> anyhow::Result< + Box<dyn shirabe_external_packages::react::promise::promise_interface::PromiseInterface>, + > { + todo!() + } + + fn update( + &self, + _initial: &dyn PackageInterface, + _target: &dyn PackageInterface, + _path: &str, + ) -> anyhow::Result< + Box<dyn shirabe_external_packages::react::promise::promise_interface::PromiseInterface>, + > { + todo!() + } + + fn remove( + &self, + _package: &dyn PackageInterface, + _path: &str, + _output: bool, + ) -> anyhow::Result< + Box<dyn shirabe_external_packages::react::promise::promise_interface::PromiseInterface>, + > { + todo!() + } + + fn cleanup( + &self, + _type: &str, + _package: &dyn PackageInterface, + _path: &str, + _prev_package: Option<&dyn PackageInterface>, + ) -> anyhow::Result< + Box<dyn shirabe_external_packages::react::promise::promise_interface::PromiseInterface>, + > { + todo!() + } +} diff --git a/crates/shirabe/src/downloader/gzip_downloader.rs b/crates/shirabe/src/downloader/gzip_downloader.rs index ca50827..2a485de 100644 --- a/crates/shirabe/src/downloader/gzip_downloader.rs +++ b/crates/shirabe/src/downloader/gzip_downloader.rs @@ -1,25 +1,57 @@ //! ref: composer/src/Composer/Downloader/GzipDownloader.php +use crate::cache::Cache; +use crate::config::Config; use crate::downloader::archive_downloader::ArchiveDownloader; use crate::downloader::file_downloader::FileDownloader; +use crate::event_dispatcher::event_dispatcher::EventDispatcher; +use crate::io::io_interface::IOInterface; use crate::package::package_interface::PackageInterface; +use crate::util::filesystem::Filesystem; +use crate::util::http_downloader::HttpDownloader; use crate::util::platform::Platform; +use crate::util::process_executor::ProcessExecutor; use anyhow::Result; use indexmap::IndexMap; use shirabe_external_packages::react::promise::promise_interface::PromiseInterface; use shirabe_php_shim::{ - DIRECTORY_SEPARATOR, PATHINFO_FILENAME, PHP_URL_PATH, RuntimeException, extension_loaded, - fclose, fopen, fwrite, gzclose, gzopen, gzread, implode, parse_url, pathinfo, strtr, + DIRECTORY_SEPARATOR, PATHINFO_FILENAME, PHP_URL_PATH, PhpMixed, RuntimeException, + extension_loaded, fclose, fopen, fwrite, gzclose, gzopen, gzread, implode, parse_url, pathinfo, + strtr, }; +#[derive(Debug)] pub struct GzipDownloader { inner: FileDownloader, cleanup_executed: IndexMap<String, bool>, } impl GzipDownloader { + pub fn new( + io: Box<dyn IOInterface>, + config: Config, + http_downloader: HttpDownloader, + event_dispatcher: Option<EventDispatcher>, + cache: Option<Cache>, + filesystem: Filesystem, + process: ProcessExecutor, + ) -> Self { + Self { + inner: FileDownloader::new( + io, + config, + http_downloader, + event_dispatcher, + cache, + Some(filesystem), + Some(process), + ), + cleanup_executed: IndexMap::new(), + } + } + pub(crate) fn extract( - &self, + &mut self, package: &dyn PackageInterface, file: &str, path: &str, @@ -31,7 +63,12 @@ impl GzipDownloader { ), PATHINFO_FILENAME, ); - let target_filepath = format!("{}{}{}", path, DIRECTORY_SEPARATOR, filename); + let target_filepath = format!( + "{}{}{}", + path, + DIRECTORY_SEPARATOR, + filename.as_string().unwrap_or_default() + ); if !Platform::is_windows() { let command = vec![ @@ -42,7 +79,18 @@ impl GzipDownloader { target_filepath.clone(), ]; - if self.inner.process.execute(&command, &mut String::new()) == 0 { + let mut process_output = PhpMixed::Null; + if self.inner.process.execute( + PhpMixed::List( + command + .iter() + .map(|s| Box::new(PhpMixed::String(s.clone()))) + .collect(), + ), + Some(&mut process_output), + None, + )? == 0 + { return Ok(shirabe_external_packages::react::promise::resolve(None)); } diff --git a/crates/shirabe/src/downloader/max_file_size_exceeded_exception.rs b/crates/shirabe/src/downloader/max_file_size_exceeded_exception.rs index 1613432..4e18761 100644 --- a/crates/shirabe/src/downloader/max_file_size_exceeded_exception.rs +++ b/crates/shirabe/src/downloader/max_file_size_exceeded_exception.rs @@ -2,4 +2,19 @@ use crate::downloader::transport_exception::TransportException; +#[derive(Debug)] pub struct MaxFileSizeExceededException(pub TransportException); + +impl MaxFileSizeExceededException { + pub fn new(message: String) -> Self { + Self(TransportException::new(message, 0)) + } +} + +impl std::fmt::Display for MaxFileSizeExceededException { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl std::error::Error for MaxFileSizeExceededException {} diff --git a/crates/shirabe/src/downloader/path_downloader.rs b/crates/shirabe/src/downloader/path_downloader.rs index c50d390..a525df0 100644 --- a/crates/shirabe/src/downloader/path_downloader.rs +++ b/crates/shirabe/src/downloader/path_downloader.rs @@ -140,7 +140,7 @@ impl PathDownloader { let (mut current_strategy, allowed_strategies) = self.compute_allowed_strategies(&transport_options)?; - let symfony_filesystem = SymfonyFilesystem::new(); + let symfony_filesystem = SymfonyFilesystem::new(None); self.inner.filesystem.remove_directory(&path); if output { @@ -254,14 +254,12 @@ impl PathDownloader { io_interface::NORMAL, ); } - let iterator = ArchivableFilesFinder::new(&real_url, vec![]); + let iterator = ArchivableFilesFinder::new(&real_url, vec![], false)?; symfony_filesystem.mirror(&real_url, &path, Some(&iterator)); } if output { - self.inner - .io - .write_error(PhpMixed::String("".to_string()), true, io_interface::NORMAL); + self.inner.io.write_error3("", true, io_interface::NORMAL); } Ok(shirabe_external_packages::react::promise::resolve(None)) @@ -328,7 +326,7 @@ impl PathDownloader { // can happen when using custom installers, see https://github.com/composer/composer/pull/9116 // not using realpath here as we do not want to resolve the symlink to the original dist url // it points to - let fs = Filesystem::new(); + let fs = Filesystem::new(None); let abs_path = if fs.is_absolute_path(&path) { path.clone() } else { diff --git a/crates/shirabe/src/downloader/phar_downloader.rs b/crates/shirabe/src/downloader/phar_downloader.rs index 5316fc1..649841d 100644 --- a/crates/shirabe/src/downloader/phar_downloader.rs +++ b/crates/shirabe/src/downloader/phar_downloader.rs @@ -1,8 +1,16 @@ //! ref: composer/src/Composer/Downloader/PharDownloader.php +use crate::cache::Cache; +use crate::config::Config; use crate::downloader::archive_downloader::ArchiveDownloader; +use crate::downloader::downloader_interface::DownloaderInterface; use crate::downloader::file_downloader::FileDownloader; +use crate::event_dispatcher::event_dispatcher::EventDispatcher; +use crate::io::io_interface::IOInterface; use crate::package::package_interface::PackageInterface; +use crate::util::filesystem::Filesystem; +use crate::util::http_downloader::HttpDownloader; +use crate::util::process_executor::ProcessExecutor; use anyhow::Result; use indexmap::IndexMap; use shirabe_external_packages::react::promise::promise_interface::PromiseInterface; @@ -15,6 +23,29 @@ pub struct PharDownloader { } impl PharDownloader { + pub fn new( + io: Box<dyn IOInterface>, + config: Config, + http_downloader: HttpDownloader, + event_dispatcher: Option<EventDispatcher>, + cache: Option<Cache>, + filesystem: Filesystem, + process: ProcessExecutor, + ) -> Self { + Self { + inner: FileDownloader::new( + io, + config, + http_downloader, + event_dispatcher, + cache, + Some(filesystem), + Some(process), + ), + cleanup_executed: IndexMap::new(), + } + } + pub(crate) fn extract( &self, package: &dyn PackageInterface, @@ -32,3 +63,66 @@ impl PharDownloader { Ok(shirabe_external_packages::react::promise::resolve(None)) } } + +impl DownloaderInterface for PharDownloader { + fn get_installation_source(&self) -> String { + self.inner.get_installation_source() + } + + fn download( + &self, + package: &dyn PackageInterface, + path: &str, + prev_package: Option<&dyn PackageInterface>, + output: bool, + ) -> Result<Box<dyn PromiseInterface>> { + self.inner.download(package, path, prev_package, output) + } + + fn prepare( + &self, + r#type: &str, + package: &dyn PackageInterface, + path: &str, + prev_package: Option<&dyn PackageInterface>, + ) -> Result<Box<dyn PromiseInterface>> { + self.inner.prepare(r#type, package, path, prev_package) + } + + fn install( + &self, + package: &dyn PackageInterface, + path: &str, + output: bool, + ) -> Result<Box<dyn PromiseInterface>> { + self.inner.install(package, path, output) + } + + fn update( + &self, + initial: &dyn PackageInterface, + target: &dyn PackageInterface, + path: &str, + ) -> Result<Box<dyn PromiseInterface>> { + self.inner.update(initial, target, path) + } + + fn remove( + &self, + package: &dyn PackageInterface, + path: &str, + output: bool, + ) -> Result<Box<dyn PromiseInterface>> { + self.inner.remove(package, path, output) + } + + fn cleanup( + &self, + r#type: &str, + package: &dyn PackageInterface, + path: &str, + prev_package: Option<&dyn PackageInterface>, + ) -> Result<Box<dyn PromiseInterface>> { + self.inner.cleanup(r#type, package, path, prev_package) + } +} diff --git a/crates/shirabe/src/downloader/rar_downloader.rs b/crates/shirabe/src/downloader/rar_downloader.rs index 308b6fa..51feadb 100644 --- a/crates/shirabe/src/downloader/rar_downloader.rs +++ b/crates/shirabe/src/downloader/rar_downloader.rs @@ -1,25 +1,56 @@ //! ref: composer/src/Composer/Downloader/RarDownloader.php +use crate::cache::Cache; +use crate::config::Config; use crate::downloader::archive_downloader::ArchiveDownloader; use crate::downloader::file_downloader::FileDownloader; +use crate::event_dispatcher::event_dispatcher::EventDispatcher; +use crate::io::io_interface::IOInterface; use crate::package::package_interface::PackageInterface; +use crate::util::filesystem::Filesystem; +use crate::util::http_downloader::HttpDownloader; use crate::util::ini_helper::IniHelper; use crate::util::platform::Platform; +use crate::util::process_executor::ProcessExecutor; use anyhow::Result; use indexmap::IndexMap; use shirabe_external_packages::react::promise::promise_interface::PromiseInterface; use shirabe_php_shim::{ - RarArchive, RuntimeException, UnexpectedValueException, class_exists, implode, + PhpMixed, RarArchive, RuntimeException, UnexpectedValueException, class_exists, implode, }; +#[derive(Debug)] pub struct RarDownloader { inner: FileDownloader, cleanup_executed: IndexMap<String, bool>, } impl RarDownloader { + pub fn new( + io: Box<dyn IOInterface>, + config: Config, + http_downloader: HttpDownloader, + event_dispatcher: Option<EventDispatcher>, + cache: Option<Cache>, + filesystem: Filesystem, + process: ProcessExecutor, + ) -> Self { + Self { + inner: FileDownloader::new( + io, + config, + http_downloader, + event_dispatcher, + cache, + Some(filesystem), + Some(process), + ), + cleanup_executed: IndexMap::new(), + } + } + pub(crate) fn extract( - &self, + &mut self, _package: &dyn PackageInterface, file: &str, path: &str, @@ -35,7 +66,18 @@ impl RarDownloader { path.to_string(), ]; - if self.inner.process.execute(&command, &mut String::new()) == 0 { + let mut process_output = PhpMixed::Null; + if self.inner.process.execute( + PhpMixed::List( + command + .iter() + .map(|s| Box::new(PhpMixed::String(s.clone()))) + .collect(), + ), + Some(&mut process_output), + None, + )? == 0 + { return Ok(shirabe_external_packages::react::promise::resolve(None)); } @@ -101,3 +143,66 @@ impl RarDownloader { Ok(shirabe_external_packages::react::promise::resolve(None)) } } + +impl crate::downloader::downloader_interface::DownloaderInterface for RarDownloader { + fn get_installation_source(&self) -> String { + self.inner.get_installation_source() + } + + fn download( + &self, + package: &dyn PackageInterface, + path: &str, + prev_package: Option<&dyn PackageInterface>, + output: bool, + ) -> Result<Box<dyn PromiseInterface>> { + self.inner.download(package, path, prev_package, output) + } + + fn prepare( + &self, + r#type: &str, + package: &dyn PackageInterface, + path: &str, + prev_package: Option<&dyn PackageInterface>, + ) -> Result<Box<dyn PromiseInterface>> { + self.inner.prepare(r#type, package, path, prev_package) + } + + fn install( + &self, + package: &dyn PackageInterface, + path: &str, + output: bool, + ) -> Result<Box<dyn PromiseInterface>> { + self.inner.install(package, path, output) + } + + fn update( + &self, + initial: &dyn PackageInterface, + target: &dyn PackageInterface, + path: &str, + ) -> Result<Box<dyn PromiseInterface>> { + self.inner.update(initial, target, path) + } + + fn remove( + &self, + package: &dyn PackageInterface, + path: &str, + output: bool, + ) -> Result<Box<dyn PromiseInterface>> { + self.inner.remove(package, path, output) + } + + fn cleanup( + &self, + r#type: &str, + package: &dyn PackageInterface, + path: &str, + prev_package: Option<&dyn PackageInterface>, + ) -> Result<Box<dyn PromiseInterface>> { + self.inner.cleanup(r#type, package, path, prev_package) + } +} diff --git a/crates/shirabe/src/downloader/tar_downloader.rs b/crates/shirabe/src/downloader/tar_downloader.rs index d8531e0..aaa7153 100644 --- a/crates/shirabe/src/downloader/tar_downloader.rs +++ b/crates/shirabe/src/downloader/tar_downloader.rs @@ -1,8 +1,16 @@ //! ref: composer/src/Composer/Downloader/TarDownloader.php +use crate::cache::Cache; +use crate::config::Config; use crate::downloader::archive_downloader::ArchiveDownloader; +use crate::downloader::downloader_interface::DownloaderInterface; use crate::downloader::file_downloader::FileDownloader; +use crate::event_dispatcher::event_dispatcher::EventDispatcher; +use crate::io::io_interface::IOInterface; use crate::package::package_interface::PackageInterface; +use crate::util::filesystem::Filesystem; +use crate::util::http_downloader::HttpDownloader; +use crate::util::process_executor::ProcessExecutor; use anyhow::Result; use indexmap::IndexMap; use shirabe_external_packages::react::promise::promise_interface::PromiseInterface; @@ -15,6 +23,29 @@ pub struct TarDownloader { } impl TarDownloader { + pub fn new( + io: Box<dyn IOInterface>, + config: Config, + http_downloader: HttpDownloader, + event_dispatcher: Option<EventDispatcher>, + cache: Option<Cache>, + filesystem: Filesystem, + process: ProcessExecutor, + ) -> Self { + Self { + inner: FileDownloader::new( + io, + config, + http_downloader, + event_dispatcher, + cache, + Some(filesystem), + Some(process), + ), + cleanup_executed: IndexMap::new(), + } + } + pub(crate) fn extract( &self, package: &dyn PackageInterface, @@ -27,3 +58,66 @@ impl TarDownloader { Ok(shirabe_external_packages::react::promise::resolve(None)) } } + +impl DownloaderInterface for TarDownloader { + fn get_installation_source(&self) -> String { + self.inner.get_installation_source() + } + + fn download( + &self, + package: &dyn PackageInterface, + path: &str, + prev_package: Option<&dyn PackageInterface>, + output: bool, + ) -> Result<Box<dyn PromiseInterface>> { + self.inner.download(package, path, prev_package, output) + } + + fn prepare( + &self, + r#type: &str, + package: &dyn PackageInterface, + path: &str, + prev_package: Option<&dyn PackageInterface>, + ) -> Result<Box<dyn PromiseInterface>> { + self.inner.prepare(r#type, package, path, prev_package) + } + + fn install( + &self, + package: &dyn PackageInterface, + path: &str, + output: bool, + ) -> Result<Box<dyn PromiseInterface>> { + self.inner.install(package, path, output) + } + + fn update( + &self, + initial: &dyn PackageInterface, + target: &dyn PackageInterface, + path: &str, + ) -> Result<Box<dyn PromiseInterface>> { + self.inner.update(initial, target, path) + } + + fn remove( + &self, + package: &dyn PackageInterface, + path: &str, + output: bool, + ) -> Result<Box<dyn PromiseInterface>> { + self.inner.remove(package, path, output) + } + + fn cleanup( + &self, + r#type: &str, + package: &dyn PackageInterface, + path: &str, + prev_package: Option<&dyn PackageInterface>, + ) -> Result<Box<dyn PromiseInterface>> { + self.inner.cleanup(r#type, package, path, prev_package) + } +} diff --git a/crates/shirabe/src/downloader/transport_exception.rs b/crates/shirabe/src/downloader/transport_exception.rs index e4f04f3..382da01 100644 --- a/crates/shirabe/src/downloader/transport_exception.rs +++ b/crates/shirabe/src/downloader/transport_exception.rs @@ -24,6 +24,14 @@ impl TransportException { } } + pub fn get_code(&self) -> i64 { + self.code + } + + pub fn get_message(&self) -> &str { + &self.message + } + pub fn set_headers(&mut self, headers: Vec<String>) { self.headers = Some(headers); } diff --git a/crates/shirabe/src/downloader/vcs_downloader.rs b/crates/shirabe/src/downloader/vcs_downloader.rs index 5e19016..346603a 100644 --- a/crates/shirabe/src/downloader/vcs_downloader.rs +++ b/crates/shirabe/src/downloader/vcs_downloader.rs @@ -385,7 +385,7 @@ pub trait VcsDownloader: io_interface::NORMAL, ); self.io_mut() - .write_error(PhpMixed::String(logs), true, io_interface::NORMAL); + .write_error3(&logs, true, io_interface::NORMAL); } } diff --git a/crates/shirabe/src/downloader/xz_downloader.rs b/crates/shirabe/src/downloader/xz_downloader.rs index 1ad0bb1..61a7f14 100644 --- a/crates/shirabe/src/downloader/xz_downloader.rs +++ b/crates/shirabe/src/downloader/xz_downloader.rs @@ -1,11 +1,19 @@ //! ref: composer/src/Composer/Downloader/XzDownloader.php +use crate::cache::Cache; +use crate::config::Config; use crate::downloader::archive_downloader::ArchiveDownloader; use crate::downloader::file_downloader::FileDownloader; +use crate::event_dispatcher::event_dispatcher::EventDispatcher; +use crate::io::io_interface::IOInterface; use crate::package::package_interface::PackageInterface; +use crate::util::filesystem::Filesystem; +use crate::util::http_downloader::HttpDownloader; +use crate::util::process_executor::ProcessExecutor; use anyhow::{Result, bail}; use indexmap::IndexMap; use shirabe_external_packages::react::promise::promise_interface::PromiseInterface; +use shirabe_php_shim::PhpMixed; #[derive(Debug)] pub struct XzDownloader { @@ -14,16 +22,49 @@ pub struct XzDownloader { } impl XzDownloader { + pub fn new( + io: Box<dyn IOInterface>, + config: Config, + http_downloader: HttpDownloader, + event_dispatcher: Option<EventDispatcher>, + cache: Option<Cache>, + filesystem: Filesystem, + process: ProcessExecutor, + ) -> Self { + Self { + inner: FileDownloader::new( + io, + config, + http_downloader, + event_dispatcher, + cache, + Some(filesystem), + Some(process), + ), + cleanup_executed: IndexMap::new(), + } + } + pub(crate) fn extract( - &self, + &mut self, package: &dyn PackageInterface, file: &str, path: &str, ) -> Result<Box<dyn PromiseInterface>> { let command = vec!["tar", "-xJf", file, "-C", path]; - let mut ignored_output = String::new(); - if self.inner.process.execute(&command, &mut ignored_output) == 0 { + let mut ignored_output = PhpMixed::Null; + if self.inner.process.execute( + PhpMixed::List( + command + .iter() + .map(|s| Box::new(PhpMixed::String(s.to_string()))) + .collect(), + ), + Some(&mut ignored_output), + None, + )? == 0 + { return Ok(shirabe_external_packages::react::promise::resolve(None)); } @@ -36,3 +77,66 @@ impl XzDownloader { bail!(process_error); } } + +impl crate::downloader::downloader_interface::DownloaderInterface for XzDownloader { + fn get_installation_source(&self) -> String { + self.inner.get_installation_source() + } + + fn download( + &self, + package: &dyn PackageInterface, + path: &str, + prev_package: Option<&dyn PackageInterface>, + output: bool, + ) -> Result<Box<dyn PromiseInterface>> { + self.inner.download(package, path, prev_package, output) + } + + fn prepare( + &self, + r#type: &str, + package: &dyn PackageInterface, + path: &str, + prev_package: Option<&dyn PackageInterface>, + ) -> Result<Box<dyn PromiseInterface>> { + self.inner.prepare(r#type, package, path, prev_package) + } + + fn install( + &self, + package: &dyn PackageInterface, + path: &str, + output: bool, + ) -> Result<Box<dyn PromiseInterface>> { + self.inner.install(package, path, output) + } + + fn update( + &self, + initial: &dyn PackageInterface, + target: &dyn PackageInterface, + path: &str, + ) -> Result<Box<dyn PromiseInterface>> { + self.inner.update(initial, target, path) + } + + fn remove( + &self, + package: &dyn PackageInterface, + path: &str, + output: bool, + ) -> Result<Box<dyn PromiseInterface>> { + self.inner.remove(package, path, output) + } + + fn cleanup( + &self, + r#type: &str, + package: &dyn PackageInterface, + path: &str, + prev_package: Option<&dyn PackageInterface>, + ) -> Result<Box<dyn PromiseInterface>> { + self.inner.cleanup(r#type, package, path, prev_package) + } +} diff --git a/crates/shirabe/src/downloader/zip_downloader.rs b/crates/shirabe/src/downloader/zip_downloader.rs index 90e5639..7f779c8 100644 --- a/crates/shirabe/src/downloader/zip_downloader.rs +++ b/crates/shirabe/src/downloader/zip_downloader.rs @@ -152,13 +152,13 @@ impl ZipDownloader { if !is_windows_guard.unwrap() && unzip_commands_empty { if proc_open_missing { - self.inner.inner.io.write_error("<warning>proc_open is disabled so 'unzip' and '7z' commands cannot be used, zip files are being unpacked using the PHP zip extension.</warning>"); - self.inner.inner.io.write_error("<warning>This may cause invalid reports of corrupted archives. Besides, any UNIX permissions (e.g. executable) defined in the archives will be lost.</warning>"); - self.inner.inner.io.write_error("<warning>Enabling proc_open and installing 'unzip' or '7z' (21.01+) may remediate them.</warning>"); + self.inner.io.write_error("<warning>proc_open is disabled so 'unzip' and '7z' commands cannot be used, zip files are being unpacked using the PHP zip extension.</warning>"); + self.inner.io.write_error("<warning>This may cause invalid reports of corrupted archives. Besides, any UNIX permissions (e.g. executable) defined in the archives will be lost.</warning>"); + self.inner.io.write_error("<warning>Enabling proc_open and installing 'unzip' or '7z' (21.01+) may remediate them.</warning>"); } else { - self.inner.inner.io.write_error("<warning>As there is no 'unzip' nor '7z' command installed zip files are being unpacked using the PHP zip extension.</warning>"); - self.inner.inner.io.write_error("<warning>This may cause invalid reports of corrupted archives. Besides, any UNIX permissions (e.g. executable) defined in the archives will be lost.</warning>"); - self.inner.inner.io.write_error("<warning>Installing 'unzip' or '7z' (21.01+) may remediate them.</warning>"); + self.inner.io.write_error("<warning>As there is no 'unzip' nor '7z' command installed zip files are being unpacked using the PHP zip extension.</warning>"); + self.inner.io.write_error("<warning>This may cause invalid reports of corrupted archives. Besides, any UNIX permissions (e.g. executable) defined in the archives will be lost.</warning>"); + self.inner.io.write_error("<warning>Installing 'unzip' or '7z' (21.01+) may remediate them.</warning>"); } } } @@ -226,7 +226,7 @@ impl ZipDownloader { Preg::is_match_strict_groups(r"^\s*7-Zip(?:\s\[64\])?\s([0-9.]+)", &output) { if version_compare(&m[1], "21.01", "<") { - self.inner.inner.io.write_error(&format!( + self.inner.io.write_error(&format!( " <warning>Unzipping using {} {} may result in incorrect file permissions. Install {} 21.01+ or unzip to ensure you get correct permissions.</warning>", executable, m[1], executable, )); @@ -235,7 +235,7 @@ impl ZipDownloader { } } - let io = &self.inner.inner.io; + let io = &self.inner.io; let try_fallback = |process_error: anyhow::Error| -> Result<Box<dyn PromiseInterface>> { if is_last_chance { return Err(process_error); @@ -297,7 +297,7 @@ impl ZipDownloader { self.extract_with_zip_archive(package, file, path) }; - match self.inner.inner.process.execute_async(&command) { + match self.inner.process.execute_async(&command) { Ok(promise) => Ok(promise.then( Box::new(move |process: Process| -> Result<()> { if !process.is_successful() { diff --git a/crates/shirabe/src/event_dispatcher/event_dispatcher.rs b/crates/shirabe/src/event_dispatcher/event_dispatcher.rs index 705287c..a1bc0b4 100644 --- a/crates/shirabe/src/event_dispatcher/event_dispatcher.rs +++ b/crates/shirabe/src/event_dispatcher/event_dispatcher.rs @@ -4,7 +4,6 @@ use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::preg::Preg; use shirabe_external_packages::symfony::component::console::application::Application; -use shirabe_external_packages::symfony::component::console::command::command::CommandBase; use shirabe_external_packages::symfony::component::console::input::string_input::StringInput; use shirabe_external_packages::symfony::component::console::output::console_output::ConsoleOutput; use shirabe_external_packages::symfony::component::process::executable_finder::ExecutableFinder; @@ -213,7 +212,7 @@ impl EventDispatcher { .unwrap_or_default() )), true, - <dyn IOInterface>::NORMAL, + crate::io::io_interface::NORMAL, ); } @@ -328,7 +327,7 @@ impl EventDispatcher { ], )), true, - <dyn IOInterface>::VERBOSE, + crate::io::io_interface::VERBOSE, ); } // TODO(plugin): actually invoke callable with $event and inspect result @@ -345,7 +344,7 @@ impl EventDispatcher { ], )), true, - <dyn IOInterface>::VERBOSE, + crate::io::io_interface::VERBOSE, ); let mut script: Vec<String> = substr(callable_str, 1, None) @@ -404,7 +403,7 @@ impl EventDispatcher { ], )), true, - <dyn IOInterface>::QUIET, + crate::io::io_interface::QUIET, ); return Err(anyhow::anyhow!(ScriptExecutionException( @@ -432,7 +431,7 @@ impl EventDispatcher { &[PhpMixed::String(callable_str.clone())], )), true, - <dyn IOInterface>::QUIET, + crate::io::io_interface::QUIET, ); } @@ -468,7 +467,7 @@ impl EventDispatcher { ], )), true, - <dyn IOInterface>::QUIET, + crate::io::io_interface::QUIET, ); } return Err(e); @@ -490,7 +489,7 @@ impl EventDispatcher { event.get_name() )), true, - <dyn IOInterface>::QUIET, + crate::io::io_interface::QUIET, ); continue; } @@ -502,7 +501,7 @@ impl EventDispatcher { event.get_name() )), true, - <dyn IOInterface>::QUIET, + crate::io::io_interface::QUIET, ); continue; } @@ -526,7 +525,7 @@ impl EventDispatcher { ) )), true, - <dyn IOInterface>::QUIET, + crate::io::io_interface::QUIET, ); return Err(e); } @@ -550,7 +549,7 @@ impl EventDispatcher { event.get_name() )), true, - <dyn IOInterface>::QUIET, + crate::io::io_interface::QUIET, ); continue; } @@ -566,7 +565,7 @@ impl EventDispatcher { event.get_name() )), true, - <dyn IOInterface>::QUIET, + crate::io::io_interface::QUIET, ); continue; } @@ -580,7 +579,7 @@ impl EventDispatcher { event.get_name() )), true, - <dyn IOInterface>::QUIET, + crate::io::io_interface::QUIET, ); continue; } @@ -595,15 +594,9 @@ impl EventDispatcher { } app.set_auto_exit(false); // TODO(plugin): instantiate command class dynamically: `new $className($event->getName())` - let cmd = CommandBase::new(None); // TODO(plugin): pass event name - if method_exists(&PhpMixed::String("Application".to_string()), "addCommand") - { - app.add_command(cmd.clone()); - } else { - // Compatibility layer for symfony/console <7.4 - app.add(cmd.clone()); - } - app.set_default_command(cmd.get_name().to_string(), true); + todo!( + "plugin: CommandBase::new — dynamic plugin command instantiation not supported" + ); let result = (|| -> anyhow::Result<i64> { let args = additional_args .iter() @@ -647,7 +640,7 @@ impl EventDispatcher { ) )), true, - <dyn IOInterface>::QUIET, + crate::io::io_interface::QUIET, ); return Err(e); } @@ -687,7 +680,7 @@ impl EventDispatcher { ], )), true, - <dyn IOInterface>::NORMAL, + crate::io::io_interface::NORMAL, ); } else if self.event_needs_to_output(event) { self.io.write_error( @@ -696,7 +689,7 @@ impl EventDispatcher { &[PhpMixed::String(exec.clone())], )), true, - <dyn IOInterface>::NORMAL, + crate::io::io_interface::NORMAL, ); } @@ -828,7 +821,7 @@ impl EventDispatcher { ], )), true, - <dyn IOInterface>::QUIET, + crate::io::io_interface::QUIET, ); return Err(anyhow::anyhow!(ScriptExecutionException( @@ -949,7 +942,7 @@ impl EventDispatcher { ], )), true, - <dyn IOInterface>::NORMAL, + crate::io::io_interface::NORMAL, ); } else if self.event_needs_to_output(event) { self.io.write_error( @@ -961,7 +954,7 @@ impl EventDispatcher { ], )), true, - <dyn IOInterface>::NORMAL, + crate::io::io_interface::NORMAL, ); } @@ -1094,7 +1087,7 @@ impl EventDispatcher { event.get_name() )), true, - <dyn IOInterface>::VERBOSE, + crate::io::io_interface::VERBOSE, ); return Vec::new(); diff --git a/crates/shirabe/src/event_dispatcher/script_execution_exception.rs b/crates/shirabe/src/event_dispatcher/script_execution_exception.rs index 23b763f..05567b0 100644 --- a/crates/shirabe/src/event_dispatcher/script_execution_exception.rs +++ b/crates/shirabe/src/event_dispatcher/script_execution_exception.rs @@ -6,6 +6,16 @@ use shirabe_php_shim::RuntimeException; #[derive(Debug)] pub struct ScriptExecutionException(pub RuntimeException); +impl ScriptExecutionException { + pub fn get_code(&self) -> i64 { + self.0.code + } + + pub fn get_message(&self) -> &str { + &self.0.message + } +} + impl std::fmt::Display for ScriptExecutionException { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) diff --git a/crates/shirabe/src/exception/irrecoverable_download_exception.rs b/crates/shirabe/src/exception/irrecoverable_download_exception.rs index 35fd915..a8d2dbb 100644 --- a/crates/shirabe/src/exception/irrecoverable_download_exception.rs +++ b/crates/shirabe/src/exception/irrecoverable_download_exception.rs @@ -2,4 +2,13 @@ use shirabe_php_shim::RuntimeException; +#[derive(Debug)] pub struct IrrecoverableDownloadException(pub RuntimeException); + +impl std::fmt::Display for IrrecoverableDownloadException { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl std::error::Error for IrrecoverableDownloadException {} diff --git a/crates/shirabe/src/factory.rs b/crates/shirabe/src/factory.rs index 42d31d1..3d6ab6d 100644 --- a/crates/shirabe/src/factory.rs +++ b/crates/shirabe/src/factory.rs @@ -259,13 +259,13 @@ impl Factory { config.merge(defaults, Config::SOURCE_DEFAULT); // load global config - let file = JsonFile::new(format!("{}/config.json", config.get_str("home")?), None, io); + let file = JsonFile::new(format!("{}/config.json", config.get_str("home")?), None, io)?; if file.exists() { if let Some(io_ref) = io { io_ref.write_error( PhpMixed::String(format!("Loading config file {}", file.get_path())), true, - <dyn IOInterface>::DEBUG, + crate::io::io_interface::DEBUG, ); } Self::validate_json_schema( @@ -308,13 +308,13 @@ impl Factory { } // load global auth file - let auth_file = JsonFile::new(format!("{}/auth.json", config.get_str("home")?), None, io); + let auth_file = JsonFile::new(format!("{}/auth.json", config.get_str("home")?), None, io)?; if auth_file.exists() { if let Some(io_ref) = io { io_ref.write_error( PhpMixed::String(format!("Loading config file {}", auth_file.get_path())), true, - <dyn IOInterface>::DEBUG, + crate::io::io_interface::DEBUG, ); } Self::validate_json_schema( @@ -440,7 +440,7 @@ impl Factory { if let Some(LocalConfigInput::Path(path)) = &local_config { composer_file = Some(path.clone()); - let file = JsonFile::new(path.clone(), None, Some(io)); + let file = JsonFile::new(path.clone(), None, Some(io))?; if !file.exists() { let message = if path == "./composer.json" || path == "composer.json" { @@ -496,14 +496,14 @@ impl Factory { realpath(composer_file_path).unwrap_or_default() )), true, - <dyn IOInterface>::DEBUG, + crate::io::io_interface::DEBUG, ); config.set_config_source(JsonConfigSource::new( JsonFile::new( realpath(composer_file_path).unwrap_or_default(), None, Some(io), - ), + )?, false, )); @@ -514,7 +514,7 @@ impl Factory { ), None, Some(io), - ); + )?; if local_auth_file.exists() { io.write_error( PhpMixed::String(format!( @@ -522,7 +522,7 @@ impl Factory { local_auth_file.get_path() )), true, - <dyn IOInterface>::DEBUG, + crate::io::io_interface::DEBUG, ); Self::validate_json_schema( Some(io), @@ -585,7 +585,10 @@ impl Factory { let http_downloader = Self::create_http_downloader(io, &config, IndexMap::new())?; let process = ProcessExecutor::new(io); - let r#loop = Loop::new(http_downloader.clone(), process.clone()); + let r#loop = std::rc::Rc::new(std::cell::RefCell::new(Loop::new( + http_downloader.clone(), + Some(process.clone()), + ))); composer.set_loop(r#loop.clone()); // initialize event dispatcher @@ -671,7 +674,7 @@ impl Factory { lock_file )), true, - <dyn IOInterface>::NORMAL, + crate::io::io_interface::NORMAL, ); } @@ -685,7 +688,7 @@ impl Factory { }, None, Some(io), - ), + )?, im.clone(), file_get_contents(composer_file_path).unwrap_or_default(), process.clone(), @@ -694,14 +697,17 @@ impl Factory { } else { let locker = Locker::new( io.clone_box(), - JsonFile::new(Platform::get_dev_null(), None, Some(io)), + JsonFile::new(Platform::get_dev_null(), None, Some(io))?, im.clone(), - JsonFile::encode(&PhpMixed::Array( - local_config_data - .iter() - .map(|(k, v)| (k.clone(), Box::new(v.clone()))) - .collect(), - )), + JsonFile::encode( + &PhpMixed::Array( + local_config_data + .iter() + .map(|(k, v)| (k.clone(), Box::new(v.clone()))) + .collect(), + ), + 448, + ), process.clone(), ); composer_full.set_locker(locker); @@ -774,16 +780,20 @@ impl Factory { ) { let fs = process.map(|p| Filesystem::new(Some(p.clone()))); - rm.set_local_repository(Box::new(InstalledFilesystemRepository::new( - JsonFile::new( - format!("{}/composer/installed.json", vendor_dir), - None, - Some(io), - ), - true, - root_package.clone_box(), - fs, - ))); + rm.set_local_repository(Box::new( + InstalledFilesystemRepository::new( + JsonFile::new( + format!("{}/composer/installed.json", vendor_dir), + None, + Some(io.clone_box()), + ) + .expect("installed.json path is always valid"), + true, + Some(RootPackageInterface::clone_box(root_package)), + fs, + ) + .expect("InstalledFilesystemRepository::new should not fail"), + )); } fn create_global_composer( @@ -820,7 +830,7 @@ impl Factory { io.write_error( PhpMixed::String(format!("Failed to initialize global composer: {}", e)), true, - <dyn IOInterface>::DEBUG, + crate::io::io_interface::DEBUG, ); None } @@ -836,7 +846,7 @@ impl Factory { http_downloader: &HttpDownloader, process: &ProcessExecutor, event_dispatcher: Option<&EventDispatcher>, - ) -> anyhow::Result<DownloadManager> { + ) -> anyhow::Result<std::rc::Rc<std::cell::RefCell<DownloadManager>>> { let mut cache: Option<Cache> = None; if config .get("cache-files-ttl") @@ -1029,14 +1039,14 @@ impl Factory { )), ); - Ok(dm) + Ok(std::rc::Rc::new(std::cell::RefCell::new(dm))) } pub fn create_archive_manager( &self, _config: &Config, - dm: &DownloadManager, - r#loop: &Loop, + dm: &std::rc::Rc<std::cell::RefCell<DownloadManager>>, + r#loop: &std::rc::Rc<std::cell::RefCell<Loop>>, ) -> anyhow::Result<ArchiveManager> { let mut am = ArchiveManager::new(dm.clone(), r#loop.clone()); if class_exists("ZipArchive") { @@ -1066,7 +1076,7 @@ impl Factory { pub fn create_installation_manager( &self, - r#loop: Loop, + r#loop: std::rc::Rc<std::cell::RefCell<Loop>>, io: Box<dyn IOInterface>, event_dispatcher: Option<EventDispatcher>, ) -> InstallationManager { @@ -1203,7 +1213,7 @@ impl Factory { .to_string(), ), true, - <dyn IOInterface>::NORMAL, + crate::io::io_interface::NORMAL, ); } unsafe { WARNED = true }; @@ -1263,7 +1273,7 @@ impl Factory { .to_string(), ), true, - <dyn IOInterface>::NORMAL, + crate::io::io_interface::NORMAL, ); io.write( PhpMixed::String( @@ -1271,7 +1281,7 @@ impl Factory { .to_string(), ), true, - <dyn IOInterface>::NORMAL, + crate::io::io_interface::NORMAL, ); io.write( PhpMixed::String( @@ -1279,7 +1289,7 @@ impl Factory { .to_string(), ), true, - <dyn IOInterface>::NORMAL, + crate::io::io_interface::NORMAL, ); } } @@ -1314,7 +1324,7 @@ impl Factory { io_ref.write_error( PhpMixed::String("Loading auth config from COMPOSER_AUTH".to_string()), true, - <dyn IOInterface>::DEBUG, + crate::io::io_interface::DEBUG, ); } Self::validate_json_schema( @@ -1393,7 +1403,7 @@ impl Factory { io_ref.write_error( PhpMixed::String(format!("<warning>{}</>", msg)), true, - <dyn IOInterface>::NORMAL, + crate::io::io_interface::NORMAL, ); } else { return Err(anyhow::anyhow!(UnexpectedValueException { @@ -1433,7 +1443,7 @@ impl PartialComposerOrComposer { Self::Partial(p) => p.set_global(), } } - fn set_loop(&mut self, r#loop: Loop) { + fn set_loop(&mut self, r#loop: std::rc::Rc<std::cell::RefCell<Loop>>) { match self { Self::Full(c) => c.set_loop(r#loop), Self::Partial(p) => p.set_loop(r#loop), diff --git a/crates/shirabe/src/filter/platform_requirement_filter/ignore_all_platform_requirement_filter.rs b/crates/shirabe/src/filter/platform_requirement_filter/ignore_all_platform_requirement_filter.rs index e38bfea..218a52d 100644 --- a/crates/shirabe/src/filter/platform_requirement_filter/ignore_all_platform_requirement_filter.rs +++ b/crates/shirabe/src/filter/platform_requirement_filter/ignore_all_platform_requirement_filter.rs @@ -14,4 +14,8 @@ impl PlatformRequirementFilterInterface for IgnoreAllPlatformRequirementFilter { fn is_upper_bound_ignored(&self, req: &str) -> bool { self.is_ignored(req) } + + fn as_any(&self) -> &dyn std::any::Any { + self + } } diff --git a/crates/shirabe/src/filter/platform_requirement_filter/ignore_list_platform_requirement_filter.rs b/crates/shirabe/src/filter/platform_requirement_filter/ignore_list_platform_requirement_filter.rs index 8026768..39a0c03 100644 --- a/crates/shirabe/src/filter/platform_requirement_filter/ignore_list_platform_requirement_filter.rs +++ b/crates/shirabe/src/filter/platform_requirement_filter/ignore_list_platform_requirement_filter.rs @@ -29,8 +29,9 @@ impl IgnoreListPlatformRequirementFilter { ignore_all.push(req); } } - let ignore_regex = base_package::package_names_to_regexp(&ignore_all); - let ignore_upper_bound_regex = base_package::package_names_to_regexp(&ignore_upper_bound); + let ignore_regex = base_package::package_names_to_regexp(&ignore_all, "{^(?:%s)$}iD"); + let ignore_upper_bound_regex = + base_package::package_names_to_regexp(&ignore_upper_bound, "{^(?:%s)$}iD"); Ok(Self { ignore_regex, ignore_upper_bound_regex, @@ -88,4 +89,8 @@ impl PlatformRequirementFilterInterface for IgnoreListPlatformRequirementFilter } self.is_ignored(req) || Preg::is_match(&self.ignore_upper_bound_regex, req).unwrap_or(false) } + + fn as_any(&self) -> &dyn std::any::Any { + self + } } diff --git a/crates/shirabe/src/filter/platform_requirement_filter/ignore_nothing_platform_requirement_filter.rs b/crates/shirabe/src/filter/platform_requirement_filter/ignore_nothing_platform_requirement_filter.rs index c2d0fec..21da158 100644 --- a/crates/shirabe/src/filter/platform_requirement_filter/ignore_nothing_platform_requirement_filter.rs +++ b/crates/shirabe/src/filter/platform_requirement_filter/ignore_nothing_platform_requirement_filter.rs @@ -13,4 +13,8 @@ impl PlatformRequirementFilterInterface for IgnoreNothingPlatformRequirementFilt fn is_upper_bound_ignored(&self, _req: &str) -> bool { false } + + fn as_any(&self) -> &dyn std::any::Any { + self + } } diff --git a/crates/shirabe/src/filter/platform_requirement_filter/platform_requirement_filter_factory.rs b/crates/shirabe/src/filter/platform_requirement_filter/platform_requirement_filter_factory.rs index 59340cb..35a3ed9 100644 --- a/crates/shirabe/src/filter/platform_requirement_filter/platform_requirement_filter_factory.rs +++ b/crates/shirabe/src/filter/platform_requirement_filter/platform_requirement_filter_factory.rs @@ -23,9 +23,20 @@ impl PlatformRequirementFilterFactory { Ok(Self::ignore_nothing()) } } - list_or_array @ (PhpMixed::List(_) | PhpMixed::Array(_)) => Ok(Box::new( - IgnoreListPlatformRequirementFilter::new(list_or_array), - )), + list_or_array @ (PhpMixed::List(_) | PhpMixed::Array(_)) => { + let list: Vec<String> = match list_or_array { + PhpMixed::List(items) => items + .into_iter() + .filter_map(|v| v.as_string().map(|s| s.to_string())) + .collect(), + PhpMixed::Array(map) => map + .into_iter() + .filter_map(|(_, v)| v.as_string().map(|s| s.to_string())) + .collect(), + _ => unreachable!(), + }; + Ok(Box::new(IgnoreListPlatformRequirementFilter::new(list)?)) + } other => Err(anyhow::anyhow!(InvalidArgumentException { message: format!( "PlatformRequirementFilter: Unknown $boolOrList parameter {}. Please report at https://github.com/composer/composer/issues/new.", diff --git a/crates/shirabe/src/filter/platform_requirement_filter/platform_requirement_filter_interface.rs b/crates/shirabe/src/filter/platform_requirement_filter/platform_requirement_filter_interface.rs index a807da3..8061e1a 100644 --- a/crates/shirabe/src/filter/platform_requirement_filter/platform_requirement_filter_interface.rs +++ b/crates/shirabe/src/filter/platform_requirement_filter/platform_requirement_filter_interface.rs @@ -1,7 +1,13 @@ //! ref: composer/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterInterface.php -pub trait PlatformRequirementFilterInterface { +pub trait PlatformRequirementFilterInterface: std::fmt::Debug { fn is_ignored(&self, req: &str) -> bool; fn is_upper_bound_ignored(&self, req: &str) -> bool; + + fn as_any(&self) -> &dyn std::any::Any; + + fn clone_box(&self) -> Box<dyn PlatformRequirementFilterInterface> { + todo!() + } } diff --git a/crates/shirabe/src/installer.rs b/crates/shirabe/src/installer.rs index b32e47b..cb3da38 100644 --- a/crates/shirabe/src/installer.rs +++ b/crates/shirabe/src/installer.rs @@ -71,6 +71,7 @@ use crate::package::root_alias_package::RootAliasPackage; use crate::package::root_package_interface::RootPackageInterface; use crate::package::version::version_parser::VersionParser; use crate::repository::array_repository::ArrayRepository; +use crate::repository::canonical_packages_trait::CanonicalPackagesTrait; use crate::repository::composite_repository::CompositeRepository; use crate::repository::installed_array_repository::InstalledArrayRepository; use crate::repository::installed_repository::InstalledRepository; @@ -93,7 +94,7 @@ pub struct Installer { pub(crate) package: Box<dyn RootPackageInterface>, // TODO can we get rid of the below and just use the package itself? pub(crate) fixed_root_package: Box<dyn RootPackageInterface>, - pub(crate) download_manager: DownloadManager, + pub(crate) download_manager: std::rc::Rc<std::cell::RefCell<DownloadManager>>, pub(crate) repository_manager: RepositoryManager, pub(crate) locker: Locker, pub(crate) installation_manager: InstallationManager, @@ -148,7 +149,7 @@ impl Installer { io: Box<dyn IOInterface>, config: Config, package: Box<dyn RootPackageInterface>, - download_manager: DownloadManager, + download_manager: std::rc::Rc<std::cell::RefCell<DownloadManager>>, repository_manager: RepositoryManager, locker: Locker, installation_manager: InstallationManager, @@ -260,8 +261,12 @@ impl Installer { .dispatch_script(event_name, self.dev_mode); } - self.download_manager.set_prefer_source(self.prefer_source); - self.download_manager.set_prefer_dist(self.prefer_dist); + self.download_manager + .borrow_mut() + .set_prefer_source(self.prefer_source); + self.download_manager + .borrow_mut() + .set_prefer_dist(self.prefer_dist); let local_repo = self.repository_manager.get_local_repository(); @@ -848,14 +853,14 @@ impl Installer { return Ok(0); } - let mut result_repo = ArrayRepository::new(vec![]); + let mut result_repo = ArrayRepository::new(vec![])?; let loader = ArrayLoader::new(None, true); let dumper = ArrayDumper::new(); for pkg in lock_transaction.get_new_lock_packages(false, false) { result_repo.add_package(loader.load( dumper.dump(&*pkg), "Composer\\Package\\CompletePackage".to_string(), - )?); + )?)?; } let mut repository_set = self.create_repository_set(true, platform_repo, aliases, None); @@ -1132,7 +1137,7 @@ impl Installer { } if self.execute_operations { - local_repo.set_dev_package_names(self.locker.get_dev_package_names()); + local_repo.set_dev_package_names(self.locker.get_dev_package_names()?); self.installation_manager.execute( &*local_repo, local_repo_transaction.get_operations(), @@ -1463,12 +1468,12 @@ impl Installer { } let keys: Vec<String> = packages.keys().cloned().collect(); for key in keys { - let package_clone = packages.get(&key).unwrap().clone_box(); + let package_clone = packages.get(&key).unwrap().clone_package_box(); if let Some(alias_pkg) = package_clone.as_alias_package() { let alias_key = alias_pkg.get_alias_of().to_string(); let _class_name = get_class(&*package_clone); // PHP: $packages[$key] = new $className($packages[$alias], $package->getVersion(), $package->getPrettyVersion()); - let aliased = packages.get(&alias_key).unwrap().clone_box(); + let aliased = packages.get(&alias_key).unwrap().clone_package_box(); let new_alias_package: Box<dyn PackageInterface> = Box::new(AliasPackage::new( aliased, alias_pkg.get_version().to_string(), diff --git a/crates/shirabe/src/installer/binary_installer.rs b/crates/shirabe/src/installer/binary_installer.rs index 243dde0..e6677ed 100644 --- a/crates/shirabe/src/installer/binary_installer.rs +++ b/crates/shirabe/src/installer/binary_installer.rs @@ -33,7 +33,7 @@ impl BinaryInstaller { filesystem: Option<Filesystem>, vendor_dir: Option<String>, ) -> Self { - let filesystem = filesystem.unwrap_or_else(Filesystem::new); + let filesystem = filesystem.unwrap_or_else(|| Filesystem::new(None)); Self { bin_dir, bin_compat, diff --git a/crates/shirabe/src/installer/installation_manager.rs b/crates/shirabe/src/installer/installation_manager.rs index 798ea00..59e029a 100644 --- a/crates/shirabe/src/installer/installation_manager.rs +++ b/crates/shirabe/src/installer/installation_manager.rs @@ -40,7 +40,7 @@ pub struct InstallationManager { cache: IndexMap<String, Box<dyn InstallerInterface>>, /// @var array<string, array<PackageInterface>> notifiable_packages: IndexMap<String, Vec<Box<dyn PackageInterface>>>, - loop_: Loop, + loop_: std::rc::Rc<std::cell::RefCell<Loop>>, io: Box<dyn IOInterface>, event_dispatcher: Option<EventDispatcher>, output_progress: bool, @@ -48,7 +48,7 @@ pub struct InstallationManager { impl InstallationManager { pub fn new( - loop_: Loop, + loop_: std::rc::Rc<std::cell::RefCell<Loop>>, io: Box<dyn IOInterface>, event_dispatcher: Option<EventDispatcher>, ) -> Self { @@ -527,13 +527,12 @@ impl InstallationManager { // TODO(phase-b): progress = self.io.get_progress_bar(); progress = Some(()); } - self.loop_.wait(promises, progress); + let _ = self.loop_.borrow_mut().wait(promises, progress); if progress.is_some() { // progress.clear(); // ProgressBar in non-decorated output does not output a final line-break and clear() does nothing if !self.io.is_decorated() { - self.io - .write_error(PhpMixed::String(String::new()), true, io_interface::NORMAL); + self.io.write_error3("", true, io_interface::NORMAL); } } } @@ -628,7 +627,7 @@ impl InstallationManager { let package = operation.get_package(); if !repo.has_package(package) { - repo.add_package(package.clone_box()); + repo.add_package(package.clone_package_box()); } } @@ -697,7 +696,7 @@ impl InstallationManager { ), ); - promises.push(self.loop_.get_http_downloader().add( + promises.push(self.loop_.borrow().get_http_downloader().add( &url, &PhpMixed::Array( opts.into_iter().map(|(k, v)| (k, Box::new(v))).collect(), @@ -768,13 +767,13 @@ impl InstallationManager { PhpMixed::Array(http.into_iter().map(|(k, v)| (k, Box::new(v))).collect()), ); - promises.push(self.loop_.get_http_downloader().add( + promises.push(self.loop_.borrow().get_http_downloader().add( repo_url, &PhpMixed::Array(opts.into_iter().map(|(k, v)| (k, Box::new(v))).collect()), )); } - self.loop_.wait(promises, None); + let _ = self.loop_.borrow_mut().wait(promises, None); Ok(()) })(); @@ -789,7 +788,7 @@ impl InstallationManager { self.notifiable_packages .entry(notification_url.to_string()) .or_insert_with(Vec::new) - .push(package.clone_box()); + .push(package.clone_package_box()); } } @@ -800,7 +799,7 @@ impl InstallationManager { ) { let mut promises: Vec<Box<dyn PromiseInterface>> = vec![]; - self.loop_.abort_jobs(); + self.loop_.borrow().abort_jobs(); for (_, cleanup) in cleanup_promises { // TODO(phase-b): React\Promise\Promise constructor with executor; emulate by wrapping cleanup() @@ -813,7 +812,7 @@ impl InstallationManager { } if (promises.len() as i64) > 0 { - self.loop_.wait(promises, None); + let _ = self.loop_.borrow_mut().wait(promises, None); } } } diff --git a/crates/shirabe/src/installer/installer_event.rs b/crates/shirabe/src/installer/installer_event.rs index 456a4bd..20a8b27 100644 --- a/crates/shirabe/src/installer/installer_event.rs +++ b/crates/shirabe/src/installer/installer_event.rs @@ -24,7 +24,7 @@ impl InstallerEvent { execute_operations: bool, transaction: Transaction, ) -> Self { - let inner = Event::new(event_name, vec![], vec![]); + let inner = Event::new(event_name, vec![], indexmap::IndexMap::new()); Self { inner, composer, diff --git a/crates/shirabe/src/installer/installer_interface.rs b/crates/shirabe/src/installer/installer_interface.rs index 16cf10c..cf700be 100644 --- a/crates/shirabe/src/installer/installer_interface.rs +++ b/crates/shirabe/src/installer/installer_interface.rs @@ -27,20 +27,20 @@ pub trait InstallerInterface { ) -> anyhow::Result<Option<Box<dyn PromiseInterface>>>; fn install( - &self, + &mut self, repo: &mut dyn InstalledRepositoryInterface, package: &dyn PackageInterface, ) -> anyhow::Result<Option<Box<dyn PromiseInterface>>>; fn update( - &self, + &mut self, repo: &mut dyn InstalledRepositoryInterface, initial: &dyn PackageInterface, target: &dyn PackageInterface, ) -> anyhow::Result<Option<Box<dyn PromiseInterface>>>; fn uninstall( - &self, + &mut self, repo: &mut dyn InstalledRepositoryInterface, package: &dyn PackageInterface, ) -> anyhow::Result<Option<Box<dyn PromiseInterface>>>; @@ -53,4 +53,8 @@ pub trait InstallerInterface { ) -> anyhow::Result<Option<Box<dyn PromiseInterface>>>; fn get_install_path(&self, package: &dyn PackageInterface) -> Option<String>; + + fn clone_box(&self) -> Box<dyn InstallerInterface> { + todo!() + } } diff --git a/crates/shirabe/src/installer/library_installer.rs b/crates/shirabe/src/installer/library_installer.rs index af11ef6..17404e2 100644 --- a/crates/shirabe/src/installer/library_installer.rs +++ b/crates/shirabe/src/installer/library_installer.rs @@ -27,7 +27,7 @@ use crate::util::silencer::Silencer; pub struct LibraryInstaller { pub(crate) composer: PartialComposer, pub(crate) vendor_dir: String, - pub(crate) download_manager: Option<DownloadManager>, + pub(crate) download_manager: Option<std::rc::Rc<std::cell::RefCell<DownloadManager>>>, pub(crate) io: Box<dyn IOInterface>, pub(crate) r#type: Option<String>, pub(crate) filesystem: Filesystem, @@ -53,7 +53,7 @@ impl LibraryInstaller { None }; - let filesystem = filesystem.unwrap_or_else(Filesystem::new); + let filesystem = filesystem.unwrap_or_else(|| Filesystem::new(None)); let vendor_dir = rtrim( // TODO(phase-b): composer.get_config().get("vendor-dir") returns a PhpMixed/String &composer.get_config().get("vendor-dir"), @@ -123,7 +123,9 @@ impl LibraryInstaller { ) -> Result<Option<Box<dyn PromiseInterface>>> { let download_path = self.get_install_path(package).unwrap(); - self.get_download_manager().install(package, &download_path) + self.get_download_manager() + .borrow() + .install(package, &download_path) } /// @return PromiseInterface|null @@ -165,6 +167,7 @@ impl LibraryInstaller { } self.get_download_manager() + .borrow() .update(initial, target, &target_download_path) } @@ -176,7 +179,9 @@ impl LibraryInstaller { ) -> Result<Option<Box<dyn PromiseInterface>>> { let download_path = self.get_package_base_path(package); - self.get_download_manager().remove(package, &download_path) + self.get_download_manager() + .borrow() + .remove(package, &download_path) } pub(crate) fn initialize_vendor_dir(&mut self) { @@ -185,7 +190,7 @@ impl LibraryInstaller { self.vendor_dir = realpath(&self.vendor_dir).unwrap(); } - pub(crate) fn get_download_manager(&self) -> &DownloadManager { + pub(crate) fn get_download_manager(&self) -> &std::rc::Rc<std::cell::RefCell<DownloadManager>> { // PHP: assert($this->downloadManager instanceof DownloadManager, new \LogicException(...)) assert!( self.download_manager.is_some(), @@ -252,6 +257,7 @@ impl InstallerInterface for LibraryInstaller { let download_path = self.get_install_path(package).unwrap(); self.get_download_manager() + .borrow() .download(package, &download_path, prev_package) } @@ -266,6 +272,7 @@ impl InstallerInterface for LibraryInstaller { let download_path = self.get_install_path(package).unwrap(); self.get_download_manager() + .borrow() .prepare(r#type, package, &download_path, prev_package) } @@ -280,11 +287,12 @@ impl InstallerInterface for LibraryInstaller { let download_path = self.get_install_path(package).unwrap(); self.get_download_manager() + .borrow() .cleanup(r#type, package, &download_path, prev_package) } fn install( - &self, + &mut self, repo: &mut dyn InstalledRepositoryInterface, package: &dyn PackageInterface, ) -> Result<Option<Box<dyn PromiseInterface>>> { @@ -310,14 +318,14 @@ impl InstallerInterface for LibraryInstaller { Ok(Some(promise.then(Box::new(move || -> Result<()> { binary_installer.install_binaries(package, &install_path, true); if !repo.has_package(package) { - repo.add_package(package.clone_box())?; + repo.add_package(package.clone_package_box())?; } Ok(()) })))) } fn update( - &self, + &mut self, repo: &mut dyn InstalledRepositoryInterface, initial: &dyn PackageInterface, target: &dyn PackageInterface, @@ -348,14 +356,14 @@ impl InstallerInterface for LibraryInstaller { binary_installer.install_binaries(target, &install_path, true); repo.remove_package(initial)?; if !repo.has_package(target) { - repo.add_package(target.clone_box())?; + repo.add_package(target.clone_package_box())?; } Ok(()) })))) } fn uninstall( - &self, + &mut self, repo: &mut dyn InstalledRepositoryInterface, package: &dyn PackageInterface, ) -> Result<Option<Box<dyn PromiseInterface>>> { diff --git a/crates/shirabe/src/installer/metapackage_installer.rs b/crates/shirabe/src/installer/metapackage_installer.rs index 3a47f70..e30ef85 100644 --- a/crates/shirabe/src/installer/metapackage_installer.rs +++ b/crates/shirabe/src/installer/metapackage_installer.rs @@ -69,17 +69,17 @@ impl InstallerInterface for MetapackageInstaller { } fn install( - &self, + &mut self, repo: &mut dyn InstalledRepositoryInterface, package: &dyn PackageInterface, ) -> Result<Option<Box<dyn PromiseInterface>>> { - self.io.write_error( + self.io.write_error3( &format!(" - {}", InstallOperation::format(package, false)), true, io_interface::NORMAL, ); - repo.add_package(package.clone_box()); + repo.add_package(package.clone_package_box()); Ok(Some(shirabe_external_packages::react::promise::resolve( None, @@ -87,7 +87,7 @@ impl InstallerInterface for MetapackageInstaller { } fn update( - &self, + &mut self, repo: &mut dyn InstalledRepositoryInterface, initial: &dyn PackageInterface, target: &dyn PackageInterface, @@ -100,14 +100,14 @@ impl InstallerInterface for MetapackageInstaller { .into()); } - self.io.write_error( + self.io.write_error3( &format!(" - {}", UpdateOperation::format(initial, target, false)), true, io_interface::NORMAL, ); repo.remove_package(initial); - repo.add_package(target.clone_box()); + repo.add_package(target.clone_package_box()); Ok(Some(shirabe_external_packages::react::promise::resolve( None, @@ -115,7 +115,7 @@ impl InstallerInterface for MetapackageInstaller { } fn uninstall( - &self, + &mut self, repo: &mut dyn InstalledRepositoryInterface, package: &dyn PackageInterface, ) -> Result<Option<Box<dyn PromiseInterface>>> { @@ -127,7 +127,7 @@ impl InstallerInterface for MetapackageInstaller { .into()); } - self.io.write_error( + self.io.write_error3( &format!(" - {}", UninstallOperation::format(package, false)), true, io_interface::NORMAL, diff --git a/crates/shirabe/src/installer/noop_installer.rs b/crates/shirabe/src/installer/noop_installer.rs index 09e6afd..8180402 100644 --- a/crates/shirabe/src/installer/noop_installer.rs +++ b/crates/shirabe/src/installer/noop_installer.rs @@ -55,12 +55,12 @@ impl InstallerInterface for NoopInstaller { } fn install( - &self, + &mut self, repo: &mut dyn InstalledRepositoryInterface, package: &dyn PackageInterface, ) -> anyhow::Result<Option<Box<dyn PromiseInterface>>> { if !repo.has_package(package) { - repo.add_package(package.clone_box()); + repo.add_package(package.clone_package_box()); } Ok(Some(shirabe_external_packages::react::promise::resolve( @@ -69,7 +69,7 @@ impl InstallerInterface for NoopInstaller { } fn update( - &self, + &mut self, repo: &mut dyn InstalledRepositoryInterface, initial: &dyn PackageInterface, target: &dyn PackageInterface, @@ -84,7 +84,7 @@ impl InstallerInterface for NoopInstaller { repo.remove_package(initial); if !repo.has_package(target) { - repo.add_package(target.clone_box()); + repo.add_package(target.clone_package_box()); } Ok(Some(shirabe_external_packages::react::promise::resolve( @@ -93,7 +93,7 @@ impl InstallerInterface for NoopInstaller { } fn uninstall( - &self, + &mut self, repo: &mut dyn InstalledRepositoryInterface, package: &dyn PackageInterface, ) -> anyhow::Result<Option<Box<dyn PromiseInterface>>> { diff --git a/crates/shirabe/src/installer/package_event.rs b/crates/shirabe/src/installer/package_event.rs index 2bf1c6b..8b6fbbe 100644 --- a/crates/shirabe/src/installer/package_event.rs +++ b/crates/shirabe/src/installer/package_event.rs @@ -39,6 +39,10 @@ impl PackageEvent { } } + pub fn get_name(&self) -> &str { + self.inner.get_name() + } + pub fn get_composer(&self) -> &Composer { &self.composer } diff --git a/crates/shirabe/src/installer/plugin_installer.rs b/crates/shirabe/src/installer/plugin_installer.rs index 1fd2334..77bd669 100644 --- a/crates/shirabe/src/installer/plugin_installer.rs +++ b/crates/shirabe/src/installer/plugin_installer.rs @@ -37,13 +37,13 @@ impl PluginInstaller { } } - pub fn disable_plugins(&self) { + pub fn disable_plugins(&mut self) { // TODO(plugin): disable plugins via plugin manager - self.get_plugin_manager().disable_plugins(); + self.get_plugin_manager_mut().disable_plugins(); } fn rollback_install( - &self, + &mut self, e: anyhow::Error, repo: &mut dyn InstalledRepositoryInterface, package: &dyn PackageInterface, @@ -71,6 +71,11 @@ impl PluginInstaller { // TODO(plugin): return plugin manager from composer self.inner.composer.get_plugin_manager() } + + fn get_plugin_manager_mut(&mut self) -> &mut PluginManager { + // TODO(plugin): return mutable plugin manager from composer + todo!() + } } impl InstallerInterface for PluginInstaller { @@ -129,7 +134,7 @@ impl InstallerInterface for PluginInstaller { } fn install( - &self, + &mut self, repo: &mut dyn InstalledRepositoryInterface, package: &dyn PackageInterface, ) -> Result<Option<Box<dyn PromiseInterface>>> { @@ -149,7 +154,7 @@ impl InstallerInterface for PluginInstaller { } fn update( - &self, + &mut self, repo: &mut dyn InstalledRepositoryInterface, initial: &dyn PackageInterface, target: &dyn PackageInterface, @@ -171,12 +176,12 @@ impl InstallerInterface for PluginInstaller { } fn uninstall( - &self, + &mut self, repo: &mut dyn InstalledRepositoryInterface, package: &dyn PackageInterface, ) -> Result<Option<Box<dyn PromiseInterface>>> { // TODO(plugin): uninstall package from plugin manager - self.get_plugin_manager().uninstall_package(package); + self.get_plugin_manager_mut().uninstall_package(package); self.inner.uninstall(repo, package) } diff --git a/crates/shirabe/src/installer/project_installer.rs b/crates/shirabe/src/installer/project_installer.rs index 1a097b0..f8b0bba 100644 --- a/crates/shirabe/src/installer/project_installer.rs +++ b/crates/shirabe/src/installer/project_installer.rs @@ -11,13 +11,17 @@ use shirabe_php_shim::InvalidArgumentException; #[derive(Debug)] pub struct ProjectInstaller { install_path: String, - download_manager: DownloadManager, + download_manager: std::rc::Rc<std::cell::RefCell<DownloadManager>>, filesystem: Filesystem, } impl ProjectInstaller { - pub fn new(install_path: &str, dm: DownloadManager, fs: Filesystem) -> Self { - let install_path = format!("{}/", install_path.replace('\\', '/').trim_end_matches('/')); + pub fn new( + install_path: &str, + dm: std::rc::Rc<std::cell::RefCell<DownloadManager>>, + fs: Filesystem, + ) -> Self { + let install_path = format!("{}/", install_path.replace('\\', "/").trim_end_matches('/')); Self { install_path, download_manager: dm, @@ -59,7 +63,9 @@ impl InstallerInterface for ProjectInstaller { } self.download_manager + .borrow() .download(package, install_path, prev_package) + .map(Some) } fn prepare( @@ -69,7 +75,9 @@ impl InstallerInterface for ProjectInstaller { prev_package: Option<&dyn PackageInterface>, ) -> anyhow::Result<Option<Box<dyn PromiseInterface>>> { self.download_manager + .borrow() .prepare(r#type, package, &self.install_path, prev_package) + .map(Some) } fn cleanup( @@ -79,19 +87,23 @@ impl InstallerInterface for ProjectInstaller { prev_package: Option<&dyn PackageInterface>, ) -> anyhow::Result<Option<Box<dyn PromiseInterface>>> { self.download_manager + .borrow() .cleanup(r#type, package, &self.install_path, prev_package) + .map(Some) } fn install( - &self, + &mut self, _repo: &mut dyn InstalledRepositoryInterface, package: &dyn PackageInterface, ) -> anyhow::Result<Option<Box<dyn PromiseInterface>>> { - self.download_manager.install(package, &self.install_path) + self.download_manager + .borrow() + .install(package, &self.install_path) } fn update( - &self, + &mut self, _repo: &mut dyn InstalledRepositoryInterface, _initial: &dyn PackageInterface, _target: &dyn PackageInterface, @@ -104,7 +116,7 @@ impl InstallerInterface for ProjectInstaller { } fn uninstall( - &self, + &mut self, _repo: &mut dyn InstalledRepositoryInterface, _package: &dyn PackageInterface, ) -> anyhow::Result<Option<Box<dyn PromiseInterface>>> { diff --git a/crates/shirabe/src/installer/suggested_packages_reporter.rs b/crates/shirabe/src/installer/suggested_packages_reporter.rs index cfee20b..31a34d8 100644 --- a/crates/shirabe/src/installer/suggested_packages_reporter.rs +++ b/crates/shirabe/src/installer/suggested_packages_reporter.rs @@ -3,6 +3,7 @@ use crate::io::io_interface::IOInterface; use crate::package::package_interface::PackageInterface; use crate::repository::installed_repository::InstalledRepository; +use crate::repository::repository_interface::RepositoryInterface; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::preg::Preg; use shirabe_external_packages::symfony::component::console::formatter::output_formatter::OutputFormatter; diff --git a/crates/shirabe/src/io/base_io.rs b/crates/shirabe/src/io/base_io.rs index f467de1..d4e8f73 100644 --- a/crates/shirabe/src/io/base_io.rs +++ b/crates/shirabe/src/io/base_io.rs @@ -98,7 +98,7 @@ pub trait BaseIO: IOInterface { let custom_headers = config.get("custom-headers"); let client_certificate = config.get("client-certificate"); - if let Some(map) = bitbucket_oauth.as_ref().and_then(|v| v.as_array()) { + if let Some(map) = bitbucket_oauth.as_opt().and_then(|v| v.as_array()) { for (domain, cred) in map.clone() { if let Some(cred_map) = cred.as_array() { let consumer_key = cred_map @@ -115,7 +115,7 @@ pub trait BaseIO: IOInterface { } } - if let Some(map) = github_oauth.as_ref().and_then(|v| v.as_array()) { + if let Some(map) = github_oauth.as_opt().and_then(|v| v.as_array()) { for (domain, token) in map.clone() { let token_str = token.as_string().unwrap_or("").to_string(); let github_domains = config.get("github-domains"); @@ -139,9 +139,9 @@ pub trait BaseIO: IOInterface { ); let mut inner = IndexMap::new(); inner.insert("github-domains".to_string(), Box::new(merged)); - let mut outer = IndexMap::new(); - outer.insert("config".to_string(), Box::new(PhpMixed::Array(inner))); - config.merge(PhpMixed::Array(outer), "implicit-due-to-auth"); + let mut config_outer: IndexMap<String, PhpMixed> = IndexMap::new(); + config_outer.insert("config".to_string(), PhpMixed::Array(inner)); + config.merge(&config_outer, "implicit-due-to-auth"); } if !Preg::is_match(r"^[.A-Za-z0-9_]+$", &token_str).unwrap_or(false) { @@ -161,7 +161,7 @@ pub trait BaseIO: IOInterface { } } - if let Some(map) = gitlab_oauth.as_ref().and_then(|v| v.as_array()) { + if let Some(map) = gitlab_oauth.as_opt().and_then(|v| v.as_array()) { for (domain, token) in map.clone() { let gitlab_domains = config.get("gitlab-domains"); if domain != "gitlab.com" @@ -184,9 +184,9 @@ pub trait BaseIO: IOInterface { ); let mut inner = IndexMap::new(); inner.insert("gitlab-domains".to_string(), Box::new(merged)); - let mut outer = IndexMap::new(); - outer.insert("config".to_string(), Box::new(PhpMixed::Array(inner))); - config.merge(PhpMixed::Array(outer), "implicit-due-to-auth"); + let mut config_outer: IndexMap<String, PhpMixed> = IndexMap::new(); + config_outer.insert("config".to_string(), PhpMixed::Array(inner)); + config.merge(&config_outer, "implicit-due-to-auth"); } let token_str = if let Some(arr) = token.as_array() { @@ -201,7 +201,7 @@ pub trait BaseIO: IOInterface { } } - if let Some(map) = gitlab_token.as_ref().and_then(|v| v.as_array()) { + if let Some(map) = gitlab_token.as_opt().and_then(|v| v.as_array()) { for (domain, token) in map.clone() { let gitlab_domains = config.get("gitlab-domains"); if domain != "gitlab.com" @@ -224,9 +224,9 @@ pub trait BaseIO: IOInterface { ); let mut inner = IndexMap::new(); inner.insert("gitlab-domains".to_string(), Box::new(merged)); - let mut outer = IndexMap::new(); - outer.insert("config".to_string(), Box::new(PhpMixed::Array(inner))); - config.merge(PhpMixed::Array(outer), "implicit-due-to-auth"); + let mut config_outer: IndexMap<String, PhpMixed> = IndexMap::new(); + config_outer.insert("config".to_string(), PhpMixed::Array(inner)); + config.merge(&config_outer, "implicit-due-to-auth"); } let (username, password) = if let Some(arr) = token.as_array() { @@ -250,7 +250,7 @@ pub trait BaseIO: IOInterface { } } - if let Some(map) = forgejo_token.as_ref().and_then(|v| v.as_array()) { + if let Some(map) = forgejo_token.as_opt().and_then(|v| v.as_array()) { for (domain, cred) in map.clone() { let forgejo_domains = config.get("forgejo-domains"); if !in_array( @@ -271,9 +271,9 @@ pub trait BaseIO: IOInterface { ); let mut inner = IndexMap::new(); inner.insert("forgejo-domains".to_string(), Box::new(merged)); - let mut outer = IndexMap::new(); - outer.insert("config".to_string(), Box::new(PhpMixed::Array(inner))); - config.merge(PhpMixed::Array(outer), "implicit-due-to-auth"); + let mut config_outer: IndexMap<String, PhpMixed> = IndexMap::new(); + config_outer.insert("config".to_string(), PhpMixed::Array(inner)); + config.merge(&config_outer, "implicit-due-to-auth"); } if let Some(cred_map) = cred.as_array() { @@ -291,7 +291,7 @@ pub trait BaseIO: IOInterface { } } - if let Some(map) = http_basic.as_ref().and_then(|v| v.as_array()) { + if let Some(map) = http_basic.as_opt().and_then(|v| v.as_array()) { for (domain, cred) in map.clone() { if let Some(cred_map) = cred.as_array() { let username = cred_map @@ -308,14 +308,14 @@ pub trait BaseIO: IOInterface { } } - if let Some(map) = bearer_token.as_ref().and_then(|v| v.as_array()) { + if let Some(map) = bearer_token.as_opt().and_then(|v| v.as_array()) { for (domain, token) in map.clone() { let token_str = token.as_string().unwrap_or("").to_string(); self.check_and_set_authentication(domain, token_str, Some("bearer".to_string())); } } - if let Some(map) = custom_headers.as_ref().and_then(|v| v.as_array()) { + if let Some(map) = custom_headers.as_opt().and_then(|v| v.as_array()) { for (domain, headers) in map.clone() { if !headers.is_null() { let json_str = json_encode_ex(&headers, 0).unwrap_or_default(); @@ -328,7 +328,7 @@ pub trait BaseIO: IOInterface { } } - if let Some(map) = client_certificate.as_ref().and_then(|v| v.as_array()) { + if let Some(map) = client_certificate.as_opt().and_then(|v| v.as_array()) { for (domain, cred) in map.clone() { if let Some(cred_map) = cred.as_array() { let local_cert = cred_map diff --git a/crates/shirabe/src/io/console_io.rs b/crates/shirabe/src/io/console_io.rs index 91a15d3..6f17a3e 100644 --- a/crates/shirabe/src/io/console_io.rs +++ b/crates/shirabe/src/io/console_io.rs @@ -420,38 +420,38 @@ impl IOInterface for ConsoleIO { self.output.is_decorated() } - fn write(&mut self, messages: PhpMixed, newline: bool, verbosity: i64) { - let messages = Self::sanitize(messages, true); + fn write3(&mut self, message: &str, newline: bool, verbosity: i64) { + let message = Self::sanitize(message, true); - self.do_write(messages, newline, false, verbosity, false); + self.do_write(message, newline, false, verbosity, false); } - fn write_error(&mut self, messages: PhpMixed, newline: bool, verbosity: i64) { - let messages = Self::sanitize(messages, true); + fn write_error3(&mut self, message: &str, newline: bool, verbosity: i64) { + let message = Self::sanitize(message, true); - self.do_write(messages, newline, true, verbosity, false); + self.do_write(message, newline, true, verbosity, false); } - fn write_raw(&mut self, messages: PhpMixed, newline: bool, verbosity: i64) { - self.do_write(messages, newline, false, verbosity, true); + fn write_raw3(&mut self, message: &str, newline: bool, verbosity: i64) { + self.do_write(message, newline, false, verbosity, true); } - fn write_error_raw(&mut self, messages: PhpMixed, newline: bool, verbosity: i64) { - self.do_write(messages, newline, true, verbosity, true); + fn write_error_raw3(&mut self, message: &str, newline: bool, verbosity: i64) { + self.do_write(message, newline, true, verbosity, true); } - fn overwrite(&mut self, messages: PhpMixed, newline: bool, size: Option<i64>, verbosity: i64) { - self.do_overwrite(messages, newline, size, false, verbosity); + fn overwrite4(&mut self, message: &str, newline: bool, size: Option<i64>, verbosity: i64) { + self.do_overwrite(message, newline, size, false, verbosity); } - fn overwrite_error( + fn overwrite_error4( &mut self, - messages: PhpMixed, + message: &str, newline: bool, size: Option<i64>, verbosity: i64, ) { - self.do_overwrite(messages, newline, size, true, verbosity); + self.do_overwrite(message, newline, size, true, verbosity); } fn ask(&mut self, question: String, default: PhpMixed) -> PhpMixed { @@ -471,14 +471,17 @@ impl IOInterface for ConsoleIO { fn ask_confirmation(&mut self, question: String, default: bool) -> bool { let helper = self.helper_set.get("question"); - let default_mixed = PhpMixed::Bool(default); + // TODO(phase-b): Self::sanitize returns PhpMixed but new() expects String; + // also true/false regexes need to come through composer/symfony defaults. + let sanitized = Self::sanitize(PhpMixed::String(question), true) + .as_string() + .unwrap_or("") + .to_string(); let question = StrictConfirmationQuestion::new( - Self::sanitize(PhpMixed::String(question), true), - if is_string(&default_mixed) { - Self::sanitize(default_mixed, true) - } else { - default_mixed - }, + sanitized, + default, + "/^y(?:es)?$/i".to_string(), + "/^no?$/i".to_string(), ); helper diff --git a/crates/shirabe/src/io/io_interface.rs b/crates/shirabe/src/io/io_interface.rs index 3826bb3..e78b608 100644 --- a/crates/shirabe/src/io/io_interface.rs +++ b/crates/shirabe/src/io/io_interface.rs @@ -11,7 +11,7 @@ pub const VERBOSE: i64 = 4; pub const VERY_VERBOSE: i64 = 8; pub const DEBUG: i64 = 16; -pub trait IOInterface: LoggerInterface { +pub trait IOInterface: LoggerInterface + std::fmt::Debug { fn is_interactive(&self) -> bool; fn is_verbose(&self) -> bool; @@ -22,23 +22,65 @@ pub trait IOInterface: LoggerInterface { fn is_decorated(&self) -> bool; - fn write(&mut self, messages: PhpMixed, newline: bool, verbosity: i64); + fn write(&mut self, message: &str) { + self.write3(message, true, NORMAL) + } + fn write2(&mut self, message: &str, newline: bool) { + self.write3(message, newline, NORMAL) + } + fn write_no_newline(&mut self, message: &str) { + self.write3(message, false, NORMAL) + } + fn write3(&mut self, message: &str, newline: bool, verbosity: i64); - fn write_error(&mut self, messages: PhpMixed, newline: bool, verbosity: i64); + fn write_error(&mut self, message: &str) { + self.write_error3(message, true, NORMAL) + } + fn write_error2(&mut self, message: &str, newline: bool) { + self.write_error3(message, newline, NORMAL) + } + fn write_error_no_newline(&mut self, message: &str) { + self.write_error3(message, false, NORMAL) + } + fn write_error3(&mut self, message: &str, newline: bool, verbosity: i64); - fn write_raw(&mut self, messages: PhpMixed, newline: bool, verbosity: i64); + fn write_raw(&mut self, message: &str) { + self.write_raw3(message, true, NORMAL) + } + fn write_raw2(&mut self, message: &str, newline: bool) { + self.write_raw3(message, newline, NORMAL) + } + fn write_raw3(&mut self, message: &str, newline: bool, verbosity: i64); - fn write_error_raw(&mut self, messages: PhpMixed, newline: bool, verbosity: i64); + fn write_error_raw(&mut self, message: &str) { + self.write_error_raw3(message, true, NORMAL) + } + fn write_error_raw2(&mut self, message: &str, newline: bool) { + self.write_error_raw3(message, newline, NORMAL) + } + fn write_error_raw3(&mut self, message: &str, newline: bool, verbosity: i64); - fn overwrite(&mut self, messages: PhpMixed, newline: bool, size: Option<i64>, verbosity: i64); + fn overwrite(&mut self, message: &str) { + self.overwrite4(message, true, None, NORMAL) + } + fn overwrite2(&mut self, message: &str, newline: bool) { + self.overwrite4(message, newline, None, NORMAL) + } + fn overwrite3(&mut self, message: &str, newline: bool, size: Option<i64>) { + self.overwrite4(message, newline, size, NORMAL) + } + fn overwrite4(&mut self, message: &str, newline: bool, size: Option<i64>, verbosity: i64); - fn overwrite_error( - &mut self, - messages: PhpMixed, - newline: bool, - size: Option<i64>, - verbosity: i64, - ); + fn overwrite_error(&mut self, message: &str) { + self.overwrite_error4(message, true, None, NORMAL) + } + fn overwrite_error2(&mut self, message: &str, newline: bool) { + self.overwrite_error4(message, newline, None, NORMAL) + } + fn overwrite_error3(&mut self, message: &str, newline: bool, size: Option<i64>) { + self.overwrite_error4(message, newline, size, NORMAL) + } + fn overwrite_error4(&mut self, message: &str, newline: bool, size: Option<i64>, verbosity: i64); fn ask(&mut self, question: String, default: PhpMixed) -> PhpMixed; @@ -78,4 +120,8 @@ pub trait IOInterface: LoggerInterface { ); fn load_configuration(&mut self, config: &mut Config) -> anyhow::Result<()>; + + fn clone_box(&self) -> Box<dyn IOInterface> { + todo!() + } } diff --git a/crates/shirabe/src/io/null_io.rs b/crates/shirabe/src/io/null_io.rs index f229839..4ec5be0 100644 --- a/crates/shirabe/src/io/null_io.rs +++ b/crates/shirabe/src/io/null_io.rs @@ -10,6 +10,14 @@ pub struct NullIO { authentications: indexmap::IndexMap<String, indexmap::IndexMap<String, Option<String>>>, } +impl NullIO { + pub fn new() -> Self { + Self { + authentications: indexmap::IndexMap::new(), + } + } +} + impl IOInterface for NullIO { fn is_interactive(&self) -> bool { false @@ -31,22 +39,19 @@ impl IOInterface for NullIO { false } - fn write(&mut self, _messages: PhpMixed, _newline: bool, _verbosity: i64) {} + fn write3(&mut self, _message: &str, _newline: bool, _verbosity: i64) {} - fn write_error(&mut self, _messages: PhpMixed, _newline: bool, _verbosity: i64) {} + fn write_error3(&mut self, _message: &str, _newline: bool, _verbosity: i64) {} - fn overwrite( - &mut self, - _messages: PhpMixed, - _newline: bool, - _size: Option<i64>, - _verbosity: i64, - ) { - } + fn write_raw3(&mut self, _message: &str, _newline: bool, _verbosity: i64) {} + + fn write_error_raw3(&mut self, _message: &str, _newline: bool, _verbosity: i64) {} - fn overwrite_error( + fn overwrite4(&mut self, _message: &str, _newline: bool, _size: Option<i64>, _verbosity: i64) {} + + fn overwrite_error4( &mut self, - _messages: PhpMixed, + _message: &str, _newline: bool, _size: Option<i64>, _verbosity: i64, @@ -87,14 +92,6 @@ impl IOInterface for NullIO { default } - fn write_raw(&mut self, messages: PhpMixed, newline: bool, verbosity: i64) { - <Self as BaseIO>::write_raw(self, messages, newline, verbosity) - } - - fn write_error_raw(&mut self, messages: PhpMixed, newline: bool, verbosity: i64) { - <Self as BaseIO>::write_error_raw(self, messages, newline, verbosity) - } - fn get_authentications( &self, ) -> indexmap::IndexMap<String, indexmap::IndexMap<String, Option<String>>> { diff --git a/crates/shirabe/src/json/json_file.rs b/crates/shirabe/src/json/json_file.rs index c1fa495..d08e207 100644 --- a/crates/shirabe/src/json/json_file.rs +++ b/crates/shirabe/src/json/json_file.rs @@ -401,7 +401,11 @@ impl JsonFile { /// @param int $options json_encode options (defaults to JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) /// @param string $indent Indentation string /// @return string Encoded json - pub fn encode(data: &PhpMixed, options: i64, indent: &str) -> String { + pub fn encode(data: &PhpMixed, options: i64) -> String { + Self::encode_with_indent(data, options, Self::INDENT_DEFAULT) + } + + pub fn encode_with_indent(data: &PhpMixed, options: i64, indent: &str) -> String { let json = json_encode_ex(data, options); let json = match json { diff --git a/crates/shirabe/src/json/json_formatter.rs b/crates/shirabe/src/json/json_formatter.rs index 1696982..47680b1 100644 --- a/crates/shirabe/src/json/json_formatter.rs +++ b/crates/shirabe/src/json/json_formatter.rs @@ -1,6 +1,6 @@ //! ref: composer/src/Composer/Json/JsonFormatter.php -use shirabe_external_packages::composer::pcre::preg::Preg; +use shirabe_external_packages::composer::pcre::preg::{CaptureKey, Preg}; use shirabe_php_shim::{PhpMixed, function_exists, mb_convert_encoding, pack}; pub struct JsonFormatter; @@ -44,27 +44,40 @@ impl JsonFormatter { if unescape_unicode && function_exists("mb_convert_encoding") { buffer = Preg::replace_callback( r"/(\\+)u([0-9a-f]{4})/i", - |matches: &[String]| -> String { - let l = matches[1].len(); + |matches: &indexmap::IndexMap<CaptureKey, String>| -> String { + let m0 = matches + .get(&CaptureKey::ByIndex(0)) + .cloned() + .unwrap_or_default(); + let m1 = matches + .get(&CaptureKey::ByIndex(1)) + .cloned() + .unwrap_or_default(); + let m2 = matches + .get(&CaptureKey::ByIndex(2)) + .cloned() + .unwrap_or_default(); + let l = m1.len(); if l % 2 != 0 { - let code = i64::from_str_radix(&matches[2], 16).unwrap_or(0); + let code = i64::from_str_radix(&m2, 16).unwrap_or(0); if code >= 0xD800 && code <= 0xDFFF { - return matches[0].clone(); + return m0; } return "\\".repeat(l - 1) + &mb_convert_encoding( - pack("H*", &[PhpMixed::String(matches[2].clone())]), + pack("H*", &[PhpMixed::String(m2)]), "UTF-8", "UCS-2BE", ); } - matches[0].clone() + m0 }, &buffer, - ); + ) + .unwrap_or(buffer); } result.push_str(&buffer); diff --git a/crates/shirabe/src/json/json_manipulator.rs b/crates/shirabe/src/json/json_manipulator.rs index ebbfff9..b9c6a5c 100644 --- a/crates/shirabe/src/json/json_manipulator.rs +++ b/crates/shirabe/src/json/json_manipulator.rs @@ -128,8 +128,7 @@ impl JsonManipulator { JsonFile::encode( &PhpMixed::String(str_replace("\\/", "/", &existing_owned)), 0 - ) - .unwrap_or_default(), + ), m.get("separator").cloned().unwrap_or_default(), constraint_owned ) @@ -1024,7 +1023,7 @@ impl JsonManipulator { if now_empty { arr.insert( name_owned.clone(), - Box::new(PhpMixed::Object(ArrayObject::new())), + Box::new(PhpMixed::Object(ArrayObject::new(None))), ); } } @@ -1068,7 +1067,7 @@ impl JsonManipulator { if now_empty { arr.insert( name_capture.clone(), - Box::new(PhpMixed::Object(ArrayObject::new())), + Box::new(PhpMixed::Object(ArrayObject::new(None))), ); } } diff --git a/crates/shirabe/src/json/json_validation_exception.rs b/crates/shirabe/src/json/json_validation_exception.rs index 5f3cbbf..4549a0f 100644 --- a/crates/shirabe/src/json/json_validation_exception.rs +++ b/crates/shirabe/src/json/json_validation_exception.rs @@ -19,6 +19,10 @@ impl JsonValidationException { pub fn get_errors(&self) -> &Vec<String> { &self.errors } + + pub fn get_message(&self) -> &str { + &self.inner.message + } } impl std::fmt::Display for JsonValidationException { diff --git a/crates/shirabe/src/package/alias_package.rs b/crates/shirabe/src/package/alias_package.rs index cd2e5f1..e115362 100644 --- a/crates/shirabe/src/package/alias_package.rs +++ b/crates/shirabe/src/package/alias_package.rs @@ -38,12 +38,12 @@ pub struct AliasPackage { pub(crate) requires: IndexMap<String, Link>, /// @var Link[] pub(crate) dev_requires: IndexMap<String, Link>, - /// @var Link[] - pub(crate) conflicts: Vec<Link>, - /// @var Link[] - pub(crate) provides: Vec<Link>, - /// @var Link[] - pub(crate) replaces: Vec<Link>, + /// @var array<string, Link> + pub(crate) conflicts: IndexMap<String, Link>, + /// @var array<string, Link> + pub(crate) provides: IndexMap<String, Link>, + /// @var array<string, Link> + pub(crate) replaces: IndexMap<String, Link>, } impl AliasPackage { @@ -69,9 +69,9 @@ impl AliasPackage { alias_of, requires: IndexMap::new(), dev_requires: IndexMap::new(), - conflicts: vec![], - provides: vec![], - replaces: vec![], + conflicts: IndexMap::new(), + provides: IndexMap::new(), + replaces: IndexMap::new(), }; for r#type in Link::types() { @@ -101,9 +101,24 @@ impl AliasPackage { .map(|l| (l.get_target().to_string(), l)) .collect(); } - Link::TYPE_PROVIDE => this.provides = replaced, - Link::TYPE_CONFLICT => this.conflicts = replaced, - Link::TYPE_REPLACE => this.replaces = replaced, + Link::TYPE_PROVIDE => { + this.provides = replaced + .into_iter() + .map(|l| (l.get_target().to_string(), l)) + .collect() + } + Link::TYPE_CONFLICT => { + this.conflicts = replaced + .into_iter() + .map(|l| (l.get_target().to_string(), l)) + .collect() + } + Link::TYPE_REPLACE => { + this.replaces = replaced + .into_iter() + .map(|l| (l.get_target().to_string(), l)) + .collect() + } _ => {} } } @@ -202,7 +217,7 @@ impl std::fmt::Display for AliasPackage { write!( f, "{} ({}alias of {})", - self.inner, + self.alias_of, if self.root_package_alias { "root " } else { "" }, self.alias_of.get_version(), ) @@ -210,24 +225,28 @@ impl std::fmt::Display for AliasPackage { } impl PackageInterface for AliasPackage { + fn as_any(&self) -> &dyn std::any::Any { + self + } + fn get_name(&self) -> &str { - self.inner.get_name() + self.alias_of.get_name() } fn get_pretty_name(&self) -> &str { - self.inner.get_pretty_name() + self.alias_of.get_pretty_name() } fn get_names(&self, provides: bool) -> Vec<String> { - self.inner.get_names(provides) + self.alias_of.get_names(provides) } fn set_id(&mut self, id: i64) { - self.inner.set_id(id); + self.alias_of.set_id(id); } fn get_id(&self) -> i64 { - self.inner.get_id() + self.alias_of.get_id() } fn is_dev(&self) -> bool { @@ -251,20 +270,20 @@ impl PackageInterface for AliasPackage { } /// @inheritDoc - /// @return array<string|int, Link> - fn get_conflicts(&self) -> Vec<Link> { + /// @return array<string, Link> + fn get_conflicts(&self) -> IndexMap<String, Link> { self.conflicts.clone() } /// @inheritDoc - /// @return array<string|int, Link> - fn get_provides(&self) -> Vec<Link> { + /// @return array<string, Link> + fn get_provides(&self) -> IndexMap<String, Link> { self.provides.clone() } /// @inheritDoc - /// @return array<string|int, Link> - fn get_replaces(&self) -> Vec<Link> { + /// @return array<string, Link> + fn get_replaces(&self) -> IndexMap<String, Link> { self.replaces.clone() } @@ -410,25 +429,25 @@ impl PackageInterface for AliasPackage { fn get_full_pretty_version(&self, truncate: bool, display_mode: i64) -> String { // TODO(phase-b): BasePackage.get_full_pretty_version returns Result; bridge here - self.inner + self.alias_of .get_full_pretty_version(truncate, display_mode) .unwrap_or_default() } fn get_unique_name(&self) -> String { - self.inner.get_unique_name() + self.alias_of.get_unique_name() } fn get_pretty_string(&self) -> String { - self.inner.get_pretty_string() + self.alias_of.get_pretty_string() } fn set_repository(&mut self, repository: Box<dyn RepositoryInterface>) -> anyhow::Result<()> { - self.inner.set_repository(repository) + self.alias_of.set_repository(repository) } fn get_repository(&self) -> Option<&dyn RepositoryInterface> { - self.inner.get_repository() + self.alias_of.get_repository() } } @@ -469,10 +488,6 @@ impl BasePackage for AliasPackage { todo!() } - fn as_any(&self) -> &dyn std::any::Any { - todo!() - } - fn clone_box(&self) -> Box<dyn BasePackage> { todo!() } diff --git a/crates/shirabe/src/package/archiver/archivable_files_finder.rs b/crates/shirabe/src/package/archiver/archivable_files_finder.rs index 35ec36f..72ffabf 100644 --- a/crates/shirabe/src/package/archiver/archivable_files_finder.rs +++ b/crates/shirabe/src/package/archiver/archivable_files_finder.rs @@ -23,7 +23,7 @@ impl std::fmt::Debug for ArchivableFilesFinder { impl ArchivableFilesFinder { pub fn new(sources: &str, excludes: Vec<String>, ignore_filters: bool) -> anyhow::Result<Self> { - let fs = Filesystem::new(); + let fs = Filesystem::new(None); let sources_real_path = realpath(sources); if sources_real_path.is_none() { @@ -39,8 +39,8 @@ impl ArchivableFilesFinder { vec![] } else { vec![ - Box::new(GitExcludeFilter::new(&sources)), - Box::new(ComposerExcludeFilter::new(&sources, excludes)), + Box::new(GitExcludeFilter::new(sources.clone())), + Box::new(ComposerExcludeFilter::new(sources.clone(), excludes)), ] }; @@ -61,7 +61,8 @@ impl ArchivableFilesFinder { &format!("^{}", preg_quote(&sources_clone, Some('#'))), "", &fs.normalize_path(&realpath), - ); + ) + .unwrap_or_default(); let mut exclude = false; for f in &filters { @@ -72,13 +73,14 @@ impl ArchivableFilesFinder { }; finder - .in_dir(&sources) - .filter(Box::new(filter)) + .r#in(&sources) + // TODO(phase-b): symfony Finder filter takes Box<dyn Fn(&SplFileInfo) -> bool>; signature not yet wired .ignore_vcs(true) .ignore_dot_files(false) .sort_by_name(); + let _ = filter; - let inner_iter = finder.get_iterator(); + let inner_iter: Box<dyn Iterator<Item = SplFileInfo>> = Box::new(finder.get_iterator()); Ok(Self { finder, inner_iter }) } @@ -88,7 +90,7 @@ impl ArchivableFilesFinder { return true; } - let path = current.to_string(); + let path = current.get_pathname(); match std::fs::read_dir(&path) { Ok(mut iter) => iter.next().is_none(), Err(_) => false, diff --git a/crates/shirabe/src/package/archiver/archive_manager.rs b/crates/shirabe/src/package/archiver/archive_manager.rs index 3f7bfe0..73b0d84 100644 --- a/crates/shirabe/src/package/archiver/archive_manager.rs +++ b/crates/shirabe/src/package/archiver/archive_manager.rs @@ -19,8 +19,8 @@ use crate::util::r#loop::Loop; use crate::util::sync_helper::SyncHelper; pub struct ArchiveManager { - pub(crate) download_manager: DownloadManager, - pub(crate) r#loop: Loop, + pub(crate) download_manager: std::rc::Rc<std::cell::RefCell<DownloadManager>>, + pub(crate) r#loop: std::rc::Rc<std::cell::RefCell<Loop>>, pub(crate) archivers: Vec<Box<dyn ArchiverInterface>>, pub(crate) overwrite_files: bool, } @@ -34,7 +34,10 @@ impl std::fmt::Debug for ArchiveManager { } impl ArchiveManager { - pub fn new(download_manager: DownloadManager, r#loop: Loop) -> Self { + pub fn new( + download_manager: std::rc::Rc<std::cell::RefCell<DownloadManager>>, + r#loop: std::rc::Rc<std::cell::RefCell<Loop>>, + ) -> Self { Self { download_manager, r#loop, @@ -144,7 +147,7 @@ impl ArchiveManager { } }; - let filesystem = Filesystem::new(); + let filesystem = Filesystem::new(None); let is_root = package.as_any().is::<dyn RootPackageInterface>(); let source_path: String; @@ -158,10 +161,16 @@ impl ArchiveManager { filesystem.ensure_directory_exists(&source_path)?; let download_result = (|| -> anyhow::Result<()> { - let promise = self.download_manager.download(package, &source_path)?; - SyncHelper::r#await(&self.r#loop, promise)?; - let promise = self.download_manager.install(package, &source_path)?; - SyncHelper::r#await(&self.r#loop, promise)?; + let promise = + self.download_manager + .borrow() + .download(package, &source_path, None)?; + SyncHelper::r#await(&self.r#loop, Some(promise))?; + let promise = self + .download_manager + .borrow() + .install(package, &source_path)?; + SyncHelper::r#await(&self.r#loop, Some(promise))?; Ok(()) })(); @@ -172,7 +181,7 @@ impl ArchiveManager { let composer_json_path = format!("{}/composer.json", source_path); if file_exists(&composer_json_path) { - let json_file = JsonFile::new(composer_json_path, None, None); + let json_file = JsonFile::new(composer_json_path, None, None)?; let json_data = json_file.read()?; if let Some(archive) = json_data.get("archive") { if let Some(name) = archive.get("name").and_then(|v| v.as_str()) { diff --git a/crates/shirabe/src/package/archiver/base_exclude_filter.rs b/crates/shirabe/src/package/archiver/base_exclude_filter.rs index c7d2557..1a716d0 100644 --- a/crates/shirabe/src/package/archiver/base_exclude_filter.rs +++ b/crates/shirabe/src/package/archiver/base_exclude_filter.rs @@ -59,7 +59,7 @@ impl BaseExcludeFilterBase { let rule = rule.trim_matches('/'); - let glob_regex = Glob::to_regex(rule); + let glob_regex = Glob::to_regex(rule, true, true); let rule_regex = &glob_regex[2..glob_regex.len() - 2]; ( @@ -143,7 +143,7 @@ pub trait BaseExcludeFilter { let rule = rule.trim_matches('/'); // remove delimiters as well as caret (^) and dollar sign ($) from the regex - let glob_regex = Glob::to_regex(rule); + let glob_regex = Glob::to_regex(rule, true, true); let rule_regex = &glob_regex[2..glob_regex.len() - 2]; ( diff --git a/crates/shirabe/src/package/archiver/git_exclude_filter.rs b/crates/shirabe/src/package/archiver/git_exclude_filter.rs index dddad12..1a0ec4d 100644 --- a/crates/shirabe/src/package/archiver/git_exclude_filter.rs +++ b/crates/shirabe/src/package/archiver/git_exclude_filter.rs @@ -34,14 +34,17 @@ impl GitExcludeFilter { } fn parse_git_attributes_line_static(line: &str) -> Option<(String, bool, bool)> { - let parts = Preg::split(r"\s+", line); + let parts = Preg::split(r"\s+", line).unwrap_or_default(); if parts.len() == 2 && parts[1] == "export-ignore" { return Some(BaseExcludeFilterBase::generate_pattern(&parts[0])); } if parts.len() == 2 && parts[1] == "-export-ignore" { - return BaseExcludeFilterBase::generate_pattern(&format!("!{}", parts[0])); + return Some(BaseExcludeFilterBase::generate_pattern(&format!( + "!{}", + parts[0] + ))); } None diff --git a/crates/shirabe/src/package/archiver/phar_archiver.rs b/crates/shirabe/src/package/archiver/phar_archiver.rs index 2e9d96e..7b5142b 100644 --- a/crates/shirabe/src/package/archiver/phar_archiver.rs +++ b/crates/shirabe/src/package/archiver/phar_archiver.rs @@ -29,6 +29,12 @@ fn compress_formats() -> IndexMap<&'static str, i64> { #[derive(Debug)] pub struct PharArchiver; +impl PharArchiver { + pub fn new() -> Self { + Self + } +} + impl ArchiverInterface for PharArchiver { fn archive( &self, @@ -46,6 +52,7 @@ impl ArchiverInterface for PharArchiver { unlink(&target); } + let target_outer = target.clone(); let inner = (|| -> anyhow::Result<String> { let pos = strrpos(&target, &format).unwrap_or(target.len()); let filename = target[..pos.saturating_sub(1)].to_string(); @@ -63,7 +70,10 @@ impl ArchiverInterface for PharArchiver { *formats.get(format.as_str()).unwrap_or(&Phar::TAR), ); let files = ArchivableFilesFinder::new(&sources, excludes, ignore_filters)?; - let mut files_only = ArchivableFilesFilter::new(files); + // TODO(phase-b): unify iterator types (ArchivableFilesFinder yields SplFileInfo, + // ArchivableFilesFilter expects PathBuf). + let mut files_only = + ArchivableFilesFilter::new(Box::new(files.map(|f| f.get_pathname().into()))); phar.build_from_iterator(&mut files_only, &sources); files_only.add_empty_dir(&phar, &sources); @@ -137,7 +147,7 @@ impl ArchiverInterface for PharArchiver { inner.map_err(|e| { let message = format!( "Could not create archive '{}' from '{}': {}", - target, sources, e + target_outer, sources, e ); anyhow::anyhow!(RuntimeException { message, code: 0 }) }) diff --git a/crates/shirabe/src/package/archiver/zip_archiver.rs b/crates/shirabe/src/package/archiver/zip_archiver.rs index ef5b40a..471352f 100644 --- a/crates/shirabe/src/package/archiver/zip_archiver.rs +++ b/crates/shirabe/src/package/archiver/zip_archiver.rs @@ -13,6 +13,10 @@ use shirabe_php_shim::{ pub struct ZipArchiver; impl ZipArchiver { + pub fn new() -> Self { + Self + } + fn formats() -> IndexMap<String, bool> { let mut map = IndexMap::new(); map.insert("zip".to_string(), true); @@ -33,7 +37,7 @@ impl ArchiverInterface for ZipArchiver { excludes: Vec<String>, ignore_filters: bool, ) -> anyhow::Result<String> { - let fs = Filesystem::new(); + let fs = Filesystem::new(None); let sources_realpath = realpath(&sources); let sources = if let Some(p) = sources_realpath { p @@ -47,7 +51,7 @@ impl ArchiverInterface for ZipArchiver { let files = ArchivableFilesFinder::new(&sources, excludes, ignore_filters)?; for file in files { let filepath = file.get_pathname(); - let mut relative_path = file.get_relative_pathname(); + let mut relative_path = file.get_relative_path_name(); if Platform::is_windows() { relative_path = shirabe_php_shim::strtr(&relative_path, "\\", "/"); diff --git a/crates/shirabe/src/package/base_package.rs b/crates/shirabe/src/package/base_package.rs index 2e50c44..fb906d1 100644 --- a/crates/shirabe/src/package/base_package.rs +++ b/crates/shirabe/src/package/base_package.rs @@ -83,62 +83,20 @@ pub trait BasePackage: PackageInterface + std::fmt::Display { fn set_repository_box(&mut self, repository: Box<dyn RepositoryInterface>); fn take_repository(&mut self) -> Option<Box<dyn RepositoryInterface>>; - fn as_any(&self) -> &dyn std::any::Any; fn clone_box(&self) -> Box<dyn BasePackage>; - fn get_name(&self) -> &str { - self.name() - } - - fn get_pretty_name(&self) -> &str { - self.pretty_name() - } - - fn get_names(&self, provides: bool) -> Vec<String> { - let mut names: IndexMap<String, bool> = IndexMap::new(); - names.insert(self.get_name().to_string(), true); - - if provides { - for link in self.get_provides().values() { - names.insert(link.get_target().to_string(), true); - } - } - - for link in self.get_replaces().values() { - names.insert(link.get_target().to_string(), true); - } - - names.into_keys().collect() - } - - fn set_id(&mut self, id: i64) { - *self.id_mut() = id; - } + // as_alias_package / as_complete_package_interface inherited from PackageInterface. - fn get_id(&self) -> i64 { - self.id() + fn as_alias_package_mut(&mut self) -> Option<&mut crate::package::alias_package::AliasPackage> { + None } - fn set_repository(&mut self, repository: Box<dyn RepositoryInterface>) -> anyhow::Result<()> { - if let Some(existing) = self.repository_opt() { - // TODO(phase-b): proper reference identity check before raising error - return Err(anyhow::anyhow!(LogicException { - message: format!( - "Package \"{}\" cannot be added to repository \"{}\" as it is already in repository \"{}\".", - self.get_pretty_name(), - repository.get_repo_name(), - existing.get_repo_name(), - ), - code: 0, - })); - } - self.set_repository_box(repository); - Ok(()) - } + // get_name / get_pretty_name / get_names live on PackageInterface; the BasePackage + // duplicates were causing ambiguity at every call site (`pkg.get_name()` with + // pkg: &dyn BasePackage). Concrete impls already forward to name()/pretty_name(). - fn get_repository(&self) -> Option<&dyn RepositoryInterface> { - self.repository_opt() - } + // set_id, get_id, get_repository, get_unique_name, set_repository are inherited + // from PackageInterface; do not redeclare here to avoid trait-method ambiguity. fn is_platform(&self) -> bool { self.repository_opt() @@ -146,24 +104,18 @@ pub trait BasePackage: PackageInterface + std::fmt::Display { .is_some() } - fn get_unique_name(&self) -> String { - format!("{}-{}", self.get_name(), self.get_version()) - } - fn equals(&self, _package: &dyn PackageInterface) -> bool { // TODO(phase-b): implement via reference identity (requires Rc/Arc) // PHP uses === which is reference equality; unwraps AliasPackage on both sides todo!("equals requires reference identity which needs Rc/Arc") } - fn get_pretty_string(&self) -> String { - format!("{} {}", self.get_pretty_name(), self.get_pretty_version()) - } + // get_pretty_string is inherited from PackageInterface. fn get_full_pretty_version(&self, truncate: bool, display_mode: i64) -> anyhow::Result<String> { - const DISPLAY_SOURCE_REF_IF_DEV: i64 = PackageInterface::DISPLAY_SOURCE_REF_IF_DEV; - const DISPLAY_SOURCE_REF: i64 = PackageInterface::DISPLAY_SOURCE_REF; - const DISPLAY_DIST_REF: i64 = PackageInterface::DISPLAY_DIST_REF; + const DISPLAY_SOURCE_REF_IF_DEV: i64 = <dyn PackageInterface>::DISPLAY_SOURCE_REF_IF_DEV; + const DISPLAY_SOURCE_REF: i64 = <dyn PackageInterface>::DISPLAY_SOURCE_REF; + const DISPLAY_DIST_REF: i64 = <dyn PackageInterface>::DISPLAY_DIST_REF; if display_mode == DISPLAY_SOURCE_REF_IF_DEV && (!self.is_dev() @@ -207,7 +159,7 @@ pub trait BasePackage: PackageInterface + std::fmt::Display { fn get_stability_priority(&self) -> i64 { *STABILITIES .get(self.get_stability()) - .unwrap_or(&Self::STABILITY_STABLE) + .unwrap_or(&STABILITY_STABLE) } fn php_clone(&mut self) { diff --git a/crates/shirabe/src/package/comparer/comparer.rs b/crates/shirabe/src/package/comparer/comparer.rs index 0e0d295..8f406a5 100644 --- a/crates/shirabe/src/package/comparer/comparer.rs +++ b/crates/shirabe/src/package/comparer/comparer.rs @@ -65,7 +65,7 @@ impl Comparer { let mut source: IndexMap<String, IndexMap<String, Option<String>>> = IndexMap::new(); let mut destination: IndexMap<String, IndexMap<String, Option<String>>> = IndexMap::new(); self.changed = IndexMap::new(); - let current_directory = Platform::get_cwd(); + let current_directory = Platform::get_cwd(false).unwrap_or_default(); shirabe_php_shim::chdir(&self.source); if !Self::do_tree(".", &mut source) { return; diff --git a/crates/shirabe/src/package/complete_alias_package.rs b/crates/shirabe/src/package/complete_alias_package.rs index 134ed3a..d187eea 100644 --- a/crates/shirabe/src/package/complete_alias_package.rs +++ b/crates/shirabe/src/package/complete_alias_package.rs @@ -13,7 +13,13 @@ pub struct CompleteAliasPackage { impl CompleteAliasPackage { pub fn new(alias_of: CompletePackage, version: String, pretty_version: String) -> Self { - let inner = AliasPackage::new(alias_of.clone(), version, pretty_version); + // TODO(phase-b): alias_of is a PHP class (shared semantics); cloning is wrong. + // Use a dummy BasePackage placeholder until the field is migrated to Rc<CompletePackage>. + let inner = AliasPackage::new( + todo!("share CompletePackage via Rc"), + version, + pretty_version, + ); Self { inner, alias_of } } @@ -61,7 +67,8 @@ impl CompleteAliasPackage { } pub fn set_description(&mut self, description: Option<String>) { - self.alias_of.set_description(description); + self.alias_of + .set_description(description.unwrap_or_default()); } pub fn get_homepage(&self) -> Option<&str> { @@ -69,7 +76,7 @@ impl CompleteAliasPackage { } pub fn set_homepage(&mut self, homepage: Option<String>) { - self.alias_of.set_homepage(homepage); + self.alias_of.set_homepage(homepage.unwrap_or_default()); } pub fn get_authors(&self) -> Vec<indexmap::IndexMap<String, String>> { @@ -116,7 +123,7 @@ impl CompleteAliasPackage { } pub fn set_archive_name(&mut self, name: Option<String>) { - self.alias_of.set_archive_name(name); + self.alias_of.set_archive_name(name.unwrap_or_default()); } pub fn get_archive_excludes(&self) -> Vec<String> { diff --git a/crates/shirabe/src/package/complete_package.rs b/crates/shirabe/src/package/complete_package.rs index a3db372..27c49c6 100644 --- a/crates/shirabe/src/package/complete_package.rs +++ b/crates/shirabe/src/package/complete_package.rs @@ -133,6 +133,10 @@ impl CompletePackageInterface for CompletePackage { } impl PackageInterface for CompletePackage { + fn as_any(&self) -> &dyn std::any::Any { + self + } + fn get_name(&self) -> &str { todo!() } @@ -253,15 +257,15 @@ impl PackageInterface for CompletePackage { todo!() } - fn get_conflicts(&self) -> Vec<super::link::Link> { + fn get_conflicts(&self) -> IndexMap<String, super::link::Link> { todo!() } - fn get_provides(&self) -> Vec<super::link::Link> { + fn get_provides(&self) -> IndexMap<String, super::link::Link> { todo!() } - fn get_replaces(&self) -> Vec<super::link::Link> { + fn get_replaces(&self) -> IndexMap<String, super::link::Link> { todo!() } diff --git a/crates/shirabe/src/package/complete_package_interface.rs b/crates/shirabe/src/package/complete_package_interface.rs index 31780a7..d1120ac 100644 --- a/crates/shirabe/src/package/complete_package_interface.rs +++ b/crates/shirabe/src/package/complete_package_interface.rs @@ -55,4 +55,8 @@ pub trait CompletePackageInterface: PackageInterface { fn get_archive_excludes(&self) -> Vec<String>; fn set_archive_excludes(&mut self, excludes: Vec<String>); + + fn as_package_interface(&self) -> &dyn crate::package::package_interface::PackageInterface { + todo!() + } } diff --git a/crates/shirabe/src/package/dumper/array_dumper.rs b/crates/shirabe/src/package/dumper/array_dumper.rs index 02f1092..d81fd6c 100644 --- a/crates/shirabe/src/package/dumper/array_dumper.rs +++ b/crates/shirabe/src/package/dumper/array_dumper.rs @@ -131,8 +131,10 @@ impl ArrayDumper { } // corresponds to: foreach (BasePackage::$supportedLinkTypes as $type => $opts) { $links = $package->{'get'.ucfirst($opts['method'])}(); ... } - for (type_name, method_name) in BasePackage::supported_link_types() { - let links = package.get_links_by_method(&method_name); + for (type_name, method_name) in <dyn BasePackage>::supported_link_types() { + // TODO(phase-b): PackageInterface needs get_links_by_method to mimic PHP magic call + let links: Vec<crate::package::link::Link> = Vec::new(); + let _ = (&method_name, package); if links.is_empty() { continue; } @@ -186,10 +188,9 @@ impl ArrayDumper { ), ); } - if let Some(pkg_type) = package.get_type() { - if !pkg_type.is_empty() { - data.insert("type".to_string(), PhpMixed::String(pkg_type.to_string())); - } + let pkg_type = package.get_type(); + if !pkg_type.is_empty() { + data.insert("type".to_string(), PhpMixed::String(pkg_type.to_string())); } let extra = package.get_extra(); if !extra.is_empty() { @@ -247,10 +248,15 @@ impl ArrayDumper { ); } let php_ext = package.get_php_ext(); - if !php_ext.is_empty() { + if let Some(php_ext) = php_ext.as_ref().filter(|m| !m.is_empty()) { data.insert( "php-ext".to_string(), - PhpMixed::Array(php_ext.into_iter().map(|(k, v)| (k, Box::new(v))).collect()), + PhpMixed::Array( + php_ext + .iter() + .map(|(k, v)| (k.clone(), Box::new(v.clone()))) + .collect(), + ), ); } diff --git a/crates/shirabe/src/package/link.rs b/crates/shirabe/src/package/link.rs index 2233eb1..e41d431 100644 --- a/crates/shirabe/src/package/link.rs +++ b/crates/shirabe/src/package/link.rs @@ -13,6 +13,20 @@ pub struct Link { pub(crate) pretty_constraint: Option<String>, } +impl Clone for Link { + fn clone(&self) -> Self { + // TODO(phase-b): Link is a PHP class; this clone is a shallow placeholder until + // Link is shared via Rc<Link>. + Self { + source: self.source.clone(), + target: self.target.clone(), + constraint: self.constraint.clone_box(), + description: self.description.clone(), + pretty_constraint: self.pretty_constraint.clone(), + } + } +} + impl std::fmt::Debug for Link { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Link") diff --git a/crates/shirabe/src/package/loader/array_loader.rs b/crates/shirabe/src/package/loader/array_loader.rs index 28e13af..275d718 100644 --- a/crates/shirabe/src/package/loader/array_loader.rs +++ b/crates/shirabe/src/package/loader/array_loader.rs @@ -588,7 +588,7 @@ impl ArrayLoader { let alias_normalized = self.get_branch_alias(config)?; if let Some(alias_normalized) = alias_normalized { if !alias_normalized.is_empty() { - let pretty_alias = Preg::replace(r"{(\.9{7})+}", ".x", &alias_normalized); + let pretty_alias = Preg::replace(r"{(\.9{7})+}", ".x", &alias_normalized)?; // TODO(phase-b): `$package instanceof RootPackage` downcast from CompletePackage let package_as_root: Option<RootPackage> = None; diff --git a/crates/shirabe/src/package/loader/invalid_package_exception.rs b/crates/shirabe/src/package/loader/invalid_package_exception.rs index 96725ba..764b31f 100644 --- a/crates/shirabe/src/package/loader/invalid_package_exception.rs +++ b/crates/shirabe/src/package/loader/invalid_package_exception.rs @@ -1,5 +1,6 @@ //! ref: composer/src/Composer/Package/Loader/InvalidPackageException.php +use indexmap::IndexMap; use shirabe_php_shim::{Exception, PhpMixed}; #[derive(Debug)] @@ -7,11 +8,15 @@ pub struct InvalidPackageException { inner: Exception, errors: Vec<String>, warnings: Vec<String>, - data: Vec<PhpMixed>, + data: IndexMap<String, PhpMixed>, } impl InvalidPackageException { - pub fn new(errors: Vec<String>, warnings: Vec<String>, data: Vec<PhpMixed>) -> Self { + pub fn new( + errors: Vec<String>, + warnings: Vec<String>, + data: IndexMap<String, PhpMixed>, + ) -> Self { let message = format!( "Invalid package information: \n{}", errors @@ -29,7 +34,7 @@ impl InvalidPackageException { } } - pub fn get_data(&self) -> &[PhpMixed] { + pub fn get_data(&self) -> &IndexMap<String, PhpMixed> { &self.data } diff --git a/crates/shirabe/src/package/loader/json_loader.rs b/crates/shirabe/src/package/loader/json_loader.rs index 978bc28..cb3cbeb 100644 --- a/crates/shirabe/src/package/loader/json_loader.rs +++ b/crates/shirabe/src/package/loader/json_loader.rs @@ -22,13 +22,16 @@ impl JsonLoader { pub fn load(&self, json: JsonLoaderInput) -> Result<Box<dyn BasePackage>> { let config = match json { - JsonLoaderInput::File(json_file) => json_file.read()?, + JsonLoaderInput::File(mut json_file) => json_file.read()?, JsonLoaderInput::String(ref s) if Path::new(s).exists() => { - JsonFile::parse_json(&std::fs::read_to_string(s)?, Some(s))? + let contents = std::fs::read_to_string(s)?; + JsonFile::parse_json(Some(&contents), Some(s))? } - JsonLoaderInput::String(ref s) => JsonFile::parse_json(s, None)?, + JsonLoaderInput::String(ref s) => JsonFile::parse_json(Some(s), None)?, }; - self.loader.load(config, None) + // TODO(phase-b): JsonFile::parse_json returns PhpMixed; loader::load expects IndexMap + let _ = config; + self.loader.load(indexmap::IndexMap::new(), None) } } diff --git a/crates/shirabe/src/package/loader/validating_array_loader.rs b/crates/shirabe/src/package/loader/validating_array_loader.rs index 3ba9212..5009a0d 100644 --- a/crates/shirabe/src/package/loader/validating_array_loader.rs +++ b/crates/shirabe/src/package/loader/validating_array_loader.rs @@ -1288,7 +1288,10 @@ impl ValidatingArrayLoader { return Err(anyhow::anyhow!(InvalidPackageException::new( self.errors.clone(), self.warnings.clone(), - config.values().map(|v| (**v).clone()).collect(), + config + .iter() + .map(|(k, v)| (k.clone(), (**v).clone())) + .collect(), ))); } diff --git a/crates/shirabe/src/package/locker.rs b/crates/shirabe/src/package/locker.rs index 5950437..397af08 100644 --- a/crates/shirabe/src/package/locker.rs +++ b/crates/shirabe/src/package/locker.rs @@ -134,7 +134,6 @@ impl Locker { .collect(), ), 0, - JsonFile::INDENT_DEFAULT, ), )) } @@ -616,13 +615,13 @@ impl Locker { } else { self.virtual_file_written = true; self.lock_data_cache = Some(JsonFile::parse_json( - &JsonFile::encode( + Some(&JsonFile::encode_with_indent( &PhpMixed::Array(lock.into_iter().map(|(k, v)| (k, Box::new(v))).collect()), shirabe_php_shim::JSON_UNESCAPED_SLASHES | shirabe_php_shim::JSON_PRETTY_PRINT | shirabe_php_shim::JSON_UNESCAPED_UNICODE, JsonFile::INDENT_DEFAULT, - ), + )), None, )?); } diff --git a/crates/shirabe/src/package/package.rs b/crates/shirabe/src/package/package.rs index b8b6770..74286a0 100644 --- a/crates/shirabe/src/package/package.rs +++ b/crates/shirabe/src/package/package.rs @@ -71,7 +71,10 @@ impl Package { let stability = VersionParser::parse_stability(&version).to_string(); let dev = stability == "dev"; Self { - inner: BasePackage::new(name), + id: -1, + name: name.to_lowercase(), + pretty_name: name, + repository: None, r#type: None, target_dir: None, installation_source: None, @@ -458,7 +461,7 @@ impl Package { let url = if url_type == "dist" && strpos(url, "%").is_some() { ComposerMirror::process_url( url, - &self.inner.name, + &self.name, &self.version, r#ref.unwrap_or(""), r#type.unwrap_or(""), @@ -474,7 +477,7 @@ impl Package { let mirror_url = if url_type == "dist" { ComposerMirror::process_url( &mirror.url, - &self.inner.name, + &self.name, &self.version, r#ref.unwrap_or(""), r#type.unwrap_or(""), @@ -483,14 +486,14 @@ impl Package { } else if url_type == "source" && r#type == Some("git") { ComposerMirror::process_git_url( &mirror.url, - &self.inner.name, + &self.name, &url, r#type.unwrap_or(""), ) } else if url_type == "source" && r#type == Some("hg") { ComposerMirror::process_hg_url( &mirror.url, - &self.inner.name, + &self.name, &url, r#type.unwrap_or(""), ) @@ -568,10 +571,6 @@ impl BasePackage for Package { todo!() } - fn as_any(&self) -> &dyn std::any::Any { - todo!() - } - fn clone_box(&self) -> Box<dyn BasePackage> { todo!() } @@ -584,6 +583,9 @@ impl std::fmt::Display for Package { } impl PackageInterface for Package { + fn as_any(&self) -> &dyn std::any::Any { + self + } fn get_name(&self) -> &str { todo!() } @@ -674,13 +676,13 @@ impl PackageInterface for Package { fn get_requires(&self) -> IndexMap<String, Link> { todo!() } - fn get_conflicts(&self) -> Vec<Link> { + fn get_conflicts(&self) -> IndexMap<String, Link> { todo!() } - fn get_provides(&self) -> Vec<Link> { + fn get_provides(&self) -> IndexMap<String, Link> { todo!() } - fn get_replaces(&self) -> Vec<Link> { + fn get_replaces(&self) -> IndexMap<String, Link> { todo!() } fn get_dev_requires(&self) -> IndexMap<String, Link> { diff --git a/crates/shirabe/src/package/package_interface.rs b/crates/shirabe/src/package/package_interface.rs index 6a07556..c6ccce8 100644 --- a/crates/shirabe/src/package/package_interface.rs +++ b/crates/shirabe/src/package/package_interface.rs @@ -14,7 +14,9 @@ use crate::repository::repository_interface::RepositoryInterface; /// @phpstan-type AutoloadRules array{psr-0?: array<string, string|string[]>, psr-4?: array<string, string|string[]>, classmap?: list<string>, files?: list<string>, exclude-from-classmap?: list<string>} /// @phpstan-type DevAutoloadRules array{psr-0?: array<string, string|string[]>, psr-4?: array<string, string|string[]>, classmap?: list<string>, files?: list<string>} /// @phpstan-type PhpExtConfig array{extension-name?: string, priority?: int, support-zts?: bool, support-nts?: bool, build-path?: string|null, download-url-method?: string|list<string>, os-families?: non-empty-list<non-empty-string>, os-families-exclude?: non-empty-list<non-empty-string>, configure-options?: list<array{name: string, description?: string}>} -pub trait PackageInterface: std::fmt::Display { +pub trait PackageInterface: std::fmt::Display + std::fmt::Debug { + fn as_any(&self) -> &dyn std::any::Any; + /// Returns the package's name without version info, thus not a unique identifier /// /// @return string package name @@ -170,20 +172,20 @@ pub trait PackageInterface: std::fmt::Display { /// Returns a set of links to packages which must not be installed at the /// same time as this package /// - /// @return Link[] An array of package links defining conflicting packages - fn get_conflicts(&self) -> Vec<Link>; + /// @return array<string, Link> A map of package links defining conflicting packages + fn get_conflicts(&self) -> IndexMap<String, Link>; /// Returns a set of links to virtual packages that are provided through /// this package /// - /// @return Link[] An array of package links defining provided packages - fn get_provides(&self) -> Vec<Link>; + /// @return array<string, Link> A map of package links defining provided packages + fn get_provides(&self) -> IndexMap<String, Link>; /// Returns a set of links to packages which can alternatively be /// satisfied by installing this package /// - /// @return Link[] An array of package links defining replaced packages - fn get_replaces(&self) -> Vec<Link>; + /// @return array<string, Link> A map of package links defining replaced packages + fn get_replaces(&self) -> IndexMap<String, Link>; /// Returns a set of links to packages which are required to develop /// this package. These are installed if in dev mode. @@ -275,6 +277,30 @@ pub trait PackageInterface: std::fmt::Display { /// Set dist and source references and update dist URL for ones that contain a reference fn set_source_dist_references(&mut self, reference: &str); + + // clone_box was moved to BasePackage with a Box<dyn BasePackage> return type; + // exposing it here too caused trait-method ambiguity at every BasePackage call site. + // Callers holding `&dyn PackageInterface` (rather than `&dyn BasePackage`) can use + // `clone_package_box` instead. + fn clone_package_box(&self) -> Box<dyn PackageInterface> { + todo!() + } + + fn as_alias_package(&self) -> Option<&crate::package::alias_package::AliasPackage> { + None + } + + fn as_complete_package_interface( + &self, + ) -> Option<&dyn crate::package::complete_package_interface::CompletePackageInterface> { + None + } + + fn as_complete_package( + &self, + ) -> Option<&dyn crate::package::complete_package_interface::CompletePackageInterface> { + None + } } impl dyn PackageInterface { diff --git a/crates/shirabe/src/package/root_alias_package.rs b/crates/shirabe/src/package/root_alias_package.rs index 703c1f9..cb458f3 100644 --- a/crates/shirabe/src/package/root_alias_package.rs +++ b/crates/shirabe/src/package/root_alias_package.rs @@ -231,6 +231,10 @@ impl std::fmt::Display for RootAliasPackage { } impl PackageInterface for RootAliasPackage { + fn as_any(&self) -> &dyn std::any::Any { + self + } + fn get_name(&self) -> &str { todo!() } @@ -321,13 +325,13 @@ impl PackageInterface for RootAliasPackage { fn get_requires(&self) -> IndexMap<String, Link> { todo!() } - fn get_conflicts(&self) -> Vec<Link> { + fn get_conflicts(&self) -> IndexMap<String, Link> { todo!() } - fn get_provides(&self) -> Vec<Link> { + fn get_provides(&self) -> IndexMap<String, Link> { todo!() } - fn get_replaces(&self) -> Vec<Link> { + fn get_replaces(&self) -> IndexMap<String, Link> { todo!() } fn get_dev_requires(&self) -> IndexMap<String, Link> { diff --git a/crates/shirabe/src/package/root_package.rs b/crates/shirabe/src/package/root_package.rs index 2557c8a..af8d1f5 100644 --- a/crates/shirabe/src/package/root_package.rs +++ b/crates/shirabe/src/package/root_package.rs @@ -24,6 +24,21 @@ pub struct RootPackage { impl RootPackage { pub const DEFAULT_PRETTY_VERSION: &'static str = "1.0.0+no-version-set"; + + pub fn new(name: String, version: String, pretty_version: String) -> Self { + // TODO(phase-b): CompletePackage::new signature is not yet pinned down + let inner: CompletePackage = todo!(); + let _ = (name, version, pretty_version); + Self { + inner, + minimum_stability: "stable".to_string(), + prefer_stable: false, + stability_flags: IndexMap::new(), + config: IndexMap::new(), + references: IndexMap::new(), + aliases: Vec::new(), + } + } } impl RootPackageInterface for RootPackage { @@ -221,6 +236,10 @@ impl std::fmt::Display for RootPackage { } impl PackageInterface for RootPackage { + fn as_any(&self) -> &dyn std::any::Any { + self + } + fn get_name(&self) -> &str { todo!() } @@ -311,13 +330,13 @@ impl PackageInterface for RootPackage { fn get_requires(&self) -> IndexMap<String, Link> { todo!() } - fn get_conflicts(&self) -> Vec<Link> { + fn get_conflicts(&self) -> IndexMap<String, Link> { todo!() } - fn get_provides(&self) -> Vec<Link> { + fn get_provides(&self) -> IndexMap<String, Link> { todo!() } - fn get_replaces(&self) -> Vec<Link> { + fn get_replaces(&self) -> IndexMap<String, Link> { todo!() } fn get_dev_requires(&self) -> IndexMap<String, Link> { diff --git a/crates/shirabe/src/package/root_package_interface.rs b/crates/shirabe/src/package/root_package_interface.rs index a8634af..a053e28 100644 --- a/crates/shirabe/src/package/root_package_interface.rs +++ b/crates/shirabe/src/package/root_package_interface.rs @@ -3,6 +3,8 @@ use indexmap::IndexMap; use shirabe_php_shim::PhpMixed; +use crate::package::package_interface::PackageInterface; + use crate::package::complete_package_interface::CompletePackageInterface; use crate::package::link::Link; @@ -48,4 +50,16 @@ pub trait RootPackageInterface: CompletePackageInterface { fn set_suggests(&mut self, suggests: IndexMap<String, String>); fn set_extra(&mut self, extra: IndexMap<String, PhpMixed>); + + fn clone_as_package_interface(&self) -> Box<dyn PackageInterface> { + todo!() + } + + fn clone_box(&self) -> Box<dyn RootPackageInterface> { + todo!() + } + + fn as_package_interface(&self) -> &dyn PackageInterface { + todo!() + } } diff --git a/crates/shirabe/src/package/version/stability_filter.rs b/crates/shirabe/src/package/version/stability_filter.rs index d08492c..5053e9d 100644 --- a/crates/shirabe/src/package/version/stability_filter.rs +++ b/crates/shirabe/src/package/version/stability_filter.rs @@ -1,6 +1,6 @@ //! ref: composer/src/Composer/Package/Version/StabilityFilter.php -use crate::package::base_package::BasePackage; +use crate::package::base_package::STABILITIES; use indexmap::IndexMap; pub struct StabilityFilter; @@ -15,7 +15,7 @@ impl StabilityFilter { for name in names { // allow if package matches the package-specific stability flag if let Some(&flag) = stability_flags.get(name) { - if let Some(&stability_value) = BasePackage::STABILITIES.get(stability) { + if let Some(&stability_value) = STABILITIES.get(stability) { if stability_value <= flag { return true; } diff --git a/crates/shirabe/src/package/version/version_bumper.rs b/crates/shirabe/src/package/version/version_bumper.rs index 5911458..8963fd6 100644 --- a/crates/shirabe/src/package/version/version_bumper.rs +++ b/crates/shirabe/src/package/version/version_bumper.rs @@ -26,11 +26,15 @@ impl VersionBumper { return Ok(pretty_constraint); } - let mut version = package.get_version(); - if package.get_version().starts_with("dev-") { - let loader = ArrayLoader::new(&parser); + let mut version = package.get_version().to_string(); + if version.starts_with("dev-") { + // TODO(phase-b): ArrayLoader::new takes Option<VersionParser> by value; pass None until + // VersionParser sharing is reconciled. + let _ = &parser; + let loader = ArrayLoader::new(None, false); let dumper = ArrayDumper::new(); - let extra = loader.get_branch_alias(dumper.dump(package)); + let dumped = dumper.dump(package); + let extra = loader.get_branch_alias(&dumped)?; if extra.is_none() || extra.as_deref() == Some(VersionParser::DEFAULT_BRANCH_ALIAS) { return Ok(pretty_constraint); diff --git a/crates/shirabe/src/package/version/version_parser.rs b/crates/shirabe/src/package/version/version_parser.rs index 6bb1004..6dbf425 100644 --- a/crates/shirabe/src/package/version/version_parser.rs +++ b/crates/shirabe/src/package/version/version_parser.rs @@ -74,6 +74,28 @@ impl VersionParser { Ok(result) } + pub fn new() -> Self { + Self { + inner: SemverVersionParser, + } + } + + pub fn normalize(&self, version: &str, full_version: Option<&str>) -> anyhow::Result<String> { + self.inner.normalize(version, full_version) + } + + pub fn normalize_stability(stability: &str) -> anyhow::Result<String> { + SemverVersionParser::normalize_stability(stability) + } + + pub fn normalize_branch(&self, name: &str) -> anyhow::Result<String> { + self.inner.normalize_branch(name) + } + + pub fn parse_stability(version: &str) -> String { + SemverVersionParser::parse_stability(version) + } + pub fn is_upgrade(normalized_from: &str, normalized_to: &str) -> anyhow::Result<bool> { if normalized_from == normalized_to { return Ok(true); diff --git a/crates/shirabe/src/partial_composer.rs b/crates/shirabe/src/partial_composer.rs index a08e272..5547b5a 100644 --- a/crates/shirabe/src/partial_composer.rs +++ b/crates/shirabe/src/partial_composer.rs @@ -11,7 +11,7 @@ use crate::util::r#loop::Loop; pub struct PartialComposer { global: bool, package: Option<Box<dyn RootPackageInterface>>, - r#loop: Option<Loop>, + r#loop: Option<std::rc::Rc<std::cell::RefCell<Loop>>>, repository_manager: Option<RepositoryManager>, installation_manager: Option<InstallationManager>, config: Option<Config>, @@ -35,11 +35,11 @@ impl PartialComposer { self.config.as_ref().unwrap() } - pub fn set_loop(&mut self, r#loop: Loop) { + pub fn set_loop(&mut self, r#loop: std::rc::Rc<std::cell::RefCell<Loop>>) { self.r#loop = Some(r#loop); } - pub fn get_loop(&self) -> &Loop { + pub fn get_loop(&self) -> &std::rc::Rc<std::cell::RefCell<Loop>> { self.r#loop.as_ref().unwrap() } diff --git a/crates/shirabe/src/platform/hhvm_detector.rs b/crates/shirabe/src/platform/hhvm_detector.rs index c6a53cb..b425bfe 100644 --- a/crates/shirabe/src/platform/hhvm_detector.rs +++ b/crates/shirabe/src/platform/hhvm_detector.rs @@ -9,6 +9,7 @@ use std::sync::Mutex; // None = null (uninitialized), Some(None) = false (not found), Some(Some(v)) = version static HHVM_VERSION_CACHE: Mutex<Option<Option<String>>> = Mutex::new(None); +#[derive(Debug)] pub struct HhvmDetector { executable_finder: Option<ExecutableFinder>, process_executor: Option<ProcessExecutor>, @@ -47,25 +48,30 @@ impl HhvmDetector { let finder = self .executable_finder .get_or_insert_with(ExecutableFinder::new); - let hhvm_path = finder.find("hhvm"); + let hhvm_path = finder.find("hhvm", None, &[]); if let Some(hhvm_path) = hhvm_path { let executor = self .process_executor - .get_or_insert_with(ProcessExecutor::new); - let mut version_output = String::new(); - let exit_code = executor.execute( - &[ - &hhvm_path, + .get_or_insert_with(|| ProcessExecutor::new(None, None)); + let mut version_output = shirabe_php_shim::PhpMixed::Null; + let cmd = shirabe_php_shim::PhpMixed::List( + [ + hhvm_path.as_str(), "--php", "-d", "hhvm.jit=0", "-r", "echo HHVM_VERSION;", - ], - &mut version_output, + ] + .into_iter() + .map(|s| Box::new(shirabe_php_shim::PhpMixed::String(s.to_string()))) + .collect(), ); + let exit_code = executor + .execute(cmd, Some(&mut version_output), None) + .unwrap_or(1); if exit_code == 0 { - *cache = Some(Some(version_output)); + *cache = Some(version_output.as_string().map(|s| s.to_string())); } } } diff --git a/crates/shirabe/src/plugin/command_event.rs b/crates/shirabe/src/plugin/command_event.rs index aa5b366..715f29a 100644 --- a/crates/shirabe/src/plugin/command_event.rs +++ b/crates/shirabe/src/plugin/command_event.rs @@ -1,6 +1,7 @@ //! ref: composer/src/Composer/Plugin/CommandEvent.php use crate::event_dispatcher::event::Event; +use indexmap::IndexMap; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; use shirabe_php_shim::PhpMixed; @@ -9,34 +10,28 @@ use shirabe_php_shim::PhpMixed; pub struct CommandEvent { inner: Event, command_name: String, - input: Box<dyn InputInterface>, - output: Box<dyn OutputInterface>, } impl CommandEvent { + // TODO(phase-b): input/output dropped because storing &dyn references in an event would + // require lifetime parameters; restore once Plugin API needs them. pub fn new( name: String, command_name: String, - input: Box<dyn InputInterface>, - output: Box<dyn OutputInterface>, - args: Vec<PhpMixed>, - flags: Vec<PhpMixed>, + _input: &dyn InputInterface, + _output: &dyn OutputInterface, + args: Vec<String>, + flags: IndexMap<String, PhpMixed>, ) -> Self { let inner = Event::new(name, args, flags); Self { inner, command_name, - input, - output, } } - pub fn get_input(&self) -> &dyn InputInterface { - self.input.as_ref() - } - - pub fn get_output(&self) -> &dyn OutputInterface { - self.output.as_ref() + pub fn get_name(&self) -> &str { + self.inner.get_name() } pub fn get_command_name(&self) -> &str { diff --git a/crates/shirabe/src/plugin/plugin_blocked_exception.rs b/crates/shirabe/src/plugin/plugin_blocked_exception.rs index cf3c7ce..fcf52ac 100644 --- a/crates/shirabe/src/plugin/plugin_blocked_exception.rs +++ b/crates/shirabe/src/plugin/plugin_blocked_exception.rs @@ -6,6 +6,12 @@ use shirabe_php_shim::UnexpectedValueException; #[derive(Debug)] pub struct PluginBlockedException(pub UnexpectedValueException); +impl PluginBlockedException { + pub fn new(message: String) -> Self { + Self(UnexpectedValueException { message, code: 0 }) + } +} + impl std::fmt::Display for PluginBlockedException { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) diff --git a/crates/shirabe/src/plugin/plugin_interface.rs b/crates/shirabe/src/plugin/plugin_interface.rs index d536380..a7bb384 100644 --- a/crates/shirabe/src/plugin/plugin_interface.rs +++ b/crates/shirabe/src/plugin/plugin_interface.rs @@ -5,10 +5,14 @@ use crate::io::io_interface::IOInterface; pub const PLUGIN_API_VERSION: &'static str = "2.9.0"; -pub trait PluginInterface { +pub trait PluginInterface: std::fmt::Debug { fn activate(&mut self, composer: &Composer, io: &dyn IOInterface); fn deactivate(&mut self, composer: &Composer, io: &dyn IOInterface); fn uninstall(&mut self, composer: &Composer, io: &dyn IOInterface); + + fn clone_box(&self) -> Box<dyn PluginInterface> { + todo!() + } } diff --git a/crates/shirabe/src/plugin/plugin_manager.rs b/crates/shirabe/src/plugin/plugin_manager.rs index adf5954..24b8e0b 100644 --- a/crates/shirabe/src/plugin/plugin_manager.rs +++ b/crates/shirabe/src/plugin/plugin_manager.rs @@ -832,14 +832,15 @@ impl PluginManager { "allow-plugins", PhpMixed::Array(allow_plugins.clone()), ); - let mut wrap: IndexMap<String, Box<PhpMixed>> = IndexMap::new(); + // TODO(phase-b): get_config() returns &Config, but merge needs &mut Config; ownership needs refactoring let mut inner: IndexMap<String, Box<PhpMixed>> = IndexMap::new(); inner.insert( "allow-plugins".to_string(), Box::new(PhpMixed::Array(allow_plugins)), ); - wrap.insert("config".to_string(), Box::new(PhpMixed::Array(inner))); - composer_ref.get_config().merge(PhpMixed::Array(wrap), ""); + let mut wrap: IndexMap<String, PhpMixed> = IndexMap::new(); + wrap.insert("config".to_string(), PhpMixed::Array(inner)); + todo!("see TODO(phase-b) above"); } } diff --git a/crates/shirabe/src/plugin/post_file_download_event.rs b/crates/shirabe/src/plugin/post_file_download_event.rs index f0e2569..549807a 100644 --- a/crates/shirabe/src/plugin/post_file_download_event.rs +++ b/crates/shirabe/src/plugin/post_file_download_event.rs @@ -33,6 +33,10 @@ impl PostFileDownloadEvent { } } + pub fn get_name(&self) -> &str { + self.inner.get_name() + } + pub fn get_file_name(&self) -> Option<&str> { self.file_name.as_deref() } diff --git a/crates/shirabe/src/plugin/pre_command_run_event.rs b/crates/shirabe/src/plugin/pre_command_run_event.rs index 40672c0..7a8b2f1 100644 --- a/crates/shirabe/src/plugin/pre_command_run_event.rs +++ b/crates/shirabe/src/plugin/pre_command_run_event.rs @@ -4,24 +4,21 @@ use crate::event_dispatcher::event::Event; use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; +#[derive(Debug)] pub struct PreCommandRunEvent { inner: Event, - input: Box<dyn InputInterface>, command: String, } impl PreCommandRunEvent { - pub fn new(name: String, input: Box<dyn InputInterface>, command: String) -> Self { - let inner = Event::new(name); - Self { - inner, - input, - command, - } + // TODO(phase-b): input dropped because storing a &dyn reference would need lifetime params. + pub fn new(name: String, _input: &dyn InputInterface, command: String) -> Self { + let inner = Event::new(name, vec![], indexmap::IndexMap::new()); + Self { inner, command } } - pub fn get_input(&self) -> &dyn InputInterface { - self.input.as_ref() + pub fn get_name(&self) -> &str { + self.inner.get_name() } pub fn get_command(&self) -> &str { diff --git a/crates/shirabe/src/plugin/pre_pool_create_event.rs b/crates/shirabe/src/plugin/pre_pool_create_event.rs index 5d1a4e9..a1a6bc6 100644 --- a/crates/shirabe/src/plugin/pre_pool_create_event.rs +++ b/crates/shirabe/src/plugin/pre_pool_create_event.rs @@ -21,6 +21,10 @@ pub struct PrePoolCreateEvent { } impl PrePoolCreateEvent { + pub fn get_name(&self) -> &str { + self.inner.get_name() + } + #[allow(clippy::too_many_arguments)] pub fn new( name: String, diff --git a/crates/shirabe/src/question/strict_confirmation_question.rs b/crates/shirabe/src/question/strict_confirmation_question.rs index eea6629..b3a7196 100644 --- a/crates/shirabe/src/question/strict_confirmation_question.rs +++ b/crates/shirabe/src/question/strict_confirmation_question.rs @@ -19,7 +19,7 @@ impl StrictConfirmationQuestion { true_answer_regex: String, false_answer_regex: String, ) -> Self { - let inner = Question::new(question, PhpMixed::Bool(default)); + let inner = Question::new(&question, Some(PhpMixed::Bool(default))); let mut this = Self { inner, true_answer_regex, @@ -28,7 +28,7 @@ impl StrictConfirmationQuestion { let normalizer = this.get_default_normalizer(); let validator = this.get_default_validator(); this.inner.set_normalizer(normalizer); - this.inner.set_validator(validator); + this.inner.set_validator(Some(validator)); this } @@ -41,14 +41,14 @@ impl StrictConfirmationQuestion { if is_bool(answer) { return answer.clone(); } - if empty(answer) && !empty(&default) { - return default.clone(); + if empty(answer) && default.as_ref().is_some_and(|d| !empty(d)) { + return default.clone().unwrap_or(PhpMixed::Null); } if let PhpMixed::String(s) = answer { - if Preg::is_match(&true_regex, s) { + if Preg::is_match(&true_regex, s).unwrap_or(false) { return PhpMixed::Bool(true); } - if Preg::is_match(&false_regex, s) { + if Preg::is_match(&false_regex, s).unwrap_or(false) { return PhpMixed::Bool(false); } } @@ -56,16 +56,17 @@ impl StrictConfirmationQuestion { }) } - fn get_default_validator(&self) -> Box<dyn Fn(&PhpMixed) -> Result<PhpMixed>> { - Box::new(|answer: &PhpMixed| { - if !is_bool(answer) { + fn get_default_validator(&self) -> Box<dyn Fn(Option<PhpMixed>) -> Result<PhpMixed>> { + Box::new(|answer: Option<PhpMixed>| { + let answer = answer.unwrap_or(PhpMixed::Null); + if !is_bool(&answer) { return Err(InvalidArgumentException { message: "Please answer yes, y, no, or n.".to_string(), code: 0, } .into()); } - Ok(answer.clone()) + Ok(answer) }) } } diff --git a/crates/shirabe/src/repository/array_repository.rs b/crates/shirabe/src/repository/array_repository.rs index 5262b5e..5652dad 100644 --- a/crates/shirabe/src/repository/array_repository.rs +++ b/crates/shirabe/src/repository/array_repository.rs @@ -176,8 +176,10 @@ impl RepositoryInterface for ArrayRepository { let mut result: IndexMap<String, Box<dyn BasePackage>> = IndexMap::new(); let mut names_found: IndexMap<String, bool> = IndexMap::new(); for package in &packages { - if package_name_map.contains_key(package.get_name()) { - let constraint_opt = package_name_map.get(package.get_name()).unwrap(); + if package_name_map.contains_key(PackageInterface::get_name(package.as_ref())) { + let constraint_opt = package_name_map + .get(PackageInterface::get_name(package.as_ref())) + .unwrap(); let constraint_matches = match constraint_opt { None => true, Some(c) => c.matches(&Constraint::new("==", package.get_version())), @@ -186,11 +188,11 @@ impl RepositoryInterface for ArrayRepository { && StabilityFilter::is_package_acceptable( &acceptable_stabilities, &stability_flags, - &package.get_names(true), + &PackageInterface::get_names(package.as_ref(), true), package.get_stability(), ) && !already_loaded - .get(package.get_name()) + .get(PackageInterface::get_name(package.as_ref())) .map(|v| v.contains_key(package.get_version())) .unwrap_or(false) { @@ -207,7 +209,10 @@ impl RepositoryInterface for ArrayRepository { } } - names_found.insert(package.get_name().to_string(), true); + names_found.insert( + PackageInterface::get_name(package.as_ref()).to_string(), + true, + ); } } @@ -244,7 +249,7 @@ impl RepositoryInterface for ArrayRepository { }; for package in self.get_packages() { - if name == package.get_name() { + if name == PackageInterface::get_name(package.as_ref()) { let pkg_constraint = Constraint::new("==", package.get_version()); if constraint.matches(&pkg_constraint) { return Some(package); @@ -275,7 +280,7 @@ impl RepositoryInterface for ArrayRepository { }; for package in self.get_packages() { - if name == package.get_name() { + if name == PackageInterface::get_name(package.as_ref()) { if constraint.is_none() || constraint .as_ref() @@ -303,7 +308,7 @@ impl RepositoryInterface for ArrayRepository { let mut matches: IndexMap<String, SearchResult> = IndexMap::new(); for package in self.get_packages() { - let mut name = package.get_name().to_string(); + let mut name = PackageInterface::get_name(package.as_ref()).to_string(); if mode == Self::SEARCH_VENDOR { // PHP: [$name] = explode('/', $name); let parts: Vec<&str> = name.splitn(2, '/').collect(); @@ -395,7 +400,7 @@ impl RepositoryInterface for ArrayRepository { let mut result: IndexMap<String, ProviderInfo> = IndexMap::new(); 'candidates: for candidate in self.get_packages() { - if result.contains_key(candidate.get_name()) { + if result.contains_key(PackageInterface::get_name(candidate.as_ref())) { continue; } for link in candidate.get_provides().values() { @@ -404,9 +409,9 @@ impl RepositoryInterface for ArrayRepository { (candidate.as_any() as &dyn Any).downcast_ref::<CompletePackage>(); let description = complete.and_then(|c| c.get_description().map(String::from)); result.insert( - candidate.get_name().to_string(), + PackageInterface::get_name(candidate.as_ref()).to_string(), ProviderInfo { - name: candidate.get_name().to_string(), + name: PackageInterface::get_name(candidate.as_ref()).to_string(), description, r#type: candidate.get_type().to_string(), }, diff --git a/crates/shirabe/src/repository/composer_repository.rs b/crates/shirabe/src/repository/composer_repository.rs index 9acebe0..dfbb0b5 100644 --- a/crates/shirabe/src/repository/composer_repository.rs +++ b/crates/shirabe/src/repository/composer_repository.rs @@ -37,6 +37,7 @@ use crate::repository::advisory_provider_interface::{ use crate::repository::array_repository::ArrayRepository; use crate::repository::configurable_repository_interface::ConfigurableRepositoryInterface; use crate::repository::platform_repository::PlatformRepository; +use crate::repository::repository_interface::RepositoryInterface; use crate::repository::repository_security_exception::RepositorySecurityException; use crate::util::http::response::Response; use crate::util::http_downloader::HttpDownloader; @@ -84,7 +85,7 @@ pub struct ComposerRepository { base_url: String, io: Box<dyn IOInterface>, http_downloader: HttpDownloader, - r#loop: Loop, + r#loop: std::rc::Rc<std::cell::RefCell<Loop>>, pub(crate) cache: Cache, pub(crate) notify_url: Option<String>, pub(crate) search_url: Option<String>, @@ -270,7 +271,10 @@ impl ComposerRepository { let version_parser = VersionParser::new(); let loader = ArrayLoader::new_with_parser(version_parser.clone()); - let r#loop = Loop::new(http_downloader.clone(), None); + let r#loop = std::rc::Rc::new(std::cell::RefCell::new(Loop::new( + http_downloader.clone(), + None, + ))); let mut this = Self { inner, @@ -1010,13 +1014,13 @@ impl ComposerRepository { } let parser = VersionParser::new(); + let semver_parser = shirabe_semver::version_parser::VersionParser; let repo_name = self.get_repo_name(); let create = |data: &IndexMap<String, PhpMixed>, name: &str, package_constraint_map: &IndexMap<String, Box<dyn ConstraintInterface>>| -> anyhow::Result<Option<PartialOrSecurityAdvisory>> { - let advisory = - PartialSecurityAdvisory::create(name.to_string(), data.clone(), &parser)?; + let advisory = PartialSecurityAdvisory::create(name, data, &semver_parser)?; let is_full = matches!(advisory, PartialOrSecurityAdvisory::Full(_)); if !allow_partial_advisories && !is_full { let data_mixed = PhpMixed::Array( @@ -1036,13 +1040,13 @@ impl ComposerRepository { } .into()); } - let affected_versions = match &advisory { - PartialOrSecurityAdvisory::Partial(p) => &p.affected_versions, - PartialOrSecurityAdvisory::Full(p) => &p.inner.affected_versions, + let affected_versions: &dyn ConstraintInterface = match &advisory { + PartialOrSecurityAdvisory::Partial(p) => &*p.affected_versions, + PartialOrSecurityAdvisory::Full(p) => p.affected_versions(), }; let constraint = package_constraint_map.get(name).map(|c| &**c); if let Some(c) = constraint { - if !affected_versions.matches_constraint(c) { + if !affected_versions.matches(c) { return Ok(None); } } else { @@ -1126,7 +1130,7 @@ impl ComposerRepository { promises.push(promise); } - self.r#loop.wait(promises, None)?; + self.r#loop.borrow_mut().wait(promises, None)?; } if let Some(api_url) = api_url { @@ -1980,7 +1984,7 @@ impl ComposerRepository { promises.push(promise); } - self.r#loop.wait(promises, None)?; + self.r#loop.borrow_mut().wait(promises, None)?; Ok(LoadAsyncPackagesResult { names_found, @@ -2907,7 +2911,7 @@ impl ComposerRepository { .map(|(k, v)| (k.clone(), Box::new(v.clone()))) .collect(), ); - json = JsonFile::encode(&as_mixed, 0)?; + json = JsonFile::encode(&as_mixed, 0); } } self.cache.write(ck, &json); @@ -3086,7 +3090,7 @@ impl ComposerRepository { .map(|(k, v)| (k.clone(), Box::new(v.clone()))) .collect(), ); - json = JsonFile::encode(&as_mixed, 0)?; + json = JsonFile::encode(&as_mixed, 0); } if !self.cache.is_read_only() { self.cache.write(cache_key, &json); @@ -3262,7 +3266,7 @@ impl ComposerRepository { json = JsonFile::encode( &as_mixed, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE, - )?; + ); } let is_ro = unsafe { (*cache_ptr).is_read_only() }; if !is_ro { diff --git a/crates/shirabe/src/repository/filesystem_repository.rs b/crates/shirabe/src/repository/filesystem_repository.rs index 0acc3b6..3d785e7 100644 --- a/crates/shirabe/src/repository/filesystem_repository.rs +++ b/crates/shirabe/src/repository/filesystem_repository.rs @@ -7,10 +7,10 @@ use anyhow::Result; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::preg::Preg; use shirabe_php_shim::{ - InvalidArgumentException, LogicException, PhpMixed, SORT_NATURAL, UnexpectedValueException, - array_flip, dirname, r#eval, file_get_contents, get_class, get_debug_type, in_array, is_array, - is_int, is_null, is_string, ksort, php_dir, realpath, sort, sort_with_flags, str_repeat, strtr, - trim, usort, var_export, + Exception, InvalidArgumentException, LogicException, PhpMixed, SORT_NATURAL, + UnexpectedValueException, array_flip, dirname, r#eval, file_get_contents, get_class, + get_debug_type, in_array, is_array, is_int, is_null, is_string, ksort, php_dir, realpath, sort, + sort_with_flags, str_repeat, strtr, trim, usort, var_export, }; use crate::installed_versions::InstalledVersions; @@ -55,7 +55,7 @@ impl FilesystemRepository { root_package: Option<Box<dyn RootPackageInterface>>, filesystem: Option<Filesystem>, ) -> Result<Self> { - let filesystem = filesystem.unwrap_or_else(Filesystem::new); + let filesystem = filesystem.unwrap_or_else(|| Filesystem::new(None)); if dump_versions && root_package.is_none() { return Err(InvalidArgumentException { message: "Expected a root package instance if $dumpVersions is true".to_string(), @@ -79,6 +79,10 @@ impl FilesystemRepository { self.dev_mode } + pub fn get_repo_name(&self) -> String { + format!("file ({})", self.file.get_path()) + } + /// Initializes repository (reads file, or remote address). pub(crate) fn initialize(&mut self) -> Result<()> { self.inner.initialize(); @@ -129,12 +133,15 @@ impl FilesystemRepository { })() { Ok(p) => p, Err(e) => { - return Err(InvalidRepositoryException::new(format!( - "Invalid repository data in {}, packages could not be loaded: [{}] {}", - self.file.get_path(), - get_class(&e), - e, - )) + return Err(InvalidRepositoryException(Exception { + message: format!( + "Invalid repository data in {}, packages could not be loaded: [{}] {}", + self.file.get_path(), + get_class(&e), + e, + ), + code: 0, + }) .into()); } }; diff --git a/crates/shirabe/src/repository/filter_repository.rs b/crates/shirabe/src/repository/filter_repository.rs index c2d5fdf..ffdb3b9 100644 --- a/crates/shirabe/src/repository/filter_repository.rs +++ b/crates/shirabe/src/repository/filter_repository.rs @@ -44,7 +44,10 @@ impl FilterRepository { } }) .collect(); - only = Some(base_package::package_names_to_regexp(&names)); + only = Some(base_package::package_names_to_regexp( + &names, + "{^(?:%s)$}iD", + )); } _ => { return Err(InvalidArgumentException { @@ -71,7 +74,10 @@ impl FilterRepository { } }) .collect(); - exclude = Some(base_package::package_names_to_regexp(&names)); + exclude = Some(base_package::package_names_to_regexp( + &names, + "{^(?:%s)$}iD", + )); } _ => { return Err(InvalidArgumentException { @@ -131,14 +137,14 @@ impl FilterRepository { } if let Some(only) = &self.only { - return Preg::is_match(only, name); + return Preg::is_match(only, name).unwrap_or(false); } if self.exclude.is_none() { return true; } - !Preg::is_match(self.exclude.as_ref().unwrap(), name) + !Preg::is_match(self.exclude.as_ref().unwrap(), name).unwrap_or(false) } } @@ -225,7 +231,7 @@ impl RepositoryInterface for FilterRepository { fn get_packages(&self) -> Vec<Box<dyn BasePackage>> { let mut result = Vec::new(); for package in self.repo.get_packages() { - if self.is_allowed(package.get_name()) { + if self.is_allowed(PackageInterface::get_name(package.as_ref())) { result.push(package); } } diff --git a/crates/shirabe/src/repository/installed_filesystem_repository.rs b/crates/shirabe/src/repository/installed_filesystem_repository.rs index ff28f6e..1d1caf6 100644 --- a/crates/shirabe/src/repository/installed_filesystem_repository.rs +++ b/crates/shirabe/src/repository/installed_filesystem_repository.rs @@ -1,11 +1,14 @@ //! ref: composer/src/Composer/Repository/InstalledFilesystemRepository.php +use anyhow::Result; use indexmap::IndexMap; use shirabe_php_shim::Countable; use shirabe_semver::constraint::constraint_interface::ConstraintInterface; +use crate::json::json_file::JsonFile; use crate::package::base_package::BasePackage; use crate::package::package_interface::PackageInterface; +use crate::package::root_package_interface::RootPackageInterface; use crate::repository::advisory_provider_interface::AdvisoryProviderInterface; use crate::repository::filesystem_repository::FilesystemRepository; use crate::repository::installed_repository_interface::InstalledRepositoryInterface; @@ -13,6 +16,7 @@ use crate::repository::repository_interface::{ FindPackageConstraint, LoadPackagesResult, ProviderInfo, RepositoryInterface, SearchResult, }; use crate::repository::writable_repository_interface::WritableRepositoryInterface; +use crate::util::filesystem::Filesystem; #[derive(Debug)] pub struct InstalledFilesystemRepository { @@ -20,6 +24,22 @@ pub struct InstalledFilesystemRepository { } impl InstalledFilesystemRepository { + pub fn new( + repository_file: JsonFile, + dump_versions: bool, + root_package: Option<Box<dyn RootPackageInterface>>, + filesystem: Option<Filesystem>, + ) -> Result<Self> { + Ok(Self { + inner: FilesystemRepository::new( + repository_file, + dump_versions, + root_package, + filesystem, + )?, + }) + } + pub fn get_repo_name(&self) -> String { format!("installed {}", self.inner.get_repo_name()) } diff --git a/crates/shirabe/src/repository/installed_repository.rs b/crates/shirabe/src/repository/installed_repository.rs index 3b6563d..d0130f4 100644 --- a/crates/shirabe/src/repository/installed_repository.rs +++ b/crates/shirabe/src/repository/installed_repository.rs @@ -37,14 +37,16 @@ pub struct InstalledRepository { } impl InstalledRepository { - pub fn new(repositories: Vec<Box<dyn RepositoryInterface>>) -> anyhow::Result<Self> { + pub fn new(repositories: Vec<Box<dyn RepositoryInterface>>) -> Self { let mut this = Self { inner: CompositeRepository::new(vec![]), }; for repo in repositories { - this.add_repository(repo)?; + // TODO(phase-b): add_repository validates the inner repo type and may return Err; + // ignoring the error during Phase B since callers do not handle it. + let _ = this.add_repository(repo); } - Ok(this) + this } pub fn find_packages_with_replacers_and_providers( diff --git a/crates/shirabe/src/repository/invalid_repository_exception.rs b/crates/shirabe/src/repository/invalid_repository_exception.rs index d66ed1a..e7aa850 100644 --- a/crates/shirabe/src/repository/invalid_repository_exception.rs +++ b/crates/shirabe/src/repository/invalid_repository_exception.rs @@ -6,6 +6,12 @@ use shirabe_php_shim::Exception; #[derive(Debug)] pub struct InvalidRepositoryException(pub Exception); +impl InvalidRepositoryException { + pub fn new(message: String) -> Self { + Self(Exception { message, code: 0 }) + } +} + impl std::fmt::Display for InvalidRepositoryException { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) diff --git a/crates/shirabe/src/repository/lock_array_repository.rs b/crates/shirabe/src/repository/lock_array_repository.rs index b44f27d..c0a5034 100644 --- a/crates/shirabe/src/repository/lock_array_repository.rs +++ b/crates/shirabe/src/repository/lock_array_repository.rs @@ -1,7 +1,15 @@ //! ref: composer/src/Composer/Repository/LockArrayRepository.php +use crate::package::base_package::BasePackage; +use crate::package::package_interface::PackageInterface; use crate::repository::array_repository::ArrayRepository; use crate::repository::canonical_packages_trait::CanonicalPackagesTrait; +use crate::repository::repository_interface::{ + FindPackageConstraint, LoadPackagesResult, ProviderInfo, RepositoryInterface, SearchResult, +}; +use indexmap::IndexMap; +use shirabe_php_shim::Countable; +use shirabe_semver::constraint::constraint_interface::ConstraintInterface; #[derive(Debug)] pub struct LockArrayRepository { @@ -9,13 +17,76 @@ pub struct LockArrayRepository { } impl CanonicalPackagesTrait for LockArrayRepository { - fn get_packages(&self) -> Vec<Box<dyn crate::package::package_interface::PackageInterface>> { + fn get_packages(&self) -> Vec<Box<dyn PackageInterface>> { todo!() } } impl LockArrayRepository { - pub fn get_repo_name(&self) -> &str { - "lock repo" + pub fn clone_box(&self) -> Box<dyn RepositoryInterface> { + todo!() + } +} + +impl Countable for LockArrayRepository { + fn count(&self) -> i64 { + self.inner.count() + } +} + +impl RepositoryInterface for LockArrayRepository { + fn has_package(&self, package: &dyn PackageInterface) -> bool { + self.inner.has_package(package) + } + + fn find_package( + &self, + name: String, + constraint: FindPackageConstraint, + ) -> Option<Box<dyn BasePackage>> { + self.inner.find_package(name, constraint) + } + + fn find_packages( + &self, + name: String, + constraint: Option<FindPackageConstraint>, + ) -> Vec<Box<dyn BasePackage>> { + self.inner.find_packages(name, constraint) + } + + fn get_packages(&self) -> Vec<Box<dyn BasePackage>> { + RepositoryInterface::get_packages(&self.inner) + } + + fn load_packages( + &self, + package_name_map: IndexMap<String, Option<Box<dyn ConstraintInterface>>>, + acceptable_stabilities: IndexMap<String, i64>, + stability_flags: IndexMap<String, i64>, + already_loaded: IndexMap<String, IndexMap<String, Box<dyn PackageInterface>>>, + ) -> LoadPackagesResult { + self.inner.load_packages( + package_name_map, + acceptable_stabilities, + stability_flags, + already_loaded, + ) + } + + fn search(&self, query: String, mode: i64, r#type: Option<String>) -> Vec<SearchResult> { + self.inner.search(query, mode, r#type) + } + + fn get_providers(&self, package_name: String) -> IndexMap<String, ProviderInfo> { + self.inner.get_providers(package_name) + } + + fn get_repo_name(&self) -> String { + "lock repo".to_string() + } + + fn as_any(&self) -> &dyn std::any::Any { + self } } diff --git a/crates/shirabe/src/repository/package_repository.rs b/crates/shirabe/src/repository/package_repository.rs index f11af06..64b578e 100644 --- a/crates/shirabe/src/repository/package_repository.rs +++ b/crates/shirabe/src/repository/package_repository.rs @@ -86,6 +86,8 @@ impl AdvisoryProviderInterface for PackageRepository { allow_partial_advisories: bool, ) -> anyhow::Result<SecurityAdvisoryResult> { let parser = VersionParser::new(); + let semver_parser = shirabe_semver::version_parser::VersionParser; + let _ = parser; let mut advisories: IndexMap<String, Vec<PartialOrSecurityAdvisory>> = IndexMap::new(); for (package_name, package_advisories) in &self.security_advisories { @@ -101,19 +103,9 @@ impl AdvisoryProviderInterface for PackageRepository { .collect::<IndexMap<String, PhpMixed>>(), _ => return Ok(None), }; - let advisory_any = - PartialSecurityAdvisory::create(package_name, &data_map, &parser) - .ok()?; let advisory = - if let Ok(full) = advisory_any.downcast::<SecurityAdvisory>() { - PartialOrSecurityAdvisory::Full(*full) - } else if let Ok(partial) = - advisory_any.downcast::<PartialSecurityAdvisory>() - { - PartialOrSecurityAdvisory::Partial(*partial) - } else { - return Ok(None); - }; + PartialSecurityAdvisory::create(package_name, &data_map, &semver_parser) + .ok()?; if !allow_partial_advisories && matches!(advisory, PartialOrSecurityAdvisory::Partial(_)) { diff --git a/crates/shirabe/src/repository/path_repository.rs b/crates/shirabe/src/repository/path_repository.rs index c148880..3b6ed8d 100644 --- a/crates/shirabe/src/repository/path_repository.rs +++ b/crates/shirabe/src/repository/path_repository.rs @@ -75,7 +75,7 @@ impl PathRepository { .map(|(k, v)| (k, *v)) .collect::<IndexMap<String, PhpMixed>>(); if !options.contains_key("relative") { - let filesystem = Filesystem::new(); + let filesystem = Filesystem::new(None); let is_relative = !filesystem.is_absolute_path(&url); options.insert("relative".to_string(), PhpMixed::Bool(is_relative)); } diff --git a/crates/shirabe/src/repository/platform_repository.rs b/crates/shirabe/src/repository/platform_repository.rs index 4d522f5..8fb9d0a 100644 --- a/crates/shirabe/src/repository/platform_repository.rs +++ b/crates/shirabe/src/repository/platform_repository.rs @@ -55,6 +55,13 @@ impl PlatformRepository { pub fn new( packages: Vec<Box<dyn PackageInterface>>, overrides: IndexMap<String, PhpMixed>, + ) -> anyhow::Result<Self> { + Self::new4(packages, overrides, None, None) + } + + pub fn new4( + packages: Vec<Box<dyn PackageInterface>>, + overrides: IndexMap<String, PhpMixed>, runtime: Option<Runtime>, hhvm_detector: Option<HhvmDetector>, ) -> anyhow::Result<Self> { @@ -91,7 +98,7 @@ impl PlatformRepository { ); } Ok(Self { - inner: ArrayRepository::new(packages), + inner: ArrayRepository::new(packages)?, version_parser: None, overrides: overrides_map, disabled_packages: IndexMap::new(), @@ -1668,3 +1675,77 @@ impl PlatformRepository { } } } + +impl shirabe_php_shim::Countable for PlatformRepository { + fn count(&self) -> i64 { + self.inner.count() + } +} + +impl crate::repository::repository_interface::RepositoryInterface for PlatformRepository { + fn has_package(&self, package: &dyn PackageInterface) -> bool { + self.inner.has_package(package) + } + + fn find_package( + &self, + name: String, + constraint: crate::repository::repository_interface::FindPackageConstraint, + ) -> Option<Box<dyn crate::package::base_package::BasePackage>> { + self.inner.find_package(name, constraint) + } + + fn find_packages( + &self, + name: String, + constraint: Option<crate::repository::repository_interface::FindPackageConstraint>, + ) -> Vec<Box<dyn crate::package::base_package::BasePackage>> { + self.inner.find_packages(name, constraint) + } + + fn get_packages(&self) -> Vec<Box<dyn crate::package::base_package::BasePackage>> { + self.inner.get_packages() + } + + fn load_packages( + &self, + package_name_map: IndexMap< + String, + Option<Box<dyn shirabe_semver::constraint::constraint_interface::ConstraintInterface>>, + >, + acceptable_stabilities: IndexMap<String, i64>, + stability_flags: IndexMap<String, i64>, + already_loaded: IndexMap<String, IndexMap<String, Box<dyn PackageInterface>>>, + ) -> crate::repository::repository_interface::LoadPackagesResult { + self.inner.load_packages( + package_name_map, + acceptable_stabilities, + stability_flags, + already_loaded, + ) + } + + fn search( + &self, + query: String, + mode: i64, + r#type: Option<String>, + ) -> Vec<crate::repository::repository_interface::SearchResult> { + self.inner.search(query, mode, r#type) + } + + fn get_providers( + &self, + package_name: String, + ) -> IndexMap<String, crate::repository::repository_interface::ProviderInfo> { + self.inner.get_providers(package_name) + } + + fn get_repo_name(&self) -> String { + PlatformRepository::get_repo_name(self) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} diff --git a/crates/shirabe/src/repository/repository_factory.rs b/crates/shirabe/src/repository/repository_factory.rs index 794cd3f..bb598ed 100644 --- a/crates/shirabe/src/repository/repository_factory.rs +++ b/crates/shirabe/src/repository/repository_factory.rs @@ -42,7 +42,8 @@ impl RepositoryFactory { let json = JsonFile::new( repository.to_string(), Some(Factory::create_http_downloader(io, config)?), - ); + Some(io), + )?; let data = json.read()?; let has_packages = data.get("packages").map_or(false, |v| !v.is_null()); let has_includes = data.get("includes").map_or(false, |v| !v.is_null()); diff --git a/crates/shirabe/src/repository/repository_interface.rs b/crates/shirabe/src/repository/repository_interface.rs index c321908..2a9c8f5 100644 --- a/crates/shirabe/src/repository/repository_interface.rs +++ b/crates/shirabe/src/repository/repository_interface.rs @@ -39,7 +39,7 @@ pub const SEARCH_FULLTEXT: i64 = 0; pub const SEARCH_NAME: i64 = 1; pub const SEARCH_VENDOR: i64 = 2; -pub trait RepositoryInterface: Countable { +pub trait RepositoryInterface: Countable + std::fmt::Debug { fn has_package(&self, package: &dyn PackageInterface) -> bool; fn find_package( @@ -75,4 +75,8 @@ pub trait RepositoryInterface: Countable { } fn as_any(&self) -> &dyn std::any::Any; + + fn clone_box(&self) -> Box<dyn RepositoryInterface> { + todo!() + } } diff --git a/crates/shirabe/src/repository/repository_utils.rs b/crates/shirabe/src/repository/repository_utils.rs index 526fae7..d39daa1 100644 --- a/crates/shirabe/src/repository/repository_utils.rs +++ b/crates/shirabe/src/repository/repository_utils.rs @@ -12,11 +12,11 @@ pub struct RepositoryUtils; impl RepositoryUtils { pub fn filter_required_packages( - packages: &[Box<dyn PackageInterface>], + packages: &[Box<dyn crate::package::base_package::BasePackage>], requirer: &dyn PackageInterface, include_require_dev: bool, - mut bucket: Vec<Box<dyn PackageInterface>>, - ) -> Vec<Box<dyn PackageInterface>> { + mut bucket: Vec<Box<dyn crate::package::base_package::BasePackage>>, + ) -> Vec<Box<dyn crate::package::base_package::BasePackage>> { let mut requires: IndexMap<String, Link> = requirer.get_requires(); if include_require_dev { requires.extend(requirer.get_dev_requires()); @@ -27,18 +27,17 @@ impl RepositoryUtils { if requires.contains_key(&name) { let already_in_bucket = bucket.iter().any(|b| { std::ptr::eq( - b.as_ref() as *const dyn PackageInterface as *const (), - candidate.as_ref() as *const dyn PackageInterface as *const (), + b.as_ref() as *const dyn crate::package::base_package::BasePackage + as *const (), + candidate.as_ref() + as *const dyn crate::package::base_package::BasePackage + as *const (), ) }); if !already_in_bucket { bucket.push(candidate.clone_box()); - bucket = Self::filter_required_packages( - packages, - candidate.as_ref(), - false, - bucket, - ); + // TODO(phase-b): recursion requires &dyn PackageInterface; cast pending. + let _ = (requires.contains_key("dummy"),); } break; } diff --git a/crates/shirabe/src/repository/root_package_repository.rs b/crates/shirabe/src/repository/root_package_repository.rs index 4797e25..6c6e25d 100644 --- a/crates/shirabe/src/repository/root_package_repository.rs +++ b/crates/shirabe/src/repository/root_package_repository.rs @@ -1,7 +1,11 @@ //! ref: composer/src/Composer/Repository/RootPackageRepository.php +use crate::package::base_package::BasePackage; +use crate::package::package_interface::PackageInterface; use crate::package::root_package_interface::RootPackageInterface; use crate::repository::array_repository::ArrayRepository; +use crate::repository::repository_interface::{ProviderInfo, RepositoryInterface, SearchResult}; +use indexmap::IndexMap; #[derive(Debug)] pub struct RootPackageRepository { @@ -11,7 +15,11 @@ pub struct RootPackageRepository { impl RootPackageRepository { pub fn new(package: Box<dyn RootPackageInterface>) -> Self { Self { - inner: ArrayRepository::new(vec![package]), + // TODO(phase-b): RootPackageInterface vs BasePackage upcast + ArrayRepository::new error + inner: ArrayRepository::new(vec![todo!( + "convert Box<dyn RootPackageInterface> to Box<dyn BasePackage>" + )]) + .expect("invalid root package"), } } @@ -19,3 +27,69 @@ impl RootPackageRepository { "root package repo".to_string() } } + +impl shirabe_php_shim::Countable for RootPackageRepository { + fn count(&self) -> i64 { + self.inner.count() + } +} + +impl RepositoryInterface for RootPackageRepository { + fn has_package(&self, package: &dyn PackageInterface) -> bool { + self.inner.has_package(package) + } + + fn find_package( + &self, + name: String, + constraint: crate::repository::repository_interface::FindPackageConstraint, + ) -> Option<Box<dyn BasePackage>> { + self.inner.find_package(name, constraint) + } + + fn find_packages( + &self, + name: String, + constraint: Option<crate::repository::repository_interface::FindPackageConstraint>, + ) -> Vec<Box<dyn BasePackage>> { + self.inner.find_packages(name, constraint) + } + + fn get_packages(&self) -> Vec<Box<dyn BasePackage>> { + self.inner.get_packages() + } + + fn load_packages( + &self, + package_name_map: IndexMap< + String, + Option<Box<dyn shirabe_semver::constraint::constraint_interface::ConstraintInterface>>, + >, + acceptable_stabilities: IndexMap<String, i64>, + stability_flags: IndexMap<String, i64>, + already_loaded: IndexMap<String, IndexMap<String, Box<dyn PackageInterface>>>, + ) -> crate::repository::repository_interface::LoadPackagesResult { + self.inner.load_packages( + package_name_map, + acceptable_stabilities, + stability_flags, + already_loaded, + ) + } + + fn search(&self, query: String, mode: i64, r#type: Option<String>) -> Vec<SearchResult> { + self.inner.search(query, mode, r#type) + } + + fn get_providers(&self, package_name: String) -> IndexMap<String, ProviderInfo> { + self.inner.get_providers(package_name) + } + + fn get_repo_name(&self) -> String { + RootPackageRepository::get_repo_name(self) + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} diff --git a/crates/shirabe/src/repository/vcs/fossil_driver.rs b/crates/shirabe/src/repository/vcs/fossil_driver.rs index cd3ae3a..a765c4c 100644 --- a/crates/shirabe/src/repository/vcs/fossil_driver.rs +++ b/crates/shirabe/src/repository/vcs/fossil_driver.rs @@ -96,7 +96,7 @@ impl FossilDriver { pub(crate) fn update_local_repo(&mut self) -> anyhow::Result<()> { assert!(self.repo_file.is_some()); - let fs = Filesystem::new(); + let fs = Filesystem::new(None); fs.ensure_directory_exists(&self.checkout_dir)?; if !is_writable(&dirname(&self.checkout_dir)) { diff --git a/crates/shirabe/src/repository/vcs/git_bitbucket_driver.rs b/crates/shirabe/src/repository/vcs/git_bitbucket_driver.rs index fb667b7..05e7c61 100644 --- a/crates/shirabe/src/repository/vcs/git_bitbucket_driver.rs +++ b/crates/shirabe/src/repository/vcs/git_bitbucket_driver.rs @@ -241,7 +241,7 @@ impl GitBitbucketDriver { if self.inner.should_cache(identifier) { self.inner.cache.as_ref().unwrap().write( identifier, - &JsonFile::encode( + &JsonFile::encode_with_indent( &PhpMixed::Array( composer .clone() diff --git a/crates/shirabe/src/repository/vcs/git_driver.rs b/crates/shirabe/src/repository/vcs/git_driver.rs index 40ad179..33474b1 100644 --- a/crates/shirabe/src/repository/vcs/git_driver.rs +++ b/crates/shirabe/src/repository/vcs/git_driver.rs @@ -69,7 +69,7 @@ impl GitDriver { GitUtil::clean_env(&self.inner.process); - let fs = Filesystem::new(); + let fs = Filesystem::new(None); fs.ensure_directory_exists(&dirname(&self.repo_dir))?; if !is_writable(&dirname(&self.repo_dir)) { @@ -99,7 +99,7 @@ impl GitDriver { &*self.inner.io, &self.inner.config, &self.inner.process, - &Filesystem::new(), + &Filesystem::new(None), ); if !git_util.sync_mirror(&self.inner.url, &self.repo_dir)? { if !is_dir(&self.repo_dir) { @@ -164,7 +164,7 @@ impl GitDriver { &*self.inner.io, &self.inner.config, &self.inner.process, - &Filesystem::new(), + &Filesystem::new(None), ); if !Filesystem::is_local_path(&self.inner.url) { let default_branch = @@ -396,7 +396,7 @@ impl GitDriver { } let process = ProcessExecutor::new(io); - let git_util = GitUtil::new(io, _config, &process, &Filesystem::new()); + let git_util = GitUtil::new(io, _config, &process, &Filesystem::new(None)); GitUtil::clean_env(&process); let result = git_util.run_commands( @@ -416,3 +416,63 @@ impl GitDriver { } } } + +// TODO(phase-b): implement VcsDriverInterface for GitDriver — signatures here +// differ from the trait (some &mut self vs &self, different return shapes), so +// each method delegates via todo!() until reconciled. +impl crate::repository::vcs::vcs_driver_interface::VcsDriverInterface for GitDriver { + fn initialize(&mut self) -> anyhow::Result<()> { + GitDriver::initialize(self) + } + + fn get_composer_information( + &self, + _identifier: &str, + ) -> anyhow::Result<Option<IndexMap<String, shirabe_php_shim::PhpMixed>>> { + todo!() + } + + fn get_file_content(&self, _file: &str, _identifier: &str) -> anyhow::Result<Option<String>> { + todo!() + } + + fn get_change_date(&self, _identifier: &str) -> anyhow::Result<Option<DateTime<Utc>>> { + todo!() + } + + fn get_root_identifier(&self) -> anyhow::Result<String> { + todo!() + } + + fn get_branches(&self) -> anyhow::Result<IndexMap<String, String>> { + todo!() + } + + fn get_tags(&self) -> anyhow::Result<IndexMap<String, String>> { + todo!() + } + + fn get_dist(&self, _identifier: &str) -> anyhow::Result<Option<IndexMap<String, String>>> { + todo!() + } + + fn get_source(&self, _identifier: &str) -> anyhow::Result<IndexMap<String, String>> { + todo!() + } + + fn get_url(&self) -> String { + GitDriver::get_url(self) + } + + fn has_composer_file(&self, _identifier: &str) -> anyhow::Result<bool> { + todo!() + } + + fn cleanup(&mut self) -> anyhow::Result<()> { + Ok(()) + } + + fn supports(_io: &dyn IOInterface, _config: &Config, _url: &str, _deep: bool) -> bool { + todo!() + } +} diff --git a/crates/shirabe/src/repository/vcs/hg_driver.rs b/crates/shirabe/src/repository/vcs/hg_driver.rs index 55872ae..68393ed 100644 --- a/crates/shirabe/src/repository/vcs/hg_driver.rs +++ b/crates/shirabe/src/repository/vcs/hg_driver.rs @@ -45,7 +45,7 @@ impl HgDriver { Preg::replace(r"{[^a-z0-9]}i", "-", Url::sanitize(self.inner.url.clone())); self.repo_dir = format!("{}/{}/", cache_vcs_dir, sanitized); - let fs = Filesystem::new(); + let fs = Filesystem::new(None); fs.ensure_directory_exists(&cache_vcs_dir)?; if !is_writable(&dirname(&self.repo_dir)) { @@ -84,7 +84,7 @@ impl HgDriver { ); } } else { - let fs2 = Filesystem::new(); + let fs2 = Filesystem::new(None); fs2.remove_directory(&self.repo_dir)?; let repo_dir = self.repo_dir.clone(); diff --git a/crates/shirabe/src/repository/vcs/svn_driver.rs b/crates/shirabe/src/repository/vcs/svn_driver.rs index 0a05d84..2e649df 100644 --- a/crates/shirabe/src/repository/vcs/svn_driver.rs +++ b/crates/shirabe/src/repository/vcs/svn_driver.rs @@ -194,7 +194,6 @@ impl SvnDriver { .map(PhpMixed::from) .unwrap_or(PhpMixed::Null), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES, - None, ); self.inner .cache @@ -474,7 +473,7 @@ impl SvnDriver { /// An absolute path (leading '/') is converted to a file:// url. pub(crate) fn normalize_url(url: &str) -> String { - let fs = Filesystem::new(); + let fs = Filesystem::new(None); if fs.is_absolute_path(url) { return format!("file://{}", strtr(url, "\\", "/")); } diff --git a/crates/shirabe/src/repository/vcs_repository.rs b/crates/shirabe/src/repository/vcs_repository.rs index 4374130..e53392f 100644 --- a/crates/shirabe/src/repository/vcs_repository.rs +++ b/crates/shirabe/src/repository/vcs_repository.rs @@ -23,6 +23,7 @@ use crate::package::version::version_parser::VersionParser; use crate::repository::array_repository::ArrayRepository; use crate::repository::configurable_repository_interface::ConfigurableRepositoryInterface; use crate::repository::invalid_repository_exception::InvalidRepositoryException; +use crate::repository::repository_interface::RepositoryInterface; use crate::repository::vcs::vcs_driver_interface::VcsDriverInterface; use crate::repository::version_cache_interface::VersionCacheInterface; use crate::util::http_downloader::HttpDownloader; @@ -703,7 +704,10 @@ impl VcsRepository { ); } } - self.inner.add_package(Box::new(package))?; + // TODO(phase-b): Box<dyn BasePackage> -> Box<dyn PackageInterface> coercion + self.inner.add_package( + crate::package::package_interface::PackageInterface::clone_box(&*package), + )?; Ok(()) })(); if let Err(e) = result { @@ -748,14 +752,10 @@ impl VcsRepository { } if self.inner.get_packages().is_empty() { - return Err(InvalidRepositoryException { - message: format!( - "No valid composer.json was found in any branch or tag of {}, could not load a package from it.", - self.url - ), - code: 0, - } - .into()); + return Err(InvalidRepositoryException::new(format!( + "No valid composer.json was found in any branch or tag of {}, could not load a package from it.", + self.url + )).into()); } Ok(()) @@ -950,7 +950,7 @@ impl VcsRepository { if let VersionCacheResult::Package(data) = cached_package { let loaded = self.loader.as_ref().unwrap().load(data, None)?; - return Ok(CachedPackageResult::Package(Box::new(loaded))); + return Ok(CachedPackageResult::Package(loaded)); } Ok(CachedPackageResult::None) diff --git a/crates/shirabe/src/repository/writable_array_repository.rs b/crates/shirabe/src/repository/writable_array_repository.rs index 5d77fee..793478f 100644 --- a/crates/shirabe/src/repository/writable_array_repository.rs +++ b/crates/shirabe/src/repository/writable_array_repository.rs @@ -37,4 +37,39 @@ impl WritableArrayRepository { pub fn reload(&mut self) { self.dev_mode = None; } + + pub fn add_package( + &mut self, + package: Box<dyn crate::package::package_interface::PackageInterface>, + ) -> Result<()> { + self.inner.add_package(package) + } + + pub fn remove_package( + &mut self, + package: &dyn crate::package::package_interface::PackageInterface, + ) -> Result<()> { + let _ = package; + // TODO(phase-b): delegate to ArrayRepository once it implements remove_package + Ok(()) + } + + pub fn initialize(&mut self) -> Result<()> { + // TODO(phase-b): inner ArrayRepository::initialize signature + Ok(()) + } + + pub fn get_canonical_packages( + &self, + ) -> Vec<Box<dyn crate::package::package_interface::PackageInterface>> { + // TODO(phase-b): delegate to inner once it exposes get_canonical_packages + Vec::new() + } + + pub fn get_packages( + &self, + ) -> Vec<Box<dyn crate::package::package_interface::PackageInterface>> { + // TODO(phase-b): delegate to inner ArrayRepository::get_packages + Vec::new() + } } diff --git a/crates/shirabe/src/script/script_events.rs b/crates/shirabe/src/script/script_events.rs index c3ec370..a802406 100644 --- a/crates/shirabe/src/script/script_events.rs +++ b/crates/shirabe/src/script/script_events.rs @@ -15,4 +15,22 @@ impl ScriptEvents { pub const POST_CREATE_PROJECT_CMD: &'static str = "post-create-project-cmd"; pub const PRE_ARCHIVE_CMD: &'static str = "pre-archive-cmd"; pub const POST_ARCHIVE_CMD: &'static str = "post-archive-cmd"; + + pub fn is_defined(const_name: &str) -> bool { + matches!( + const_name, + "PRE_INSTALL_CMD" + | "POST_INSTALL_CMD" + | "PRE_UPDATE_CMD" + | "POST_UPDATE_CMD" + | "PRE_STATUS_CMD" + | "POST_STATUS_CMD" + | "PRE_AUTOLOAD_DUMP" + | "POST_AUTOLOAD_DUMP" + | "POST_ROOT_PACKAGE_INSTALL" + | "POST_CREATE_PROJECT_CMD" + | "PRE_ARCHIVE_CMD" + | "POST_ARCHIVE_CMD" + ) + } } diff --git a/crates/shirabe/src/self_update/versions.rs b/crates/shirabe/src/self_update/versions.rs index f5ba5d2..ebc992b 100644 --- a/crates/shirabe/src/self_update/versions.rs +++ b/crates/shirabe/src/self_update/versions.rs @@ -45,7 +45,10 @@ impl Versions { return Ok(ch.clone()); } - let channel_file = format!("{}/update-channel", self.config.get("home")); + let channel_file = format!( + "{}/update-channel", + self.config.get("home").as_string().unwrap_or("") + ); if std::path::Path::new(&channel_file).exists() { let channel = std::fs::read_to_string(&channel_file)?.trim().to_string(); if ["stable", "preview", "snapshot", "2.2"].contains(&channel.as_str()) { @@ -74,7 +77,10 @@ impl Versions { })); } - let channel_file = format!("{}/update-channel", self.config.get("home")); + let channel_file = format!( + "{}/update-channel", + self.config.get("home").as_string().unwrap_or("") + ); self.channel = Some(channel.clone()); // rewrite '2' and '1' channels to stable for future self-updates, but LTS ones like '2.2' remain pinned diff --git a/crates/shirabe/src/util/auth_helper.rs b/crates/shirabe/src/util/auth_helper.rs index 33d0b54..384d7af 100644 --- a/crates/shirabe/src/util/auth_helper.rs +++ b/crates/shirabe/src/util/auth_helper.rs @@ -157,8 +157,7 @@ impl AuthHelper { "GitHub API token requires SSO authorization. Authorize this token at {}\n", sso_url, ); - self.io - .write_error(PhpMixed::String(message), true, io_interface::NORMAL); + self.io.write_error3(&message, true, io_interface::NORMAL); if !self.io.is_interactive() { return Err(TransportException::new( format!("Could not authenticate against {}", origin), diff --git a/crates/shirabe/src/util/bitbucket.rs b/crates/shirabe/src/util/bitbucket.rs index c6aa874..ca9aabd 100644 --- a/crates/shirabe/src/util/bitbucket.rs +++ b/crates/shirabe/src/util/bitbucket.rs @@ -36,7 +36,7 @@ impl Bitbucket { let process = process.unwrap_or_else(|| ProcessExecutor::new(&*io)); let http_downloader = match http_downloader { Some(h) => h, - None => Factory::create_http_downloader(&*io, &config)?, + None => Factory::create_http_downloader(&*io, &config, IndexMap::new())?, }; Ok(Self { io, diff --git a/crates/shirabe/src/util/composer_mirror.rs b/crates/shirabe/src/util/composer_mirror.rs index 0743e9f..d70af94 100644 --- a/crates/shirabe/src/util/composer_mirror.rs +++ b/crates/shirabe/src/util/composer_mirror.rs @@ -1,6 +1,6 @@ //! ref: composer/src/Composer/Util/ComposerMirror.php -use shirabe_external_packages::composer::pcre::preg::Preg; +use shirabe_external_packages::composer::pcre::preg::{CaptureKey, Preg}; use shirabe_php_shim::hash; pub struct ComposerMirror; @@ -15,7 +15,7 @@ impl ComposerMirror { pretty_version: Option<&str>, ) -> String { let reference = reference.map(|r| { - if Preg::is_match(r"^([a-f0-9]*|%reference%)$", r) { + if Preg::is_match(r"^([a-f0-9]*|%reference%)$", r).unwrap_or(false) { r.to_string() } else { hash("md5", r) @@ -53,17 +53,46 @@ impl ComposerMirror { url: &str, r#type: Option<&str>, ) -> String { - let normalized_url = if let Some(m) = Preg::match_( + let mut gh_matches: indexmap::IndexMap<CaptureKey, String> = indexmap::IndexMap::new(); + let mut bb_matches: indexmap::IndexMap<CaptureKey, String> = indexmap::IndexMap::new(); + let normalized_url = if Preg::match3( r"^(?:(?:https?|git)://github\.com/|git@github\.com:)([^/]+)/(.+?)(?:\.git)?$", url, - ) { - format!("gh-{}/{}", m[1], m[2]) - } else if let Some(m) = - Preg::match_(r"^https://bitbucket\.org/([^/]+)/(.+?)(?:\.git)?/?$", url) + Some(&mut gh_matches), + ) + .unwrap_or(false) { - format!("bb-{}/{}", m[1], m[2]) + format!( + "gh-{}/{}", + gh_matches + .get(&CaptureKey::ByIndex(1)) + .cloned() + .unwrap_or_default(), + gh_matches + .get(&CaptureKey::ByIndex(2)) + .cloned() + .unwrap_or_default(), + ) + } else if Preg::match3( + r"^https://bitbucket\.org/([^/]+)/(.+?)(?:\.git)?/?$", + url, + Some(&mut bb_matches), + ) + .unwrap_or(false) + { + format!( + "bb-{}/{}", + bb_matches + .get(&CaptureKey::ByIndex(1)) + .cloned() + .unwrap_or_default(), + bb_matches + .get(&CaptureKey::ByIndex(2)) + .cloned() + .unwrap_or_default(), + ) } else { - Preg::replace(r"[^a-z0-9_.-]", "-", url.trim_matches('/')) + Preg::replace(r"[^a-z0-9_.-]", "-", url.trim_matches('/')).unwrap_or_default() }; ["%package%", "%normalizedUrl%", "%type%"] diff --git a/crates/shirabe/src/util/config_validator.rs b/crates/shirabe/src/util/config_validator.rs index cbba32b..972191a 100644 --- a/crates/shirabe/src/util/config_validator.rs +++ b/crates/shirabe/src/util/config_validator.rs @@ -39,7 +39,9 @@ impl ConfigValidator { let mut lax_valid = false; let mut manifest: Option<IndexMap<String, PhpMixed>> = None; - let json = JsonFile::new(file.to_string(), None, Some(&*self.io)); + // TODO(phase-b): io type mismatch (&dyn IOInterface vs Box<dyn IOInterface>) + let json = + JsonFile::new(file.to_string(), None, None).expect("config file path is always local"); let schema_result: anyhow::Result<()> = (|| -> anyhow::Result<()> { manifest = Some(json.read()?); json.validate_schema(Some(JsonFile::LAX_SCHEMA))?; diff --git a/crates/shirabe/src/util/error_handler.rs b/crates/shirabe/src/util/error_handler.rs index aff263e..d531658 100644 --- a/crates/shirabe/src/util/error_handler.rs +++ b/crates/shirabe/src/util/error_handler.rs @@ -65,12 +65,12 @@ impl ErrorHandler { }); } - let io_guard = io().lock().unwrap(); + let mut io_guard = io().lock().unwrap(); if io_guard.is_some() { let has_shown = *HAS_SHOWN_DEPRECATION_NOTICE.lock().unwrap(); if has_shown > 0 && !io_guard.as_ref().unwrap().is_verbose() { if has_shown == 1 { - io_guard.as_ref().unwrap().write_error("<warning>More deprecation notices were hidden, run again with `-v` to show them.</warning>"); + io_guard.as_mut().unwrap().write_error("<warning>More deprecation notices were hidden, run again with `-v` to show them.</warning>"); *HAS_SHOWN_DEPRECATION_NOTICE.lock().unwrap() = 2; } return Ok(true); @@ -95,8 +95,8 @@ impl ErrorHandler { } fn output_warning(message: &str, output_even_without_io: bool) { - let io_guard = io().lock().unwrap(); - if let Some(ref io) = *io_guard { + let mut io_guard = io().lock().unwrap(); + if let Some(io) = io_guard.as_mut() { io.write_error(&format!("<warning>{}</warning>", message)); if io.is_verbose() { io.write_error("<warning>Stack trace:</warning>"); diff --git a/crates/shirabe/src/util/filesystem.rs b/crates/shirabe/src/util/filesystem.rs index 6ca20cc..ce2abf8 100644 --- a/crates/shirabe/src/util/filesystem.rs +++ b/crates/shirabe/src/util/filesystem.rs @@ -834,7 +834,7 @@ impl Filesystem { return false; } - let cwd = Platform::get_cwd(); + let cwd = Platform::get_cwd(false).unwrap_or_default(); let relative_path = self.find_shortest_path(link, target, false, false); chdir(&dirname(link)); diff --git a/crates/shirabe/src/util/forgejo.rs b/crates/shirabe/src/util/forgejo.rs index 15081fc..3aaff91 100644 --- a/crates/shirabe/src/util/forgejo.rs +++ b/crates/shirabe/src/util/forgejo.rs @@ -29,18 +29,18 @@ impl Forgejo { message: Option<&str>, ) -> anyhow::Result<Result<bool, TransportException>> { if let Some(message) = message { - self.io.write_error(message, true, io_interface::NORMAL); + self.io.write_error3(message, true, io_interface::NORMAL); } let url = format!("https://{}/user/settings/applications", origin_url); - self.io.write_error( + self.io.write_error3( "Setup a personal access token with repository:read permissions on:", true, io_interface::NORMAL, ); - self.io.write_error(&url, true, io_interface::NORMAL); + self.io.write_error3(&url, true, io_interface::NORMAL); let local_auth_config = self.config.get_local_auth_config_source(); - self.io.write_error( + self.io.write_error3( &format!( "Tokens will be stored in plain text in \"{}\" for future use by Composer.", local_auth_config @@ -52,7 +52,7 @@ impl Forgejo { true, io_interface::NORMAL, ); - self.io.write_error( + self.io.write_error3( "For additional information, check https://getcomposer.org/doc/articles/authentication-for-private-packages.md#forgejo-token", true, io_interface::NORMAL, @@ -78,13 +78,13 @@ impl Forgejo { origin_url ); if token.is_empty() || username.is_empty() { - self.io.write_error( + self.io.write_error3( "<warning>No username/token given, aborting.</warning>", true, io_interface::NORMAL, ); self.io - .write_error(&add_token_manually, true, io_interface::NORMAL); + .write_error3(&add_token_manually, true, io_interface::NORMAL); return Ok(Ok(false)); } @@ -101,13 +101,13 @@ impl Forgejo { Ok(_) => {} Err(e) => { if [403, 401, 404].contains(&e.get_code()) { - self.io.write_error( + self.io.write_error3( "<error>Invalid access token provided.</error>", true, io_interface::NORMAL, ); self.io - .write_error(&add_token_manually, true, io_interface::NORMAL); + .write_error3(&add_token_manually, true, io_interface::NORMAL); return Ok(Ok(false)); } @@ -136,7 +136,7 @@ impl Forgejo { }, ); - self.io.write_error( + self.io.write_error3( "<info>Token stored successfully.</info>", true, io_interface::NORMAL, diff --git a/crates/shirabe/src/util/forgejo_url.rs b/crates/shirabe/src/util/forgejo_url.rs index 3b42f13..5fdaab7 100644 --- a/crates/shirabe/src/util/forgejo_url.rs +++ b/crates/shirabe/src/util/forgejo_url.rs @@ -4,6 +4,7 @@ use anyhow::Result; use shirabe_external_packages::composer::pcre::preg::Preg; use shirabe_php_shim::InvalidArgumentException; +#[derive(Debug)] pub struct ForgejoUrl { pub owner: String, pub repository: String, @@ -37,7 +38,22 @@ impl ForgejoUrl { pub fn try_from(repo_url: Option<&str>) -> Option<Self> { let repo_url = repo_url?; - let m = Preg::match_(Self::URL_REGEX, repo_url)?; + let mut matches: indexmap::IndexMap< + shirabe_external_packages::composer::pcre::preg::CaptureKey, + String, + > = indexmap::IndexMap::new(); + if !Preg::match3(Self::URL_REGEX, repo_url, Some(&mut matches)).unwrap_or(false) { + return None; + } + use shirabe_external_packages::composer::pcre::preg::CaptureKey; + let m: Vec<String> = (0..5) + .map(|i| { + matches + .get(&CaptureKey::ByIndex(i)) + .cloned() + .unwrap_or_default() + }) + .collect(); let origin_url = if !m[1].is_empty() { m[1].clone() diff --git a/crates/shirabe/src/util/github.rs b/crates/shirabe/src/util/github.rs index c04873f..5fa88a6 100644 --- a/crates/shirabe/src/util/github.rs +++ b/crates/shirabe/src/util/github.rs @@ -1,6 +1,7 @@ //! ref: composer/src/Composer/Util/GitHub.php use crate::io::io_interface; +use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::preg::Preg; use shirabe_php_shim::{PhpMixed, date, stripos, strtolower}; @@ -32,7 +33,7 @@ impl GitHub { let process = process.unwrap_or_else(|| ProcessExecutor::new(&*io)); let http_downloader = match http_downloader { Some(h) => h, - None => Factory::create_http_downloader(&*io, &config)?, + None => Factory::create_http_downloader(&*io, &config, IndexMap::new())?, }; Ok(Self { io, diff --git a/crates/shirabe/src/util/gitlab.rs b/crates/shirabe/src/util/gitlab.rs index 7788ed8..191e69d 100644 --- a/crates/shirabe/src/util/gitlab.rs +++ b/crates/shirabe/src/util/gitlab.rs @@ -30,7 +30,7 @@ impl GitLab { let process = process.unwrap_or_else(|| ProcessExecutor::new(&*io)); let http_downloader = match http_downloader { Some(h) => h, - None => Factory::create_http_downloader(&*io, &config)?, + None => Factory::create_http_downloader(&*io, &config, IndexMap::new())?, }; Ok(Self { io, diff --git a/crates/shirabe/src/util/http/curl_downloader.rs b/crates/shirabe/src/util/http/curl_downloader.rs index e4d8b78..d335b8b 100644 --- a/crates/shirabe/src/util/http/curl_downloader.rs +++ b/crates/shirabe/src/util/http/curl_downloader.rs @@ -689,7 +689,7 @@ impl CurlDownloader { if_modified )), true, - <dyn IOInterface>::DEBUG, + crate::io::io_interface::DEBUG, ); } @@ -836,7 +836,7 @@ impl CurlDownloader { .to_string(), ), true, - <dyn IOInterface>::NORMAL, + crate::io::io_interface::NORMAL, ); } @@ -922,7 +922,7 @@ impl CurlDownloader { errno )), true, - <dyn IOInterface>::DEBUG, + crate::io::io_interface::DEBUG, ); self.restart_job_with_delay( &job, @@ -954,7 +954,7 @@ impl CurlDownloader { errno )), true, - <dyn IOInterface>::DEBUG, + crate::io::io_interface::DEBUG, ); let mut attrs: IndexMap<String, PhpMixed> = IndexMap::new(); attrs.insert( @@ -1068,7 +1068,10 @@ impl CurlDownloader { status_code, headers.clone().unwrap_or_default(), contents.as_string().map(|s| s.to_string()), - progress.clone(), + progress + .iter() + .map(|(k, v)| (k.clone(), (**v).clone())) + .collect(), )); self.io.write_error( PhpMixed::String(format!( @@ -1082,7 +1085,7 @@ impl CurlDownloader { ) )), true, - <dyn IOInterface>::DEBUG, + crate::io::io_interface::DEBUG, ); } else { let max_file_size: Option<i64> = job @@ -1141,7 +1144,10 @@ impl CurlDownloader { status_code, headers.clone().unwrap_or_default(), contents.as_string().map(|s| s.to_string()), - progress.clone(), + progress + .iter() + .map(|(k, v)| (k.clone(), (**v).clone())) + .collect(), )); self.io.write_error( PhpMixed::String(format!( @@ -1155,7 +1161,7 @@ impl CurlDownloader { ) )), true, - <dyn IOInterface>::DEBUG, + crate::io::io_interface::DEBUG, ); } fclose(job.get("bodyHandle").cloned().unwrap_or(PhpMixed::Null)); @@ -1304,7 +1310,7 @@ impl CurlDownloader { sc )), true, - <dyn IOInterface>::DEBUG, + crate::io::io_interface::DEBUG, ); let mut attrs: IndexMap<String, PhpMixed> = IndexMap::new(); attrs.insert( @@ -1608,7 +1614,7 @@ impl CurlDownloader { ], )), true, - <dyn IOInterface>::DEBUG, + crate::io::io_interface::DEBUG, ); return Ok(Ok(target_url)); diff --git a/crates/shirabe/src/util/http/curl_response.rs b/crates/shirabe/src/util/http/curl_response.rs index 3c4f923..c6f5d77 100644 --- a/crates/shirabe/src/util/http/curl_response.rs +++ b/crates/shirabe/src/util/http/curl_response.rs @@ -18,10 +18,10 @@ impl CurlResponse { headers: Vec<String>, body: Option<String>, curl_info: IndexMap<String, PhpMixed>, - ) -> Self { - Self { - inner: Response::new(request, code, headers, body), - curl_info, + ) -> anyhow::Result<Result<Self, shirabe_php_shim::LogicException>> { + match Response::new(request, code, headers, body)? { + Ok(inner) => Ok(Ok(Self { inner, curl_info })), + Err(e) => Ok(Err(e)), } } diff --git a/crates/shirabe/src/util/http/proxy_item.rs b/crates/shirabe/src/util/http/proxy_item.rs index 0e30c44..db71476 100644 --- a/crates/shirabe/src/util/http/proxy_item.rs +++ b/crates/shirabe/src/util/http/proxy_item.rs @@ -132,28 +132,22 @@ impl ProxyItem { } pub fn to_request_proxy(&self, scheme: String) -> RequestProxy { - let mut http_options: IndexMap<String, Box<PhpMixed>> = IndexMap::new(); + let mut http_options: IndexMap<String, PhpMixed> = IndexMap::new(); http_options.insert( "proxy".to_string(), - Box::new(PhpMixed::String(self.options_proxy.clone())), + PhpMixed::String(self.options_proxy.clone()), ); if let Some(ref auth) = self.options_auth { - http_options.insert( - "header".to_string(), - Box::new(PhpMixed::String(auth.clone())), - ); + http_options.insert("header".to_string(), PhpMixed::String(auth.clone())); } if scheme == "http" { - http_options.insert( - "request_fulluri".to_string(), - Box::new(PhpMixed::Bool(true)), - ); + http_options.insert("request_fulluri".to_string(), PhpMixed::Bool(true)); } - let mut options: IndexMap<String, Box<PhpMixed>> = IndexMap::new(); - options.insert("http".to_string(), Box::new(PhpMixed::Array(http_options))); + let mut options: IndexMap<String, IndexMap<String, PhpMixed>> = IndexMap::new(); + options.insert("http".to_string(), http_options); RequestProxy::new( Some(self.url.clone()), diff --git a/crates/shirabe/src/util/http/proxy_manager.rs b/crates/shirabe/src/util/http/proxy_manager.rs index e2838e6..5ed8269 100644 --- a/crates/shirabe/src/util/http/proxy_manager.rs +++ b/crates/shirabe/src/util/http/proxy_manager.rs @@ -67,7 +67,7 @@ impl ProxyManager { return Ok(RequestProxy::no_proxy()); } - Ok(proxy.unwrap().to_request_proxy(&scheme)) + Ok(proxy.unwrap().to_request_proxy(scheme)) } fn get_proxy_for_scheme(&self, scheme: &str) -> Option<&ProxyItem> { @@ -102,7 +102,7 @@ impl ProxyManager { let (env, _name) = Self::get_proxy_env("no_proxy"); if let Some(env) = env { - self.no_proxy_handler = Some(NoProxyPattern::new(env)); + self.no_proxy_handler = Some(NoProxyPattern::new(&env)); } Ok(()) diff --git a/crates/shirabe/src/util/http/request_proxy.rs b/crates/shirabe/src/util/http/request_proxy.rs index 5bbf5ce..a85e283 100644 --- a/crates/shirabe/src/util/http/request_proxy.rs +++ b/crates/shirabe/src/util/http/request_proxy.rs @@ -55,6 +55,7 @@ impl RequestProxy { return Err(TransportException::new( "Cannot use an HTTPS proxy. PHP >= 7.3 and cUrl >= 7.52.0 are required." .to_string(), + 0, )); } diff --git a/crates/shirabe/src/util/http/response.rs b/crates/shirabe/src/util/http/response.rs index 6a60540..cf238c7 100644 --- a/crates/shirabe/src/util/http/response.rs +++ b/crates/shirabe/src/util/http/response.rs @@ -88,4 +88,13 @@ impl Response { } value } + + // TODO(phase-b): historical helpers used in composer_repository — provide stubs. + pub fn from_php_mixed(_data: PhpMixed) -> Self { + todo!() + } + + pub fn new_fake(_body: Option<String>) -> Self { + todo!() + } } diff --git a/crates/shirabe/src/util/http_downloader.rs b/crates/shirabe/src/util/http_downloader.rs index 34f0642..97b1f6e 100644 --- a/crates/shirabe/src/util/http_downloader.rs +++ b/crates/shirabe/src/util/http_downloader.rs @@ -148,7 +148,7 @@ impl HttpDownloader { 1, min( 50, - max_jobs_env.as_string().unwrap_or("0").parse().unwrap_or(0), + max_jobs_env.as_deref().unwrap_or("0").parse().unwrap_or(0), ), ); } @@ -372,9 +372,10 @@ impl HttpDownloader { }); let canceler: Box<dyn Fn()> = Box::new(|| { // PHP canceler logic — TODO(phase-b) - let _ = IrrecoverableDownloadException { - inner: TransportException::new("Download canceled".to_string(), 0), - }; + let _ = IrrecoverableDownloadException(shirabe_php_shim::RuntimeException { + message: "Download canceled".to_string(), + code: 0, + }); let _ = Url::sanitize(""); }); let _ = (resolver, canceler); @@ -684,7 +685,7 @@ impl HttpDownloader { if false != strpos(e_as_transport.get_message(), "Resolving timed out").is_some() || false != strpos(e_as_transport.get_message(), "Could not resolve host").is_some() { - Silencer::suppress(); + Silencer::suppress(None); let mut ctx_options: IndexMap<String, PhpMixed> = IndexMap::new(); let mut ssl_map: IndexMap<String, Box<PhpMixed>> = IndexMap::new(); ssl_map.insert("verify_peer".to_string(), Box::new(PhpMixed::Bool(false))); diff --git a/crates/shirabe/src/util/no_proxy_pattern.rs b/crates/shirabe/src/util/no_proxy_pattern.rs index 7fbfd0f..2b593c3 100644 --- a/crates/shirabe/src/util/no_proxy_pattern.rs +++ b/crates/shirabe/src/util/no_proxy_pattern.rs @@ -40,7 +40,7 @@ impl NoProxyPattern { /// @param string $pattern NO_PROXY pattern pub fn new(pattern: &str) -> Self { // PHP: Preg::split('{[\s,]+}', $pattern, -1, PREG_SPLIT_NO_EMPTY) - let host_names = Preg::split(r"{[\s,]+}", pattern); + let host_names = Preg::split(r"{[\s,]+}", pattern).unwrap_or_default(); let noproxy = host_names.is_empty() || host_names[0] == "*"; Self { host_names, diff --git a/crates/shirabe/src/util/package_info.rs b/crates/shirabe/src/util/package_info.rs index 1046f0e..d8d93ef 100644 --- a/crates/shirabe/src/util/package_info.rs +++ b/crates/shirabe/src/util/package_info.rs @@ -16,14 +16,14 @@ impl PackageInfo { } } - package.get_source_url() + package.get_source_url().map(|s| s.to_string()) } pub fn get_view_source_or_homepage_url(package: &dyn PackageInterface) -> Option<String> { let url = Self::get_view_source_url(package).or_else(|| { package .as_complete_package_interface() - .and_then(|complete| complete.get_homepage()) + .and_then(|complete| complete.get_homepage().map(|s| s.to_string())) }); if url.as_deref() == Some("") { diff --git a/crates/shirabe/src/util/package_sorter.rs b/crates/shirabe/src/util/package_sorter.rs index 2f38910..a2728b5 100644 --- a/crates/shirabe/src/util/package_sorter.rs +++ b/crates/shirabe/src/util/package_sorter.rs @@ -36,7 +36,7 @@ impl PackageSorter { pub fn sort_packages_alphabetically( mut packages: Vec<Box<dyn PackageInterface>>, ) -> Vec<Box<dyn PackageInterface>> { - packages.sort_by_key(|p| p.get_name()); + packages.sort_by_key(|p| p.get_name().to_string()); packages } @@ -48,9 +48,9 @@ impl PackageSorter { for package in &packages { let mut links: IndexMap<String, Link> = package.get_requires(); - // TODO: check for RootAliasPackage as well - if let Some(root_package) = (package.as_any() as &dyn Any).downcast_ref::<RootPackage>() - { + // TODO(phase-b): check for RootAliasPackage as well; PackageInterface lacks as_any + let root_package: Option<&RootPackage> = None; + if let Some(root_package) = root_package { links.extend(root_package.get_dev_requires()); } for link in links.values() { diff --git a/crates/shirabe/src/util/platform.rs b/crates/shirabe/src/util/platform.rs index 96e564f..1f684d8 100644 --- a/crates/shirabe/src/util/platform.rs +++ b/crates/shirabe/src/util/platform.rs @@ -92,7 +92,8 @@ impl Platform { /// Parses tildes and environment variables in paths. pub fn expand_path(path: &str) -> String { - if Preg::is_match(r"#^~[\\/]#", path) { + use shirabe_external_packages::composer::pcre::preg::CaptureKey; + if Preg::is_match(r"#^~[\\/]#", path).unwrap_or(false) { return format!( "{}{}", Self::get_user_directory().unwrap(), @@ -102,35 +103,41 @@ impl Platform { Preg::replace_callback( r"#^(\$|(?P<percent>%))(?P<var>\w++)(?(percent)%)(?P<path>.*)#", - |matches| -> String { + |matches: &indexmap::IndexMap<CaptureKey, String>| -> String { + let var = matches + .get(&CaptureKey::ByName("var".to_string())) + .map(|s| s.as_str()) + .unwrap_or(""); + let path_part = matches + .get(&CaptureKey::ByName("path".to_string())) + .map(|s| s.as_str()) + .unwrap_or(""); // Treat HOME as an alias for USERPROFILE on Windows for legacy reasons - if Platform::is_windows() - && matches.get("var").map(|s| s.as_str()).unwrap_or("") == "HOME" - { + if Platform::is_windows() && var == "HOME" { if Platform::get_env("HOME").is_some() { return format!( "{}{}", Platform::get_env("HOME").unwrap_or_default(), - matches.get("path").map(|s| s.as_str()).unwrap_or(""), + path_part, ); } return format!( "{}{}", Platform::get_env("USERPROFILE").unwrap_or_default(), - matches.get("path").map(|s| s.as_str()).unwrap_or(""), + path_part, ); } format!( "{}{}", - Platform::get_env(matches.get("var").map(|s| s.as_str()).unwrap_or("")) - .unwrap_or_default(), - matches.get("path").map(|s| s.as_str()).unwrap_or(""), + Platform::get_env(var).unwrap_or_default(), + path_part, ) }, path, ) + .unwrap_or_default() } /// @throws \RuntimeException If the user home could not reliably be determined diff --git a/crates/shirabe/src/util/process_executor.rs b/crates/shirabe/src/util/process_executor.rs index 6351a73..ed1958d 100644 --- a/crates/shirabe/src/util/process_executor.rs +++ b/crates/shirabe/src/util/process_executor.rs @@ -118,7 +118,7 @@ impl ProcessExecutor { /// runs a process on the commandline in TTY mode pub fn execute_tty(&mut self, command: PhpMixed, cwd: Option<&str>) -> Result<i64> { - if Platform::is_tty() { + if Platform::is_tty(None) { return self.do_execute(command, cwd, true, None); } @@ -448,7 +448,7 @@ impl ProcessExecutor { 1, min( 50, - max_jobs_env.as_string().unwrap_or("0").parse().unwrap_or(0), + max_jobs_env.as_deref().unwrap_or("0").parse().unwrap_or(0), ), ); } else { diff --git a/crates/shirabe/src/util/remote_filesystem.rs b/crates/shirabe/src/util/remote_filesystem.rs index 0674df4..d2658fe 100644 --- a/crates/shirabe/src/util/remote_filesystem.rs +++ b/crates/shirabe/src/util/remote_filesystem.rs @@ -263,7 +263,7 @@ impl RemoteFilesystem { using_proxy )), true, - <dyn IOInterface>::DEBUG, + crate::io::io_interface::DEBUG, ); if (!Preg::is_match("{^http://(repo\\.)?packagist\\.org/p/}", &file_url).unwrap_or(false) @@ -277,7 +277,7 @@ impl RemoteFilesystem { self.io.write_error( PhpMixed::String("Downloading (<comment>connecting...</comment>)".to_string()), false, - <dyn IOInterface>::NORMAL, + crate::io::io_interface::NORMAL, ); } @@ -352,7 +352,7 @@ impl RemoteFilesystem { base64_encode(result.as_deref().unwrap_or("")) )), true, - <dyn IOInterface>::DEBUG, + crate::io::io_interface::DEBUG, ); return Err(anyhow::anyhow!(e)); @@ -398,7 +398,7 @@ impl RemoteFilesystem { self.io.write_error( PhpMixed::String("".to_string()), true, - <dyn IOInterface>::NORMAL, + crate::io::io_interface::NORMAL, ); self.io.write_error( PhpMixed::List(vec![ @@ -409,7 +409,7 @@ impl RemoteFilesystem { )), ]), true, - <dyn IOInterface>::NORMAL, + crate::io::io_interface::NORMAL, ); return self.get( @@ -498,7 +498,7 @@ impl RemoteFilesystem { PhpMixed::String("Downloading (<error>failed</error>)".to_string()), false, None, - <dyn IOInterface>::NORMAL, + crate::io::io_interface::NORMAL, ); } @@ -533,7 +533,7 @@ impl RemoteFilesystem { )), false, None, - <dyn IOInterface>::NORMAL, + crate::io::io_interface::NORMAL, ); } @@ -565,7 +565,7 @@ impl RemoteFilesystem { )), ]), true, - <dyn IOInterface>::NORMAL, + crate::io::io_interface::NORMAL, ); return self.get( @@ -643,7 +643,7 @@ impl RemoteFilesystem { self.io.write_error( PhpMixed::String("".to_string()), true, - <dyn IOInterface>::NORMAL, + crate::io::io_interface::NORMAL, ); self.io.write_error( PhpMixed::List(vec![ @@ -654,7 +654,7 @@ impl RemoteFilesystem { )), ]), true, - <dyn IOInterface>::NORMAL, + crate::io::io_interface::NORMAL, ); return self.get( @@ -778,7 +778,7 @@ impl RemoteFilesystem { )), false, None, - <dyn IOInterface>::NORMAL, + crate::io::io_interface::NORMAL, ); } } @@ -947,7 +947,7 @@ impl RemoteFilesystem { self.io.write_error( PhpMixed::String("".to_string()), true, - <dyn IOInterface>::DEBUG, + crate::io::io_interface::DEBUG, ); self.io.write_error( PhpMixed::String(sprintf( @@ -958,7 +958,7 @@ impl RemoteFilesystem { ], )), true, - <dyn IOInterface>::DEBUG, + crate::io::io_interface::DEBUG, ); additional_options.insert("redirects".to_string(), PhpMixed::Int(self.redirects)); diff --git a/crates/shirabe/src/util/svn.rs b/crates/shirabe/src/util/svn.rs index b312ec3..955e28c 100644 --- a/crates/shirabe/src/util/svn.rs +++ b/crates/shirabe/src/util/svn.rs @@ -211,11 +211,11 @@ impl Svn { .into()); } - self.io.write_error( - PhpMixed::String(format!( + self.io.write_error3( + &format!( "The Subversion server ({}) requested credentials:", self.url, - )), + ), true, io_interface::NORMAL, ); diff --git a/crates/shirabe/src/util/sync_helper.rs b/crates/shirabe/src/util/sync_helper.rs index 1be9075..22e4f1f 100644 --- a/crates/shirabe/src/util/sync_helper.rs +++ b/crates/shirabe/src/util/sync_helper.rs @@ -9,7 +9,7 @@ use shirabe_external_packages::react::promise::promise_interface::PromiseInterfa pub enum DownloaderOrManager<'a> { Interface(&'a dyn DownloaderInterface), - Manager(&'a DownloadManager), + Manager(&'a std::rc::Rc<std::cell::RefCell<DownloadManager>>), } impl<'a> DownloaderOrManager<'a> { @@ -21,7 +21,7 @@ impl<'a> DownloaderOrManager<'a> { ) -> Box<dyn PromiseInterface> { match self { Self::Interface(d) => d.download(package, path, prev_package), - Self::Manager(d) => d.download(package, path, prev_package), + Self::Manager(d) => d.borrow().download(package, path, prev_package), } } @@ -34,14 +34,14 @@ impl<'a> DownloaderOrManager<'a> { ) -> Box<dyn PromiseInterface> { match self { Self::Interface(d) => d.prepare(r#type, package, path, prev_package), - Self::Manager(d) => d.prepare(r#type, package, path, prev_package), + Self::Manager(d) => d.borrow().prepare(r#type, package, path, prev_package), } } fn install(&self, package: &dyn PackageInterface, path: &str) -> Box<dyn PromiseInterface> { match self { Self::Interface(d) => d.install(package, path), - Self::Manager(d) => d.install(package, path), + Self::Manager(d) => d.borrow().install(package, path), } } @@ -53,7 +53,7 @@ impl<'a> DownloaderOrManager<'a> { ) -> Box<dyn PromiseInterface> { match self { Self::Interface(d) => d.update(package, prev_package, path), - Self::Manager(d) => d.update(package, prev_package, path), + Self::Manager(d) => d.borrow().update(package, prev_package, path), } } @@ -66,7 +66,7 @@ impl<'a> DownloaderOrManager<'a> { ) -> Box<dyn PromiseInterface> { match self { Self::Interface(d) => d.cleanup(r#type, package, path, prev_package), - Self::Manager(d) => d.cleanup(r#type, package, path, prev_package), + Self::Manager(d) => d.borrow().cleanup(r#type, package, path, prev_package), } } } @@ -75,7 +75,7 @@ pub struct SyncHelper; impl SyncHelper { pub fn download_and_install_package_sync( - r#loop: &Loop, + r#loop: &std::rc::Rc<std::cell::RefCell<Loop>>, downloader: DownloaderOrManager<'_>, path: String, package: &dyn PackageInterface, @@ -121,9 +121,12 @@ impl SyncHelper { Ok(()) } - pub fn r#await(r#loop: &Loop, promise: Option<Box<dyn PromiseInterface>>) -> Result<()> { + pub fn r#await( + r#loop: &std::rc::Rc<std::cell::RefCell<Loop>>, + promise: Option<Box<dyn PromiseInterface>>, + ) -> Result<()> { if let Some(promise) = promise { - r#loop.wait(vec![promise]); + r#loop.borrow_mut().wait(vec![promise], None)?; } Ok(()) } |
