diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-05-19 21:46:01 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-05-19 21:46:08 +0900 |
| commit | 5e31fa33c3b5cf726a57a063b8e7a070869250fe (patch) | |
| tree | 98522466966fa7df483cad174ab5fc03db39bc09 /crates/shirabe/src/repository | |
| parent | c839244d8d09f3036ebfee8eef7eb6b147e593ab (diff) | |
| download | php-shirabe-5e31fa33c3b5cf726a57a063b8e7a070869250fe.tar.gz php-shirabe-5e31fa33c3b5cf726a57a063b8e7a070869250fe.tar.zst php-shirabe-5e31fa33c3b5cf726a57a063b8e7a070869250fe.zip | |
fix(compile): fix more random compile errors
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/src/repository')
34 files changed, 1373 insertions, 755 deletions
diff --git a/crates/shirabe/src/repository/array_repository.rs b/crates/shirabe/src/repository/array_repository.rs index 5652dad..ef15b39 100644 --- a/crates/shirabe/src/repository/array_repository.rs +++ b/crates/shirabe/src/repository/array_repository.rs @@ -53,10 +53,7 @@ impl ArrayRepository { /// Adds a new package to the repository pub fn add_package(&self, package: Box<dyn PackageInterface>) -> Result<()> { // PHP: if (!$package instanceof BasePackage) throw new \InvalidArgumentException(...) - if (package.as_any() as &dyn Any) - .downcast_ref::<BasePackage>() - .is_none() - { + if package.as_any().downcast_ref::<dyn BasePackage>().is_none() { return Err(InvalidArgumentException { message: "Only subclasses of BasePackage are supported".to_string(), code: 0, @@ -74,7 +71,7 @@ impl ArrayRepository { package.set_repository(todo!("self as Box<dyn RepositoryInterface>"))?; let aliased_package: Option<Box<dyn BasePackage>> = - if let Some(alias) = (package.as_any() as &dyn Any).downcast_ref::<AliasPackage>() { + if let Some(alias) = package.as_any().downcast_ref::<AliasPackage>() { Some(alias.get_alias_of().clone_box()) } else { None @@ -101,14 +98,11 @@ impl ArrayRepository { alias: String, pretty_alias: String, ) -> Box<dyn BasePackage> { - while let Some(alias_pkg) = (package.as_any() as &dyn Any).downcast_ref::<AliasPackage>() { + while let Some(alias_pkg) = package.as_any().downcast_ref::<AliasPackage>() { package = alias_pkg.get_alias_of().clone_box(); } - if (package.as_any() as &dyn Any) - .downcast_ref::<CompletePackage>() - .is_some() - { + if package.as_any().downcast_ref::<CompletePackage>().is_some() { // TODO(phase-b): construct CompleteAliasPackage/AliasPackage and return as Box<BasePackage> return todo!("new CompleteAliasPackage(package, alias, pretty_alias)"); } @@ -199,9 +193,7 @@ impl RepositoryInterface for ArrayRepository { // add selected packages which match stability requirements result.insert(spl_object_hash(package.as_ref()), package.clone_box()); // add the aliased package for packages where the alias matches - if let Some(alias) = - (package.as_any() as &dyn Any).downcast_ref::<AliasPackage>() - { + if let Some(alias) = package.as_any().downcast_ref::<AliasPackage>() { let aliased = alias.get_alias_of(); if !result.contains_key(&spl_object_hash(aliased.as_ref())) { result.insert(spl_object_hash(aliased.as_ref()), aliased.clone_box()); @@ -218,7 +210,7 @@ impl RepositoryInterface for ArrayRepository { // add aliases of packages that were selected, even if the aliases did not match for package in &packages { - if let Some(alias) = (package.as_any() as &dyn Any).downcast_ref::<AliasPackage>() { + if let Some(alias) = package.as_any().downcast_ref::<AliasPackage>() { let aliased = alias.get_alias_of(); if result.contains_key(&spl_object_hash(aliased.as_ref())) { result.insert(spl_object_hash(package.as_ref()), package.clone_box()); @@ -234,17 +226,16 @@ impl RepositoryInterface for ArrayRepository { fn find_package( &self, - name: String, + name: &str, constraint: FindPackageConstraint, ) -> Option<Box<dyn BasePackage>> { - let name = strtolower(&name); + let name = strtolower(name); let constraint: Box<dyn ConstraintInterface> = match constraint { FindPackageConstraint::Constraint(c) => c, FindPackageConstraint::String(s) => { let version_parser = VersionParser::new(); - // TODO(phase-b): Arc<dyn ConstraintInterface + Send + Sync> -> Box<dyn ConstraintInterface> - Box::new(version_parser.parse_constraints(&s).unwrap()) + version_parser.parse_constraints(&s).unwrap().clone_box() } }; @@ -262,11 +253,11 @@ impl RepositoryInterface for ArrayRepository { fn find_packages( &self, - name: String, + name: &str, constraint: Option<FindPackageConstraint>, ) -> Vec<Box<dyn BasePackage>> { // normalize name - let name = strtolower(&name); + let name = strtolower(name); let mut packages = vec![]; let constraint: Option<Box<dyn ConstraintInterface>> = match constraint { @@ -274,8 +265,7 @@ impl RepositoryInterface for ArrayRepository { Some(FindPackageConstraint::Constraint(c)) => Some(c), Some(FindPackageConstraint::String(s)) => { let version_parser = VersionParser::new(); - // TODO(phase-b): Arc<dyn ConstraintInterface + Send + Sync> -> Box<dyn ConstraintInterface> - Some(Box::new(version_parser.parse_constraints(&s).unwrap())) + Some(version_parser.parse_constraints(&s).unwrap().clone_box()) } }; @@ -296,7 +286,7 @@ impl RepositoryInterface for ArrayRepository { } fn search(&self, query: String, mode: i64, r#type: Option<String>) -> Vec<SearchResult> { - let regex = if mode == Self::SEARCH_FULLTEXT { + let regex = if mode == crate::repository::repository_interface::SEARCH_FULLTEXT { format!( "{{(?:{})}}i", implode("|", &Preg::split("{\\s+}", &preg_quote(&query, None))) @@ -309,7 +299,7 @@ impl RepositoryInterface for ArrayRepository { let mut matches: IndexMap<String, SearchResult> = IndexMap::new(); for package in self.get_packages() { let mut name = PackageInterface::get_name(package.as_ref()).to_string(); - if mode == Self::SEARCH_VENDOR { + if mode == crate::repository::repository_interface::SEARCH_VENDOR { // PHP: [$name] = explode('/', $name); let parts: Vec<&str> = name.splitn(2, '/').collect(); name = parts[0].to_string(); @@ -323,9 +313,9 @@ impl RepositoryInterface for ArrayRepository { } } - let complete = (package.as_any() as &dyn Any).downcast_ref::<CompletePackage>(); + let complete = package.as_any().downcast_ref::<CompletePackage>(); - let fulltext_match = mode == Self::SEARCH_FULLTEXT + let fulltext_match = mode == crate::repository::repository_interface::SEARCH_FULLTEXT && complete.is_some() && Preg::is_match( ®ex, @@ -334,10 +324,11 @@ impl RepositoryInterface for ArrayRepository { implode(" ", &complete.unwrap().get_keywords()), complete.unwrap().get_description().unwrap_or("") ), - ); + ) + .unwrap_or(false); - if Preg::is_match(®ex, &name) || fulltext_match { - if mode == Self::SEARCH_VENDOR { + if Preg::is_match(®ex, &name).unwrap_or(false) || fulltext_match { + if mode == crate::repository::repository_interface::SEARCH_VENDOR { matches.insert( name.clone(), SearchResult { @@ -405,8 +396,7 @@ impl RepositoryInterface for ArrayRepository { } for link in candidate.get_provides().values() { if package_name == link.get_target() { - let complete = - (candidate.as_any() as &dyn Any).downcast_ref::<CompletePackage>(); + let complete = candidate.as_any().downcast_ref::<CompletePackage>(); let description = complete.and_then(|c| c.get_description().map(String::from)); result.insert( PackageInterface::get_name(candidate.as_ref()).to_string(), diff --git a/crates/shirabe/src/repository/artifact_repository.rs b/crates/shirabe/src/repository/artifact_repository.rs index faef831..afaf9cb 100644 --- a/crates/shirabe/src/repository/artifact_repository.rs +++ b/crates/shirabe/src/repository/artifact_repository.rs @@ -106,7 +106,7 @@ impl ArtifactRepository { let basename = file_path.file_name().and_then(|n| n.to_str()).unwrap_or(""); match package { None => { - self.io.write_error( + self.io.write_error3( &format!( "File <comment>{}</comment> doesn't seem to hold a package", basename @@ -116,16 +116,12 @@ impl ArtifactRepository { ); } Some(package) => { - self.io.write_error( - &format!( - "Found package <info>{}</info> (<comment>{}</comment>) in file <info>{}</info>", - package.get_name(), - package.get_pretty_version(), - basename, - ), - true, - io_interface::VERBOSE, - ); + self.io.write_error3(&format!( + "Found package <info>{}</info> (<comment>{}</comment>) in file <info>{}</info>", + package.get_name(), + package.get_pretty_version(), + basename, + ), true, io_interface::VERBOSE); self.inner.add_package(package); } } @@ -169,7 +165,7 @@ impl ArtifactRepository { match get_result { Ok(j) => json = j, Err(exception) => { - self.io.write( + self.io.write3( &format!("Failed loading package {}: {}", pathname, exception), false, io_interface::VERBOSE, diff --git a/crates/shirabe/src/repository/canonical_packages_trait.rs b/crates/shirabe/src/repository/canonical_packages_trait.rs index 8fae936..3c9c75c 100644 --- a/crates/shirabe/src/repository/canonical_packages_trait.rs +++ b/crates/shirabe/src/repository/canonical_packages_trait.rs @@ -14,8 +14,12 @@ pub trait CanonicalPackagesTrait { // get at most one package of each name, preferring non-aliased ones let mut packages_by_name: IndexMap<String, Box<dyn PackageInterface>> = IndexMap::new(); for package in packages { - let name = package.get_name(); - if !packages_by_name.contains_key(&name) || packages_by_name[&name].is_alias_package() { + let name = package.get_name().to_string(); + let prefer_replace = packages_by_name + .get(&name) + .map(|existing| existing.as_alias_package().is_some()) + .unwrap_or(true); + if prefer_replace { packages_by_name.insert(name, package); } } @@ -23,11 +27,10 @@ pub trait CanonicalPackagesTrait { let mut canonical_packages = Vec::new(); // unfold aliased packages - for mut package in packages_by_name.into_values() { - while package.is_alias_package() { - package = package.get_alias_of(); - } - + for package in packages_by_name.into_values() { + // TODO(phase-b): unfolding requires `Box<dyn PackageInterface>` traversal of + // `AliasPackage::get_alias_of()` (currently returns `&BasePackage`, not an + // ownable trait object). Push the alias as-is for now. canonical_packages.push(package); } diff --git a/crates/shirabe/src/repository/composer_repository.rs b/crates/shirabe/src/repository/composer_repository.rs index dfbb0b5..1e6443f 100644 --- a/crates/shirabe/src/repository/composer_repository.rs +++ b/crates/shirabe/src/repository/composer_repository.rs @@ -2,7 +2,7 @@ use indexmap::IndexMap; use shirabe_external_packages::composer::metadata_minifier::metadata_minifier::MetadataMinifier; -use shirabe_external_packages::composer::pcre::preg::Preg; +use shirabe_external_packages::composer::pcre::preg::{CaptureKey, Preg}; use shirabe_external_packages::react::promise::promise_interface::PromiseInterface; use shirabe_php_shim::{ InvalidArgumentException, JSON_UNESCAPED_SLASHES, JSON_UNESCAPED_UNICODE, LogicException, @@ -84,7 +84,7 @@ pub struct ComposerRepository { /// non-empty-string base_url: String, io: Box<dyn IOInterface>, - http_downloader: HttpDownloader, + http_downloader: std::rc::Rc<std::cell::RefCell<HttpDownloader>>, r#loop: std::rc::Rc<std::cell::RefCell<Loop>>, pub(crate) cache: Cache, pub(crate) notify_url: Option<String>, @@ -148,7 +148,7 @@ impl ComposerRepository { mut repo_config: IndexMap<String, PhpMixed>, io: Box<dyn IOInterface>, config: &Config, - http_downloader: HttpDownloader, + http_downloader: std::rc::Rc<std::cell::RefCell<HttpDownloader>>, event_dispatcher: Option<EventDispatcher>, ) -> anyhow::Result<Self> { // parent::__construct(); @@ -246,13 +246,16 @@ impl ComposerRepository { .to_string(); // force url for packagist.org to repo.packagist.org - let mut match_packagist: Vec<String> = Vec::new(); - if Preg::is_match_with_matches( + let mut match_packagist: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match3( r"{^(?P<proto>https?)://packagist\.org/?$}i", &url, - &mut match_packagist, + Some(&mut match_packagist), )? { - let proto = match_packagist.get(1).cloned().unwrap_or_default(); + let proto = match_packagist + .get(&CaptureKey::ByName("proto".to_string())) + .cloned() + .unwrap_or_default(); url = format!("{}://repo.packagist.org", proto); } @@ -272,7 +275,7 @@ impl ComposerRepository { let loader = ArrayLoader::new_with_parser(version_parser.clone()); let r#loop = std::rc::Rc::new(std::cell::RefCell::new(Loop::new( - http_downloader.clone(), + std::rc::Rc::clone(&http_downloader), None, ))); @@ -336,10 +339,10 @@ impl ComposerRepository { let name = strtolower(&name); let constraint: Box<dyn ConstraintInterface> = match constraint { - PhpMixed::String(s) => self.version_parser.parse_constraints(&s)?, + PhpMixed::String(s) => self.version_parser.parse_constraints(&s)?.clone_box(), _ => { // already a ConstraintInterface object passed as opaque PhpMixed - self.version_parser.parse_constraints("")? + self.version_parser.parse_constraints("")?.clone_box() } }; @@ -393,7 +396,10 @@ impl ComposerRepository { return Ok(None); } - Ok(self.inner.find_package(name, Some(constraint))) + Ok(self.inner.find_package( + &name, + crate::repository::repository_interface::FindPackageConstraint::Constraint(constraint), + )) } /// @inheritDoc @@ -408,7 +414,9 @@ impl ComposerRepository { let name = strtolower(&name); let constraint: Option<Box<dyn ConstraintInterface>> = match constraint { None => None, - Some(PhpMixed::String(s)) => Some(self.version_parser.parse_constraints(&s)?), + Some(PhpMixed::String(s)) => { + Some(self.version_parser.parse_constraints(&s)?.clone_box()) + } Some(_) => None, }; @@ -458,7 +466,11 @@ impl ComposerRepository { return Ok(vec![]); } - Ok(self.inner.find_packages(name, constraint)) + Ok(self.inner.find_packages( + &name, + constraint + .map(crate::repository::repository_interface::FindPackageConstraint::Constraint), + )) } fn filter_packages( @@ -571,7 +583,10 @@ impl ComposerRepository { }; let filter_results = |results: Vec<String>| -> anyhow::Result<Vec<String>> { match &package_filter_regex { - Some(regex) => Ok(Preg::grep(regex, &results)?), + Some(regex) => { + let results_refs: Vec<&str> = results.iter().map(|s| s.as_str()).collect(); + Ok(Preg::grep(regex, &results_refs)?) + } None => Ok(results), } }; @@ -658,6 +673,7 @@ impl ComposerRepository { url.push_str(&format!("?filter={}", urlencode(filter))); let result = self .http_downloader + .borrow_mut() .get(&url, &self.options)? .decode_json()?; let package_names: Vec<String> = result @@ -689,6 +705,7 @@ impl ComposerRepository { let result = self .http_downloader + .borrow_mut() .get(&url, &self.options)? .decode_json()?; let package_names: Vec<String> = result @@ -914,15 +931,21 @@ impl ComposerRepository { if self.has_providers()? || self.lazy_providers_url.is_some() { // optimize search for "^foo/bar" where at least "^foo/" is present by loading this directly from the listUrl if present - let mut match_groups: Vec<String> = Vec::new(); - if Preg::is_match_strict_groups( + let mut match_groups: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match_strict_groups3( r"{^\^(?P<query>(?P<vendor>[a-z0-9_.-]+)/[a-z0-9_.-]*)\*?$}i", &query, - &mut match_groups, + Some(&mut match_groups), )? && self.list_url.is_some() { - let q = match_groups.get(1).cloned().unwrap_or_default(); - let vendor = match_groups.get(2).cloned().unwrap_or_default(); + let q = match_groups + .get(&CaptureKey::ByName("query".to_string())) + .cloned() + .unwrap_or_default(); + let vendor = match_groups + .get(&CaptureKey::ByName("vendor".to_string())) + .cloned() + .unwrap_or_default(); let url = format!( "{}?vendor={}&filter={}", self.list_url.as_ref().unwrap(), @@ -931,6 +954,7 @@ impl ComposerRepository { ); let result = self .http_downloader + .borrow_mut() .get(&url, &self.options)? .decode_json()?; @@ -1179,7 +1203,7 @@ impl ComposerRepository { http_map.insert("content".to_string(), Box::new(PhpMixed::String(body))); } - let response = self.http_downloader.get(&api_url, &options)?; + let response = self.http_downloader.borrow_mut().get(&api_url, &options)?; let mut warned = false; let decoded = response.decode_json()?; let advisories_response = decoded @@ -1245,7 +1269,7 @@ impl ComposerRepository { let mut result: IndexMap<String, IndexMap<String, PhpMixed>> = IndexMap::new(); if let Some(providers_api_url) = self.providers_api_url.clone() { - let api_result = match self.http_downloader.get( + let api_result = match self.http_downloader.borrow_mut().get( &providers_api_url.replace("%package%", package_name), &self.options, ) { @@ -2514,11 +2538,14 @@ impl ComposerRepository { } if url.starts_with('/') { - let mut matches: Vec<String> = Vec::new(); - if Preg::is_match_with_matches(r"{^[^:]++://[^/]*+}", &self.url, &mut matches)? { + let mut matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match3(r"{^[^:]++://[^/]*+}", &self.url, Some(&mut matches))? { return Ok(format!( "{}{}", - matches.get(0).cloned().unwrap_or_default(), + matches + .get(&CaptureKey::ByIndex(0)) + .cloned() + .unwrap_or_default(), url )); } @@ -2823,7 +2850,7 @@ impl ComposerRepository { if let Some(dispatcher) = self.event_dispatcher.as_mut() { let mut pre_file_download_event = PreFileDownloadEvent::new( PluginEvents::PRE_FILE_DOWNLOAD.to_string(), - &self.http_downloader, + std::rc::Rc::clone(&self.http_downloader), filename.clone(), "metadata".to_string(), { @@ -2842,7 +2869,7 @@ impl ComposerRepository { options = pre_file_download_event.get_transport_options(); } - let response = self.http_downloader.get(&filename, &options)?; + let response = self.http_downloader.borrow_mut().get(&filename, &options)?; let mut json = response.get_body().to_string(); if let Some(sha256_val) = sha256 { if sha256_val != hash("sha256", &json) { @@ -3004,7 +3031,7 @@ impl ComposerRepository { if let Some(dispatcher) = self.event_dispatcher.as_mut() { let mut pre_file_download_event = PreFileDownloadEvent::new( PluginEvents::PRE_FILE_DOWNLOAD.to_string(), - &self.http_downloader, + std::rc::Rc::clone(&self.http_downloader), filename.clone(), "metadata".to_string(), { @@ -3048,7 +3075,7 @@ impl ComposerRepository { http_map.insert("header".to_string(), Box::new(PhpMixed::List(headers))); } - let response = self.http_downloader.get(&filename, &options)?; + let response = self.http_downloader.borrow_mut().get(&filename, &options)?; let mut json = response.get_body().to_string(); if json.is_empty() && response.get_status_code() == 304 { return Ok(FetchFileIfLastModifiedResult::NotModified); @@ -3159,7 +3186,7 @@ impl ComposerRepository { if let Some(dispatcher) = self.event_dispatcher.as_mut() { let mut pre_file_download_event = PreFileDownloadEvent::new( PluginEvents::PRE_FILE_DOWNLOAD.to_string(), - &self.http_downloader, + std::rc::Rc::clone(&self.http_downloader), filename.clone(), "metadata".to_string(), { @@ -3332,7 +3359,7 @@ impl ComposerRepository { } }; - let initial = self.http_downloader.add(&filename, &options)?; + let initial = self.http_downloader.borrow_mut().add(&filename, &options)?; Ok(initial.then_with_reject_boxed(Box::new(accept), Box::new(reject))) } diff --git a/crates/shirabe/src/repository/composite_repository.rs b/crates/shirabe/src/repository/composite_repository.rs index 4914538..d956cdd 100644 --- a/crates/shirabe/src/repository/composite_repository.rs +++ b/crates/shirabe/src/repository/composite_repository.rs @@ -31,17 +31,16 @@ impl CompositeRepository { &self.repositories } - pub fn remove_package(&mut self, package: &dyn PackageInterface) { - for repository in &mut self.repositories { - // TODO(phase-b): only call remove_package on WritableRepositoryInterface implementors - let _ = repository.remove_package(package); + pub fn remove_package(&mut self, _package: &dyn PackageInterface) { + // TODO(phase-b): only call remove_package on WritableRepositoryInterface implementors; + // requires a downcast helper such as `as_writable() -> Option<&mut dyn WritableRepositoryInterface>` on RepositoryInterface. + for _repository in &mut self.repositories { + todo!() } } pub fn add_repository(&mut self, repository: Box<dyn RepositoryInterface>) { - if let Some(composite) = - (repository.as_any() as &dyn Any).downcast_ref::<CompositeRepository>() - { + if let Some(composite) = repository.as_any().downcast_ref::<CompositeRepository>() { for repo in composite.get_repositories() { self.repositories.push(repo.clone_box()); } @@ -78,11 +77,11 @@ impl RepositoryInterface for CompositeRepository { fn find_package( &self, - name: String, + name: &str, constraint: FindPackageConstraint, ) -> Option<Box<dyn BasePackage>> { for repository in &self.repositories { - let package = repository.find_package(name.clone(), constraint.clone()); + let package = repository.find_package(name, constraint.clone()); if package.is_some() { return package; } @@ -92,12 +91,12 @@ impl RepositoryInterface for CompositeRepository { fn find_packages( &self, - name: String, + name: &str, constraint: Option<FindPackageConstraint>, ) -> Vec<Box<dyn BasePackage>> { let mut packages = vec![]; for repository in &self.repositories { - packages.extend(repository.find_packages(name.clone(), constraint.clone())); + packages.extend(repository.find_packages(name, constraint.clone())); } packages } diff --git a/crates/shirabe/src/repository/filesystem_repository.rs b/crates/shirabe/src/repository/filesystem_repository.rs index 3d785e7..4ce8585 100644 --- a/crates/shirabe/src/repository/filesystem_repository.rs +++ b/crates/shirabe/src/repository/filesystem_repository.rs @@ -19,6 +19,7 @@ use crate::json::json_file::JsonFile; use crate::package::alias_package::AliasPackage; use crate::package::dumper::array_dumper::ArrayDumper; use crate::package::loader::array_loader::ArrayLoader; +use crate::package::loader::loader_interface::LoaderInterface; use crate::package::package_interface::PackageInterface; use crate::package::root_alias_package::RootAliasPackage; use crate::package::root_package_interface::RootPackageInterface; @@ -39,7 +40,7 @@ pub struct FilesystemRepository { /// @var ?RootPackageInterface root_package: Option<Box<dyn RootPackageInterface>>, /// @var Filesystem - filesystem: Filesystem, + filesystem: std::rc::Rc<std::cell::RefCell<Filesystem>>, /// @var bool|null dev_mode: Option<bool>, } @@ -53,9 +54,10 @@ impl FilesystemRepository { repository_file: JsonFile, dump_versions: bool, root_package: Option<Box<dyn RootPackageInterface>>, - filesystem: Option<Filesystem>, + filesystem: Option<std::rc::Rc<std::cell::RefCell<Filesystem>>>, ) -> Result<Self> { - let filesystem = filesystem.unwrap_or_else(|| Filesystem::new(None)); + let filesystem = filesystem + .unwrap_or_else(|| std::rc::Rc::new(std::cell::RefCell::new(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(), @@ -191,10 +193,13 @@ impl FilesystemRepository { // as realpath() does some additional normalizations with network paths that normalizePath does not // and we need to find shortest path correctly let repo_dir = dirname(self.file.get_path()); - self.filesystem.ensure_directory_exists(&repo_dir); + self.filesystem + .borrow_mut() + .ensure_directory_exists(&repo_dir); let repo_dir = self .filesystem + .borrow() .normalize_path(&realpath(&repo_dir).unwrap_or_default()); let mut install_paths: IndexMap<String, Option<String>> = IndexMap::new(); @@ -204,8 +209,9 @@ impl FilesystemRepository { let mut install_path: Option<String> = None; if let Some(path_str) = &path { if !path_str.is_empty() { - let normalized_path = self.filesystem.normalize_path(&if self + let normalized_path = self.filesystem.borrow_mut().normalize_path(&if self .filesystem + .borrow() .is_absolute_path(path_str) { path_str.clone() @@ -216,7 +222,7 @@ impl FilesystemRepository { path_str ) }); - install_path = Some(self.filesystem.find_shortest_path( + install_path = Some(self.filesystem.borrow_mut().find_shortest_path( &repo_dir, &normalized_path, true, @@ -282,17 +288,12 @@ impl FilesystemRepository { }); } - self.file.write( - PhpMixed::Array( - data.clone() - .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, - )?; + self.file.write(PhpMixed::Array( + data.clone() + .into_iter() + .map(|(k, v)| (k, Box::new(v))) + .collect(), + ))?; if self.dump_versions { let versions = self.generate_installed_versions( @@ -302,7 +303,7 @@ impl FilesystemRepository { &repo_dir, )?; - self.filesystem.file_put_contents_if_modified( + self.filesystem.borrow_mut().file_put_contents_if_modified( &format!("{}/installed.php", repo_dir), &format!("<?php return {};\n", self.dump_to_php_code(&versions, 0),), ); @@ -311,7 +312,7 @@ impl FilesystemRepository { // this normally should not happen but during upgrades of Composer when it is installed in the project it is a possibility if let Some(class_content) = installed_versions_class { - self.filesystem.file_put_contents_if_modified( + self.filesystem.borrow_mut().file_put_contents_if_modified( &format!("{}/InstalledVersions.php", repo_dir), &class_content, ); @@ -347,7 +348,7 @@ impl FilesystemRepository { let pattern = "{(?(DEFINE)\n (?<number> -? \\s*+ \\d++ (?:\\.\\d++)? )\n (?<boolean> true | false | null )\n (?<strings> (?&string) (?: \\s*+ \\. \\s*+ (?&string))*+ )\n (?<string> (?: \" (?:[^\"\\\\$]*+ | \\\\ [\"\\\\0] )* \" | ' (?:[^'\\\\]*+ | \\\\ ['\\\\] )* ' ) )\n (?<array> array\\( \\s*+ (?: (?:(?&number)|(?&strings)) \\s*+ => \\s*+ (?: (?:__DIR__ \\s*+ \\. \\s*+)? (?&strings) | (?&value) ) \\s*+, \\s*+ )*+ \\s*+ \\) )\n (?<value> (?: (?&number) | (?&boolean) | (?&strings) | (?&array) ) )\n)\n^<\\?php\\s++return\\s++(?&array)\\s*+;$}ix"; if let Some(data) = installed_versions_data { let mixed = PhpMixed::String(data.clone()); - if is_string(&mixed) && Preg::is_match(pattern, &trim(&data, None)) { + if is_string(&mixed) && Preg::is_match(pattern, &trim(&data, None)).unwrap_or(false) { let replaced = Preg::replace( r#"{=>\s*+__DIR__\s*+\.\s*+(['\"])}"#, &format!( @@ -356,6 +357,10 @@ impl FilesystemRepository { ), &data, ); + let replaced = match replaced { + Ok(s) => s, + Err(_) => return false, + }; let evaluated = r#eval(&format!("?>{}", replaced)); InstalledVersions::reload( evaluated @@ -411,7 +416,7 @@ impl FilesystemRepository { } } else if key == "install_path" && is_string(value) { let s = value.as_string().unwrap_or("").to_string(); - if self.filesystem.is_absolute_path(&s) { + if self.filesystem.borrow_mut().is_absolute_path(&s) { lines.push_str(&format!("{},\n", var_export(&PhpMixed::String(s), true),)); } else { lines.push_str(&format!( @@ -480,9 +485,7 @@ impl FilesystemRepository { let mut current_root: Box<dyn RootPackageInterface> = root_package; // packages.push(current_root.clone_box()); - while let Some(_alias) = - (current_root.as_any() as &dyn Any).downcast_ref::<RootAliasPackage>() - { + while let Some(_alias) = current_root.as_any().downcast_ref::<RootAliasPackage>() { current_root = todo!("RootAliasPackage::get_alias_of() returning Box<dyn RootPackageInterface>"); // packages.push(current_root.clone_box()); @@ -507,10 +510,7 @@ impl FilesystemRepository { // add real installed packages for package in &packages { - if (package.as_any() as &dyn Any) - .downcast_ref::<AliasPackage>() - .is_some() - { + if package.as_any().downcast_ref::<AliasPackage>().is_some() { continue; } @@ -532,7 +532,7 @@ impl FilesystemRepository { .as_array() .map(|m| m.contains_key(package.get_name())) .unwrap_or(false); - for replace in package.get_replaces() { + for (_, replace) in package.get_replaces() { // exclude platform replaces as when they are really there we can not check for their presence if PlatformRepository::is_platform_package(replace.get_target()) { continue; @@ -550,7 +550,7 @@ impl FilesystemRepository { todo!("append replaced to versions['versions'][target]['replaced']"); } } - for provide in package.get_provides() { + for (_, provide) in package.get_provides() { // exclude platform provides as when they are really there we can not check for their presence if PlatformRepository::is_platform_package(provide.get_target()) { continue; @@ -571,12 +571,13 @@ impl FilesystemRepository { // add aliases for package in &packages { - let Some(alias) = (package.as_any() as &dyn Any).downcast_ref::<AliasPackage>() else { + let Some(alias) = package.as_any().downcast_ref::<AliasPackage>() else { continue; }; // TODO(phase-b): mutate nested versions['versions'][name]['aliases'] todo!("append alias->getPrettyVersion() to versions['versions'][name]['aliases']"); - if (package.as_any() as &dyn Any) + if package + .as_any() .downcast_ref::<dyn RootPackageInterface>() .is_some() { @@ -641,14 +642,19 @@ impl FilesystemRepository { }; } - let install_path = if (package.as_any() as &dyn Any) + let install_path = if package + .as_any() .downcast_ref::<dyn RootPackageInterface>() .is_some() { - let to = self.filesystem.normalize_path( + let to = self.filesystem.borrow_mut().normalize_path( &realpath(&Platform::get_cwd(false).unwrap_or_default()).unwrap_or_default(), ); - Some(self.filesystem.find_shortest_path(repo_dir, &to, true)) + Some( + self.filesystem + .borrow_mut() + .find_shortest_path(repo_dir, &to, true), + ) } else { install_paths.get(package.get_name()).cloned().flatten() }; diff --git a/crates/shirabe/src/repository/filter_repository.rs b/crates/shirabe/src/repository/filter_repository.rs index ffdb3b9..482ffbe 100644 --- a/crates/shirabe/src/repository/filter_repository.rs +++ b/crates/shirabe/src/repository/filter_repository.rs @@ -165,10 +165,10 @@ impl RepositoryInterface for FilterRepository { fn find_package( &self, - name: String, + name: &str, constraint: FindPackageConstraint, ) -> Option<Box<dyn BasePackage>> { - if !self.is_allowed(&name) { + if !self.is_allowed(name) { return None; } @@ -177,10 +177,10 @@ impl RepositoryInterface for FilterRepository { fn find_packages( &self, - name: String, + name: &str, constraint: Option<FindPackageConstraint>, ) -> Vec<Box<dyn BasePackage>> { - if !self.is_allowed(&name) { + if !self.is_allowed(name) { return Vec::new(); } diff --git a/crates/shirabe/src/repository/installed_array_repository.rs b/crates/shirabe/src/repository/installed_array_repository.rs index c30d682..ddcda90 100644 --- a/crates/shirabe/src/repository/installed_array_repository.rs +++ b/crates/shirabe/src/repository/installed_array_repository.rs @@ -20,6 +20,16 @@ pub struct InstalledArrayRepository { } impl InstalledArrayRepository { + pub fn new() -> anyhow::Result<Self> { + Self::new_with_packages(Vec::new()) + } + + pub fn new_with_packages(packages: Vec<Box<dyn PackageInterface>>) -> anyhow::Result<Self> { + Ok(Self { + inner: WritableArrayRepository::new(packages)?, + }) + } + pub fn get_repo_name(&self) -> String { format!("installed {}", self.inner.get_repo_name()) } @@ -89,14 +99,14 @@ impl RepositoryInterface for InstalledArrayRepository { } fn find_package( &self, - _name: String, + _name: &str, _constraint: FindPackageConstraint, ) -> Option<Box<dyn BasePackage>> { todo!() } fn find_packages( &self, - _name: String, + _name: &str, _constraint: Option<FindPackageConstraint>, ) -> Vec<Box<dyn BasePackage>> { todo!() diff --git a/crates/shirabe/src/repository/installed_filesystem_repository.rs b/crates/shirabe/src/repository/installed_filesystem_repository.rs index 1d1caf6..c7f2a4d 100644 --- a/crates/shirabe/src/repository/installed_filesystem_repository.rs +++ b/crates/shirabe/src/repository/installed_filesystem_repository.rs @@ -28,7 +28,7 @@ impl InstalledFilesystemRepository { repository_file: JsonFile, dump_versions: bool, root_package: Option<Box<dyn RootPackageInterface>>, - filesystem: Option<Filesystem>, + filesystem: Option<std::rc::Rc<std::cell::RefCell<Filesystem>>>, ) -> Result<Self> { Ok(Self { inner: FilesystemRepository::new( @@ -109,14 +109,14 @@ impl RepositoryInterface for InstalledFilesystemRepository { } fn find_package( &self, - _name: String, + _name: &str, _constraint: FindPackageConstraint, ) -> Option<Box<dyn BasePackage>> { todo!() } fn find_packages( &self, - _name: String, + _name: &str, _constraint: Option<FindPackageConstraint>, ) -> Vec<Box<dyn BasePackage>> { todo!() diff --git a/crates/shirabe/src/repository/installed_repository.rs b/crates/shirabe/src/repository/installed_repository.rs index d0130f4..12abf1e 100644 --- a/crates/shirabe/src/repository/installed_repository.rs +++ b/crates/shirabe/src/repository/installed_repository.rs @@ -51,7 +51,7 @@ impl InstalledRepository { pub fn find_packages_with_replacers_and_providers( &self, - name: String, + name: &str, constraint: Option<FindPackageConstraint>, ) -> Vec<Box<dyn BasePackage>> { let name = name.to_lowercase(); @@ -121,7 +121,7 @@ impl InstalledRepository { let mut root_package: Option<Box<dyn BasePackage>> = None; for package in self.inner.get_packages() { - if package.as_any().is::<dyn RootPackageInterface>() { + if package.as_root_package_interface().is_some() { root_package = Some(package); break; } @@ -177,7 +177,7 @@ impl InstalledRepository { } } - if package.as_any().is::<dyn RootPackageInterface>() { + if package.as_root_package_interface().is_some() { for (k, v) in package.get_dev_requires() { links.entry(k).or_insert(v); } @@ -222,7 +222,7 @@ impl InstalledRepository { if invert && needles.contains(&package.get_name().to_string()) { for link in package.get_conflicts().values() { - for pkg in self.find_packages(link.get_target().to_string(), None) { + for pkg in self.find_packages(link.get_target(), None) { let version = Constraint::new("=", pkg.get_version()); if link.get_constraint().matches(&version) == invert { results.push(DependentsEntry(package.clone_box(), link.clone(), None)); @@ -233,7 +233,7 @@ impl InstalledRepository { for link in package.get_conflicts().values() { if needles.contains(&link.get_target().to_string()) { - for pkg in self.find_packages(link.get_target().to_string(), None) { + for pkg in self.find_packages(link.get_target(), None) { let version = Constraint::new("=", pkg.get_version()); if link.get_constraint().matches(&version) == invert { results.push(DependentsEntry(package.clone_box(), link.clone(), None)); @@ -254,7 +254,7 @@ impl InstalledRepository { if PlatformRepository::is_platform_package(link.get_target()) { if self .find_package( - link.get_target().to_string(), + link.get_target(), FindPackageConstraint::Constraint( link.get_constraint().clone_box(), ), @@ -265,7 +265,7 @@ impl InstalledRepository { } let platform_pkg = self.find_package( - link.get_target().to_string(), + link.get_target(), FindPackageConstraint::String("*".to_string()), ); let description = platform_pkg @@ -292,7 +292,7 @@ impl InstalledRepository { } for pkg in self.get_packages() { - if !pkg.get_names().contains(&link.get_target().to_string()) { + if !pkg.get_names(true).contains(&link.get_target().to_string()) { continue; } @@ -320,7 +320,9 @@ impl InstalledRepository { root_reqs.entry(k).or_insert(v); } for root_req in root_reqs.values() { - if pkg.get_names().contains(&root_req.get_target().to_string()) + if pkg + .get_names(true) + .contains(&root_req.get_target().to_string()) && !root_req.get_constraint().matches(link.get_constraint()) { results.push(DependentsEntry( @@ -422,7 +424,7 @@ impl RepositoryInterface for InstalledRepository { fn find_package( &self, - name: String, + name: &str, constraint: FindPackageConstraint, ) -> Option<Box<dyn BasePackage>> { self.inner.find_package(name, constraint) @@ -430,7 +432,7 @@ impl RepositoryInterface for InstalledRepository { fn find_packages( &self, - name: String, + name: &str, constraint: Option<FindPackageConstraint>, ) -> Vec<Box<dyn BasePackage>> { self.inner.find_packages(name, constraint) diff --git a/crates/shirabe/src/repository/lock_array_repository.rs b/crates/shirabe/src/repository/lock_array_repository.rs index c0a5034..87acf6e 100644 --- a/crates/shirabe/src/repository/lock_array_repository.rs +++ b/crates/shirabe/src/repository/lock_array_repository.rs @@ -41,7 +41,7 @@ impl RepositoryInterface for LockArrayRepository { fn find_package( &self, - name: String, + name: &str, constraint: FindPackageConstraint, ) -> Option<Box<dyn BasePackage>> { self.inner.find_package(name, constraint) @@ -49,7 +49,7 @@ impl RepositoryInterface for LockArrayRepository { fn find_packages( &self, - name: String, + name: &str, constraint: Option<FindPackageConstraint>, ) -> Vec<Box<dyn BasePackage>> { self.inner.find_packages(name, constraint) diff --git a/crates/shirabe/src/repository/package_repository.rs b/crates/shirabe/src/repository/package_repository.rs index 64b578e..dc8a8f3 100644 --- a/crates/shirabe/src/repository/package_repository.rs +++ b/crates/shirabe/src/repository/package_repository.rs @@ -40,18 +40,24 @@ impl PackageRepository { }; Self { - inner: ArrayRepository::new(), + inner: ArrayRepository::new(vec![]) + .expect("ArrayRepository::new with empty vec cannot fail"), config: config_list, security_advisories, } } pub fn initialize(&mut self) -> anyhow::Result<Result<(), InvalidRepositoryException>> { - self.inner.initialize()?; + self.inner.initialize(); - let loader = ValidatingArrayLoader::new(ArrayLoader::new(None, true), true); + let mut loader = + ValidatingArrayLoader::new(Box::new(ArrayLoader::new(None, true)), true, None, 0); for package in &self.config { - let package = match loader.load(package) { + let config_map: IndexMap<String, Box<PhpMixed>> = match package { + PhpMixed::Array(m) => m.clone(), + _ => IndexMap::new(), + }; + let package_loaded = match loader.load(config_map, "") { Ok(p) => p, Err(e) => { let msg = format!( @@ -65,13 +71,16 @@ impl PackageRepository { }))); } }; - self.inner.add_package(package)?; + // TODO(phase-b): add_package expects Box<dyn PackageInterface>; loader returns Box<dyn BasePackage> + let _ = package_loaded; } Ok(Ok(())) } pub fn get_repo_name(&self) -> String { + use crate::repository::repository_interface::RepositoryInterface; Preg::replace(r"^array ", "package ", &self.inner.get_repo_name()) + .unwrap_or_else(|_| self.inner.get_repo_name()) } } @@ -91,42 +100,45 @@ impl AdvisoryProviderInterface for PackageRepository { let mut advisories: IndexMap<String, Vec<PartialOrSecurityAdvisory>> = IndexMap::new(); for (package_name, package_advisories) in &self.security_advisories { - if package_constraint_map.contains_key(package_name.as_str()) { - let items: anyhow::Result<Vec<PartialOrSecurityAdvisory>> = match package_advisories { - PhpMixed::List(list) => list - .iter() - .filter_map(|data| { - let data_map = match data.as_ref() { - PhpMixed::Array(m) => m - .iter() - .map(|(k, v)| (k.clone(), *v.clone())) - .collect::<IndexMap<String, PhpMixed>>(), - _ => return Ok(None), - }; - let advisory = - PartialSecurityAdvisory::create(package_name, &data_map, &semver_parser) - .ok()?; - if !allow_partial_advisories - && matches!(advisory, PartialOrSecurityAdvisory::Partial(_)) - { - return Err(anyhow::anyhow!(RuntimeException { message: format!("Advisory for {} could not be loaded as a full advisory from {}\n{}", package_name, self.get_repo_name(), var_export(data, true)), code: 0 })); - } - let affected_versions = match &advisory { - PartialOrSecurityAdvisory::Full(a) => &a.affected_versions, - PartialOrSecurityAdvisory::Partial(a) => &a.affected_versions, - }; - if !affected_versions - .matches(package_constraint_map[package_name.as_str()].as_ref()) - { - return Ok(None); - } - Ok(Some(advisory)) - }) - .collect(), - _ => vec![], + if !package_constraint_map.contains_key(package_name.as_str()) { + continue; + } + let list = match package_advisories { + PhpMixed::List(list) => list, + _ => continue, + }; + let mut items: Vec<PartialOrSecurityAdvisory> = Vec::new(); + for data in list { + let data_map: IndexMap<String, PhpMixed> = match data.as_ref() { + PhpMixed::Array(m) => m.iter().map(|(k, v)| (k.clone(), *v.clone())).collect(), + _ => continue, }; - advisories.insert(package_name.clone(), items?); + let advisory = match PartialSecurityAdvisory::create( + package_name, + &data_map, + &semver_parser, + ) { + Ok(a) => a, + Err(_) => continue, + }; + if !allow_partial_advisories + && matches!(advisory, PartialOrSecurityAdvisory::Partial(_)) + { + return Err(anyhow::anyhow!(RuntimeException { + message: format!( + "Advisory for {} could not be loaded as a full advisory from {}\n{}", + package_name, + self.get_repo_name(), + var_export(data, true) + ), + code: 0, + })); + } + // TODO(phase-b): affected_versions is a method, not a field, and matches() return type may differ + let _ = (&advisory, &package_constraint_map); + items.push(advisory); } + advisories.insert(package_name.clone(), items); } let names_found: Vec<String> = advisories.keys().cloned().collect(); diff --git a/crates/shirabe/src/repository/path_repository.rs b/crates/shirabe/src/repository/path_repository.rs index 3b6ed8d..c29e6c0 100644 --- a/crates/shirabe/src/repository/path_repository.rs +++ b/crates/shirabe/src/repository/path_repository.rs @@ -12,6 +12,7 @@ use crate::event_dispatcher::event_dispatcher::EventDispatcher; use crate::io::io_interface::IOInterface; use crate::json::json_file::JsonFile; use crate::package::loader::array_loader::ArrayLoader; +use crate::package::loader::loader_interface::LoaderInterface; use crate::package::version::version_guesser::VersionGuesser; use crate::package::version::version_parser::VersionParser; use crate::repository::array_repository::ArrayRepository; @@ -30,7 +31,7 @@ pub struct PathRepository { version_guesser: VersionGuesser, url: String, repo_config: IndexMap<String, PhpMixed>, - process: ProcessExecutor, + process: std::rc::Rc<std::cell::RefCell<ProcessExecutor>>, options: IndexMap<String, PhpMixed>, } @@ -44,10 +45,10 @@ impl PathRepository { pub fn new( repo_config: IndexMap<String, PhpMixed>, io: Box<dyn IOInterface>, - config: Config, - http_downloader: Option<HttpDownloader>, + config: std::rc::Rc<std::cell::RefCell<Config>>, + http_downloader: Option<std::rc::Rc<std::cell::RefCell<HttpDownloader>>>, dispatcher: Option<EventDispatcher>, - process: Option<ProcessExecutor>, + process: Option<std::rc::Rc<std::cell::RefCell<ProcessExecutor>>>, ) -> anyhow::Result<Self> { if !repo_config.contains_key("url") { return Err(RuntimeException { @@ -64,8 +65,17 @@ impl PathRepository { .unwrap_or("") .to_string(); let url = Platform::expand_path(&url_str); - let process = process.unwrap_or_else(|| ProcessExecutor::new(&*io)); - let version_guesser = VersionGuesser::new(&config, &process, VersionParser::new(), &*io); + let process = process.unwrap_or_else(|| { + std::rc::Rc::new(std::cell::RefCell::new(ProcessExecutor::new(Some( + io.clone_box(), + )))) + }); + let version_guesser = VersionGuesser::new( + config, + std::rc::Rc::clone(&process), + shirabe_semver::version_parser::VersionParser, + Some(io.clone_box()), + ); let mut options = repo_config .get("options") .and_then(|v| v.as_array()) @@ -81,7 +91,7 @@ impl PathRepository { } Ok(Self { - inner: ArrayRepository::new(), + inner: ArrayRepository::new(vec![])?, loader: ArrayLoader::new(None, true), version_guesser, url, @@ -105,7 +115,7 @@ impl PathRepository { } pub(crate) fn initialize(&mut self) -> anyhow::Result<()> { - self.inner.initialize()?; + self.inner.initialize(); let url_matches = self.get_url_matches()?; @@ -140,8 +150,11 @@ impl PathRepository { } let json = file_get_contents(&composer_file_path).unwrap_or_default(); - let mut package = - JsonFile::parse_json(&json, Some(&composer_file_path))?.unwrap_or_default(); + let parsed = JsonFile::parse_json(Some(&json), Some(&composer_file_path))?; + let mut package: IndexMap<String, PhpMixed> = match parsed { + PhpMixed::Array(m) => m.into_iter().map(|(k, v)| (k, *v)).collect(), + _ => IndexMap::new(), + }; let dist = { let mut dist = IndexMap::new(); dist.insert( @@ -213,20 +226,20 @@ impl PathRepository { if !package.contains_key("version") { if let Some(root_version) = Platform::get_env("COMPOSER_ROOT_VERSION") { if !root_version.is_empty() { - let mut ref1 = String::new(); - let mut ref2 = String::new(); - if self.process.execute( - &["git", "rev-parse", "HEAD"].map(|s| s.to_string()).to_vec(), - &mut ref1, - Some(path.clone()), - ) == 0 - && self.process.execute( - &["git", "rev-parse", "HEAD"].map(|s| s.to_string()).to_vec(), - &mut ref2, - None, - ) == 0 - && ref1 == ref2 - { + let mut ref1 = PhpMixed::Null; + let mut ref2 = PhpMixed::Null; + let cmd = PhpMixed::from(vec!["git", "rev-parse", "HEAD"]); + let code1 = self + .process + .borrow_mut() + .execute(cmd.clone(), Some(&mut ref1), Some(path.as_str())) + .unwrap_or(1); + let code2 = self + .process + .borrow_mut() + .execute(cmd, Some(&mut ref2), None) + .unwrap_or(1); + if code1 == 0 && code2 == 0 && ref1.as_string() == ref2.as_string() { package.insert( "version".to_string(), PhpMixed::String(self.version_guesser.get_root_version_from_env()), @@ -236,7 +249,7 @@ impl PathRepository { } } - let mut output = String::new(); + let mut output = PhpMixed::Null; let command = GitUtil::build_rev_list_command(&self.process, { let mut args = vec![ "-n1".to_string(), @@ -250,10 +263,17 @@ impl PathRepository { && shirabe_php_shim::is_dir(&format!("{}/.git", path.trim_end_matches('/'))) && self .process - .execute(&command, &mut output, Some(path.clone())) + .borrow_mut() + .execute( + PhpMixed::from(command), + Some(&mut output), + Some(path.as_str()), + ) + .unwrap_or(1) == 0 { - let ref_val = GitUtil::parse_rev_list_output(&output, &self.process) + let output_str = output.as_string().unwrap_or("").to_string(); + let ref_val = GitUtil::parse_rev_list_output(&output_str, &self.process) .trim() .to_string(); if let Some(PhpMixed::Array(ref mut dist)) = package.get_mut("dist") { diff --git a/crates/shirabe/src/repository/platform_repository.rs b/crates/shirabe/src/repository/platform_repository.rs index 8fb9d0a..64bc861 100644 --- a/crates/shirabe/src/repository/platform_repository.rs +++ b/crates/shirabe/src/repository/platform_repository.rs @@ -4,7 +4,7 @@ use std::sync::{LazyLock, Mutex}; use indexmap::IndexMap; -use shirabe_external_packages::composer::pcre::preg::Preg; +use shirabe_external_packages::composer::pcre::preg::{CaptureKey, Preg}; use shirabe_external_packages::composer::xdebug_handler::xdebug_handler::XdebugHandler; use shirabe_php_shim::{ InvalidArgumentException, PhpMixed, UnexpectedValueException, array_map_str_fn, array_slice, @@ -337,14 +337,20 @@ impl PlatformRepository { let info = self.runtime.get_extension_info(name)?; // librabbitmq version => 0.9.0 - if let Ok(Some(librabbitmq_matches)) = Preg::is_match_strict_groups( + let mut librabbitmq_matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match3( "/^librabbitmq version => (?<version>.+)$/im", &info, - ) { + Some(&mut librabbitmq_matches), + ) + .unwrap_or(false) + { self.add_library( &mut libraries, &format!("{}-librabbitmq", name), - Some(&librabbitmq_matches["version"]), + librabbitmq_matches + .get(&CaptureKey::ByName("version".to_string())) + .map(|s| s.as_str()), Some("AMQP librabbitmq version"), &[], &[], @@ -352,14 +358,22 @@ impl PlatformRepository { } // AMQP protocol version => 0-9-1 - if let Ok(Some(protocol_matches)) = Preg::is_match_strict_groups( + let mut protocol_matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match_strict_groups3( "/^AMQP protocol version => (?<version>.+)$/im", &info, - ) { + Some(&mut protocol_matches), + ) + .unwrap_or(false) + { + let version_str = protocol_matches + .get(&CaptureKey::ByName("version".to_string())) + .cloned() + .unwrap_or_default(); self.add_library( &mut libraries, &format!("{}-protocol", name), - Some(&str_replace("-", ".", &protocol_matches["version"])), + Some(&str_replace("-", ".", &version_str)), Some("AMQP protocol version"), &[], &[], @@ -371,13 +385,20 @@ impl PlatformRepository { let info = self.runtime.get_extension_info(name)?; // BZip2 Version => 1.0.6, 6-Sept-2010 - if let Ok(Some(matches)) = - Preg::is_match_strict_groups("/^BZip2 Version => (?<version>.*),/im", &info) + let mut matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match3( + "/^BZip2 Version => (?<version>.*),/im", + &info, + Some(&mut matches), + ) + .unwrap_or(false) { self.add_library( &mut libraries, name, - Some(&matches["version"]), + matches + .get(&CaptureKey::ByName("version".to_string())) + .map(|s| s.as_str()), None, &[], &[], @@ -402,16 +423,27 @@ impl PlatformRepository { let info = self.runtime.get_extension_info(name)?; // SSL Version => OpenSSL/1.0.1t - if let Ok(Some(ssl_matches)) = Preg::is_match_strict_groups( + let mut ssl_matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match_strict_groups3( "{^SSL Version => (?<library>[^/]+)/(?<version>.+)$}im", &info, - ) { - let library = strtolower(&ssl_matches["library"]); + Some(&mut ssl_matches), + ) + .unwrap_or(false) + { + let ssl_library_raw = ssl_matches + .get(&CaptureKey::ByName("library".to_string())) + .cloned() + .unwrap_or_default(); + let ssl_version = ssl_matches + .get(&CaptureKey::ByName("version".to_string())) + .cloned() + .unwrap_or_default(); + let library = strtolower(&ssl_library_raw); if library == "openssl" { let mut is_fips = false; - let parsed_version = - Version::parse_openssl(&ssl_matches["version"], &mut is_fips) - .unwrap_or_default(); + let parsed_version = Version::parse_openssl(&ssl_version, &mut is_fips) + .unwrap_or_default(); self.add_library( &mut libraries, &format!("{}-openssl{}", name, if is_fips { "-fips" } else { "" }), @@ -427,14 +459,21 @@ impl PlatformRepository { } else { let (shortlib, ssl_lib); if str_starts_with(&library, "(securetransport)") { - if let Ok(Some(securetransport_matches)) = - Preg::is_match_strict_groups( - "{^\\(securetransport\\) ([a-z0-9]+)}", - &library, - ) + let mut securetransport_matches: IndexMap<CaptureKey, String> = + IndexMap::new(); + if Preg::is_match3( + "{^\\(securetransport\\) ([a-z0-9]+)}", + &library, + Some(&mut securetransport_matches), + ) + .unwrap_or(false) { shortlib = "securetransport".to_string(); - ssl_lib = format!("curl-{}", securetransport_matches["1"]); + let m1 = securetransport_matches + .get(&CaptureKey::ByIndex(1)) + .cloned() + .unwrap_or_default(); + ssl_lib = format!("curl-{}", m1); } else { shortlib = library.clone(); ssl_lib = "curl-openssl".to_string(); @@ -446,11 +485,8 @@ impl PlatformRepository { self.add_library( &mut libraries, &format!("{}-{}", name, shortlib), - Some(&ssl_matches["version"]), - Some(&format!( - "curl {} version ({})", - library, &ssl_matches["version"] - )), + Some(&ssl_version), + Some(&format!("curl {} version ({})", library, ssl_version)), &[ssl_lib], &[], )?; @@ -458,28 +494,47 @@ impl PlatformRepository { } // libSSH Version => libssh2/1.4.3 - if let Ok(Some(ssh_matches)) = Preg::is_match_strict_groups( + let mut ssh_matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match_strict_groups3( "{^libSSH Version => (?<library>[^/]+)/(?<version>.+?)(?:/.*)?$}im", &info, - ) { + Some(&mut ssh_matches), + ) + .unwrap_or(false) + { + let ssh_library = ssh_matches + .get(&CaptureKey::ByName("library".to_string())) + .cloned() + .unwrap_or_default(); + let ssh_version = ssh_matches + .get(&CaptureKey::ByName("version".to_string())) + .cloned() + .unwrap_or_default(); self.add_library( &mut libraries, - &format!("{}-{}", name, strtolower(&ssh_matches["library"])), - Some(&ssh_matches["version"]), - Some(&format!("curl {} version", &ssh_matches["library"])), + &format!("{}-{}", name, strtolower(&ssh_library)), + Some(&ssh_version), + Some(&format!("curl {} version", &ssh_library)), &[], &[], )?; } // ZLib Version => 1.2.8 - if let Ok(Some(zlib_matches)) = - Preg::is_match_strict_groups("{^ZLib Version => (?<version>.+)$}im", &info) + let mut zlib_matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match_strict_groups3( + "{^ZLib Version => (?<version>.+)$}im", + &info, + Some(&mut zlib_matches), + ) + .unwrap_or(false) { self.add_library( &mut libraries, &format!("{}-zlib", name), - Some(&zlib_matches["version"]), + zlib_matches + .get(&CaptureKey::ByName("version".to_string())) + .map(|s| s.as_str()), Some("curl zlib version"), &[], &[], @@ -491,14 +546,20 @@ impl PlatformRepository { let info = self.runtime.get_extension_info(name)?; // timelib version => 2018.03 - if let Ok(Some(timelib_matches)) = Preg::is_match_strict_groups( + let mut timelib_matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match_strict_groups3( "/^timelib version => (?<version>.+)$/im", &info, - ) { + Some(&mut timelib_matches), + ) + .unwrap_or(false) + { self.add_library( &mut libraries, &format!("{}-timelib", name), - Some(&timelib_matches["version"]), + timelib_matches + .get(&CaptureKey::ByName("version".to_string())) + .map(|s| s.as_str()), Some("date timelib version"), &[], &[], @@ -506,21 +567,36 @@ impl PlatformRepository { } // Timezone Database => internal - if let Ok(Some(zoneinfo_source_matches)) = Preg::is_match_strict_groups( + let mut zoneinfo_source_matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match_strict_groups3( "/^Timezone Database => (?<source>internal|external)$/im", &info, - ) { - let external = zoneinfo_source_matches["source"] == "external"; - if let Ok(Some(zoneinfo_matches)) = Preg::is_match_strict_groups( + Some(&mut zoneinfo_source_matches), + ) + .unwrap_or(false) + { + let external = zoneinfo_source_matches + .get(&CaptureKey::ByName("source".to_string())) + .map(|s| s == "external") + .unwrap_or(false); + let mut zoneinfo_matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match_strict_groups3( "/^\"Olson\" Timezone Database Version => (?<version>.+?)(?:\\.system)?$/im", &info, - ) { + Some(&mut zoneinfo_matches), + ) + .unwrap_or(false) + { + let zoneinfo_version = zoneinfo_matches + .get(&CaptureKey::ByName("version".to_string())) + .cloned() + .unwrap_or_default(); // If the timezonedb is provided by ext/timezonedb, register that version as a replacement if external && loaded_extensions.iter().any(|n| n == "timezonedb") { self.add_library( &mut libraries, "timezonedb-zoneinfo", - Some(&zoneinfo_matches["version"]), + Some(&zoneinfo_version), Some( "zoneinfo (\"Olson\") database for date (replaced by timezonedb)", ), @@ -531,7 +607,7 @@ impl PlatformRepository { self.add_library( &mut libraries, &format!("{}-zoneinfo", name), - Some(&zoneinfo_matches["version"]), + Some(&zoneinfo_version), Some("zoneinfo (\"Olson\") database for date"), &[], &[], @@ -545,13 +621,20 @@ impl PlatformRepository { let info = self.runtime.get_extension_info(name)?; // libmagic => 537 - if let Ok(Some(magic_matches)) = - Preg::is_match_strict_groups("/^libmagic => (?<version>.+)$/im", &info) + let mut magic_matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match3( + "/^libmagic => (?<version>.+)$/im", + &info, + Some(&mut magic_matches), + ) + .unwrap_or(false) { self.add_library( &mut libraries, &format!("{}-libmagic", name), - Some(&magic_matches["version"]), + magic_matches + .get(&CaptureKey::ByName("version".to_string())) + .map(|s| s.as_str()), Some("fileinfo libmagic version"), &[], &[], @@ -576,12 +659,19 @@ impl PlatformRepository { let info = self.runtime.get_extension_info(name)?; - if let Ok(Some(libjpeg_matches)) = Preg::is_match_strict_groups( + let mut libjpeg_matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match_strict_groups3( "/^libJPEG Version => (?<version>.+?)(?: compatible)?$/im", &info, - ) { - let parsed = - Version::parse_libjpeg(&libjpeg_matches["version"]).unwrap_or_default(); + Some(&mut libjpeg_matches), + ) + .unwrap_or(false) + { + let libjpeg_version = libjpeg_matches + .get(&CaptureKey::ByName("version".to_string())) + .cloned() + .unwrap_or_default(); + let parsed = Version::parse_libjpeg(&libjpeg_version).unwrap_or_default(); self.add_library( &mut libraries, &format!("{}-libjpeg", name), @@ -592,41 +682,59 @@ impl PlatformRepository { )?; } - if let Ok(Some(libpng_matches)) = Preg::is_match_strict_groups( + let mut libpng_matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match_strict_groups3( "/^libPNG Version => (?<version>.+)$/im", &info, - ) { + Some(&mut libpng_matches), + ) + .unwrap_or(false) + { self.add_library( &mut libraries, &format!("{}-libpng", name), - Some(&libpng_matches["version"]), + libpng_matches + .get(&CaptureKey::ByName("version".to_string())) + .map(|s| s.as_str()), Some("libpng version for gd"), &[], &[], )?; } - if let Ok(Some(freetype_matches)) = Preg::is_match_strict_groups( + let mut freetype_matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match_strict_groups3( "/^FreeType Version => (?<version>.+)$/im", &info, - ) { + Some(&mut freetype_matches), + ) + .unwrap_or(false) + { self.add_library( &mut libraries, &format!("{}-freetype", name), - Some(&freetype_matches["version"]), + freetype_matches + .get(&CaptureKey::ByName("version".to_string())) + .map(|s| s.as_str()), Some("freetype version for gd"), &[], &[], )?; } - if let Ok(Some(libxpm_matches)) = Preg::is_match_strict_groups( + let mut libxpm_matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match_strict_groups3( "/^libXpm Version => (?<versionId>\\d+)$/im", &info, - ) { - let version_id: i64 = libxpm_matches["versionId"].parse().unwrap_or(0); - let converted = - Version::convert_libxpm_version_id(version_id).unwrap_or_default(); + Some(&mut libxpm_matches), + ) + .unwrap_or(false) + { + let version_id: i64 = libxpm_matches + .get(&CaptureKey::ByName("versionId".to_string())) + .and_then(|s| s.parse().ok()) + .unwrap_or(0); + let converted = Version::convert_libxpm_version_id(version_id); self.add_library( &mut libraries, &format!("{}-libxpm", name), @@ -689,27 +797,42 @@ impl PlatformRepository { &[], &[], )?; - } else if let Ok(Some(matches)) = - Preg::is_match_strict_groups("/^ICU version => (?<version>.+)$/im", &info) - { - self.add_library( - &mut libraries, - "icu", - Some(&matches["version"]), - Some(description), - &[], - &[], - )?; + } else { + let mut matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match3( + "/^ICU version => (?<version>.+)$/im", + &info, + Some(&mut matches), + ) + .unwrap_or(false) + { + self.add_library( + &mut libraries, + "icu", + matches + .get(&CaptureKey::ByName("version".to_string())) + .map(|s| s.as_str()), + Some(description), + &[], + &[], + )?; + } } // ICU TZData version => 2019c - if let Ok(Some(zoneinfo_matches)) = Preg::is_match_strict_groups( + let mut zoneinfo_matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match_strict_groups3( "/^ICU TZData version => (?<version>.*)$/im", &info, - ) { - if let Some(parsed) = - Version::parse_zoneinfo_version(&zoneinfo_matches["version"]) - { + Some(&mut zoneinfo_matches), + ) + .unwrap_or(false) + { + let zi_version = zoneinfo_matches + .get(&CaptureKey::ByName("version".to_string())) + .cloned() + .unwrap_or_default(); + if let Some(parsed) = Version::parse_zoneinfo_version(&zi_version) { self.add_library( &mut libraries, "icu-zoneinfo", @@ -784,12 +907,19 @@ impl PlatformRepository { Self::imagick_get_version_string(&image_magick_version); // 6.x: ImageMagick 6.2.9 08/24/06 Q16 http://www.imagemagick.org // 7.x: ImageMagick 7.0.8-34 Q16 x86_64 2019-03-23 https://imagemagick.org - if let Ok(Some(matches)) = Preg::is_match_strict_groups( + let mut matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match3( "/^ImageMagick (?<version>[\\d.]+)(?:-(?<patch>\\d+))?/", &image_magick_version_str, - ) { - let mut version_built = matches["version"].clone(); - if let Some(patch) = matches.get("patch") { + Some(&mut matches), + ) + .unwrap_or(false) + { + let mut version_built = matches + .get(&CaptureKey::ByName("version".to_string())) + .cloned() + .unwrap_or_default(); + if let Some(patch) = matches.get(&CaptureKey::ByName("patch".to_string())) { version_built = format!("{}.{}", version_built, patch); } @@ -807,21 +937,35 @@ impl PlatformRepository { "ldap" => { let info = self.runtime.get_extension_info(name)?; - if let (Ok(Some(matches)), Ok(Some(vendor_matches))) = ( - Preg::is_match_strict_groups( - "/^Vendor Version => (?<versionId>\\d+)$/im", + let mut matches: IndexMap<CaptureKey, String> = IndexMap::new(); + let mut vendor_matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match_strict_groups3( + "/^Vendor Version => (?<versionId>\\d+)$/im", + &info, + Some(&mut matches), + ) + .unwrap_or(false) + && Preg::is_match_strict_groups3( + "/^Vendor Name => (?<vendor>.+)$/im", &info, - ), - Preg::is_match_strict_groups("/^Vendor Name => (?<vendor>.+)$/im", &info), - ) { - let version_id: i64 = matches["versionId"].parse().unwrap_or(0); - let converted = - Version::convert_openldap_version_id(version_id).unwrap_or_default(); + Some(&mut vendor_matches), + ) + .unwrap_or(false) + { + let version_id: i64 = matches + .get(&CaptureKey::ByName("versionId".to_string())) + .and_then(|s| s.parse().ok()) + .unwrap_or(0); + let converted = Version::convert_openldap_version_id(version_id); + let vendor = vendor_matches + .get(&CaptureKey::ByName("vendor".to_string())) + .cloned() + .unwrap_or_default(); self.add_library( &mut libraries, - &format!("{}-{}", name, strtolower(&vendor_matches["vendor"])), + &format!("{}-{}", name, strtolower(&vendor)), Some(&converted), - Some(&format!("{} version of ldap", &vendor_matches["vendor"])), + Some(&format!("{} version of ldap", vendor)), &[], &[], )?; @@ -857,14 +1001,20 @@ impl PlatformRepository { let info = self.runtime.get_extension_info(name)?; // libmbfl version => 1.3.2 - if let Ok(Some(libmbfl_matches)) = Preg::is_match_strict_groups( + let mut libmbfl_matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match3( "/^libmbfl version => (?<version>.+)$/im", &info, - ) { + Some(&mut libmbfl_matches), + ) + .unwrap_or(false) + { self.add_library( &mut libraries, &format!("{}-libmbfl", name), - Some(&libmbfl_matches["version"]), + libmbfl_matches + .get(&CaptureKey::ByName("version".to_string())) + .map(|s| s.as_str()), Some("mbstring libmbfl version"), &[], &[], @@ -888,18 +1038,26 @@ impl PlatformRepository { // Multibyte regex (oniguruma) version => 5.9.5 // oniguruma version => 6.9.0 - } else if let Ok(Some(oniguruma_matches)) = Preg::is_match_strict_groups( - "/^(?:oniguruma|Multibyte regex \\(oniguruma\\)) version => (?<version>.+)$/im", - &info, - ) { - self.add_library( - &mut libraries, - &format!("{}-oniguruma", name), - Some(&oniguruma_matches["version"]), - Some("mbstring oniguruma version"), - &[], - &[], - )?; + } else { + let mut oniguruma_matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match3( + "/^(?:oniguruma|Multibyte regex \\(oniguruma\\)) version => (?<version>.+)$/im", + &info, + Some(&mut oniguruma_matches), + ) + .unwrap_or(false) + { + self.add_library( + &mut libraries, + &format!("{}-oniguruma", name), + oniguruma_matches + .get(&CaptureKey::ByName("version".to_string())) + .map(|s| s.as_str()), + Some("mbstring oniguruma version"), + &[], + &[], + )?; + } } } @@ -907,14 +1065,20 @@ impl PlatformRepository { let info = self.runtime.get_extension_info(name)?; // libmemcached version => 1.0.18 - if let Ok(Some(matches)) = Preg::is_match_strict_groups( + let mut matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match3( "/^libmemcached version => (?<version>.+)$/im", &info, - ) { + Some(&mut matches), + ) + .unwrap_or(false) + { self.add_library( &mut libraries, &format!("{}-libmemcached", name), - Some(&matches["version"]), + matches + .get(&CaptureKey::ByName("version".to_string())) + .map(|s| s.as_str()), Some("libmemcached version"), &[], &[], @@ -929,14 +1093,21 @@ impl PlatformRepository { _ => "".to_string(), }; // OpenSSL 1.1.1g 21 Apr 2020 - if let Ok(Some(matches)) = Preg::is_match_strict_groups( + let mut matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match_strict_groups3( "{^(?:OpenSSL|LibreSSL)?\\s*(?<version>\\S+)}i", &openssl_text_str, - ) { + Some(&mut matches), + ) + .unwrap_or(false) + { + let version = matches + .get(&CaptureKey::ByName("version".to_string())) + .cloned() + .unwrap_or_default(); let mut is_fips = false; let parsed_version = - Version::parse_openssl(&matches["version"], &mut is_fips) - .unwrap_or_default(); + Version::parse_openssl(&version, &mut is_fips).unwrap_or_default(); let mut provides_list: Vec<String> = Vec::new(); if is_fips { provides_list.push(name.to_string()); @@ -965,14 +1136,20 @@ impl PlatformRepository { let info = self.runtime.get_extension_info(name)?; // PCRE Unicode Version => 12.1.0 - if let Ok(Some(pcre_unicode_matches)) = Preg::is_match_strict_groups( + let mut pcre_unicode_matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match_strict_groups3( "/^PCRE Unicode Version => (?<version>.+)$/im", &info, - ) { + Some(&mut pcre_unicode_matches), + ) + .unwrap_or(false) + { self.add_library( &mut libraries, &format!("{}-unicode", name), - Some(&pcre_unicode_matches["version"]), + pcre_unicode_matches + .get(&CaptureKey::ByName("version".to_string())) + .map(|s| s.as_str()), Some("PCRE Unicode version support"), &[], &[], @@ -983,14 +1160,20 @@ impl PlatformRepository { "mysqlnd" | "pdo_mysql" => { let info = self.runtime.get_extension_info(name)?; - if let Ok(Some(matches)) = Preg::is_match_strict_groups( + let mut matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match_strict_groups3( "/^(?:Client API version|Version) => mysqlnd (?<version>.+?) /mi", &info, - ) { + Some(&mut matches), + ) + .unwrap_or(false) + { self.add_library( &mut libraries, &format!("{}-mysqlnd", name), - Some(&matches["version"]), + matches + .get(&CaptureKey::ByName("version".to_string())) + .map(|s| s.as_str()), Some(&format!("mysqlnd library version for {}", name)), &[], &[], @@ -1001,28 +1184,40 @@ impl PlatformRepository { "mongodb" => { let info = self.runtime.get_extension_info(name)?; - if let Ok(Some(libmongoc_matches)) = Preg::is_match_strict_groups( + let mut libmongoc_matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match_strict_groups3( "/^libmongoc bundled version => (?<version>.+)$/im", &info, - ) { + Some(&mut libmongoc_matches), + ) + .unwrap_or(false) + { self.add_library( &mut libraries, &format!("{}-libmongoc", name), - Some(&libmongoc_matches["version"]), + libmongoc_matches + .get(&CaptureKey::ByName("version".to_string())) + .map(|s| s.as_str()), Some("libmongoc version of mongodb"), &[], &[], )?; } - if let Ok(Some(libbson_matches)) = Preg::is_match_strict_groups( + let mut libbson_matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match_strict_groups3( "/^libbson bundled version => (?<version>.+)$/im", &info, - ) { + Some(&mut libbson_matches), + ) + .unwrap_or(false) + { self.add_library( &mut libraries, &format!("{}-libbson", name), - Some(&libbson_matches["version"]), + libbson_matches + .get(&CaptureKey::ByName("version".to_string())) + .map(|s| s.as_str()), Some("libbson version of mongodb"), &[], &[], @@ -1049,14 +1244,20 @@ impl PlatformRepository { // intentional fall-through to next case... let info = self.runtime.get_extension_info(name)?; - if let Ok(Some(matches)) = Preg::is_match_strict_groups( + let mut matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match3( "/^PostgreSQL\\(libpq\\) Version => (?<version>.*)$/im", &info, - ) { + Some(&mut matches), + ) + .unwrap_or(false) + { self.add_library( &mut libraries, &format!("{}-libpq", name), - Some(&matches["version"]), + matches + .get(&CaptureKey::ByName("version".to_string())) + .map(|s| s.as_str()), Some(&format!("libpq for {}", name)), &[], &[], @@ -1068,14 +1269,20 @@ impl PlatformRepository { "pdo_pgsql" => { let info = self.runtime.get_extension_info(name)?; - if let Ok(Some(matches)) = Preg::is_match_strict_groups( + let mut matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match3( "/^PostgreSQL\\(libpq\\) Version => (?<version>.*)$/im", &info, - ) { + Some(&mut matches), + ) + .unwrap_or(false) + { self.add_library( &mut libraries, &format!("{}-libpq", name), - Some(&matches["version"]), + matches + .get(&CaptureKey::ByName("version".to_string())) + .map(|s| s.as_str()), Some(&format!("libpq for {}", name)), &[], &[], @@ -1088,14 +1295,20 @@ impl PlatformRepository { // Used Library => Compiled => Linked // libpq => 14.3 (Ubuntu 14.3-1.pgdg22.04+1) => 15.0.2 - if let Ok(Some(matches)) = Preg::is_match_strict_groups( + let mut matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match3( "/^libpq => (?<compiled>.+) => (?<linked>.+)$/im", &info, - ) { + Some(&mut matches), + ) + .unwrap_or(false) + { self.add_library( &mut libraries, &format!("{}-libpq", name), - Some(&matches["linked"]), + matches + .get(&CaptureKey::ByName("linked".to_string())) + .map(|s| s.as_str()), Some(&format!("libpq for {}", name)), &[], &[], @@ -1165,14 +1378,20 @@ impl PlatformRepository { "sqlite3" | "pdo_sqlite" => { let info = self.runtime.get_extension_info(name)?; - if let Ok(Some(matches)) = Preg::is_match_strict_groups( + let mut matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match3( "/^SQLite Library => (?<version>.+)$/im", &info, - ) { + Some(&mut matches), + ) + .unwrap_or(false) + { self.add_library( &mut libraries, &format!("{}-sqlite", name), - Some(&matches["version"]), + matches + .get(&CaptureKey::ByName("version".to_string())) + .map(|s| s.as_str()), None, &[], &[], @@ -1183,14 +1402,20 @@ impl PlatformRepository { "ssh2" => { let info = self.runtime.get_extension_info(name)?; - if let Ok(Some(matches)) = Preg::is_match_strict_groups( + let mut matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match3( "/^libssh2 version => (?<version>.+)$/im", &info, - ) { + Some(&mut matches), + ) + .unwrap_or(false) + { self.add_library( &mut libraries, &format!("{}-libssh2", name), - Some(&matches["version"]), + matches + .get(&CaptureKey::ByName("version".to_string())) + .map(|s| s.as_str()), None, &[], &[], @@ -1214,14 +1439,20 @@ impl PlatformRepository { )?; let info = self.runtime.get_extension_info("xsl")?; - if let Ok(Some(matches)) = Preg::is_match_strict_groups( + let mut matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match3( "/^libxslt compiled against libxml Version => (?<version>.+)$/im", &info, - ) { + Some(&mut matches), + ) + .unwrap_or(false) + { self.add_library( &mut libraries, "libxslt-libxml", - Some(&matches["version"]), + matches + .get(&CaptureKey::ByName("version".to_string())) + .map(|s| s.as_str()), Some("libxml version libxslt is compiled against"), &[], &[], @@ -1232,14 +1463,20 @@ impl PlatformRepository { "yaml" => { let info = self.runtime.get_extension_info("yaml")?; - if let Ok(Some(matches)) = Preg::is_match_strict_groups( + let mut matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match3( "/^LibYAML Version => (?<version>.+)$/im", &info, - ) { + Some(&mut matches), + ) + .unwrap_or(false) + { self.add_library( &mut libraries, &format!("{}-libyaml", name), - Some(&matches["version"]), + matches + .get(&CaptureKey::ByName("version".to_string())) + .map(|s| s.as_str()), Some("libyaml version of yaml"), &[], &[], @@ -1289,14 +1526,20 @@ impl PlatformRepository { // Linked Version => 1.2.8 } else { let info = self.runtime.get_extension_info(name)?; - if let Ok(Some(matches)) = Preg::is_match_strict_groups( + let mut matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match3( "/^Linked Version => (?<version>.+)$/im", &info, - ) { + Some(&mut matches), + ) + .unwrap_or(false) + { self.add_library( &mut libraries, name, - Some(&matches["version"]), + matches + .get(&CaptureKey::ByName("version".to_string())) + .map(|s| s.as_str()), None, &[], &[], @@ -1362,9 +1605,7 @@ impl PlatformRepository { return Ok(()); } - let overrider = self - .inner - .find_package(package.get_name().to_string(), "*".to_string()); + let overrider = self.inner.find_package(package.get_name(), "*".to_string()); let actual_text = if let Some(ref ov) = overrider { if package.get_version() == ov.get_version() { "same as actual".to_string() @@ -1475,11 +1716,15 @@ impl PlatformRepository { Ok(v) => v, Err(_) => { extra_description = Some(format!(" (actual version: {})", pretty_version)); - if let Ok(Some(m)) = Preg::is_match_strict_groups( + let mut m: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match_strict_groups3( "{^(\\d+\\.\\d+\\.\\d+(?:\\.\\d+)?)}", &pretty_version, - ) { - pretty_version = m["1"].clone(); + Some(&mut m), + ) + .unwrap_or(false) + { + pretty_version = m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(); } else { pretty_version = "0".to_string(); } @@ -1689,7 +1934,7 @@ impl crate::repository::repository_interface::RepositoryInterface for PlatformRe fn find_package( &self, - name: String, + name: &str, constraint: crate::repository::repository_interface::FindPackageConstraint, ) -> Option<Box<dyn crate::package::base_package::BasePackage>> { self.inner.find_package(name, constraint) @@ -1697,7 +1942,7 @@ impl crate::repository::repository_interface::RepositoryInterface for PlatformRe fn find_packages( &self, - name: String, + name: &str, constraint: Option<crate::repository::repository_interface::FindPackageConstraint>, ) -> Vec<Box<dyn crate::package::base_package::BasePackage>> { self.inner.find_packages(name, constraint) diff --git a/crates/shirabe/src/repository/repository_factory.rs b/crates/shirabe/src/repository/repository_factory.rs index bb598ed..11e5f61 100644 --- a/crates/shirabe/src/repository/repository_factory.rs +++ b/crates/shirabe/src/repository/repository_factory.rs @@ -22,7 +22,7 @@ pub struct RepositoryFactory; impl RepositoryFactory { pub fn config_from_string( io: &dyn IOInterface, - config: &Config, + config: &std::rc::Rc<std::cell::RefCell<Config>>, repository: &str, allow_filesystem: bool, ) -> anyhow::Result<IndexMap<String, PhpMixed>> { @@ -41,8 +41,10 @@ impl RepositoryFactory { if extension == "json" { let json = JsonFile::new( repository.to_string(), - Some(Factory::create_http_downloader(io, config)?), - Some(io), + Some(std::rc::Rc::new(std::cell::RefCell::new( + Factory::create_http_downloader(io, config, IndexMap::new())?, + ))), + Some(io.clone_box()), )?; let data = json.read()?; let has_packages = data.get("packages").map_or(false, |v| !v.is_null()); @@ -92,7 +94,7 @@ impl RepositoryFactory { pub fn from_string( io: &dyn IOInterface, - config: &Config, + config: &std::rc::Rc<std::cell::RefCell<Config>>, repository: &str, allow_filesystem: bool, rm: Option<&mut RepositoryManager>, @@ -103,7 +105,7 @@ impl RepositoryFactory { pub fn create_repo( io: &dyn IOInterface, - config: &Config, + config: &std::rc::Rc<std::cell::RefCell<Config>>, repo_config: IndexMap<String, PhpMixed>, rm: Option<&mut RepositoryManager>, ) -> anyhow::Result<Box<dyn RepositoryInterface>> { @@ -128,15 +130,15 @@ impl RepositoryFactory { pub fn default_repos( io: Option<&dyn IOInterface>, - config: Option<Config>, + config: Option<std::rc::Rc<std::cell::RefCell<Config>>>, rm: Option<&mut RepositoryManager>, ) -> anyhow::Result<Vec<Box<dyn RepositoryInterface>>> { let config = match config { Some(c) => c, - None => Factory::create_config(None, None)?, + None => std::rc::Rc::new(std::cell::RefCell::new(Factory::create_config(None, None)?)), }; if let Some(io) = io { - io.load_configuration(&config); + io.load_configuration(&mut *config.borrow_mut())?; } let mut owned_rm; @@ -151,38 +153,50 @@ impl RepositoryFactory { owned_rm = Self::manager( io, &config, - Some(Factory::create_http_downloader(io, &config)?), + Some(std::rc::Rc::new(std::cell::RefCell::new( + Factory::create_http_downloader(io, &config, IndexMap::new())?, + ))), None, None, )?; &mut owned_rm }; - let repo_configs = config.get_repositories(); + let repo_configs = config.borrow().get_repositories(); Self::create_repos(rm, repo_configs) } pub fn manager( io: &dyn IOInterface, - config: &Config, - http_downloader: Option<HttpDownloader>, + config: &std::rc::Rc<std::cell::RefCell<Config>>, + http_downloader: Option<std::rc::Rc<std::cell::RefCell<HttpDownloader>>>, event_dispatcher: Option<EventDispatcher>, - process: Option<ProcessExecutor>, + process: Option<std::rc::Rc<std::cell::RefCell<ProcessExecutor>>>, ) -> anyhow::Result<RepositoryManager> { let http_downloader = match http_downloader { Some(h) => h, - None => Factory::create_http_downloader(io, config)?, + None => std::rc::Rc::new(std::cell::RefCell::new(Factory::create_http_downloader( + io, + config, + IndexMap::new(), + )?)), }; let process = match process { Some(p) => p, None => { let mut p = ProcessExecutor::new(io); p.enable_async(); - p + std::rc::Rc::new(std::cell::RefCell::new(p)) } }; - let mut rm = RepositoryManager::new(io, config, http_downloader, event_dispatcher, process); + let mut rm = RepositoryManager::new( + io, + std::rc::Rc::clone(config), + http_downloader, + event_dispatcher, + Some(process), + ); rm.set_repository_class("composer", "Composer\\Repository\\ComposerRepository"); rm.set_repository_class("vcs", "Composer\\Repository\\VcsRepository"); rm.set_repository_class("package", "Composer\\Repository\\PackageRepository"); @@ -205,9 +219,12 @@ impl RepositoryFactory { pub fn default_repos_with_default_manager( io: &dyn IOInterface, ) -> anyhow::Result<Vec<Box<dyn RepositoryInterface>>> { - let config = Factory::create_config(Some(io), None)?; + let config = std::rc::Rc::new(std::cell::RefCell::new(Factory::create_config( + Some(io), + None, + )?)); let mut manager = Self::manager(io, &config, None, None, None)?; - io.load_configuration(&config); + io.load_configuration(&mut *config.borrow_mut())?; Self::default_repos(Some(io), Some(config), Some(&mut manager)) } diff --git a/crates/shirabe/src/repository/repository_interface.rs b/crates/shirabe/src/repository/repository_interface.rs index 2a9c8f5..6113997 100644 --- a/crates/shirabe/src/repository/repository_interface.rs +++ b/crates/shirabe/src/repository/repository_interface.rs @@ -12,6 +12,15 @@ pub enum FindPackageConstraint { Constraint(Box<dyn ConstraintInterface>), } +impl Clone for FindPackageConstraint { + fn clone(&self) -> Self { + match self { + Self::String(s) => Self::String(s.clone()), + Self::Constraint(c) => Self::Constraint(c.clone_box()), + } + } +} + pub struct LoadPackagesResult { pub names_found: Vec<String>, pub packages: Vec<Box<dyn BasePackage>>, @@ -44,13 +53,13 @@ pub trait RepositoryInterface: Countable + std::fmt::Debug { fn find_package( &self, - name: String, + name: &str, constraint: FindPackageConstraint, ) -> Option<Box<dyn BasePackage>>; fn find_packages( &self, - name: String, + name: &str, constraint: Option<FindPackageConstraint>, ) -> Vec<Box<dyn BasePackage>>; diff --git a/crates/shirabe/src/repository/repository_manager.rs b/crates/shirabe/src/repository/repository_manager.rs index cc43aed..a3c5b78 100644 --- a/crates/shirabe/src/repository/repository_manager.rs +++ b/crates/shirabe/src/repository/repository_manager.rs @@ -14,32 +14,34 @@ use crate::repository::repository_interface::RepositoryInterface; use crate::util::http_downloader::HttpDownloader; use crate::util::process_executor::ProcessExecutor; +#[derive(Debug)] pub struct RepositoryManager { local_repository: Option<Box<dyn InstalledRepositoryInterface>>, repositories: Vec<Box<dyn RepositoryInterface>>, repository_classes: IndexMap<String, String>, io: Box<dyn IOInterface>, - config: Config, - http_downloader: HttpDownloader, + config: std::rc::Rc<std::cell::RefCell<Config>>, + http_downloader: std::rc::Rc<std::cell::RefCell<HttpDownloader>>, event_dispatcher: Option<EventDispatcher>, - process: ProcessExecutor, + process: std::rc::Rc<std::cell::RefCell<ProcessExecutor>>, } impl RepositoryManager { pub fn new( io: &dyn IOInterface, - config: &Config, - http_downloader: HttpDownloader, + config: std::rc::Rc<std::cell::RefCell<Config>>, + http_downloader: std::rc::Rc<std::cell::RefCell<HttpDownloader>>, event_dispatcher: Option<EventDispatcher>, - process: Option<ProcessExecutor>, + process: Option<std::rc::Rc<std::cell::RefCell<ProcessExecutor>>>, ) -> Self { - let process = process.unwrap_or_else(|| ProcessExecutor::new(io)); + let process = process + .unwrap_or_else(|| std::rc::Rc::new(std::cell::RefCell::new(ProcessExecutor::new(io)))); Self { local_repository: None, repositories: vec![], repository_classes: IndexMap::new(), io: io.clone_box(), - config: config.clone(), + config, http_downloader, event_dispatcher, process, diff --git a/crates/shirabe/src/repository/repository_set.rs b/crates/shirabe/src/repository/repository_set.rs index 6985a18..18c3ba6 100644 --- a/crates/shirabe/src/repository/repository_set.rs +++ b/crates/shirabe/src/repository/repository_set.rs @@ -180,18 +180,17 @@ impl RepositorySet { .into()); } - let repos: Vec<Box<dyn RepositoryInterface>> = if let Some(composite) = - (repo.as_any() as &dyn Any).downcast_ref::<CompositeRepository>() - { - // TODO(phase-b): clone composite.get_repositories() — Box<dyn RepositoryInterface> cloning - composite - .get_repositories() - .iter() - .map(|r| r.clone_box()) - .collect() - } else { - vec![repo] - }; + let repos: Vec<Box<dyn RepositoryInterface>> = + if let Some(composite) = repo.as_any().downcast_ref::<CompositeRepository>() { + // TODO(phase-b): clone composite.get_repositories() — Box<dyn RepositoryInterface> cloning + composite + .get_repositories() + .iter() + .map(|r| r.clone_box()) + .collect() + } else { + vec![repo] + }; for repo in repos { self.repositories.push(repo); @@ -321,7 +320,7 @@ impl RepositorySet { let mut map: IndexMap<String, Box<dyn ConstraintInterface>> = IndexMap::new(); for package in packages { // ignore root alias versions as they are not actual package versions and should not matter when it comes to vulnerabilities - if let Some(alias) = (package.as_any() as &dyn Any).downcast_ref::<AliasPackage>() { + if let Some(alias) = package.as_any().downcast_ref::<AliasPackage>() { if alias.is_root_package_alias() { continue; } @@ -475,10 +474,12 @@ impl RepositorySet { pool_builder.set_allowed_types(allowed_types); for repo in &self.repositories { - let is_installed = (repo.as_any() as &dyn Any) + let is_installed = repo + .as_any() .downcast_ref::<dyn InstalledRepositoryInterface>() .is_some() - || (repo.as_any() as &dyn Any) + || repo + .as_any() .downcast_ref::<InstalledRepository>() .is_some(); if is_installed && !self.allow_installed_repositories { @@ -500,10 +501,12 @@ impl RepositorySet { /// Create a pool for dependency resolution from the packages in this repository set. pub fn create_pool_with_all_packages(&mut self) -> Result<Pool> { for repo in &self.repositories { - let is_installed = (repo.as_any() as &dyn Any) + let is_installed = repo + .as_any() .downcast_ref::<dyn InstalledRepositoryInterface>() .is_some() - || (repo.as_any() as &dyn Any) + || repo + .as_any() .downcast_ref::<InstalledRepository>() .is_some(); if is_installed && !self.allow_installed_repositories { @@ -527,12 +530,12 @@ impl RepositorySet { if let Some(versions) = self.root_aliases.get(&name) { if let Some(alias) = versions.get(&version) { - while let Some(alias_pkg) = - (package.as_any() as &dyn Any).downcast_ref::<AliasPackage>() + while let Some(alias_pkg) = package.as_any().downcast_ref::<AliasPackage>() { package = alias_pkg.get_alias_of().clone_box(); } - let alias_package: Box<dyn BasePackage> = if (package.as_any() as &dyn Any) + let alias_package: Box<dyn BasePackage> = if package + .as_any() .downcast_ref::<CompletePackage>() .is_some() { diff --git a/crates/shirabe/src/repository/repository_utils.rs b/crates/shirabe/src/repository/repository_utils.rs index d39daa1..0f4f6a8 100644 --- a/crates/shirabe/src/repository/repository_utils.rs +++ b/crates/shirabe/src/repository/repository_utils.rs @@ -23,7 +23,7 @@ impl RepositoryUtils { } for candidate in packages { - for name in candidate.get_names() { + for name in candidate.get_names(true) { if requires.contains_key(&name) { let already_in_bucket = bucket.iter().any(|b| { std::ptr::eq( @@ -52,10 +52,8 @@ impl RepositoryUtils { unwrap_filter_repos: bool, ) -> Vec<Box<dyn RepositoryInterface>> { let repo: Box<dyn RepositoryInterface> = if unwrap_filter_repos { - if let Some(filter_repo) = - (repo.as_any() as &dyn Any).downcast_ref::<FilterRepository>() - { - filter_repo.get_repository() + if let Some(filter_repo) = repo.as_any().downcast_ref::<FilterRepository>() { + filter_repo.get_repository().clone_box() } else { repo } @@ -63,12 +61,10 @@ impl RepositoryUtils { repo }; - if let Some(composite_repo) = - (repo.as_any() as &dyn Any).downcast_ref::<CompositeRepository>() - { + if let Some(composite_repo) = repo.as_any().downcast_ref::<CompositeRepository>() { let mut repos = Vec::new(); for r in composite_repo.get_repositories() { - for r2 in Self::flatten_repositories(r, unwrap_filter_repos) { + for r2 in Self::flatten_repositories(r.clone_box(), unwrap_filter_repos) { repos.push(r2); } } diff --git a/crates/shirabe/src/repository/root_package_repository.rs b/crates/shirabe/src/repository/root_package_repository.rs index 6c6e25d..56131f1 100644 --- a/crates/shirabe/src/repository/root_package_repository.rs +++ b/crates/shirabe/src/repository/root_package_repository.rs @@ -41,7 +41,7 @@ impl RepositoryInterface for RootPackageRepository { fn find_package( &self, - name: String, + name: &str, constraint: crate::repository::repository_interface::FindPackageConstraint, ) -> Option<Box<dyn BasePackage>> { self.inner.find_package(name, constraint) @@ -49,7 +49,7 @@ impl RepositoryInterface for RootPackageRepository { fn find_packages( &self, - name: String, + name: &str, constraint: Option<crate::repository::repository_interface::FindPackageConstraint>, ) -> Vec<Box<dyn BasePackage>> { self.inner.find_packages(name, constraint) diff --git a/crates/shirabe/src/repository/vcs/forgejo_driver.rs b/crates/shirabe/src/repository/vcs/forgejo_driver.rs index 2efbfb7..179f2db 100644 --- a/crates/shirabe/src/repository/vcs/forgejo_driver.rs +++ b/crates/shirabe/src/repository/vcs/forgejo_driver.rs @@ -3,7 +3,7 @@ use crate::io::io_interface; use anyhow::Result; use indexmap::IndexMap; -use shirabe_external_packages::composer::pcre::preg::Preg; +use shirabe_external_packages::composer::pcre::preg::{CaptureKey, Preg}; use shirabe_php_shim::{ PhpMixed, RuntimeException, base64_decode, explode, extension_loaded, urlencode, }; @@ -15,6 +15,7 @@ use crate::io::io_interface::IOInterface; use crate::json::json_file::JsonFile; use crate::repository::vcs::git_driver::GitDriver; use crate::repository::vcs::vcs_driver::VcsDriverBase; +use crate::repository::vcs::vcs_driver_interface::VcsDriverInterface; use crate::util::forgejo::Forgejo; use crate::util::forgejo_repository_data::ForgejoRepositoryData; use crate::util::forgejo_url::ForgejoUrl; @@ -39,6 +40,7 @@ impl ForgejoDriver { "{}/{}/{}/{}", self.inner .config + .borrow_mut() .get("cache-repo-dir") .as_string() .unwrap_or(""), @@ -53,6 +55,7 @@ impl ForgejoDriver { c.set_read_only( self.inner .config + .borrow_mut() .get("cache-read-only") .as_bool() .unwrap_or(false), @@ -313,7 +316,7 @@ impl ForgejoDriver { identifier: &str, ) -> Result<Option<IndexMap<String, PhpMixed>>> { if let Some(ref mut git_driver) = self.git_driver { - return git_driver.inner.get_composer_information(identifier); + return git_driver.get_composer_information(identifier); } if !self.inner.info_cache.contains_key(identifier) { @@ -321,7 +324,12 @@ impl ForgejoDriver { if let Some(res) = self.inner.cache.as_ref().and_then(|c| c.read(identifier)) { JsonFile::parse_json(&res, None)? } else { - let c = self.inner.get_base_composer_information(identifier)?; + let file_content = self.get_file_content("composer.json", identifier)?; + let c = VcsDriverBase::finish_base_composer_information( + identifier, + file_content, + || self.get_change_date(identifier), + )?; if self.inner.should_cache(identifier) { if let Some(ref composer_map) = c { let encoded = JsonFile::encode_with_options( @@ -338,7 +346,10 @@ impl ForgejoDriver { c } } else { - self.inner.get_base_composer_information(identifier)? + let file_content = self.get_file_content("composer.json", identifier)?; + VcsDriverBase::finish_base_composer_information(identifier, file_content, || { + self.get_change_date(identifier) + })? }; let mut composer = composer; @@ -484,11 +495,11 @@ impl ForgejoDriver { } if !extension_loaded("openssl") { - io.write_error( - PhpMixed::String(format!( + io.write_error3( + &format!( "Skipping Forgejo driver for {} because the OpenSSL PHP extension is missing.", url - )), + ), true, io_interface::VERBOSE, ); @@ -510,7 +521,7 @@ impl ForgejoDriver { todo!("clone io for GitDriver setup"), self.inner.config.clone(), self.inner.http_downloader.clone(), - self.inner.process.clone(), + std::rc::Rc::clone(&self.inner.process), ), tags: None, branches: None, @@ -556,8 +567,11 @@ impl ForgejoDriver { let links = explode(",", &header); for link in links { - if let Some(m) = Preg::match_strict_groups(r#"{<(.+?)>; *rel="next"}"#, &link) { - if let Some(url) = m.get("1") { + let mut m: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::match_strict_groups3(r#"{<(.+?)>; *rel="next"}"#, &link, Some(&mut m)) + .unwrap_or(false) + { + if let Some(url) = m.get(&CaptureKey::ByIndex(1)) { return Some(url.clone()); } } @@ -650,14 +664,10 @@ impl ForgejoDriver { Ok(()) => Ok(true), Err(e) => { self.git_driver = None; - self.inner.io.write_error( - PhpMixed::String(format!( - "<error>Failed to clone the {} repository, try running in interactive mode so that you can enter your Forgejo credentials</error>", - ssh_url - )), - true, - io_interface::NORMAL, - ); + self.inner.io.write_error3(&format!( + "<error>Failed to clone the {} repository, try running in interactive mode so that you can enter your Forgejo credentials</error>", + ssh_url + ), true, io_interface::NORMAL); Err(e) } } diff --git a/crates/shirabe/src/repository/vcs/fossil_driver.rs b/crates/shirabe/src/repository/vcs/fossil_driver.rs index a765c4c..f0c3468 100644 --- a/crates/shirabe/src/repository/vcs/fossil_driver.rs +++ b/crates/shirabe/src/repository/vcs/fossil_driver.rs @@ -29,9 +29,11 @@ impl FossilDriver { self.check_fossil()?; // Ensure we are allowed to use this URL by config. - self.inner - .config - .prohibit_url_by_config(&self.inner.url, &*self.inner.io)?; + self.inner.config.borrow_mut().prohibit_url_by_config( + &self.inner.url, + Some(&*self.inner.io), + &indexmap::IndexMap::new(), + )?; // Only if url points to a locally accessible directory, assume it's the checkout directory. // Otherwise, it should be something fossil can clone from. @@ -41,6 +43,7 @@ impl FossilDriver { let cache_repo_dir = self .inner .config + .borrow_mut() .get("cache-repo-dir") .as_string() .unwrap_or("") @@ -48,6 +51,7 @@ impl FossilDriver { let cache_vcs_dir = self .inner .config + .borrow_mut() .get("cache-vcs-dir") .as_string() .unwrap_or("") @@ -60,7 +64,7 @@ impl FossilDriver { .into()); } - let local_name = Preg::replace(r"{[^a-z0-9]}i", "-", self.inner.url.clone()); + let local_name = Preg::replace(r"{[^a-z0-9]}i", "-", &self.inner.url); self.repo_file = Some(format!("{}/{}.fossil", cache_repo_dir, local_name)); self.checkout_dir = format!("{}/{}/", cache_vcs_dir, local_name); @@ -75,7 +79,7 @@ impl FossilDriver { pub(crate) fn check_fossil(&self) -> anyhow::Result<()> { let mut ignored_output = String::new(); - if self.inner.process.execute( + if self.inner.process.borrow_mut().execute_args( &["fossil", "version"].map(|s| s.to_string()).to_vec(), &mut ignored_output, None, @@ -84,7 +88,7 @@ impl FossilDriver { return Err(RuntimeException { message: format!( "fossil was not found, check that it is installed and in your PATH env.\n\n{}", - self.inner.process.get_error_output() + self.inner.process.borrow().get_error_output() ), code: 0, } @@ -115,27 +119,23 @@ impl FossilDriver { // update the repo if it is a valid fossil repository if is_file(&repo_file) && is_dir(&self.checkout_dir) - && self.inner.process.execute( + && self.inner.process.borrow_mut().execute_args( &["fossil", "info"].map(|s| s.to_string()).to_vec(), &mut String::new(), Some(self.checkout_dir.clone()), ) == 0 { - if self.inner.process.execute( + if self.inner.process.borrow_mut().execute_args( &["fossil", "pull"].map(|s| s.to_string()).to_vec(), &mut String::new(), Some(self.checkout_dir.clone()), ) != 0 { - self.inner.io.write_error( - PhpMixed::String(format!( - "<error>Failed to update {}, package information from this repository may be outdated ({})</error>", - self.inner.url, - self.inner.process.get_error_output() - )), - true, - io_interface::NORMAL, - ); + self.inner.io.write_error3(&format!( + "<error>Failed to update {}, package information from this repository may be outdated ({})</error>", + self.inner.url, + self.inner.process.borrow().get_error_output() + ), true, io_interface::NORMAL); } } else { // clean up directory and do a fresh clone into it @@ -144,7 +144,7 @@ impl FossilDriver { fs.ensure_directory_exists(&self.checkout_dir)?; let mut output = String::new(); - if self.inner.process.execute( + if self.inner.process.borrow_mut().execute_args( &["fossil", "clone", "--", &self.inner.url, &repo_file] .map(|s| s.to_string()) .to_vec(), @@ -152,7 +152,7 @@ impl FossilDriver { None, ) != 0 { - let output = self.inner.process.get_error_output(); + let output = self.inner.process.borrow().get_error_output(); return Err(RuntimeException { message: format!( "Failed to clone {} to repository {}\n\n{}", @@ -163,7 +163,7 @@ impl FossilDriver { .into()); } - if self.inner.process.execute( + if self.inner.process.borrow_mut().execute_args( &["fossil", "open", "--nested", "--", &repo_file] .map(|s| s.to_string()) .to_vec(), @@ -171,7 +171,7 @@ impl FossilDriver { Some(self.checkout_dir.clone()), ) != 0 { - let output = self.inner.process.get_error_output(); + let output = self.inner.process.borrow().get_error_output(); return Err(RuntimeException { message: format!( "Failed to open repository {} in {}\n\n{}", @@ -222,7 +222,7 @@ impl FossilDriver { } let mut content = String::new(); - self.inner.process.execute( + self.inner.process.borrow_mut().execute_args( &["fossil", "cat", "-r", identifier, "--", file] .map(|s| s.to_string()) .to_vec(), @@ -239,7 +239,7 @@ impl FossilDriver { pub fn get_change_date(&self, _identifier: &str) -> anyhow::Result<Option<DateTime<Utc>>> { let mut output = String::new(); - self.inner.process.execute( + self.inner.process.borrow_mut().execute_args( &["fossil", "finfo", "-b", "-n", "1", "composer.json"] .map(|s| s.to_string()) .to_vec(), @@ -257,12 +257,12 @@ impl FossilDriver { if self.tags.is_none() { let mut tags: IndexMap<String, String> = IndexMap::new(); let mut output = String::new(); - self.inner.process.execute( + self.inner.process.borrow_mut().execute_args( &["fossil", "tag", "list"].map(|s| s.to_string()).to_vec(), &mut output, Some(self.checkout_dir.clone()), ); - for tag in self.inner.process.split_lines(&output) { + for tag in self.inner.process.borrow().split_lines(&output) { tags.insert(tag.clone(), tag); } self.tags = Some(tags); @@ -274,13 +274,13 @@ impl FossilDriver { if self.branches.is_none() { let mut branches: IndexMap<String, String> = IndexMap::new(); let mut output = String::new(); - self.inner.process.execute( + self.inner.process.borrow_mut().execute_args( &["fossil", "branch", "list"].map(|s| s.to_string()).to_vec(), &mut output, Some(self.checkout_dir.clone()), ); - for branch in self.inner.process.split_lines(&output) { - let branch = Preg::replace(r"/^\*/", "", branch.trim().to_string()); + for branch in self.inner.process.borrow().split_lines(&output) { + let branch = Preg::replace(r"/^\*/", "", &branch.trim()); let branch = branch.trim().to_string(); branches.insert(branch.clone(), branch); } @@ -312,7 +312,7 @@ impl FossilDriver { let process = ProcessExecutor::new(io); let mut output = String::new(); - if process.execute( + if process.execute_args( &["fossil", "info"].map(|s| s.to_string()).to_vec(), &mut output, Some(url), diff --git a/crates/shirabe/src/repository/vcs/git_bitbucket_driver.rs b/crates/shirabe/src/repository/vcs/git_bitbucket_driver.rs index 05e7c61..689c0e8 100644 --- a/crates/shirabe/src/repository/vcs/git_bitbucket_driver.rs +++ b/crates/shirabe/src/repository/vcs/git_bitbucket_driver.rs @@ -4,7 +4,7 @@ use crate::io::io_interface; use anyhow::Result; use chrono::{DateTime, Utc}; use indexmap::IndexMap; -use shirabe_external_packages::composer::pcre::preg::Preg; +use shirabe_external_packages::composer::pcre::preg::{CaptureKey, Preg}; use shirabe_php_shim::{ InvalidArgumentException, LogicException, PhpMixed, RuntimeException, array_key_exists, array_search_mixed, extension_loaded, http_build_query_mixed, implode, in_array, is_array, @@ -58,11 +58,14 @@ pub struct GitBitbucketDriver { impl GitBitbucketDriver { /// @inheritDoc pub fn initialize(&mut self) -> Result<()> { - let matched = Preg::is_match_strict_groups( + let mut m: indexmap::IndexMap<CaptureKey, String> = indexmap::IndexMap::new(); + if !Preg::is_match_strict_groups3( r"#^https?://bitbucket\.org/([^/]+)/([^/]+?)(?:\.git|/?)?$#i", &self.inner.url, - ); - if matched.is_none() { + Some(&mut m), + ) + .unwrap_or(false) + { return Err(InvalidArgumentException { message: sprintf( "The Bitbucket repository URL %s is invalid. It must be the HTTPS URL of a Bitbucket repository.", @@ -72,10 +75,9 @@ impl GitBitbucketDriver { } .into()); } - let m = matched.unwrap(); - self.owner = m.get(1).cloned().unwrap_or_default(); - self.repository = m.get(2).cloned().unwrap_or_default(); + self.owner = m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(); + self.repository = m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default(); self.inner.origin_url = "bitbucket.org".to_string(); self.inner.cache = Some(Cache::new( &*self.inner.io, @@ -84,6 +86,7 @@ impl GitBitbucketDriver { &[ self.inner .config + .borrow_mut() .get("cache-repo-dir") .as_string() .unwrap_or("") @@ -98,6 +101,7 @@ impl GitBitbucketDriver { self.inner.cache.as_mut().unwrap().set_read_only( self.inner .config + .borrow_mut() .get("cache-read-only") .as_bool() .unwrap_or(false), @@ -236,7 +240,12 @@ impl GitBitbucketDriver { } { // composer already set above } else { - composer = self.inner.get_base_composer_information(identifier)?; + let file_content = self.get_file_content("composer.json", identifier)?; + composer = VcsDriverBase::finish_base_composer_information( + identifier, + file_content, + || self.get_change_date(identifier), + )?; if self.inner.should_cache(identifier) { self.inner.cache.as_ref().unwrap().write( @@ -664,16 +673,17 @@ impl GitBitbucketDriver { url: &str, fetching_repo_data: bool, ) -> Result<Response> { - match self.inner.get_contents(url, false) { + match self.inner.get_contents(url) { Ok(r) => Ok(r), Err(e) => { // TODO(phase-b): only handle TransportException - let bitbucket_util = Bitbucket::new( - &*self.inner.io, - &self.inner.config, - Some(self.inner.process.clone()), - Some(self.inner.http_downloader.clone()), - ); + let mut bitbucket_util = Bitbucket::new( + self.inner.io.clone_box(), + std::rc::Rc::clone(&self.inner.config), + Some(std::rc::Rc::clone(&self.inner.process)), + Some(std::rc::Rc::clone(&self.inner.http_downloader)), + None, + )?; if let Some(te) = e.downcast_ref::<TransportException>() { let code = te.get_code(); @@ -693,7 +703,7 @@ impl GitBitbucketDriver { if !self.inner.io.has_authentication(&self.inner.origin_url) && bitbucket_util.authorize_oauth(&self.inner.origin_url) { - return self.inner.get_contents(url, false); + return self.inner.get_contents(url); } if !self.inner.io.is_interactive() && fetching_repo_data { @@ -833,7 +843,9 @@ impl GitBitbucketDriver { if !Preg::is_match( r"#^https?://bitbucket\.org/([^/]+)/([^/]+?)(\.git|/?)?$#i", url, - ) { + ) + .unwrap_or(false) + { return false; } diff --git a/crates/shirabe/src/repository/vcs/git_driver.rs b/crates/shirabe/src/repository/vcs/git_driver.rs index 33474b1..07836bf 100644 --- a/crates/shirabe/src/repository/vcs/git_driver.rs +++ b/crates/shirabe/src/repository/vcs/git_driver.rs @@ -4,7 +4,7 @@ use crate::io::io_interface; use chrono::TimeZone; use chrono::{DateTime, Utc}; use indexmap::IndexMap; -use shirabe_external_packages::composer::pcre::preg::Preg; +use shirabe_external_packages::composer::pcre::preg::{CaptureKey, Preg}; use shirabe_php_shim::{ InvalidArgumentException, RuntimeException, dirname, is_dir, is_writable, realpath, sys_get_temp_dir, @@ -32,7 +32,7 @@ impl GitDriver { pub fn initialize(&mut self) -> anyhow::Result<()> { let cache_url; if Filesystem::is_local_path(&self.inner.url) { - self.inner.url = Preg::replace(r"{[\\/]\.git/?$}", "", self.inner.url.clone())?; + self.inner.url = Preg::replace(r"{[\\/]\.git/?$}", "", &self.inner.url)?; if !is_dir(&self.inner.url) { return Err(RuntimeException { message: format!( @@ -49,6 +49,7 @@ impl GitDriver { let cache_vcs_dir = self .inner .config + .borrow_mut() .get("cache-vcs-dir") .as_string() .unwrap_or("") @@ -97,9 +98,9 @@ impl GitDriver { let git_util = GitUtil::new( &*self.inner.io, - &self.inner.config, - &self.inner.process, - &Filesystem::new(None), + std::rc::Rc::clone(&self.inner.config), + std::rc::Rc::clone(&self.inner.process), + std::rc::Rc::new(std::cell::RefCell::new(Filesystem::new(None))), ); if !git_util.sync_mirror(&self.inner.url, &self.repo_dir)? { if !is_dir(&self.repo_dir) { @@ -112,14 +113,10 @@ impl GitDriver { } .into()); } - self.inner.io.write_error( - shirabe_php_shim::PhpMixed::String(format!( - "<error>Failed to update {}, package information from this repository may be outdated</error>", - self.inner.url - )), - true, - io_interface::NORMAL, - ); + self.inner.io.write_error3(shirabe_php_shim::PhpMixed::String(format!( + "<error>Failed to update {}, package information from this repository may be outdated</error>", + self.inner.url + )), true, io_interface::NORMAL); } cache_url = self.inner.url.clone(); @@ -131,6 +128,7 @@ impl GitDriver { let cache_repo_dir = self .inner .config + .borrow_mut() .get("cache-repo-dir") .as_string() .unwrap_or("") @@ -147,6 +145,7 @@ impl GitDriver { c.set_read_only( self.inner .config + .borrow_mut() .get("cache-read-only") .as_bool() .unwrap_or(false), @@ -162,9 +161,9 @@ impl GitDriver { let git_util = GitUtil::new( &*self.inner.io, - &self.inner.config, - &self.inner.process, - &Filesystem::new(None), + std::rc::Rc::clone(&self.inner.config), + std::rc::Rc::clone(&self.inner.process), + std::rc::Rc::new(std::cell::RefCell::new(Filesystem::new(None))), ); if !Filesystem::is_local_path(&self.inner.url) { let default_branch = @@ -176,7 +175,7 @@ impl GitDriver { } let mut output = String::new(); - self.inner.process.execute( + self.inner.process.borrow_mut().execute_args( &[ "git".to_string(), "branch".to_string(), @@ -185,12 +184,15 @@ impl GitDriver { &mut output, Some(self.repo_dir.clone()), ); - let branches = self.inner.process.split_lines(&output); + let branches = self.inner.process.borrow().split_lines(&output); if !branches.contains(&"* master".to_string()) { for branch in &branches { if !branch.is_empty() { - if let Some(caps) = Preg::match_strict_groups(r"{^\* +(\S+)}", branch) { - if let Some(name) = caps.get("1") { + let mut caps: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::match_strict_groups3(r"{^\* +(\S+)}", branch, Some(&mut caps)) + .unwrap_or(false) + { + if let Some(name) = caps.get(&CaptureKey::ByIndex(1)) { self.root_identifier = Some(name.clone()); break; } @@ -236,7 +238,7 @@ impl GitDriver { } let mut content = String::new(); - self.inner.process.execute( + self.inner.process.borrow_mut().execute_args( &[ "git".to_string(), "show".to_string(), @@ -274,9 +276,11 @@ impl GitDriver { ], ); let mut output = String::new(); - self.inner - .process - .execute(&command, &mut output, Some(self.repo_dir.clone())); + self.inner.process.borrow_mut().execute_args( + &command, + &mut output, + Some(self.repo_dir.clone()), + ); let timestamp_str = GitUtil::parse_rev_list_output(&output, &self.inner.process); let timestamp: i64 = timestamp_str.trim().parse().unwrap_or(0); @@ -288,7 +292,7 @@ impl GitDriver { self.tags = Some(IndexMap::new()); let mut output = String::new(); - self.inner.process.execute( + self.inner.process.borrow_mut().execute_args( &[ "git".to_string(), "show-ref".to_string(), @@ -298,13 +302,20 @@ impl GitDriver { &mut output, Some(self.repo_dir.clone()), ); - for tag in self.inner.process.split_lines(&output) { + for tag in self.inner.process.borrow().split_lines(&output) { if !tag.is_empty() { - if let Some(caps) = Preg::match_strict_groups( + let mut caps: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::match_strict_groups3( r"{^([a-f0-9]{40}) refs/tags/(\S+?)(\^\{\})?$}", &tag, - ) { - if let (Some(hash), Some(name)) = (caps.get("1"), caps.get("2")) { + Some(&mut caps), + ) + .unwrap_or(false) + { + if let (Some(hash), Some(name)) = ( + caps.get(&CaptureKey::ByIndex(1)), + caps.get(&CaptureKey::ByIndex(2)), + ) { self.tags .as_mut() .unwrap() @@ -323,7 +334,7 @@ impl GitDriver { let mut branches = IndexMap::new(); let mut output = String::new(); - self.inner.process.execute( + self.inner.process.borrow_mut().execute_args( &[ "git".to_string(), "branch".to_string(), @@ -334,15 +345,22 @@ impl GitDriver { &mut output, Some(self.repo_dir.clone()), ); - for branch in self.inner.process.split_lines(&output) { + for branch in self.inner.process.borrow().split_lines(&output) { if !branch.is_empty() && !Preg::is_match(r"{^ *[^/]+/HEAD }", &branch).unwrap_or(false) { - if let Some(caps) = Preg::match_strict_groups( + let mut caps: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::match_strict_groups3( r"{^(?:\* )? *(\S+) *([a-f0-9]+)(?: .*)?$}", &branch, - ) { - if let (Some(name), Some(hash)) = (caps.get("1"), caps.get("2")) { + Some(&mut caps), + ) + .unwrap_or(false) + { + if let (Some(name), Some(hash)) = ( + caps.get(&CaptureKey::ByIndex(1)), + caps.get(&CaptureKey::ByIndex(2)), + ) { if !name.starts_with('-') { branches.insert(name.clone(), hash.clone()); } @@ -378,9 +396,9 @@ impl GitDriver { return Ok(false); } - let process = ProcessExecutor::new(io); + let process = std::rc::Rc::new(std::cell::RefCell::new(ProcessExecutor::new(io))); let mut output = String::new(); - if process.execute( + if process.borrow_mut().execute_args( &["git".to_string(), "tag".to_string()], &mut output, Some(url.clone()), @@ -388,15 +406,27 @@ impl GitDriver { { return Ok(true); } - GitUtil::check_for_repo_ownership_error(&process.get_error_output(), &url); + GitUtil::check_for_repo_ownership_error(&process.borrow().get_error_output(), &url); } if !deep { return Ok(false); } - let process = ProcessExecutor::new(io); - let git_util = GitUtil::new(io, _config, &process, &Filesystem::new(None)); + let process = std::rc::Rc::new(std::cell::RefCell::new(ProcessExecutor::new(io))); + // TODO(phase-b): supports() takes &Config; GitUtil now needs Rc<RefCell<Config>>. + // Skipping clean Rc construction since we cannot reconstruct one from a borrowed &Config. + let _ = _config; + return Err(anyhow::anyhow!( + "GitDriver::supports requires Rc<RefCell<Config>>: not yet ported" + )); + #[allow(unreachable_code)] + let git_util = GitUtil::new( + io.clone_box(), + todo!(), + std::rc::Rc::clone(&process), + std::rc::Rc::new(std::cell::RefCell::new(Filesystem::new(None))), + ); GitUtil::clean_env(&process); let result = git_util.run_commands( diff --git a/crates/shirabe/src/repository/vcs/github_driver.rs b/crates/shirabe/src/repository/vcs/github_driver.rs index 7bf15bf..93bfcdb 100644 --- a/crates/shirabe/src/repository/vcs/github_driver.rs +++ b/crates/shirabe/src/repository/vcs/github_driver.rs @@ -4,7 +4,7 @@ use crate::io::io_interface; use anyhow::Result; use chrono::{DateTime, Utc}; use indexmap::IndexMap; -use shirabe_external_packages::composer::pcre::preg::Preg; +use shirabe_external_packages::composer::pcre::preg::{CaptureKey, Preg}; use shirabe_php_shim::{ InvalidArgumentException, PhpMixed, RuntimeException, array_diff, array_key_exists, array_map, array_search_mixed, base64_decode, basename, count, empty, explode, extension_loaded, in_array, @@ -18,6 +18,7 @@ use crate::io::io_interface::IOInterface; use crate::json::json_file::JsonFile; use crate::repository::vcs::git_driver::GitDriver; use crate::repository::vcs::vcs_driver::VcsDriverBase; +use crate::repository::vcs::vcs_driver_interface::VcsDriverInterface; use crate::util::github::GitHub; use crate::util::http::response::Response; @@ -45,31 +46,43 @@ pub struct GitHubDriver { impl GitHubDriver { pub fn initialize(&mut self) -> Result<()> { - let match_ = match Preg::is_match_strict_groups( + let mut match_: IndexMap<CaptureKey, String> = IndexMap::new(); + if !Preg::is_match_strict_groups3( r"#^(?:(?:https?|git)://([^/]+)/|git@([^:]+):/?)([^/]+)/([^/]+?)(?:\.git|/)?$#", &self.inner.url, - ) { - Some(m) => m, - None => { - return Err(InvalidArgumentException { - message: sprintf( - "The GitHub repository URL %s is invalid.", - &[PhpMixed::String(self.inner.url.clone())], - ), - code: 0, - } - .into()); + Some(&mut match_), + ) + .unwrap_or(false) + { + return Err(InvalidArgumentException { + message: sprintf( + "The GitHub repository URL %s is invalid.", + &[PhpMixed::String(self.inner.url.clone())], + ), + code: 0, } - }; + .into()); + } - self.owner = match_.get(3).cloned().unwrap_or_default(); - self.repository = match_.get(4).cloned().unwrap_or_default(); + self.owner = match_ + .get(&CaptureKey::ByIndex(3)) + .cloned() + .unwrap_or_default(); + self.repository = match_ + .get(&CaptureKey::ByIndex(4)) + .cloned() + .unwrap_or_default(); self.inner.origin_url = strtolower( &match_ - .get(1) + .get(&CaptureKey::ByIndex(1)) .cloned() .filter(|s| !s.is_empty()) - .unwrap_or_else(|| match_.get(2).cloned().unwrap_or_default()), + .unwrap_or_else(|| { + match_ + .get(&CaptureKey::ByIndex(2)) + .cloned() + .unwrap_or_default() + }), ); if self.inner.origin_url == "www.github.com" { self.inner.origin_url = "github.com".to_string(); @@ -80,6 +93,7 @@ impl GitHubDriver { "{}/{}/{}/{}", self.inner .config + .borrow_mut() .get("cache-repo-dir") .as_string() .unwrap_or(""), @@ -95,6 +109,7 @@ impl GitHubDriver { c.set_read_only( self.inner .config + .borrow_mut() .get("cache-read-only") .as_bool() .unwrap_or(false), @@ -111,7 +126,13 @@ impl GitHubDriver { self.allow_git_fallback = false; } - if self.inner.config.get("use-github-api").as_bool() == Some(false) + if self + .inner + .config + .borrow_mut() + .get("use-github-api") + .as_bool() + == Some(false) || self .inner .repo_config @@ -230,7 +251,12 @@ impl GitHubDriver { .unwrap_or_default(); JsonFile::parse_json(&res, None)? } else { - let composer = self.inner.get_base_composer_information(identifier)?; + let file_content = self.get_file_content("composer.json", identifier)?; + let composer = VcsDriverBase::finish_base_composer_information( + identifier, + file_content, + || self.get_change_date(identifier), + )?; if self.inner.should_cache(identifier) { if let Some(ref composer_map) = composer { @@ -384,7 +410,7 @@ impl GitHubDriver { ] { let mut options: IndexMap<String, PhpMixed> = IndexMap::new(); options.insert("retry-auth-failure".to_string(), PhpMixed::Bool(false)); - let response = self.inner.http_downloader.get( + let response = self.inner.http_downloader.borrow_mut().get( file_url, &PhpMixed::Array(options.into_iter().map(|(k, v)| (k, Box::new(v))).collect()), ); @@ -436,20 +462,26 @@ impl GitHubDriver { let mut result: Vec<IndexMap<String, PhpMixed>> = vec![]; let mut key: Option<String> = None; - for line in Preg::split(r"{\r?\n}", &funding) { + for line in Preg::split(r"{\r?\n}", &funding).unwrap_or_default() { let line = trim(&line, None); - if let Some(m) = Preg::is_match_strict_groups(r"{^(\w+)\s*:\s*(.+)$}", &line) { - let g1 = m.get(1).cloned().unwrap_or_default(); - let g2 = m.get(2).cloned().unwrap_or_default(); + let mut m: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match_strict_groups3(r"{^(\w+)\s*:\s*(.+)$}", &line, Some(&mut m)) + .unwrap_or(false) + { + let g1 = m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(); + let g2 = m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default(); if g2 == "[" { key = Some(g1); continue; } - if let Some(m2) = Preg::is_match_strict_groups(r"{^\[(.*?)\](?:\s*#.*)?$}", &g2) { - let inner = m2.get(1).cloned().unwrap_or_default(); + let mut m2: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match_strict_groups3(r"{^\[(.*?)\](?:\s*#.*)?$}", &g2, Some(&mut m2)) + .unwrap_or(false) + { + let inner = m2.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(); for item in array_map( |s: &String| trim(s, None), - &Preg::split(r#"{[\'\"]?\s*,\s*[\'\"]?}"#, &inner), + &Preg::split(r#"{[\'\"]?\s*,\s*[\'\"]?}"#, &inner).unwrap_or_default(), ) { let mut entry = IndexMap::new(); entry.insert("type".to_string(), PhpMixed::String(g1.clone())); @@ -459,30 +491,40 @@ impl GitHubDriver { ); result.push(entry); } - } else if let Some(m2) = - Preg::is_match_strict_groups(r"{^([^#].*?)(?:\s+#.*)?$}", &g2) + } else if Preg::is_match_strict_groups3( + r"{^([^#].*?)(?:\s+#.*)?$}", + &g2, + Some(&mut m2), + ) + .unwrap_or(false) { let mut entry = IndexMap::new(); entry.insert("type".to_string(), PhpMixed::String(g1.clone())); entry.insert( "url".to_string(), PhpMixed::String(trim( - &m2.get(1).cloned().unwrap_or_default(), + &m2.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(), Some("\"' "), )), ); result.push(entry); } key = None; - } else if let Some(m) = Preg::is_match_strict_groups(r"{^(\w+)\s*:\s*#\s*$}", &line) { - key = Some(m.get(1).cloned().unwrap_or_default()); - } else if key.is_some() - && (Preg::is_match_strict_groups(r"{^-\s*(.+)(?:\s+#.*)?$}", &line).is_some() - || Preg::is_match_strict_groups(r"{^(.+),(?:\s*#.*)?$}", &line).is_some()) + } else if Preg::is_match_strict_groups3(r"{^(\w+)\s*:\s*#\s*$}", &line, Some(&mut m)) + .unwrap_or(false) { - let m = Preg::is_match_strict_groups(r"{^-\s*(.+)(?:\s+#.*)?$}", &line) - .or_else(|| Preg::is_match_strict_groups(r"{^(.+),(?:\s*#.*)?$}", &line)) - .unwrap(); + key = Some(m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default()); + } else if key.is_some() && { + let mut tmp: IndexMap<CaptureKey, String> = IndexMap::new(); + Preg::is_match_strict_groups3(r"{^-\s*(.+)(?:\s+#.*)?$}", &line, Some(&mut m)) + .unwrap_or(false) + || Preg::is_match_strict_groups3(r"{^(.+),(?:\s*#.*)?$}", &line, Some(&mut tmp)) + .unwrap_or(false) + && { + m = tmp; + true + } + } { let mut entry = IndexMap::new(); entry.insert( "type".to_string(), @@ -490,7 +532,10 @@ impl GitHubDriver { ); entry.insert( "url".to_string(), - PhpMixed::String(trim(&m.get(1).cloned().unwrap_or_default(), Some("\"' "))), + PhpMixed::String(trim( + &m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(), + Some("\"' "), + )), ); result.push(entry); } else if key.is_some() && line == "]" { @@ -623,11 +668,11 @@ impl GitHubDriver { continue; } - self.inner.io.write_error( - PhpMixed::String(format!( + self.inner.io.write_error3( + &format!( "<warning>Funding URL {} not in a supported format.</warning>", item_url - )), + ), true, io_interface::NORMAL, ); @@ -874,21 +919,31 @@ impl GitHubDriver { } pub fn supports(io: &dyn IOInterface, config: &Config, url: &str, _deep: bool) -> bool { - let matches = match Preg::is_match_strict_groups( + let mut matches: IndexMap<CaptureKey, String> = IndexMap::new(); + if !Preg::is_match_strict_groups3( r"#^((?:https?|git)://([^/]+)/|git@([^:]+):/?)([^/]+)/([^/]+?)(?:\.git|/)?$#", url, - ) { - Some(m) => m, - None => return false, - }; + Some(&mut matches), + ) + .unwrap_or(false) + { + return false; + } let origin_url = matches - .get(2) + .get(&CaptureKey::ByIndex(2)) .cloned() .filter(|s| !s.is_empty()) - .unwrap_or_else(|| matches.get(3).cloned().unwrap_or_default()); + .unwrap_or_else(|| { + matches + .get(&CaptureKey::ByIndex(3)) + .cloned() + .unwrap_or_default() + }); if !in_array( - PhpMixed::String(strtolower(&Preg::replace(r"{^www\.}i", "", origin_url))), + PhpMixed::String(strtolower( + &Preg::replace(r"{^www\.}i", "", &origin_url).unwrap_or_default(), + )), &config.get("github-domains"), false, ) { @@ -896,11 +951,11 @@ impl GitHubDriver { } if !extension_loaded("openssl") { - io.write_error( - PhpMixed::String(format!( + io.write_error3( + &format!( "Skipping GitHub driver for {} because the OpenSSL PHP extension is missing.", url - )), + ), true, io_interface::VERBOSE, ); @@ -945,11 +1000,11 @@ impl GitHubDriver { Ok(r) => Ok(r), Err(e) => { let mut git_hub_util = GitHub::new( - self.inner.io.as_ref(), - &self.inner.config, - &self.inner.process, - &self.inner.http_downloader, - ); + self.inner.io.clone_box(), + std::rc::Rc::clone(&self.inner.config), + Some(std::rc::Rc::clone(&self.inner.process)), + Some(std::rc::Rc::clone(&self.inner.http_downloader)), + )?; match e.code { 401 | 404 => { @@ -1057,11 +1112,11 @@ impl GitHubDriver { if !self.inner.io.has_authentication(&self.inner.origin_url) { if !self.inner.io.is_interactive() { - self.inner.io.write_error( - PhpMixed::String(format!( + self.inner.io.write_error3( + &format!( "<error>GitHub API limit exhausted. Failed to get metadata for the {} repository, try running in interactive mode so that you can enter your GitHub credentials to increase the API limit</error>", self.inner.url - )), + ), true, io_interface::NORMAL, ); @@ -1083,14 +1138,14 @@ impl GitHubDriver { let rate_limit = git_hub_util.get_rate_limit( e.get_headers().map(|h| h.as_slice()).unwrap_or(&[]), ); - self.inner.io.write_error( - PhpMixed::String(sprintf( + self.inner.io.write_error3( + &sprintf( "<error>GitHub API limit (%d calls/hr) is exhausted. You are already authorized so you have to wait until %s before doing more requests</error>", &[ rate_limit.get("limit").cloned().unwrap_or(PhpMixed::Null), rate_limit.get("reset").cloned().unwrap_or(PhpMixed::Null), ], - )), + ), true, io_interface::NORMAL, ); @@ -1206,11 +1261,11 @@ impl GitHubDriver { Err(setup_err) => { self.git_driver = None; - self.inner.io.write_error( - PhpMixed::String(format!( + self.inner.io.write_error3( + &format!( "<error>Failed to clone the {} repository, try running in interactive mode so that you can enter your GitHub credentials</error>", self.generate_ssh_url() - )), + ), true, io_interface::NORMAL, ); @@ -1233,8 +1288,8 @@ impl GitHubDriver { repo_config, self.inner.io.clone(), self.inner.config.clone(), - self.inner.http_downloader.clone(), - self.inner.process.clone(), + std::rc::Rc::clone(&self.inner.http_downloader), + std::rc::Rc::clone(&self.inner.process), ); git_driver.initialize()?; self.git_driver = Some(git_driver); @@ -1249,8 +1304,11 @@ impl GitHubDriver { let links = explode(",", &header); for link in &links { - if let Some(m) = Preg::is_match_strict_groups(r#"{<(.+?)>; *rel="next"}"#, link) { - return Some(m.get(1).cloned().unwrap_or_default()); + let mut m: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match_strict_groups3(r#"{<(.+?)>; *rel="next"}"#, link, Some(&mut m)) + .unwrap_or(false) + { + return Some(m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default()); } } diff --git a/crates/shirabe/src/repository/vcs/gitlab_driver.rs b/crates/shirabe/src/repository/vcs/gitlab_driver.rs index dea1bf6..e00bbf8 100644 --- a/crates/shirabe/src/repository/vcs/gitlab_driver.rs +++ b/crates/shirabe/src/repository/vcs/gitlab_driver.rs @@ -4,7 +4,7 @@ use crate::io::io_interface; use anyhow::Result; use chrono::{DateTime, Utc}; use indexmap::IndexMap; -use shirabe_external_packages::composer::pcre::preg::Preg; +use shirabe_external_packages::composer::pcre::preg::{CaptureKey, Preg}; use shirabe_php_shim::{ InvalidArgumentException, LogicException, PhpMixed, RuntimeException, array_search_mixed, array_shift, ctype_alnum, empty, explode, extension_loaded, implode, in_array, is_array, @@ -18,6 +18,7 @@ use crate::io::io_interface::IOInterface; use crate::json::json_file::JsonFile; use crate::repository::vcs::git_driver::GitDriver; use crate::repository::vcs::vcs_driver::VcsDriverBase; +use crate::repository::vcs::vcs_driver_interface::VcsDriverInterface; use crate::util::gitlab::GitLab; use crate::util::http::response::Response; use crate::util::http_downloader::HttpDownloader; @@ -57,30 +58,43 @@ impl GitLabDriver { /// /// SSH urls use https by default. Set "secure-http": false on the repository config to use http instead. pub fn initialize(&mut self) -> Result<()> { - let match_ = match Preg::is_match_strict_groups(Self::URL_REGEX, &self.inner.url) { - Some(m) => m, - None => { - return Err(InvalidArgumentException { - message: sprintf( - "The GitLab repository URL %s is invalid. It must be the HTTP URL of a GitLab project.", - &[PhpMixed::String(self.inner.url.clone())], - ), - code: 0, - } - .into()); + let mut match_: IndexMap<CaptureKey, String> = IndexMap::new(); + if !Preg::is_match_strict_groups3(Self::URL_REGEX, &self.inner.url, Some(&mut match_)) + .unwrap_or(false) + { + return Err(InvalidArgumentException { + message: sprintf( + "The GitLab repository URL %s is invalid. It must be the HTTP URL of a GitLab project.", + &[PhpMixed::String(self.inner.url.clone())], + ), + code: 0, } - }; + .into()); + } let guessed_domain = match_ - .get("domain") + .get(&CaptureKey::ByName("domain".to_string())) .cloned() .filter(|s| !s.is_empty()) - .unwrap_or_else(|| match_.get("domain2").cloned().unwrap_or_default()); - let configured_domains = self.inner.config.get("gitlab-domains"); - let mut url_parts: Vec<String> = - explode("/", &match_.get("parts").cloned().unwrap_or_default()); + .unwrap_or_else(|| { + match_ + .get(&CaptureKey::ByName("domain2".to_string())) + .cloned() + .unwrap_or_default() + }); + let configured_domains = self.inner.config.borrow_mut().get("gitlab-domains"); + let mut url_parts: Vec<String> = explode( + "/", + &match_ + .get(&CaptureKey::ByName("parts".to_string())) + .cloned() + .unwrap_or_default(), + ); - let scheme_match = match_.get("scheme").cloned().unwrap_or_default(); + let scheme_match = match_ + .get(&CaptureKey::ByName("scheme".to_string())) + .cloned() + .unwrap_or_default(); self.scheme = if in_array( PhpMixed::String(scheme_match.clone()), &PhpMixed::List(vec![ @@ -101,7 +115,7 @@ impl GitLabDriver { } else { "https".to_string() }; - let port = match_.get("port").cloned(); + let port = match_.get(&CaptureKey::ByName("port".to_string())).cloned(); let origin = Self::determine_origin( &configured_domains, guessed_domain, @@ -123,7 +137,7 @@ impl GitLabDriver { }; self.inner.origin_url = origin; - let protocol_value = self.inner.config.get("gitlab-protocol"); + let protocol_value = self.inner.config.borrow_mut().get("gitlab-protocol"); if let Some(protocol) = protocol_value .as_string() .filter(|_| is_string(&protocol_value)) @@ -161,8 +175,12 @@ impl GitLabDriver { self.repository = Preg::replace( r"#(\.git)$#", "", - match_.get("repo").cloned().unwrap_or_default(), - ); + &match_ + .get(&CaptureKey::ByName("repo".to_string())) + .cloned() + .unwrap_or_default(), + ) + .unwrap_or_default(); self.inner.cache = Some(Cache::new( self.inner.io.as_ref(), @@ -170,6 +188,7 @@ impl GitLabDriver { "{}/{}/{}/{}", self.inner .config + .borrow_mut() .get("cache-repo-dir") .as_string() .unwrap_or(""), @@ -185,6 +204,7 @@ impl GitLabDriver { c.set_read_only( self.inner .config + .borrow_mut() .get("cache-read-only") .as_bool() .unwrap_or(false), @@ -200,7 +220,10 @@ impl GitLabDriver { /// Mainly useful for tests. /// /// @internal - pub fn set_http_downloader(&mut self, http_downloader: HttpDownloader) { + pub fn set_http_downloader( + &mut self, + http_downloader: std::rc::Rc<std::cell::RefCell<HttpDownloader>>, + ) { self.inner.http_downloader = http_downloader; } @@ -229,7 +252,12 @@ impl GitLabDriver { .unwrap_or_default(); JsonFile::parse_json(&res, None)? } else { - let composer = self.inner.get_base_composer_information(identifier)?; + let file_content = self.get_file_content("composer.json", identifier)?; + let composer = VcsDriverBase::finish_base_composer_information( + identifier, + file_content, + || self.get_change_date(identifier), + )?; if self.inner.should_cache(identifier) { if let Some(ref composer_map) = composer { @@ -679,11 +707,11 @@ impl GitLabDriver { Err(e) => { self.git_driver = None; - self.inner.io.write_error( - PhpMixed::String(format!( + self.inner.io.write_error3( + &format!( "<error>Failed to clone the {} repository, try running in interactive mode so that you can enter your credentials</error>", url - )), + ), true, io_interface::NORMAL, ); @@ -721,8 +749,8 @@ impl GitLabDriver { repo_config, self.inner.io.clone(), self.inner.config.clone(), - self.inner.http_downloader.clone(), - self.inner.process.clone(), + std::rc::Rc::clone(&self.inner.http_downloader), + std::rc::Rc::clone(&self.inner.process), ); git_driver.initialize()?; self.git_driver = Some(git_driver); @@ -780,11 +808,8 @@ impl GitLabDriver { } if !more_than_guest_access { - self.inner.io.write_error( - PhpMixed::String( - "<warning>GitLab token with Guest or Planner only access detected</warning>" - .to_string(), - ), + self.inner.io.write_error3( + "<warning>GitLab token with Guest or Planner only access detected</warning>", true, io_interface::NORMAL, ); @@ -840,11 +865,11 @@ impl GitLabDriver { } Err(e) => { let mut git_lab_util = GitLab::new( - self.inner.io.as_ref(), - &self.inner.config, - &self.inner.process, - &self.inner.http_downloader, - ); + self.inner.io.clone_box(), + std::rc::Rc::clone(&self.inner.config), + Some(std::rc::Rc::clone(&self.inner.process)), + Some(std::rc::Rc::clone(&self.inner.http_downloader)), + )?; match e.code { 401 | 404 => { @@ -882,11 +907,11 @@ impl GitLabDriver { .unwrap() .unwrap()); } - self.inner.io.write_error( - PhpMixed::String(format!( + self.inner.io.write_error3( + &format!( "<warning>Failed to download {}/{}:{}</warning>", self.namespace, self.repository, e.message - )), + ), true, io_interface::NORMAL, ); @@ -938,25 +963,39 @@ impl GitLabDriver { /// Uses the config `gitlab-domains` to see if the driver supports the url for the /// repository given. pub fn supports(io: &dyn IOInterface, config: &Config, url: &str, _deep: bool) -> bool { - let match_ = match Preg::is_match_strict_groups(Self::URL_REGEX, url) { - Some(m) => m, - None => return false, - }; + let mut match_: IndexMap<CaptureKey, String> = IndexMap::new(); + if !Preg::is_match_strict_groups3(Self::URL_REGEX, url, Some(&mut match_)).unwrap_or(false) + { + return false; + } - let scheme = match_.get("scheme").cloned().unwrap_or_default(); + let scheme = match_ + .get(&CaptureKey::ByName("scheme".to_string())) + .cloned() + .unwrap_or_default(); let guessed_domain = match_ - .get("domain") + .get(&CaptureKey::ByName("domain".to_string())) .cloned() .filter(|s| !s.is_empty()) - .unwrap_or_else(|| match_.get("domain2").cloned().unwrap_or_default()); - let mut url_parts: Vec<String> = - explode("/", &match_.get("parts").cloned().unwrap_or_default()); + .unwrap_or_else(|| { + match_ + .get(&CaptureKey::ByName("domain2".to_string())) + .cloned() + .unwrap_or_default() + }); + let mut url_parts: Vec<String> = explode( + "/", + &match_ + .get(&CaptureKey::ByName("parts".to_string())) + .cloned() + .unwrap_or_default(), + ); if Self::determine_origin( &config.get("gitlab-domains"), guessed_domain, &mut url_parts, - match_.get("port").cloned(), + match_.get(&CaptureKey::ByName("port".to_string())).cloned(), ) .is_none() { @@ -964,11 +1003,11 @@ impl GitLabDriver { } if scheme == "https" && !extension_loaded("openssl") { - io.write_error( - PhpMixed::String(format!( + io.write_error3( + &format!( "Skipping GitLab driver for {} because the OpenSSL PHP extension is missing.", url - )), + ), true, io_interface::VERBOSE, ); @@ -993,8 +1032,16 @@ impl GitLabDriver { let links = explode(",", &header); for link in &links { - if let Some(match_) = Preg::is_match_strict_groups(r#"{<(.+?)>; *rel="next"}"#, link) { - return Some(match_.get(1).cloned().unwrap_or_default()); + let mut match_: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match_strict_groups3(r#"{<(.+?)>; *rel="next"}"#, link, Some(&mut match_)) + .unwrap_or(false) + { + return Some( + match_ + .get(&CaptureKey::ByIndex(1)) + .cloned() + .unwrap_or_default(), + ); } } @@ -1048,7 +1095,7 @@ impl GitLabDriver { false, ) || (port_number.is_some() && in_array( - PhpMixed::String(Preg::replace(r"{:\d+}", "", guessed_domain.clone())), + PhpMixed::String(Preg::replace(r"{:\d+}", "", &guessed_domain)), configured_domains, false, )) diff --git a/crates/shirabe/src/repository/vcs/hg_driver.rs b/crates/shirabe/src/repository/vcs/hg_driver.rs index 68393ed..f7c0c16 100644 --- a/crates/shirabe/src/repository/vcs/hg_driver.rs +++ b/crates/shirabe/src/repository/vcs/hg_driver.rs @@ -30,6 +30,7 @@ impl HgDriver { let cache_vcs_dir = self .inner .config + .borrow_mut() .get("cache-vcs-dir") .as_string() .unwrap_or("") @@ -58,30 +59,32 @@ impl HgDriver { }.into()); } - self.inner - .config - .prohibit_url_by_config(&self.inner.url, &*self.inner.io)?; + self.inner.config.borrow_mut().prohibit_url_by_config( + &self.inner.url, + Some(&*self.inner.io), + &indexmap::IndexMap::new(), + )?; - let hg_utils = HgUtils::new(&*self.inner.io, &self.inner.config, &self.inner.process); + let hg_utils = HgUtils::new( + &*self.inner.io, + &*self.inner.config.borrow(), + &self.inner.process, + ); if is_dir(&self.repo_dir) - && self.inner.process.execute( + && self.inner.process.borrow_mut().execute_args( &["hg", "summary"].map(|s| s.to_string()).to_vec(), &mut String::new(), Some(self.repo_dir.clone()), ) == 0 { - if self.inner.process.execute( + if self.inner.process.borrow_mut().execute_args( &["hg", "pull"].map(|s| s.to_string()).to_vec(), &mut String::new(), Some(self.repo_dir.clone()), ) != 0 { - self.inner.io.write_error( - format!("<error>Failed to update {}, package information from this repository may be outdated ({})</error>", self.inner.url, self.inner.process.get_error_output()).into(), - true, - crate::io::io_interface::NORMAL, - ); + self.inner.io.write_error3(format!("<error>Failed to update {}, package information from this repository may be outdated ({})</error>", self.inner.url, self.inner.process.borrow().get_error_output()).into(), true, crate::io::io_interface::NORMAL); } } else { let fs2 = Filesystem::new(None); @@ -112,14 +115,14 @@ impl HgDriver { pub fn get_root_identifier(&mut self) -> anyhow::Result<String> { if self.root_identifier.is_none() { let mut output = String::new(); - self.inner.process.execute( + self.inner.process.borrow_mut().execute_args( &["hg", "tip", "--template", "{node}"] .map(|s| s.to_string()) .to_vec(), &mut output, Some(self.repo_dir.clone()), ); - let lines = self.inner.process.split_lines(&output); + let lines = self.inner.process.borrow().split_lines(&output); self.root_identifier = lines.into_iter().next(); } @@ -163,9 +166,11 @@ impl HgDriver { file.to_string(), ]; let mut content = String::new(); - self.inner - .process - .execute(&resource, &mut content, Some(self.repo_dir.clone())); + self.inner.process.borrow_mut().execute_args( + &resource, + &mut content, + Some(self.repo_dir.clone()), + ); if content.trim().is_empty() { return Ok(None); @@ -187,7 +192,7 @@ impl HgDriver { } let mut output = String::new(); - self.inner.process.execute( + self.inner.process.borrow_mut().execute_args( &[ "hg", "log", @@ -210,12 +215,12 @@ impl HgDriver { if self.tags.is_none() { let mut tags: IndexMap<String, String> = IndexMap::new(); let mut output = String::new(); - self.inner.process.execute( + self.inner.process.borrow_mut().execute_args( &["hg", "tags"].map(|s| s.to_string()).to_vec(), &mut output, Some(self.repo_dir.clone()), ); - for tag in self.inner.process.split_lines(&output) { + for tag in self.inner.process.borrow().split_lines(&output) { if !tag.is_empty() { if let Some(m) = Preg::match_(r"^([^\s]+)\s+\d+:(.*)$", &tag) { tags.insert( @@ -239,12 +244,12 @@ impl HgDriver { let mut bookmarks: IndexMap<String, String> = IndexMap::new(); let mut output = String::new(); - self.inner.process.execute( + self.inner.process.borrow_mut().execute_args( &["hg", "branches"].map(|s| s.to_string()).to_vec(), &mut output, Some(self.repo_dir.clone()), ); - for branch in self.inner.process.split_lines(&output) { + for branch in self.inner.process.borrow().split_lines(&output) { if !branch.is_empty() { if let Some(m) = Preg::match_(r"^([^\s]+)\s+\d+:([a-f0-9]+)", &branch) { let name = m.get("1").cloned().unwrap_or_default(); @@ -256,12 +261,12 @@ impl HgDriver { } output.clear(); - self.inner.process.execute( + self.inner.process.borrow_mut().execute_args( &["hg", "bookmarks"].map(|s| s.to_string()).to_vec(), &mut output, Some(self.repo_dir.clone()), ); - for branch in self.inner.process.split_lines(&output) { + for branch in self.inner.process.borrow().split_lines(&output) { if !branch.is_empty() { if let Some(m) = Preg::match_(r"^(?:[\s*]*)([^\s]+)\s+\d+:(.*)$", &branch) { let name = m.get("1").cloned().unwrap_or_default(); @@ -298,7 +303,7 @@ impl HgDriver { let process = crate::util::process_executor::ProcessExecutor::new(io); let mut output = String::new(); - if process.execute( + if process.execute_args( &["hg", "summary"].map(|s| s.to_string()).to_vec(), &mut output, Some(url), @@ -314,7 +319,7 @@ impl HgDriver { let process = crate::util::process_executor::ProcessExecutor::new(io); let mut ignored = String::new(); - let exit = process.execute( + let exit = process.execute_args( &["hg", "identify", "--", url] .map(|s| s.to_string()) .to_vec(), diff --git a/crates/shirabe/src/repository/vcs/perforce_driver.rs b/crates/shirabe/src/repository/vcs/perforce_driver.rs index ee09dee..e3aa868 100644 --- a/crates/shirabe/src/repository/vcs/perforce_driver.rs +++ b/crates/shirabe/src/repository/vcs/perforce_driver.rs @@ -59,6 +59,7 @@ impl PerforceDriver { let cache_vcs_dir = self .inner .config + .borrow_mut() .get("cache-vcs-dir") .as_string() .unwrap_or("") diff --git a/crates/shirabe/src/repository/vcs/svn_driver.rs b/crates/shirabe/src/repository/vcs/svn_driver.rs index 2e649df..8218563 100644 --- a/crates/shirabe/src/repository/vcs/svn_driver.rs +++ b/crates/shirabe/src/repository/vcs/svn_driver.rs @@ -3,7 +3,7 @@ use anyhow::Result; use chrono::{DateTime, TimeZone, Utc}; use indexmap::IndexMap; -use shirabe_external_packages::composer::pcre::preg::Preg; +use shirabe_external_packages::composer::pcre::preg::{CaptureKey, Preg}; use shirabe_php_shim::{ JSON_UNESCAPED_SLASHES, JSON_UNESCAPED_UNICODE, PhpMixed, RuntimeException, array_key_exists, is_array, max, sprintf, stripos, strrpos, strtr, substr, trim, @@ -90,6 +90,7 @@ impl SvnDriver { "{}/{}", self.inner .config + .borrow_mut() .get("cache-repo-dir") .as_string() .unwrap_or(""), @@ -102,6 +103,7 @@ impl SvnDriver { self.inner.cache.as_mut().unwrap().set_read_only( self.inner .config + .borrow_mut() .get("cache-read-only") .as_bool() .unwrap_or(false), @@ -135,7 +137,10 @@ impl SvnDriver { } pub(crate) fn should_cache(&self, identifier: &str) -> bool { - self.inner.cache.is_some() && Preg::is_match(r"{@\d+$}", identifier) + self.inner.cache.is_some() + && Preg::is_match(r"{@\d+$}", identifier) + .unwrap_or(false) + .unwrap_or(false) } pub fn get_composer_information( @@ -170,22 +175,30 @@ impl SvnDriver { } // TODO(phase-b): use anyhow::Result<Result<T, E>> to model PHP try/catch - let composer: Option<IndexMap<String, PhpMixed>> = - match self.inner.get_base_composer_information(identifier) { - Ok(c) => c, - Err(e) => { - // TODO(phase-b): downcast to TransportException - let _te: &TransportException = todo!("downcast e to TransportException"); - let message = e.to_string(); - if stripos(&message, "path not found").is_none() - && stripos(&message, "svn: warning: W160013").is_none() - { - return Err(e); - } - // remember a not-existent composer.json - None + let base_result = + self.get_file_content("composer.json", identifier) + .and_then(|file_content| { + VcsDriverBase::finish_base_composer_information( + identifier, + file_content, + || self.get_change_date(identifier), + ) + }); + let composer: Option<IndexMap<String, PhpMixed>> = match base_result { + Ok(c) => c, + Err(e) => { + // TODO(phase-b): downcast to TransportException + let _te: &TransportException = todo!("downcast e to TransportException"); + let message = e.to_string(); + if stripos(&message, "path not found").is_none() + && stripos(&message, "svn: warning: W160013").is_none() + { + return Err(e); } - }; + // remember a not-existent composer.json + None + } + }; if self.should_cache(identifier) { let encoded = JsonFile::encode( @@ -282,12 +295,17 @@ impl SvnDriver { vec!["svn".to_string(), "info".to_string()], &format!("{}{}{}", self.base_url, path, rev), )?; - for line in self.inner.process.split_lines(&output) { + for line in self.inner.process.borrow().split_lines(&output) { if !line.is_empty() { - if let Some(m) = - Preg::is_match_strict_groups(r"{^Last Changed Date: ([^(]+)}", &line) + let mut m: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match_strict_groups3( + r"{^Last Changed Date: ([^(]+)}", + &line, + Some(&mut m), + ) + .unwrap_or(false) { - let date_str = m.get(1).cloned().unwrap_or_default(); + let date_str = m.get(&CaptureKey::ByIndex(1)).cloned().unwrap_or_default(); // PHP: new \DateTimeImmutable($match[1], new \DateTimeZone('UTC')) return Ok(Utc .datetime_from_str(date_str.trim(), "%Y-%m-%d %H:%M:%S %z") @@ -313,15 +331,23 @@ impl SvnDriver { .unwrap_or_default(); if !output.is_empty() { let mut last_rev: i64 = 0; - for line in self.inner.process.split_lines(&output) { + for line in self.inner.process.borrow().split_lines(&output) { let line = trim(&line, None); if !line.is_empty() { - if let Some(m) = - Preg::is_match_strict_groups(r"{^\s*(\S+).*?(\S+)\s*$}", &line) + let mut m: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match_strict_groups3( + r"{^\s*(\S+).*?(\S+)\s*$}", + &line, + Some(&mut m), + ) + .unwrap_or(false) { - let rev: i64 = - m.get(1).map(|s| s.parse().unwrap_or(0)).unwrap_or(0); - let path = m.get(2).cloned().unwrap_or_default(); + let rev: i64 = m + .get(&CaptureKey::ByIndex(1)) + .and_then(|s| s.parse().ok()) + .unwrap_or(0); + let path = + m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default(); if path == "./" { last_rev = rev; } else { @@ -360,14 +386,22 @@ impl SvnDriver { ) .unwrap_or_default(); if !output.is_empty() { - for line in self.inner.process.split_lines(&output) { + for line in self.inner.process.borrow().split_lines(&output) { let line = trim(&line, None); if !line.is_empty() { - if let Some(m) = - Preg::is_match_strict_groups(r"{^\s*(\S+).*?(\S+)\s*$}", &line) + let mut m: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match_strict_groups3( + r"{^\s*(\S+).*?(\S+)\s*$}", + &line, + Some(&mut m), + ) + .unwrap_or(false) { - let rev: i64 = m.get(1).map(|s| s.parse().unwrap_or(0)).unwrap_or(0); - let path = m.get(2).cloned().unwrap_or_default(); + let rev: i64 = m + .get(&CaptureKey::ByIndex(1)) + .and_then(|s| s.parse().ok()) + .unwrap_or(0); + let path = m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default(); if path == "./" { let identifier = self.build_identifier( &format!("/{}", self.trunk_path.clone().unwrap_or_default()), @@ -393,15 +427,28 @@ impl SvnDriver { .unwrap_or_default(); if !output.is_empty() { let mut last_rev: i64 = 0; - for line in self.inner.process.split_lines(&trim(&output, None)) { + for line in self + .inner + .process + .borrow() + .split_lines(&trim(&output, None)) + { let line = trim(&line, None); if !line.is_empty() { - if let Some(m) = - Preg::is_match_strict_groups(r"{^\s*(\S+).*?(\S+)\s*$}", &line) + let mut m: IndexMap<CaptureKey, String> = IndexMap::new(); + if Preg::is_match_strict_groups3( + r"{^\s*(\S+).*?(\S+)\s*$}", + &line, + Some(&mut m), + ) + .unwrap_or(false) { - let rev: i64 = - m.get(1).map(|s| s.parse().unwrap_or(0)).unwrap_or(0); - let path = m.get(2).cloned().unwrap_or_default(); + let rev: i64 = m + .get(&CaptureKey::ByIndex(1)) + .and_then(|s| s.parse().ok()) + .unwrap_or(0); + let path = + m.get(&CaptureKey::ByIndex(2)).cloned().unwrap_or_default(); if path == "./" { last_rev = rev; } else { @@ -426,7 +473,10 @@ impl SvnDriver { pub fn supports(io: &dyn IOInterface, _config: &Config, url: &str, deep: bool) -> bool { let url = Self::normalize_url(url); - if Preg::is_match(r"#(^svn://|^svn\+ssh://|svn\.)#i", &url) { + if Preg::is_match(r"#(^svn://|^svn\+ssh://|svn\.)#i", &url) + .unwrap_or(false) + .unwrap_or(false) + { return true; } @@ -437,7 +487,7 @@ impl SvnDriver { let mut process = ProcessExecutor::new(io); let mut ignored_output = String::new(); - let exit = process.execute( + let exit = process.execute_args( &[ "svn".to_string(), "info".to_string(), @@ -516,7 +566,7 @@ impl SvnDriver { message: format!( "Failed to load {}, svn was not found, check that it is installed and in your PATH env.\n\n{}", self.inner.url, - self.inner.process.get_error_output(), + self.inner.process.borrow().get_error_output(), ), code: 0, } diff --git a/crates/shirabe/src/repository/vcs/vcs_driver.rs b/crates/shirabe/src/repository/vcs/vcs_driver.rs index 162792e..e356a6f 100644 --- a/crates/shirabe/src/repository/vcs/vcs_driver.rs +++ b/crates/shirabe/src/repository/vcs/vcs_driver.rs @@ -1,5 +1,6 @@ //! ref: composer/src/Composer/Repository/Vcs/VcsDriver.php +use chrono::{DateTime, Utc}; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::preg::Preg; use shirabe_php_shim::{ @@ -23,9 +24,9 @@ pub struct VcsDriverBase { pub origin_url: String, pub repo_config: IndexMap<String, PhpMixed>, pub io: Box<dyn IOInterface>, - pub config: Config, - pub process: ProcessExecutor, - pub http_downloader: HttpDownloader, + pub config: std::rc::Rc<std::cell::RefCell<Config>>, + pub process: std::rc::Rc<std::cell::RefCell<ProcessExecutor>>, + pub http_downloader: std::rc::Rc<std::cell::RefCell<HttpDownloader>>, pub info_cache: IndexMap<String, Option<IndexMap<String, PhpMixed>>>, pub cache: Option<Cache>, } @@ -34,9 +35,9 @@ impl VcsDriverBase { pub fn new( repo_config: IndexMap<String, PhpMixed>, io: Box<dyn IOInterface>, - config: Config, - http_downloader: HttpDownloader, - process: ProcessExecutor, + config: std::rc::Rc<std::cell::RefCell<Config>>, + http_downloader: std::rc::Rc<std::cell::RefCell<HttpDownloader>>, + process: std::rc::Rc<std::cell::RefCell<ProcessExecutor>>, ) -> Self { let url = repo_config .get("url") @@ -74,7 +75,52 @@ impl VcsDriverBase { .get("options") .cloned() .unwrap_or(PhpMixed::Array(IndexMap::new())); - self.http_downloader.get(url, &options) + self.http_downloader.borrow_mut().get(url, &options) + } + + // Helper for concrete drivers: produces the same value as the trait default + // `get_base_composer_information`, but receives a pre-fetched composer.json + // body and a lazy change-date callback. Concrete drivers in the Rust port + // wrap `VcsDriverBase` as `self.inner` instead of inheriting from it, so + // they cannot dispatch back into a base method that calls `get_file_content` + // / `get_change_date` hooks; the caller threads those calls in itself. + pub fn finish_base_composer_information( + identifier: &str, + composer_file_content: Option<String>, + change_date: impl FnOnce() -> anyhow::Result<Option<DateTime<Utc>>>, + ) -> anyhow::Result<Option<IndexMap<String, PhpMixed>>> { + let content = match composer_file_content { + None => return Ok(None), + Some(c) if c.is_empty() => return Ok(None), + Some(c) => c, + }; + + let parsed = JsonFile::parse_json( + Some(&content), + Some(&format!("{}:composer.json", identifier)), + )?; + + let array = match parsed { + PhpMixed::Array(a) if !a.is_empty() => a, + _ => return Ok(None), + }; + + // PHP arrays own their nested values; the Rust representation wraps them + // in Box<PhpMixed>. Unbox the outer level so callers can mutate keys. + let mut composer: IndexMap<String, PhpMixed> = + array.into_iter().map(|(k, v)| (k, *v)).collect(); + + if !composer.contains_key("time") + || composer + .get("time") + .map_or(true, |v| v.as_string().map_or(true, |s| s.is_empty())) + { + if let Some(d) = change_date()? { + composer.insert("time".to_string(), PhpMixed::String(d.to_rfc3339())); + } + } + + Ok(Some(composer)) } } @@ -93,8 +139,7 @@ pub trait VcsDriver: VcsDriverInterface { fn config_mut(&mut self) -> &mut Config; fn process(&self) -> &ProcessExecutor; fn process_mut(&mut self) -> &mut ProcessExecutor; - fn http_downloader(&self) -> &HttpDownloader; - fn http_downloader_mut(&mut self) -> &mut HttpDownloader; + fn http_downloader(&self) -> &std::rc::Rc<std::cell::RefCell<HttpDownloader>>; fn info_cache(&self) -> &IndexMap<String, Option<IndexMap<String, PhpMixed>>>; fn info_cache_mut(&mut self) -> &mut IndexMap<String, Option<IndexMap<String, PhpMixed>>>; fn cache(&self) -> Option<&Cache>; @@ -195,7 +240,7 @@ pub trait VcsDriver: VcsDriverInterface { .get("options") .cloned() .unwrap_or(PhpMixed::Array(IndexMap::new())); - self.http_downloader().get(url, &options) + self.http_downloader().borrow_mut().get(url, &options) } fn cleanup(&self) {} diff --git a/crates/shirabe/src/repository/vcs/vcs_driver_interface.rs b/crates/shirabe/src/repository/vcs/vcs_driver_interface.rs index 5b15c2c..236b9b4 100644 --- a/crates/shirabe/src/repository/vcs/vcs_driver_interface.rs +++ b/crates/shirabe/src/repository/vcs/vcs_driver_interface.rs @@ -6,7 +6,7 @@ use chrono::{DateTime, Utc}; use indexmap::IndexMap; use shirabe_php_shim::PhpMixed; -pub trait VcsDriverInterface { +pub trait VcsDriverInterface: std::fmt::Debug { fn initialize(&mut self) -> anyhow::Result<()>; fn get_composer_information( diff --git a/crates/shirabe/src/repository/vcs_repository.rs b/crates/shirabe/src/repository/vcs_repository.rs index e53392f..1a511fd 100644 --- a/crates/shirabe/src/repository/vcs_repository.rs +++ b/crates/shirabe/src/repository/vcs_repository.rs @@ -45,7 +45,7 @@ pub struct VcsRepository { /// @var IOInterface pub(crate) io: Box<dyn IOInterface>, /// @var Config - pub(crate) config: Config, + pub(crate) config: std::rc::Rc<std::cell::RefCell<Config>>, /// @var VersionParser pub(crate) version_parser: Option<VersionParser>, /// @var string @@ -55,9 +55,9 @@ pub struct VcsRepository { /// @var array<string, mixed> pub(crate) repo_config: IndexMap<String, PhpMixed>, /// @var HttpDownloader - pub(crate) http_downloader: HttpDownloader, + pub(crate) http_downloader: std::rc::Rc<std::cell::RefCell<HttpDownloader>>, /// @var ProcessExecutor - pub(crate) process_executor: ProcessExecutor, + pub(crate) process_executor: std::rc::Rc<std::cell::RefCell<ProcessExecutor>>, /// @var bool pub(crate) branch_error_occurred: bool, /// @var array<string, class-string<VcsDriverInterface>> @@ -86,10 +86,10 @@ impl VcsRepository { pub fn new( mut repo_config: IndexMap<String, PhpMixed>, io: Box<dyn IOInterface>, - config: Config, - http_downloader: HttpDownloader, + config: std::rc::Rc<std::cell::RefCell<Config>>, + http_downloader: std::rc::Rc<std::cell::RefCell<HttpDownloader>>, dispatcher: Option<EventDispatcher>, - process: Option<ProcessExecutor>, + process: Option<std::rc::Rc<std::cell::RefCell<ProcessExecutor>>>, drivers: Option<IndexMap<String, String>>, version_cache: Option<Box<dyn VersionCacheInterface>>, ) -> Result<Self> { @@ -154,8 +154,11 @@ impl VcsRepository { .to_string(); let is_verbose = io.is_verbose(); let is_very_verbose = io.is_very_verbose(); - let process_executor = - process.unwrap_or_else(|| ProcessExecutor::new(Some(Box::new(&*io)), None)); + let process_executor = process.unwrap_or_else(|| { + std::rc::Rc::new(std::cell::RefCell::new(ProcessExecutor::new(Some( + Box::new(&*io), + )))) + }); Ok(Self { inner, @@ -477,7 +480,7 @@ impl VcsRepository { // broken package, version doesn't match tag if version_normalized != parsed_tag { if is_very_verbose { - if Preg::is_match(r"{(^dev-|[.-]?dev$)}i", &parsed_tag) { + if Preg::is_match(r"{(^dev-|[.-]?dev$)}i", &parsed_tag).unwrap_or(false) { self.io.write_error(&format!( "<warning>Skipped tag {}, invalid tag name, tags can not use dev prefixes or suffixes</warning>", tag @@ -706,7 +709,7 @@ impl VcsRepository { } // TODO(phase-b): Box<dyn BasePackage> -> Box<dyn PackageInterface> coercion self.inner.add_package( - crate::package::package_interface::PackageInterface::clone_box(&*package), + <dyn crate::package::package_interface::PackageInterface>::clone_box(&*package), )?; Ok(()) })(); diff --git a/crates/shirabe/src/repository/version_cache_interface.rs b/crates/shirabe/src/repository/version_cache_interface.rs index ce0b591..6dfeec0 100644 --- a/crates/shirabe/src/repository/version_cache_interface.rs +++ b/crates/shirabe/src/repository/version_cache_interface.rs @@ -1,6 +1,6 @@ //! ref: composer/src/Composer/Repository/VersionCacheInterface.php -pub trait VersionCacheInterface { +pub trait VersionCacheInterface: std::fmt::Debug { // No class implementing this interface exists in Composer's codebase; a plugin may provide // one, but plugin support is not yet decided. Using () as a placeholder until then. fn get_version_package(&self, version: &str, identifier: &str) -> (); diff --git a/crates/shirabe/src/repository/writable_array_repository.rs b/crates/shirabe/src/repository/writable_array_repository.rs index 793478f..05c6bfa 100644 --- a/crates/shirabe/src/repository/writable_array_repository.rs +++ b/crates/shirabe/src/repository/writable_array_repository.rs @@ -2,7 +2,9 @@ use crate::installer::installation_manager::InstallationManager; use crate::repository::array_repository::ArrayRepository; +use crate::repository::repository_interface::RepositoryInterface; use anyhow::Result; +use shirabe_php_shim::Countable; #[derive(Debug)] pub struct WritableArrayRepository { @@ -12,6 +14,16 @@ pub struct WritableArrayRepository { } impl WritableArrayRepository { + pub fn new( + packages: Vec<Box<dyn crate::package::package_interface::PackageInterface>>, + ) -> Result<Self> { + Ok(Self { + inner: ArrayRepository::new(packages)?, + dev_package_names: Vec::new(), + dev_mode: None, + }) + } + /// Returns true if dev requirements were installed, false if --no-dev was used, None if yet unknown. pub fn get_dev_mode(&self) -> Option<bool> { self.dev_mode @@ -72,4 +84,12 @@ impl WritableArrayRepository { // TODO(phase-b): delegate to inner ArrayRepository::get_packages Vec::new() } + + pub fn get_repo_name(&self) -> String { + self.inner.get_repo_name() + } + + pub fn count(&self) -> i64 { + Countable::count(&self.inner) + } } |
