diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-05-17 11:52:08 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-05-17 11:52:20 +0900 |
| commit | 93a7671c98a9f022d757781f8fe583a2d55df07b (patch) | |
| tree | 66ad0cef7ac58823262280a6bf94961c1d73f92a /crates/shirabe/src/downloader | |
| parent | 35690acf83fa4473311a18e970ecd8156e1e6ac0 (diff) | |
| download | php-shirabe-93a7671c98a9f022d757781f8fe583a2d55df07b.tar.gz php-shirabe-93a7671c98a9f022d757781f8fe583a2d55df07b.tar.zst php-shirabe-93a7671c98a9f022d757781f8fe583a2d55df07b.zip | |
refactor(shirabe): convert PHP abstract classes to Rust traits
PHP abstract classes are represented as traits to better align with
Rust's type system.
Diffstat (limited to 'crates/shirabe/src/downloader')
| -rw-r--r-- | crates/shirabe/src/downloader/archive_downloader.rs | 118 | ||||
| -rw-r--r-- | crates/shirabe/src/downloader/vcs_downloader.rs | 313 |
2 files changed, 146 insertions, 285 deletions
diff --git a/crates/shirabe/src/downloader/archive_downloader.rs b/crates/shirabe/src/downloader/archive_downloader.rs index 59704e3..03edffe 100644 --- a/crates/shirabe/src/downloader/archive_downloader.rs +++ b/crates/shirabe/src/downloader/archive_downloader.rs @@ -1,9 +1,5 @@ //! ref: composer/src/Composer/Downloader/ArchiveDownloader.php -use crate::dependency_resolver::operation::install_operation::InstallOperation; -use crate::downloader::file_downloader::FileDownloader; -use crate::package::package_interface::PackageInterface; -use crate::util::platform::Platform; use anyhow::Result; use indexmap::IndexMap; use shirabe_external_packages::react::promise::promise_interface::PromiseInterface; @@ -12,64 +8,84 @@ use shirabe_php_shim::{ DIRECTORY_SEPARATOR, RuntimeException, bin2hex, file_exists, is_dir, random_bytes, realpath, }; -#[derive(Debug)] -pub struct ArchiveDownloader { - pub(crate) inner: FileDownloader, - pub(crate) cleanup_executed: IndexMap<String, bool>, -} +use crate::dependency_resolver::operation::install_operation::InstallOperation; +use crate::downloader::file_downloader::FileDownloader; +use crate::package::package_interface::PackageInterface; +use crate::util::platform::Platform; + +pub trait ArchiveDownloader { + fn inner(&self) -> &FileDownloader; + fn inner_mut(&mut self) -> &mut FileDownloader; + fn cleanup_executed(&self) -> &IndexMap<String, bool>; + fn cleanup_executed_mut(&mut self) -> &mut IndexMap<String, bool>; + + fn extract( + &self, + package: &dyn PackageInterface, + file: &str, + path: &str, + ) -> Result<Box<dyn PromiseInterface>>; -impl ArchiveDownloader { - pub fn prepare( + fn prepare( &mut self, r#type: &str, package: &dyn PackageInterface, path: &str, prev_package: Option<&dyn PackageInterface>, ) -> Result<Box<dyn PromiseInterface>> { - self.cleanup_executed.remove(package.get_name()); - - self.inner.prepare(r#type, package, path, prev_package) + self.cleanup_executed_mut().remove(package.get_name()); + self.inner_mut() + .prepare(r#type, package, path, prev_package) } - pub fn cleanup( + fn cleanup( &mut self, r#type: &str, package: &dyn PackageInterface, path: &str, prev_package: Option<&dyn PackageInterface>, ) -> Result<Box<dyn PromiseInterface>> { - self.cleanup_executed + self.cleanup_executed_mut() .insert(package.get_name().to_string(), true); - - self.inner.cleanup(r#type, package, path, prev_package) + self.inner_mut() + .cleanup(r#type, package, path, prev_package) } - pub fn install( + /// @inheritDoc + /// + /// @throws \RuntimeException + /// @throws \UnexpectedValueException + fn install( &mut self, package: &dyn PackageInterface, path: &str, output: bool, ) -> Result<Box<dyn PromiseInterface>> { if output { - self.inner.io.write_error(&format!( + self.inner().io.write_error(&format!( " - {}{}", InstallOperation::format(package, false), self.get_install_operation_appendix(package, path) )); } - let vendor_dir = self.inner.config.get("vendor-dir"); + let vendor_dir = self.inner().config.get("vendor-dir"); // clean up the target directory, unless it contains the vendor dir, as the vendor dir contains // the archive to be extracted. This is the case when installing with create-project in the current directory // but in that case we ensure the directory is empty already in ProjectInstaller so no need to empty it here. - if !self.inner.filesystem.normalize_path(&vendor_dir).contains( - &self - .inner - .filesystem - .normalize_path(&format!("{}{}", path, DIRECTORY_SEPARATOR)), - ) { - self.inner.filesystem.empty_directory(path); + if !self + .inner() + .filesystem + .normalize_path(&vendor_dir) + .contains( + &self + .inner() + .filesystem + .normalize_path(&format!("{}{}", path, DIRECTORY_SEPARATOR)), + ) + { + self.inner_mut().filesystem.empty_directory(path); } let temporary_dir; @@ -80,33 +96,34 @@ impl ArchiveDownloader { } } - self.inner.add_cleanup_path(package, &temporary_dir); + self.inner_mut().add_cleanup_path(package, &temporary_dir); // avoid cleaning up $path if installing in "." for eg create-project as we can not // delete the directory we are currently in on windows if !is_dir(path) || realpath(path) != Platform::get_cwd() { - self.inner.add_cleanup_path(package, path); + self.inner_mut().add_cleanup_path(package, path); } - self.inner + self.inner_mut() .filesystem .ensure_directory_exists(&temporary_dir); - let file_name = self.inner.get_file_name(package, path); + let file_name = self.inner().get_file_name(package, path); - let filesystem = &self.inner.filesystem; + let filesystem = &self.inner().filesystem; let cleanup = move || { // remove cache if the file was corrupted - self.inner.clear_last_cache_write(package); + self.inner_mut().clear_last_cache_write(package); // clean up filesystem.remove_directory(&temporary_dir); if is_dir(path) && realpath(path) != Platform::get_cwd() { filesystem.remove_directory(path); } - self.inner.remove_cleanup_path(package, &temporary_dir); + self.inner_mut() + .remove_cleanup_path(package, &temporary_dir); let realpath_result = realpath(path); if let Some(realpath_val) = realpath_result { - self.inner.remove_cleanup_path(package, &realpath_val); + self.inner_mut().remove_cleanup_path(package, &realpath_val); } }; @@ -155,7 +172,10 @@ impl ArchiveDownloader { code: 0, }.into()); } - rename_recursively.as_ref().unwrap()(&file, &format!("{}/{}", to, file_basename))?; + rename_recursively.as_ref().unwrap()( + &file, + &format!("{}/{}", to, file_basename), + )?; } else { filesystem.rename(&file, &format!("{}/{}", to, file_basename)); } @@ -179,7 +199,9 @@ impl ArchiveDownloader { } let content_dir = get_folder_content(&temporary_dir); - let single_dir_at_top_level = content_dir.len() == 1 && is_dir(&content_dir[0].to_string_lossy().to_string()); + let single_dir_at_top_level = + content_dir.len() == 1 + && is_dir(&content_dir[0].to_string_lossy().to_string()); if rename_as_one { // if the target $path is clear, we can rename the whole package in one go instead of looping over the contents @@ -204,8 +226,8 @@ impl ArchiveDownloader { Ok(promise.then( Box::new(move || -> Result<()> { - self.inner.remove_cleanup_path(package, &temporary_dir); - self.inner.remove_cleanup_path(package, path); + self.inner_mut().remove_cleanup_path(package, &temporary_dir); + self.inner_mut().remove_cleanup_path(package, path); Ok(()) }), None, @@ -218,20 +240,8 @@ impl ArchiveDownloader { )) } - pub fn get_install_operation_appendix( - &self, - _package: &dyn PackageInterface, - _path: &str, - ) -> &str { + /// @inheritDoc + fn get_install_operation_appendix(&self, _package: &dyn PackageInterface, _path: &str) -> &str { ": Extracting archive" } - - pub(crate) fn extract( - &self, - _package: &dyn PackageInterface, - _file: &str, - _path: &str, - ) -> Result<Box<dyn PromiseInterface>> { - todo!() - } } diff --git a/crates/shirabe/src/downloader/vcs_downloader.rs b/crates/shirabe/src/downloader/vcs_downloader.rs index 37954d2..b76c242 100644 --- a/crates/shirabe/src/downloader/vcs_downloader.rs +++ b/crates/shirabe/src/downloader/vcs_downloader.rs @@ -23,40 +23,58 @@ use crate::package::version::version_parser::VersionParser; use crate::util::filesystem::Filesystem; use crate::util::process_executor::ProcessExecutor; -#[derive(Debug)] -pub struct VcsDownloader { - pub(crate) io: Box<dyn IOInterface>, - pub(crate) config: Config, - pub(crate) process: ProcessExecutor, - pub(crate) filesystem: Filesystem, - /// @var array<string, true> - pub(crate) has_cleaned_changes: IndexMap<String, bool>, -} +pub trait VcsDownloader: + DownloaderInterface + ChangeReportInterface + VcsCapableDownloaderInterface +{ + fn io(&self) -> &dyn IOInterface; + fn io_mut(&mut self) -> &mut dyn IOInterface; + fn config(&self) -> &Config; + fn config_mut(&mut self) -> &mut Config; + fn process(&self) -> &ProcessExecutor; + fn process_mut(&mut self) -> &mut ProcessExecutor; + fn filesystem(&self) -> &Filesystem; + fn filesystem_mut(&mut self) -> &mut Filesystem; + fn has_cleaned_changes(&self) -> &IndexMap<String, bool>; + fn has_cleaned_changes_mut(&mut self) -> &mut IndexMap<String, bool>; -impl VcsDownloader { - pub fn new( - io: Box<dyn IOInterface>, - config: Config, - process: Option<ProcessExecutor>, - fs: Option<Filesystem>, - ) -> Self { - // TODO(phase-b): ProcessExecutor::new takes &dyn IOInterface; Filesystem::new takes ProcessExecutor - let process = process.unwrap_or_else(|| ProcessExecutor::new(&*io)); - let filesystem = fs.unwrap_or_else(|| Filesystem::new(&process)); - Self { - io, - config, - process, - filesystem, - has_cleaned_changes: IndexMap::new(), - } - } + /// Downloads data needed to run an install/update later + fn do_download( + &mut self, + package: &dyn PackageInterface, + path: &str, + url: &str, + prev_package: Option<&dyn PackageInterface>, + ) -> Result<Box<dyn PromiseInterface>>; + + /// Downloads specific package into specific folder. + fn do_install( + &mut self, + package: &dyn PackageInterface, + path: &str, + url: &str, + ) -> Result<Box<dyn PromiseInterface>>; + + /// Updates specific package in specific folder from initial to target version. + fn do_update( + &mut self, + initial: &dyn PackageInterface, + target: &dyn PackageInterface, + path: &str, + url: &str, + ) -> Result<Box<dyn PromiseInterface>>; + + /// Fetches the commit logs between two commits + fn get_commit_logs(&self, from_reference: &str, to_reference: &str, path: &str) -> String; - pub fn get_installation_source(&self) -> String { + /// Checks if VCS metadata repository has been initialized + /// repository example: .git|.svn|.hg + fn has_metadata_repository(&self, path: &str) -> bool; + + fn get_installation_source(&self) -> String { "source".to_string() } - pub fn download( + fn download( &mut self, package: &dyn PackageInterface, path: &str, @@ -88,8 +106,8 @@ impl VcsDownloader { if is_phpunit_exception { return Err(e); } - if self.io.is_debug() { - self.io.write_error( + if self.io().is_debug() { + self.io_mut().write_error( PhpMixed::String(format!("Failed: [{}] {}", get_class(&e), e,)), true, IOInterface::NORMAL, @@ -100,7 +118,7 @@ impl VcsDownloader { .collect(), )) > 0 { - self.io.write_error( + self.io_mut().write_error( PhpMixed::String(" Failed, trying the next URL".to_string()), true, IOInterface::NORMAL, @@ -121,7 +139,7 @@ impl VcsDownloader { Ok(shirabe_external_packages::react::promise::resolve(None)) } - pub fn prepare( + fn prepare( &mut self, r#type: &str, package: &dyn PackageInterface, @@ -130,10 +148,10 @@ impl VcsDownloader { ) -> Result<Box<dyn PromiseInterface>> { if r#type == "update" { self.clean_changes(prev_package.unwrap(), path, true)?; - self.has_cleaned_changes + self.has_cleaned_changes_mut() .insert(prev_package.unwrap().get_unique_name(), true); } else if r#type == "install" { - self.filesystem.empty_directory(path); + self.filesystem_mut().empty_directory(path); } else if r#type == "uninstall" { self.clean_changes(package, path, false)?; } @@ -141,7 +159,7 @@ impl VcsDownloader { Ok(shirabe_external_packages::react::promise::resolve(None)) } - pub fn cleanup( + fn cleanup( &mut self, r#type: &str, _package: &dyn PackageInterface, @@ -150,18 +168,21 @@ impl VcsDownloader { ) -> Result<Box<dyn PromiseInterface>> { if r#type == "update" && prev_package - .map(|p| self.has_cleaned_changes.contains_key(&p.get_unique_name())) + .map(|p| { + self.has_cleaned_changes() + .contains_key(&p.get_unique_name()) + }) .unwrap_or(false) { self.reapply_changes(path); - self.has_cleaned_changes + self.has_cleaned_changes_mut() .shift_remove(&prev_package.unwrap().get_unique_name()); } Ok(shirabe_external_packages::react::promise::resolve(None)) } - pub fn install( + fn install( &mut self, package: &dyn PackageInterface, path: &str, @@ -177,7 +198,7 @@ impl VcsDownloader { .into()); } - self.io.write_error( + self.io_mut().write_error( PhpMixed::String(format!( " - {}: ", InstallOperation::format(package, false) @@ -199,8 +220,8 @@ impl VcsDownloader { if is_phpunit_exception { return Err(e); } - if self.io.is_debug() { - self.io.write_error( + if self.io().is_debug() { + self.io_mut().write_error( PhpMixed::String(format!("Failed: [{}] {}", get_class(&e), e,)), true, IOInterface::NORMAL, @@ -211,7 +232,7 @@ impl VcsDownloader { .collect(), )) > 0 { - self.io.write_error( + self.io_mut().write_error( PhpMixed::String(" Failed, trying the next URL".to_string()), true, IOInterface::NORMAL, @@ -232,7 +253,7 @@ impl VcsDownloader { Ok(shirabe_external_packages::react::promise::resolve(None)) } - pub fn update( + fn update( &mut self, initial: &dyn PackageInterface, target: &dyn PackageInterface, @@ -249,7 +270,7 @@ impl VcsDownloader { .into()); } - self.io.write_error( + self.io_mut().write_error( PhpMixed::String(format!( " - {}: ", UpdateOperation::format(initial, target, false), @@ -277,8 +298,8 @@ impl VcsDownloader { if is_phpunit_exception { return Err(e); } - if self.io.is_debug() { - self.io.write_error( + if self.io().is_debug() { + self.io_mut().write_error( PhpMixed::String(format!("Failed: [{}] {}", get_class(&e), e,)), true, IOInterface::NORMAL, @@ -289,7 +310,7 @@ impl VcsDownloader { .collect(), )) > 0 { - self.io.write_error( + self.io_mut().write_error( PhpMixed::String(" Failed, trying the next URL".to_string()), true, IOInterface::NORMAL, @@ -302,7 +323,7 @@ impl VcsDownloader { // print the commit logs if in verbose mode and VCS metadata is present // because in case of missing metadata code would trigger another exception - if exception.is_none() && self.io.is_verbose() && self.has_metadata_repository(path) { + if exception.is_none() && self.io().is_verbose() && self.has_metadata_repository(path) { let mut message = "Pulling in changes:"; let mut logs = self.get_commit_logs( initial.get_source_reference().unwrap_or(""), @@ -329,12 +350,12 @@ impl VcsDownloader { // escape angle brackets for proper output in the console logs = str_replace("<", "\\<", &logs); - self.io.write_error( + self.io_mut().write_error( PhpMixed::String(format!(" {}", message)), true, IOInterface::NORMAL, ); - self.io + self.io_mut() .write_error(PhpMixed::String(logs), true, IOInterface::NORMAL); } } @@ -348,12 +369,12 @@ impl VcsDownloader { Ok(shirabe_external_packages::react::promise::resolve(None)) } - pub fn remove( + fn remove( &mut self, package: &dyn PackageInterface, path: &str, ) -> Result<Box<dyn PromiseInterface>> { - self.io.write_error( + self.io_mut().write_error( PhpMixed::String(format!( " - {}", UninstallOperation::format(package, false) @@ -362,7 +383,7 @@ impl VcsDownloader { IOInterface::NORMAL, ); - let promise = self.filesystem.remove_directory_async(path); + let promise = self.filesystem_mut().remove_directory_async(path); let path = path.to_string(); Ok( @@ -380,9 +401,9 @@ impl VcsDownloader { ) } - pub fn get_vcs_reference(&self, package: &dyn PackageInterface, path: &str) -> Option<String> { + fn get_vcs_reference(&self, package: &dyn PackageInterface, path: &str) -> Option<String> { let parser = VersionParser::new(); - let guesser = VersionGuesser::new(&self.config, &self.process, &parser, &*self.io); + let guesser = VersionGuesser::new(self.config(), self.process(), &parser, self.io()); let dumper = ArrayDumper::new(); let package_config = dumper.dump(package); @@ -398,12 +419,9 @@ impl VcsDownloader { /// Prompt the user to check if changes should be stashed/removed or the operation aborted /// - /// @param bool $update if true (update) the changes can be stashed and reapplied after an update, - /// if false (remove) the changes should be assumed to be lost if the operation is not aborted - /// - /// @throws \RuntimeException in case the operation must be aborted - /// @phpstan-return PromiseInterface<void|null> - pub(crate) fn clean_changes( + /// @param bool $update if true (update) the changes can be stashed and reapplied after an update, + /// if false (remove) the changes should be assumed to be lost if the operation is not aborted + fn clean_changes( &self, package: &dyn PackageInterface, path: &str, @@ -421,90 +439,10 @@ impl VcsDownloader { Ok(shirabe_external_packages::react::promise::resolve(None)) } - /// Reapply previously stashes changes if applicable, only called after an update (regardless if successful or not) - /// - /// @throws \RuntimeException in case the operation must be aborted or the patch does not apply cleanly - pub(crate) fn reapply_changes(&self, _path: &str) {} - - /// Downloads data needed to run an install/update later - /// - /// @param PackageInterface $package package instance - /// @param string $path download path - /// @param string $url package url - /// @param PackageInterface|null $prevPackage previous package (in case of an update) - /// @phpstan-return PromiseInterface<void|null> - // TODO(phase-b): abstract; overridden by concrete subclasses (GitDownloader, SvnDownloader, ...) - pub(crate) fn do_download( - &mut self, - _package: &dyn PackageInterface, - _path: &str, - _url: &str, - _prev_package: Option<&dyn PackageInterface>, - ) -> Result<Box<dyn PromiseInterface>> { - todo!("abstract: implemented by subclass") - } - - /// Downloads specific package into specific folder. - /// - /// @param PackageInterface $package package instance - /// @param string $path download path - /// @param string $url package url - /// @phpstan-return PromiseInterface<void|null> - // TODO(phase-b): abstract; overridden by concrete subclasses - pub(crate) fn do_install( - &mut self, - _package: &dyn PackageInterface, - _path: &str, - _url: &str, - ) -> Result<Box<dyn PromiseInterface>> { - todo!("abstract: implemented by subclass") - } - - /// Updates specific package in specific folder from initial to target version. - /// - /// @param PackageInterface $initial initial package - /// @param PackageInterface $target updated package - /// @param string $path download path - /// @param string $url package url - /// @phpstan-return PromiseInterface<void|null> - // TODO(phase-b): abstract; overridden by concrete subclasses - pub(crate) fn do_update( - &mut self, - _initial: &dyn PackageInterface, - _target: &dyn PackageInterface, - _path: &str, - _url: &str, - ) -> Result<Box<dyn PromiseInterface>> { - todo!("abstract: implemented by subclass") - } - - /// Fetches the commit logs between two commits - /// - /// @param string $fromReference the source reference - /// @param string $toReference the target reference - /// @param string $path the package path - // TODO(phase-b): abstract; overridden by concrete subclasses - pub(crate) fn get_commit_logs( - &self, - _from_reference: &str, - _to_reference: &str, - _path: &str, - ) -> String { - todo!("abstract: implemented by subclass") - } - - /// Checks if VCS metadata repository has been initialized - /// repository example: .git|.svn|.hg - // TODO(phase-b): abstract; overridden by concrete subclasses - pub(crate) fn has_metadata_repository(&self, _path: &str) -> bool { - todo!("abstract: implemented by subclass") - } + /// Reapply previously stashed changes if applicable, only called after an update (regardless if successful or not) + fn reapply_changes(&self, _path: &str) {} - /// @param string[] $urls - /// - /// @return string[] fn prepare_urls(&self, mut urls: Vec<String>) -> Vec<String> { - // PHP: foreach ($urls as $index => $url) — mutates in place for index in 0..urls.len() { let mut url = urls[index].clone(); if Filesystem::is_local_path(&url) { @@ -532,91 +470,4 @@ impl VcsDownloader { urls } - - // TODO(phase-b): get_local_changes belongs to ChangeReportInterface, implemented by subclasses - pub(crate) fn get_local_changes( - &self, - _package: &dyn PackageInterface, - _path: String, - ) -> Option<String> { - todo!("abstract: implemented by ChangeReportInterface subclasses") - } -} - -impl DownloaderInterface for VcsDownloader { - fn get_installation_source(&self) -> String { - VcsDownloader::get_installation_source(self) - } - - fn download( - &self, - _package: &dyn PackageInterface, - _path: &str, - _prev_package: Option<&dyn PackageInterface>, - ) -> Result<Box<dyn PromiseInterface>> { - // TODO(phase-b): download mutates state; trait method takes &self - todo!("download requires &mut self") - } - - fn prepare( - &self, - _type: &str, - _package: &dyn PackageInterface, - _path: &str, - _prev_package: Option<&dyn PackageInterface>, - ) -> Result<Box<dyn PromiseInterface>> { - // TODO(phase-b): prepare mutates state; trait method takes &self - todo!("prepare requires &mut self") - } - - fn install( - &self, - _package: &dyn PackageInterface, - _path: &str, - ) -> Result<Box<dyn PromiseInterface>> { - // TODO(phase-b): install mutates state; trait method takes &self - todo!("install requires &mut self") - } - - fn update( - &self, - _initial: &dyn PackageInterface, - _target: &dyn PackageInterface, - _path: &str, - ) -> Result<Box<dyn PromiseInterface>> { - // TODO(phase-b): update mutates state; trait method takes &self - todo!("update requires &mut self") - } - - fn remove( - &self, - _package: &dyn PackageInterface, - _path: &str, - ) -> Result<Box<dyn PromiseInterface>> { - // TODO(phase-b): remove mutates state; trait method takes &self - todo!("remove requires &mut self") - } - - fn cleanup( - &self, - _type: &str, - _package: &dyn PackageInterface, - _path: &str, - _prev_package: Option<&dyn PackageInterface>, - ) -> Result<Box<dyn PromiseInterface>> { - // TODO(phase-b): cleanup mutates state; trait method takes &self - todo!("cleanup requires &mut self") - } -} - -impl ChangeReportInterface for VcsDownloader { - fn get_local_changes(&self, package: &dyn PackageInterface, path: String) -> Option<String> { - VcsDownloader::get_local_changes(self, package, path) - } -} - -impl VcsCapableDownloaderInterface for VcsDownloader { - fn get_vcs_reference(&self, package: &dyn PackageInterface, path: String) -> Option<String> { - VcsDownloader::get_vcs_reference(self, package, &path) - } } |
