diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-06-25 17:49:35 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-06-26 00:20:05 +0900 |
| commit | d4ec1dcb6fc04ff029ded35cc2fcc14f3bbc02ad (patch) | |
| tree | 0a85bd085961fcdfd5570cafc3b44ba5eb8cb152 /crates/shirabe/src | |
| parent | 22d0b327368e5f39de6f381046c081d08efdba15 (diff) | |
| download | php-shirabe-d4ec1dcb6fc04ff029ded35cc2fcc14f3bbc02ad.tar.gz php-shirabe-d4ec1dcb6fc04ff029ded35cc2fcc14f3bbc02ad.tar.zst php-shirabe-d4ec1dcb6fc04ff029ded35cc2fcc14f3bbc02ad.zip | |
test: port 32 command/repository/downloader tests
Add create_installed_json/create_composer_lock test helpers. Port command (8),
repository path/forgejo/perforce/vcs (11), and fossil/hg/download_manager (13)
tests. Fix production porting bugs: root_package_loader/forgejo_url/version_bumper
regex delimiters, repository_manager create_repository_by_class, array_loader
isset, licenses_command RefCell borrow; implement disk_free_space and
touch2/touch3 via libc.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/src')
| -rw-r--r-- | crates/shirabe/src/command/licenses_command.rs | 10 | ||||
| -rw-r--r-- | crates/shirabe/src/package/loader/array_loader.rs | 22 | ||||
| -rw-r--r-- | crates/shirabe/src/package/loader/root_package_loader.rs | 14 | ||||
| -rw-r--r-- | crates/shirabe/src/package/version/version_bumper.rs | 12 | ||||
| -rw-r--r-- | crates/shirabe/src/repository/handle.rs | 8 | ||||
| -rw-r--r-- | crates/shirabe/src/repository/installed_repository.rs | 6 | ||||
| -rw-r--r-- | crates/shirabe/src/repository/repository_manager.rs | 26 | ||||
| -rw-r--r-- | crates/shirabe/src/repository/vcs_repository.rs | 12 | ||||
| -rw-r--r-- | crates/shirabe/src/util/forgejo_url.rs | 2 |
9 files changed, 84 insertions, 28 deletions
diff --git a/crates/shirabe/src/command/licenses_command.rs b/crates/shirabe/src/command/licenses_command.rs index 3c1745a..3be4df3 100644 --- a/crates/shirabe/src/command/licenses_command.rs +++ b/crates/shirabe/src/command/licenses_command.rs @@ -107,8 +107,7 @@ impl Command for LicensesCommand { input: Rc<RefCell<dyn InputInterface>>, output: Rc<RefCell<dyn OutputInterface>>, ) -> anyhow::Result<i64> { - let composer = self.require_composer(None, None)?; - let mut composer = crate::command::composer_full_mut(&composer); + let composer_handle = self.require_composer(None, None)?; // TODO(plugin): dispatch COMMAND event for plugin hooks let command_event = CommandEvent::new( @@ -117,11 +116,14 @@ impl Command for LicensesCommand { input.clone(), output.clone(), ); - composer - .get_event_dispatcher() + // The event dispatcher reads back through the shared Composer handle (script listeners), so the + // dispatch must run while no other borrow of that handle is held. + let event_dispatcher = composer_handle.borrow_partial().get_event_dispatcher(); + event_dispatcher .borrow_mut() .dispatch(Some(command_event.get_name()), None); + let mut composer = crate::command::composer_full_mut(&composer_handle); let root = composer.get_package(); let packages = if input diff --git a/crates/shirabe/src/package/loader/array_loader.rs b/crates/shirabe/src/package/loader/array_loader.rs index 844b3ef..20280e0 100644 --- a/crates/shirabe/src/package/loader/array_loader.rs +++ b/crates/shirabe/src/package/loader/array_loader.rs @@ -370,7 +370,7 @@ impl ArrayLoader { package.package_mut().set_is_default_branch(true); } - if let Some(source) = config.get("source").cloned() { + if let Some(source) = config.get("source").filter(|v| !v.is_null()).cloned() { let source_map = match &source { PhpMixed::Array(m) => Some(m.clone()), _ => None, @@ -404,9 +404,12 @@ impl ArrayLoader { package .package_mut() .set_source_url(source_map.get("url").map(|v| strval(v))); - package - .package_mut() - .set_source_reference(source_map.get("reference").map(|v| strval(v))); + package.package_mut().set_source_reference( + source_map + .get("reference") + .filter(|v| !v.is_null()) + .map(|v| strval(v)), + ); if let Some(mirrors) = source_map.get("mirrors") { package .package_mut() @@ -414,7 +417,7 @@ impl ArrayLoader { } } - if let Some(dist) = config.get("dist").cloned() { + if let Some(dist) = config.get("dist").filter(|v| !v.is_null()).cloned() { let dist_map = match &dist { PhpMixed::Array(m) => Some(m.clone()), _ => None, @@ -446,9 +449,12 @@ impl ArrayLoader { package .package_mut() .set_dist_url(dist_map.get("url").map(|v| strval(v))); - package - .package_mut() - .set_dist_reference(dist_map.get("reference").map(|v| strval(v))); + package.package_mut().set_dist_reference( + dist_map + .get("reference") + .filter(|v| !v.is_null()) + .map(|v| strval(v)), + ); package .package_mut() .set_dist_sha1_checksum(dist_map.get("shasum").map(|v| strval(v))); diff --git a/crates/shirabe/src/package/loader/root_package_loader.rs b/crates/shirabe/src/package/loader/root_package_loader.rs index 9cd1eac..308706f 100644 --- a/crates/shirabe/src/package/loader/root_package_loader.rs +++ b/crates/shirabe/src/package/loader/root_package_loader.rs @@ -266,7 +266,7 @@ impl RootPackageLoader { for (req_name, req_version) in requires { let mut m: IndexMap<CaptureKey, String> = IndexMap::new(); if Preg::is_match3( - r"(?:^|\| *|, *)([^,\s#|]+)(?:#[^ ]+)? +as +([^,\s|]+)(?:$| *\|| *)", + r"{(?:^|\| *|, *)([^,\s#|]+)(?:#[^ ]+)? +as +([^,\s|]+)(?:$| *\|| *,)}", req_version, Some(&mut m), ) { @@ -320,7 +320,7 @@ impl RootPackageLoader { for (req_name, req_version) in requires { let mut constraints: Vec<String> = vec![]; - let or_split = Preg::split(r"\s*\|\|?\s*", req_version.trim()); + let or_split = Preg::split(r"{\s*\|\|?\s*}", req_version.trim()); for or_constraint in &or_split { let and_split = shirabe_semver::split_and_constraints(or_constraint); for and_constraint in and_split { @@ -329,7 +329,7 @@ impl RootPackageLoader { } let stability_names: Vec<&str> = stabilities.keys().copied().collect(); - let pattern = format!("^[^@]*?@({})$", stability_names.join("|")); + let pattern = format!("{{^[^@]*?@({})$}}i", stability_names.join("|")); let mut matched = false; for constraint in &constraints { @@ -353,8 +353,8 @@ impl RootPackageLoader { } for constraint in &constraints { - let req_version_stripped = Preg::replace(r"^([^,\s@]+) as .+$", "$1", constraint); - if Preg::is_match(r"^[^,\s@]+$", &req_version_stripped) { + let req_version_stripped = Preg::replace(r"{^([^,\s@]+) as .+$}", "$1", constraint); + if Preg::is_match(r"{^[^,\s@]+$}", &req_version_stripped) { let stability_name = VersionParser::parse_stability(&req_version_stripped); if stability_name != "stable" { let name = strtolower(req_name); @@ -378,9 +378,9 @@ impl RootPackageLoader { mut references: IndexMap<String, String>, ) -> IndexMap<String, String> { for (req_name, req_version) in requires { - let req_version = Preg::replace(r"^([^,\s@]+) as .+$", "$1", req_version); + let req_version = Preg::replace(r"{^([^,\s@]+) as .+$}", "$1", req_version); let mut m: IndexMap<CaptureKey, String> = IndexMap::new(); - if Preg::is_match3(r"^[^,\s@]+?#([a-f0-9]+)$", &req_version, Some(&mut m)) + if Preg::is_match3(r"{^[^,\s@]+?#([a-f0-9]+)$}", &req_version, Some(&mut m)) && VersionParser::parse_stability(&req_version) == "dev" { let name = strtolower(req_name); diff --git a/crates/shirabe/src/package/version/version_bumper.rs b/crates/shirabe/src/package/version/version_bumper.rs index c029e3b..4deb450 100644 --- a/crates/shirabe/src/package/version/version_bumper.rs +++ b/crates/shirabe/src/package/version/version_bumper.rs @@ -54,9 +54,17 @@ impl VersionBumper { return Ok(pretty_constraint); } + // Regex pattern compatibility: + // PHP uses zero-width look-behind `(?<=,|\ |\||^)` and look-ahead `(?=,|$|\ |\||@)` to require + // a separator around the constraint. The `regex` crate supports neither, so the separators are + // matched as *consuming* groups instead. Only the `constraint` named group's own offset/length + // is used for the replacement below (never group 0), so consuming the surrounding separators + // does not shift the rewrite. The single-character `|`/space/comma separators are not consumed + // by both an adjacent trailing and leading match, but Composer never packs two same-major + // constraints around a single such separator (alternatives use `||`, ranges use spaces). let pattern = format!( r#"{{ - (?<=,|\ |\||^) # leading separator + (?:^|[,\ |]) # leading separator (?P<constraint> \^v?{major}(?:\.\d+)* # e.g. ^2.anything | ~v?{major}(?:\.\d+){{1,3}} # e.g. ~2.2 or ~2.2.2 or ~2.2.2.2 @@ -64,7 +72,7 @@ impl VersionBumper { | >=v?\d(?:\.\d+)* # e.g. >=2 or >=1.2 etc | \* # full wildcard ) - (?=,|$|\ |\||@) # trailing separator + (?:$|[,\ |@]) # trailing separator }}x"#, major = major ); diff --git a/crates/shirabe/src/repository/handle.rs b/crates/shirabe/src/repository/handle.rs index 2e83ef8..90a9090 100644 --- a/crates/shirabe/src/repository/handle.rs +++ b/crates/shirabe/src/repository/handle.rs @@ -148,6 +148,14 @@ impl RepositoryInterfaceHandle { // --- InstalledRepositoryInterface helpers (valid only when the wrapped repository is one) --- + /// PHP `$repository instanceof InstalledRepositoryInterface`. + pub fn is_installed_repository_interface(&self) -> bool { + self.0 + .borrow() + .as_installed_repository_interface() + .is_some() + } + pub fn is_fresh(&self) -> bool { self.0 .borrow() diff --git a/crates/shirabe/src/repository/installed_repository.rs b/crates/shirabe/src/repository/installed_repository.rs index 55ec50f..cfa5a7a 100644 --- a/crates/shirabe/src/repository/installed_repository.rs +++ b/crates/shirabe/src/repository/installed_repository.rs @@ -364,12 +364,14 @@ impl InstalledRepository { } pub fn add_repository(&mut self, repository: RepositoryInterfaceHandle) { - // TODO: type guard? assert!( repository.is::<LockArrayRepository>() + || repository.is_installed_repository_interface() || repository.is::<RootPackageRepository>() || repository.is::<PlatformRepository>(), - "An InstalledRepository can contain a repository of type: LockArrayRepository, RootPackageRepository or PlatformRepository" + "An InstalledRepository can not contain a repository of type {} ({})", + repository.get_repo_name(), + repository.get_repo_name() ); self.inner.add_repository(repository); diff --git a/crates/shirabe/src/repository/repository_manager.rs b/crates/shirabe/src/repository/repository_manager.rs index 403b504..247236c 100644 --- a/crates/shirabe/src/repository/repository_manager.rs +++ b/crates/shirabe/src/repository/repository_manager.rs @@ -147,10 +147,30 @@ impl RepositoryManager { fn create_repository_by_class( &self, - _class: &str, - _config: IndexMap<String, PhpMixed>, + class: &str, + config: IndexMap<String, PhpMixed>, ) -> anyhow::Result<RepositoryInterfaceHandle> { - todo!("Phase B: dynamic class instantiation by class name") + // PHP: `new $class($config, $this->io, $this->config, $this->httpDownloader, + // $this->eventDispatcher, $this->process)`. Rust cannot instantiate by string class name, so + // dispatch over the classes registered in `createDefaultRepositoryManager`. + match class { + "Composer\\Repository\\ComposerRepository" => Ok(RepositoryInterfaceHandle::new( + crate::repository::ComposerRepository::new( + config, + self.io.clone(), + &self.config.borrow(), + self.http_downloader.clone(), + self.event_dispatcher.clone(), + )?, + )), + "Composer\\Repository\\PackageRepository" => Ok(RepositoryInterfaceHandle::new( + crate::repository::PackageRepository::new(config), + )), + other => todo!( + "Phase B: dynamic class instantiation by class name: {}", + other + ), + } } pub fn set_repository_class(&mut self, r#type: &str, class: &str) { diff --git a/crates/shirabe/src/repository/vcs_repository.rs b/crates/shirabe/src/repository/vcs_repository.rs index 526aa01..7a143eb 100644 --- a/crates/shirabe/src/repository/vcs_repository.rs +++ b/crates/shirabe/src/repository/vcs_repository.rs @@ -264,6 +264,16 @@ impl VcsRepository { &self.version_transport_exceptions } + /// For testing only: drives `initialize` (which shells out to the VCS driver to discover + /// tags/branches and load each package) and returns the packages collected by the inner + /// `ArrayRepository`, mirroring the polymorphic `RepositoryInterface::getPackages` dispatch + /// in PHP where `VcsRepository` inherits `getPackages` from `ArrayRepository`. + pub fn __get_packages(&mut self) -> anyhow::Result<Vec<crate::package::BasePackageHandle>> { + self.initialize()?; + use crate::repository::RepositoryInterface; + self.inner.get_packages() + } + pub fn initialize(&mut self) -> Result<()> { self.inner.initialize(); @@ -282,7 +292,7 @@ impl VcsRepository { self.version_parser = Some(VersionParser::new()); if self.loader.is_none() { self.loader = Some(Box::new(ArrayLoader::new( - Some(todo!("phase-b: clone VersionParser")), + self.version_parser.clone(), false, ))); } diff --git a/crates/shirabe/src/util/forgejo_url.rs b/crates/shirabe/src/util/forgejo_url.rs index 85f2238..d336b87 100644 --- a/crates/shirabe/src/util/forgejo_url.rs +++ b/crates/shirabe/src/util/forgejo_url.rs @@ -14,7 +14,7 @@ pub struct ForgejoUrl { impl ForgejoUrl { pub const URL_REGEX: &'static str = - r"^(?:(?:https?|git)://([^/]+)/|git@([^:]+):/?)([^/]+)/([^/]+?)(?:\.git|/)?$"; + r"{^(?:(?:https?|git)://([^/]+)/|git@([^:]+):/?)([^/]+)/([^/]+?)(?:\.git|/)?$}"; fn new(owner: String, repository: String, origin_url: String, api_url: String) -> Self { Self { |
