diff options
Diffstat (limited to 'crates/shirabe/src')
| -rw-r--r-- | crates/shirabe/src/command/base_command.rs | 63 | ||||
| -rw-r--r-- | crates/shirabe/src/command/base_config_command.rs | 65 | ||||
| -rw-r--r-- | crates/shirabe/src/command/base_dependency_command.rs | 45 | ||||
| -rw-r--r-- | crates/shirabe/src/dependency_resolver/rule.rs | 98 | ||||
| -rw-r--r-- | crates/shirabe/src/downloader/archive_downloader.rs | 118 | ||||
| -rw-r--r-- | crates/shirabe/src/downloader/vcs_downloader.rs | 313 | ||||
| -rw-r--r-- | crates/shirabe/src/io/base_io.rs | 57 | ||||
| -rw-r--r-- | crates/shirabe/src/package/archiver/base_exclude_filter.rs | 43 | ||||
| -rw-r--r-- | crates/shirabe/src/package/base_package.rs | 170 | ||||
| -rw-r--r-- | crates/shirabe/src/repository/vcs/vcs_driver.rs | 119 |
10 files changed, 402 insertions, 689 deletions
diff --git a/crates/shirabe/src/command/base_command.rs b/crates/shirabe/src/command/base_command.rs index 77777ef..0324737 100644 --- a/crates/shirabe/src/command/base_command.rs +++ b/crates/shirabe/src/command/base_command.rs @@ -34,19 +34,17 @@ use crate::plugin::pre_command_run_event::PreCommandRunEvent; use crate::util::platform::Platform; /// Base class for Composer commands -#[derive(Debug)] -pub struct BaseCommand { - inner: Command, - /// @var Composer|null - composer: Option<Composer>, - /// @var IOInterface - io: Option<Box<dyn IOInterface>>, -} +pub trait BaseCommand { + fn inner(&self) -> &Command; + fn inner_mut(&mut self) -> &mut Command; + fn composer(&self) -> Option<&Composer>; + fn composer_mut(&mut self) -> Option<&mut Composer>; + fn io(&self) -> Option<&dyn IOInterface>; + fn io_mut(&mut self) -> Option<&mut dyn IOInterface>; -impl BaseCommand { /// Gets the application instance for this command. pub fn get_application(&self) -> Result<Application> { - let application = self.inner.get_application(); + let application = self.inner().get_application(); // TODO(phase-b): `$application instanceof Application` downcast from generic Symfony Application let application_as_composer: Option<Application> = application; if application_as_composer.is_none() { @@ -85,12 +83,13 @@ impl BaseCommand { disable_plugins: Option<bool>, disable_scripts: Option<bool>, ) -> Result<Composer> { - if self.composer.is_none() { - let application = self.inner.get_application(); + if self.composer().is_none() { + let application = self.inner().get_application(); // TODO(phase-b): `$application instanceof Application` downcast let application_as_composer: Option<Application> = application; if let Some(app) = application_as_composer { - self.composer = Some(app.get_composer(true, disable_plugins, disable_scripts)?); + *self.composer_mut() = + Some(app.get_composer(true, disable_plugins, disable_scripts)?); // PHP: assert($this->composer instanceof Composer) — Rust types guarantee this } else { return Err(RuntimeException { @@ -103,7 +102,7 @@ impl BaseCommand { } } - Ok(self.composer.clone().unwrap()) + Ok(self.composer().clone().unwrap()) } /// Retrieves the default Composer\Composer instance or null @@ -112,27 +111,27 @@ impl BaseCommand { disable_plugins: Option<bool>, disable_scripts: Option<bool>, ) -> Option<Composer> { - if self.composer.is_none() { - let application = self.inner.get_application(); + if self.composer().is_none() { + let application = self.inner().get_application(); // TODO(phase-b): `$application instanceof Application` downcast let application_as_composer: Option<Application> = application; if let Some(app) = application_as_composer { - self.composer = app + *self.composer_mut() = app .get_composer(false, disable_plugins, disable_scripts) .ok(); } } - self.composer.clone() + self.composer().clone() } pub fn set_composer(&mut self, composer: Composer) { - self.composer = Some(composer); + *self.composer_mut() = Some(composer); } /// Removes the cached composer instance pub fn reset_composer(&mut self) -> Result<()> { - self.composer = None; + *self.composer_mut() = None; self.get_application()?.reset_composer(); Ok(()) } @@ -143,29 +142,29 @@ impl BaseCommand { } pub fn get_io(&mut self) -> &dyn IOInterface { - if self.io.is_none() { - let application = self.inner.get_application(); + if self.io().is_none() { + let application = self.inner().get_application(); // TODO(phase-b): `$application instanceof Application` downcast let application_as_composer: Option<Application> = application; if let Some(app) = application_as_composer { - self.io = Some(app.get_io()); + *self.io_mut() = Some(app.get_io()); } else { - self.io = Some(Box::new(NullIO::new())); + *self.io_mut() = Some(Box::new(NullIO::new())); } } - &**self.io.as_ref().unwrap() + &**self.io().as_ref().unwrap() } pub fn set_io(&mut self, io: Box<dyn IOInterface>) { - self.io = Some(io); + *self.io_mut() = Some(io); } /// @inheritdoc /// /// Backport suggested values definition from symfony/console 6.1+ pub fn complete(&self, input: &CompletionInput, suggestions: &mut CompletionSuggestions) { - let definition = self.inner.get_definition(); + let definition = self.inner().get_definition(); let name = input.get_completion_name().to_string(); if CompletionInput::TYPE_OPTION_VALUE == input.get_completion_type() && definition.has_option(&name) @@ -191,7 +190,7 @@ impl BaseCommand { return; } } - self.inner.complete(input, suggestions); + self.inner().complete(input, suggestions); } /// @inheritDoc @@ -206,7 +205,7 @@ impl BaseCommand { let mut disable_scripts = input.has_parameter_option(PhpMixed::String("--no-scripts".to_string())); - let application = self.inner.get_application(); + let application = self.inner().get_application(); // TODO(phase-b): `$application instanceof Application` downcast let application_as_composer: Option<&Application> = None; if let Some(app) = application_as_composer { @@ -245,7 +244,7 @@ impl BaseCommand { let pre_command_run_event = PreCommandRunEvent::new( PluginEvents::PRE_COMMAND_RUN.to_string(), Box::new(input), - self.inner.get_name().to_string(), + self.inner().get_name().to_string(), ); composer.get_event_dispatcher().dispatch( pre_command_run_event.get_name(), @@ -340,7 +339,7 @@ impl BaseCommand { } } - self.inner.initialize(input, output) + self.inner().initialize(input, output) } /// Calls {@see Factory::create()} with the given arguments, taking into account flags and default states for disabling scripts and plugins @@ -357,7 +356,7 @@ impl BaseCommand { let mut disable_scripts = disable_scripts == Some(true) || input.has_parameter_option(PhpMixed::String("--no-scripts".to_string())); - let application = self.inner.get_application(); + let application = self.inner().get_application(); // TODO(phase-b): `$application instanceof Application` downcast let application_as_composer: Option<&Application> = None; if let Some(app) = application_as_composer { diff --git a/crates/shirabe/src/command/base_config_command.rs b/crates/shirabe/src/command/base_config_command.rs index 74929da..85bcb68 100644 --- a/crates/shirabe/src/command/base_config_command.rs +++ b/crates/shirabe/src/command/base_config_command.rs @@ -12,16 +12,15 @@ use shirabe_external_packages::symfony::console::input::input_interface::InputIn use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; use shirabe_php_shim::{PhpMixed, chmod, touch}; -#[derive(Debug)] -pub struct BaseConfigCommand { - inner: BaseCommand, - pub(crate) config: Option<Config>, - pub(crate) config_file: Option<JsonFile>, - pub(crate) config_source: Option<JsonConfigSource>, -} +pub trait BaseConfigCommand: BaseCommand { + fn config(&self) -> Option<&Config>; + fn config_mut(&mut self) -> Option<&mut Config>; + fn config_file(&self) -> Option<&JsonFile>; + fn config_file_mut(&mut self) -> Option<&mut JsonFile>; + fn config_source(&self) -> Option<&JsonConfigSource>; + fn config_source_mut(&mut self) -> Option<&mut JsonConfigSource>; -impl BaseConfigCommand { - pub fn initialize( + fn initialize( &mut self, input: &dyn InputInterface, output: &dyn OutputInterface, @@ -33,8 +32,8 @@ impl BaseConfigCommand { } let io = self.inner.get_io(); - self.config = Some(Factory::create_config(io)?); - let config = self.config.as_mut().unwrap(); + *self.config_mut() = Some(Factory::create_config(io)?); + let config = self.config().as_mut().unwrap(); // When using --global flag, set baseDir to home directory for correct absolute path resolution if input.get_option("global").as_bool() { @@ -53,29 +52,33 @@ impl BaseConfigCommand { std::fs::write(&config_file, "{\n}\n")?; } - let config = self.config.as_ref().unwrap(); - self.config_file = Some(JsonFile::new(config_file.clone(), None, Some(io))); - self.config_source = Some(JsonConfigSource::new(self.config_file.as_ref().unwrap())); + let config = self.config().as_ref().unwrap(); + *self.config_file_mut() = Some(JsonFile::new(config_file.clone(), None, Some(io))); + *self.config_source_mut() = + Some(JsonConfigSource::new(self.config_file().as_ref().unwrap())); // Initialize the global file if it's not there, ignoring any warnings or notices - if input.get_option("global").as_bool() && !self.config_file.as_ref().unwrap().exists() { - let path = self.config_file.as_ref().unwrap().get_path().to_string(); + if input.get_option("global").as_bool() && !self.config_file().as_ref().unwrap().exists() { + let path = self.config_file().as_ref().unwrap().get_path().to_string(); touch(&path); - self.config_file.as_mut().unwrap().write(PhpMixed::Array({ - let mut m = IndexMap::new(); - m.insert( - "config".to_string(), - Box::new(PhpMixed::Array(IndexMap::new())), - ); - m - }))?; + self.config_file_mut() + .as_mut() + .unwrap() + .write(PhpMixed::Array({ + let mut m = IndexMap::new(); + m.insert( + "config".to_string(), + Box::new(PhpMixed::Array(IndexMap::new())), + ); + m + }))?; let _ = Silencer::call(|| { chmod(&path, 0o600); Ok(()) }); } - if !self.config_file.as_ref().unwrap().exists() { + if !self.config_file().as_ref().unwrap().exists() { return Err(anyhow::anyhow!( "File \"{}\" cannot be found in the current directory", config_file @@ -86,11 +89,7 @@ impl BaseConfigCommand { } /// Get the local composer.json, global config.json, or the file passed by the user - pub(crate) fn get_composer_config_file( - &self, - input: &dyn InputInterface, - config: &Config, - ) -> String { + fn get_composer_config_file(&self, input: &dyn InputInterface, config: &Config) -> String { if input.get_option("global").as_bool() { format!("{}/config.json", config.get("home")) } else { @@ -104,11 +103,7 @@ impl BaseConfigCommand { /// Get the local auth.json or global auth.json, or if the user passed in a file to use, /// the corresponding auth.json - pub(crate) fn get_auth_config_file( - &self, - input: &dyn InputInterface, - config: &Config, - ) -> String { + fn get_auth_config_file(&self, input: &dyn InputInterface, config: &Config) -> String { if input.get_option("global").as_bool() { format!("{}/auth.json", config.get("home")) } else { diff --git a/crates/shirabe/src/command/base_dependency_command.rs b/crates/shirabe/src/command/base_dependency_command.rs index 4ea779a..62b97ea 100644 --- a/crates/shirabe/src/command/base_dependency_command.rs +++ b/crates/shirabe/src/command/base_dependency_command.rs @@ -24,44 +24,41 @@ use crate::repository::repository_interface::{FindPackageConstraint, RepositoryI use crate::repository::root_package_repository::RootPackageRepository; use crate::util::package_info::PackageInfo; -#[derive(Debug)] -pub struct BaseDependencyCommand { - inner: BaseCommand, - pub(crate) colors: Vec<String>, -} +pub trait BaseDependencyCommand: BaseCommand { + const ARGUMENT_PACKAGE: &'static str = "package"; + const ARGUMENT_CONSTRAINT: &'static str = "version"; + const OPTION_RECURSIVE: &'static str = "recursive"; + const OPTION_TREE: &'static str = "tree"; -impl BaseDependencyCommand { - pub const ARGUMENT_PACKAGE: &'static str = "package"; - pub const ARGUMENT_CONSTRAINT: &'static str = "version"; - pub const OPTION_RECURSIVE: &'static str = "recursive"; - pub const OPTION_TREE: &'static str = "tree"; + fn colors(&self) -> &[String]; + fn colors_mut(&mut self) -> &mut [String]; - pub fn set_name(&mut self, name: &str) -> &mut Self { + fn set_name(&mut self, name: &str) -> &mut Self { self.inner.set_name(name); self } - pub fn set_aliases(&mut self, aliases: Vec<String>) -> &mut Self { + fn set_aliases(&mut self, aliases: Vec<String>) -> &mut Self { self.inner.set_aliases(aliases); self } - pub fn set_description(&mut self, description: &str) -> &mut Self { + fn set_description(&mut self, description: &str) -> &mut Self { self.inner.set_description(description); self } - pub fn set_definition(&mut self, definition: Vec<shirabe_php_shim::PhpMixed>) -> &mut Self { + fn set_definition(&mut self, definition: Vec<shirabe_php_shim::PhpMixed>) -> &mut Self { self.inner.set_definition(definition); self } - pub fn set_help(&mut self, help: &str) -> &mut Self { + fn set_help(&mut self, help: &str) -> &mut Self { self.inner.set_help(help); self } - pub(crate) fn do_execute( + fn do_execute( &mut self, input: &dyn InputInterface, output: &dyn OutputInterface, @@ -309,7 +306,7 @@ impl BaseDependencyCommand { Ok(r#return) } - pub(crate) fn print_table(&self, output: &dyn OutputInterface, results: Vec<DependentsEntry>) { + fn print_table(&self, output: &dyn OutputInterface, results: Vec<DependentsEntry>) { let mut table: Vec<Vec<String>> = vec![]; let mut doubles: IndexMap<String, bool> = IndexMap::new(); let mut results = results; @@ -362,28 +359,28 @@ impl BaseDependencyCommand { self.inner.render_table(table, output); } - pub(crate) fn init_styles(&mut self, output: &dyn OutputInterface) { - self.colors = vec![ + fn init_styles(&mut self, output: &dyn OutputInterface) { + *self.colors_mut() = vec![ "green".to_string(), "yellow".to_string(), "cyan".to_string(), "magenta".to_string(), "blue".to_string(), ]; - for color in &self.colors { + for color in &self.colors() { let style = OutputFormatterStyle::new(color.clone()); output.get_formatter().set_style(color, style); } } - pub(crate) fn print_tree(&self, results: &[DependentsEntry], prefix: &str, level: i64) { + fn print_tree(&self, results: &[DependentsEntry], prefix: &str, level: i64) { let count = results.len() as i64; let mut idx: i64 = 0; - let colors_len = self.colors.len() as i64; + let colors_len = self.colors().len() as i64; for result in results { let DependentsEntry(package, link, children) = result; - let color = &self.colors[(level % colors_len) as usize]; - let prev_color = &self.colors[((level - 1) % colors_len) as usize]; + let color = &self.colors()[(level % colors_len) as usize]; + let prev_color = &self.colors()[((level - 1) % colors_len) as usize]; idx += 1; let is_last = idx == count; let version_text = diff --git a/crates/shirabe/src/dependency_resolver/rule.rs b/crates/shirabe/src/dependency_resolver/rule.rs index 1ba1dd7..bc2c68e 100644 --- a/crates/shirabe/src/dependency_resolver/rule.rs +++ b/crates/shirabe/src/dependency_resolver/rule.rs @@ -39,19 +39,7 @@ pub enum ReasonData { }, } -/// @phpstan-type ReasonData Link|BasePackage|string|int|array{packageName: string, constraint: ConstraintInterface}|array{package: BasePackage} -#[derive(Debug)] -pub struct Rule { - /// @var int - pub(crate) bitfield: i64, - /// @var Request - pub(crate) request: Option<Request>, - /// @var Link|BasePackage|ConstraintInterface|string - /// @phpstan-var ReasonData - pub(crate) reason_data: ReasonData, -} - -impl Rule { +pub trait Rule: std::fmt::Display { // reason constants and // their reason data contents pub const RULE_ROOT_REQUIRE: i64 = 2; pub const RULE_FIXED: i64 = 3; @@ -67,11 +55,24 @@ impl Rule { const BITFIELD_REASON: i64 = 8; const BITFIELD_DISABLED: i64 = 16; + fn bitfield(&self) -> i64; + fn bitfield_mut(&mut self) -> &mut i64; + fn request(&self) -> Option<&Request>; + fn request_mut(&mut self) -> Option<&mut Request>; + fn reason_data(&self) -> Option<&ReasonData>; + fn reason_data_mut(&mut self) -> Option<&mut ReasonData>; + + fn get_literals(&self) -> Vec<i64>; + fn get_hash(&self) -> PhpMixed; + fn to_string(&self) -> String; + fn equals(&self, rule: &dyn Rule) -> bool; + fn is_assertion(&self) -> bool; + /// @param self::RULE_* $reason A RULE_* constant describing the reason for generating this rule /// @param mixed $reasonData /// /// @phpstan-param ReasonData $reasonData - pub fn new(reason: i64, reason_data: ReasonData) -> Self { + fn new(reason: i64, reason_data: ReasonData) -> Self { let bitfield = (0i64 << Self::BITFIELD_DISABLED) | (reason << Self::BITFIELD_REASON) | (255i64 << Self::BITFIELD_TYPE); @@ -83,16 +84,16 @@ impl Rule { } /// @return self::RULE_* - pub fn get_reason(&self) -> i64 { + fn get_reason(&self) -> i64 { (self.bitfield & (255 << Self::BITFIELD_REASON)) >> Self::BITFIELD_REASON } /// @phpstan-return ReasonData - pub fn get_reason_data(&self) -> &ReasonData { + fn get_reason_data(&self) -> &ReasonData { &self.reason_data } - pub fn get_required_package(&self) -> Option<String> { + fn get_required_package(&self) -> Option<String> { match self.get_reason() { r if r == Self::RULE_ROOT_REQUIRE => match self.get_reason_data() { ReasonData::RootRequire { package_name, .. } => Some(package_name.clone()), @@ -111,33 +112,33 @@ impl Rule { } /// @param RuleSet::TYPE_* $type - pub fn set_type(&mut self, r#type: i64) { + fn set_type(&mut self, r#type: i64) { self.bitfield = (self.bitfield & !(255i64 << Self::BITFIELD_TYPE)) | ((255 & r#type) << Self::BITFIELD_TYPE); } - pub fn get_type(&self) -> i64 { + fn get_type(&self) -> i64 { (self.bitfield & (255 << Self::BITFIELD_TYPE)) >> Self::BITFIELD_TYPE } - pub fn disable(&mut self) { + fn disable(&mut self) { self.bitfield = (self.bitfield & !(255i64 << Self::BITFIELD_DISABLED)) | (1i64 << Self::BITFIELD_DISABLED); } - pub fn enable(&mut self) { + fn enable(&mut self) { self.bitfield &= !(255i64 << Self::BITFIELD_DISABLED); } - pub fn is_disabled(&self) -> bool { + fn is_disabled(&self) -> bool { 0 != ((self.bitfield & (255 << Self::BITFIELD_DISABLED)) >> Self::BITFIELD_DISABLED) } - pub fn is_enabled(&self) -> bool { + fn is_enabled(&self) -> bool { 0 == ((self.bitfield & (255 << Self::BITFIELD_DISABLED)) >> Self::BITFIELD_DISABLED) } - pub fn is_caused_by_lock( + fn is_caused_by_lock( &self, _repository_set: &RepositorySet, request: &Request, @@ -205,7 +206,7 @@ impl Rule { } /// @internal - pub fn get_source_package(&self, pool: &Pool) -> Result<Box<BasePackage>> { + fn get_source_package(&self, pool: &Pool) -> Result<Box<BasePackage>> { let literals = self.get_literals(); match self.get_reason() { @@ -244,14 +245,14 @@ impl Rule { /// @param BasePackage[] $installedMap /// @param array<Rule[]> $learnedPool - pub fn get_pretty_string( + fn get_pretty_string( &self, repository_set: &RepositorySet, request: &Request, pool: &mut Pool, is_verbose: bool, installed_map: IndexMap<i64, Box<BasePackage>>, - _learned_pool: IndexMap<i64, Vec<Box<dyn RuleTrait>>>, + _learned_pool: IndexMap<i64, Vec<Box<dyn Rule>>>, ) -> String { let mut literals = self.get_literals(); @@ -621,7 +622,7 @@ impl Rule { } /// @param array<int|BasePackage> $literalsOrPackages An array containing packages or literals - pub(crate) fn format_packages_unique( + fn format_packages_unique( &self, pool: &Pool, literals_or_packages: Vec<Box<BasePackage>>, @@ -677,44 +678,3 @@ impl Rule { package } } - -/// PHP abstract methods on Rule — concrete subclasses must implement. -pub trait RuleTrait: std::any::Any { - /// @return list<int> - fn get_literals(&self) -> Vec<i64>; - - /// @return int|string - fn get_hash(&self) -> PhpMixed; - - fn to_string(&self) -> String; - - fn equals(&self, rule: &dyn RuleTrait) -> bool; - - fn is_assertion(&self) -> bool; -} - -// TODO(phase-b): abstract method dispatch — currently Rule has stubs that subclasses override. -impl Rule { - pub fn get_literals(&self) -> Vec<i64> { - todo!("abstract: implemented by subclass") - } - - pub fn get_hash(&self) -> PhpMixed { - todo!("abstract: implemented by subclass") - } - - pub fn equals(&self, _rule: &Rule) -> bool { - todo!("abstract: implemented by subclass") - } - - pub fn is_assertion(&self) -> bool { - todo!("abstract: implemented by subclass") - } -} - -impl std::fmt::Display for Rule { - fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - // TODO(phase-b): abstract; subclasses provide __toString - todo!("abstract: implemented by subclass") - } -} 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) - } } diff --git a/crates/shirabe/src/io/base_io.rs b/crates/shirabe/src/io/base_io.rs index 58d1e40..ddccd98 100644 --- a/crates/shirabe/src/io/base_io.rs +++ b/crates/shirabe/src/io/base_io.rs @@ -12,26 +12,27 @@ use shirabe_php_shim::{ UnexpectedValueException, array_merge, in_array, json_encode_ex, }; -#[derive(Debug)] -pub struct BaseIO { - pub(crate) authentications: IndexMap<String, IndexMap<String, Option<String>>>, -} +// TODO(phase-b): default implementations in a subtrait cannot override supertrait methods in Rust; +// write/write_error etc. from IOInterface are called through the supertrait and must be provided +// by concrete types implementing both BaseIO and IOInterface. +pub trait BaseIO: IOInterface { + fn authentications(&self) -> &IndexMap<String, IndexMap<String, Option<String>>>; + fn authentications_mut(&mut self) -> &mut IndexMap<String, IndexMap<String, Option<String>>>; -impl BaseIO { - pub fn get_authentications(&self) -> IndexMap<String, IndexMap<String, Option<String>>> { - self.authentications.clone() + fn get_authentications(&self) -> IndexMap<String, IndexMap<String, Option<String>>> { + self.authentications().clone() } - pub fn reset_authentications(&mut self) { - self.authentications = IndexMap::new(); + fn reset_authentications(&mut self) { + *self.authentications_mut() = IndexMap::new(); } - pub fn has_authentication(&self, repository_name: &str) -> bool { - self.authentications.contains_key(repository_name) + fn has_authentication(&self, repository_name: &str) -> bool { + self.authentications().contains_key(repository_name) } - pub fn get_authentication(&self, repository_name: &str) -> IndexMap<String, Option<String>> { - if let Some(auth) = self.authentications.get(repository_name) { + fn get_authentication(&self, repository_name: &str) -> IndexMap<String, Option<String>> { + if let Some(auth) = self.authentications().get(repository_name) { return auth.clone(); } let mut result = IndexMap::new(); @@ -40,7 +41,7 @@ impl BaseIO { result } - pub fn set_authentication( + fn set_authentication( &mut self, repository_name: String, username: String, @@ -49,18 +50,18 @@ impl BaseIO { let mut auth = IndexMap::new(); auth.insert("username".to_string(), Some(username)); auth.insert("password".to_string(), password); - self.authentications.insert(repository_name, auth); + self.authentications_mut().insert(repository_name, auth); } - pub fn write_raw(&self, messages: PhpMixed, newline: bool, verbosity: i64) { + fn write_raw(&self, messages: PhpMixed, newline: bool, verbosity: i64) { self.write(messages, newline, verbosity); } - pub fn write_error_raw(&self, messages: PhpMixed, newline: bool, verbosity: i64) { + fn write_error_raw(&self, messages: PhpMixed, newline: bool, verbosity: i64) { self.write_error(messages, newline, verbosity); } - pub(crate) fn check_and_set_authentication( + fn check_and_set_authentication( &mut self, repository_name: String, username: String, @@ -85,7 +86,7 @@ impl BaseIO { self.set_authentication(repository_name, username, password); } - pub fn load_configuration(&mut self, config: &mut Config) -> anyhow::Result<()> { + fn load_configuration(&mut self, config: &mut Config) -> anyhow::Result<()> { let bitbucket_oauth = config.get("bitbucket-oauth"); let github_oauth = config.get("github-oauth"); let gitlab_oauth = config.get("gitlab-oauth"); @@ -383,7 +384,7 @@ impl BaseIO { Ok(()) } - pub fn emergency(&mut self, message: PhpMixed, context: IndexMap<String, Box<PhpMixed>>) { + fn emergency(&mut self, message: PhpMixed, context: IndexMap<String, Box<PhpMixed>>) { self.log( PhpMixed::String(LogLevel::EMERGENCY.to_string()), message, @@ -391,7 +392,7 @@ impl BaseIO { ); } - pub fn alert(&mut self, message: PhpMixed, context: IndexMap<String, Box<PhpMixed>>) { + fn alert(&mut self, message: PhpMixed, context: IndexMap<String, Box<PhpMixed>>) { self.log( PhpMixed::String(LogLevel::ALERT.to_string()), message, @@ -399,7 +400,7 @@ impl BaseIO { ); } - pub fn critical(&mut self, message: PhpMixed, context: IndexMap<String, Box<PhpMixed>>) { + fn critical(&mut self, message: PhpMixed, context: IndexMap<String, Box<PhpMixed>>) { self.log( PhpMixed::String(LogLevel::CRITICAL.to_string()), message, @@ -407,7 +408,7 @@ impl BaseIO { ); } - pub fn error(&mut self, message: PhpMixed, context: IndexMap<String, Box<PhpMixed>>) { + fn error(&mut self, message: PhpMixed, context: IndexMap<String, Box<PhpMixed>>) { self.log( PhpMixed::String(LogLevel::ERROR.to_string()), message, @@ -415,7 +416,7 @@ impl BaseIO { ); } - pub fn warning(&mut self, message: PhpMixed, context: IndexMap<String, Box<PhpMixed>>) { + fn warning(&mut self, message: PhpMixed, context: IndexMap<String, Box<PhpMixed>>) { self.log( PhpMixed::String(LogLevel::WARNING.to_string()), message, @@ -423,7 +424,7 @@ impl BaseIO { ); } - pub fn notice(&mut self, message: PhpMixed, context: IndexMap<String, Box<PhpMixed>>) { + fn notice(&mut self, message: PhpMixed, context: IndexMap<String, Box<PhpMixed>>) { self.log( PhpMixed::String(LogLevel::NOTICE.to_string()), message, @@ -431,7 +432,7 @@ impl BaseIO { ); } - pub fn info(&mut self, message: PhpMixed, context: IndexMap<String, Box<PhpMixed>>) { + fn info(&mut self, message: PhpMixed, context: IndexMap<String, Box<PhpMixed>>) { self.log( PhpMixed::String(LogLevel::INFO.to_string()), message, @@ -439,7 +440,7 @@ impl BaseIO { ); } - pub fn debug(&mut self, message: PhpMixed, context: IndexMap<String, Box<PhpMixed>>) { + fn debug(&mut self, message: PhpMixed, context: IndexMap<String, Box<PhpMixed>>) { self.log( PhpMixed::String(LogLevel::DEBUG.to_string()), message, @@ -447,7 +448,7 @@ impl BaseIO { ); } - pub fn log( + fn log( &mut self, level: PhpMixed, message: PhpMixed, diff --git a/crates/shirabe/src/package/archiver/base_exclude_filter.rs b/crates/shirabe/src/package/archiver/base_exclude_filter.rs index f20af20..6522d79 100644 --- a/crates/shirabe/src/package/archiver/base_exclude_filter.rs +++ b/crates/shirabe/src/package/archiver/base_exclude_filter.rs @@ -3,22 +3,16 @@ use shirabe_external_packages::composer::pcre::preg::Preg; use shirabe_external_packages::symfony::component::finder::glob::Glob; -#[derive(Debug)] -pub struct BaseExcludeFilter { - pub(crate) source_path: String, - pub(crate) exclude_patterns: Vec<(String, bool, bool)>, -} - -impl BaseExcludeFilter { - pub fn new(source_path: String) -> Self { - Self { - source_path, - exclude_patterns: vec![], - } - } +pub trait BaseExcludeFilter { + fn source_path(&self) -> &str; + fn exclude_patterns(&self) -> &[(String, bool, bool)]; + fn exclude_patterns_mut(&mut self) -> &mut Vec<(String, bool, bool)>; - pub fn filter(&self, relative_path: &str, mut exclude: bool) -> bool { - for (pattern, negate, strip_leading_slash) in &self.exclude_patterns { + /// Checks the given path against all exclude patterns in this filter + /// + /// Negated patterns overwrite exclude decisions of previous filters. + fn filter(&self, relative_path: &str, mut exclude: bool) -> bool { + for (pattern, negate, strip_leading_slash) in self.exclude_patterns() { let path = if *strip_leading_slash { &relative_path[1..] } else { @@ -36,30 +30,33 @@ impl BaseExcludeFilter { exclude } - pub fn parse_lines<F>(&self, lines: Vec<String>, line_parser: F) -> Vec<(String, bool, bool)> + /// Processes a file containing exclude rules of different formats per line + fn parse_lines<F>(&self, lines: Vec<String>, line_parser: F) -> Vec<(String, bool, bool)> where F: Fn(&str) -> Option<(String, bool, bool)>, { lines .into_iter() .filter_map(|line| { - let line = line.trim(); + let line = line.trim().to_string(); if line.is_empty() || line.starts_with('#') { return None; } - line_parser(line) + line_parser(&line) }) .collect() } - pub fn generate_patterns(&self, rules: Vec<String>) -> Vec<(String, bool, bool)> { + /// Generates a set of exclude patterns for filter() from gitignore rules + fn generate_patterns(&self, rules: Vec<String>) -> Vec<(String, bool, bool)> { rules .into_iter() .map(|rule| self.generate_pattern(&rule)) .collect() } - pub fn generate_pattern(&self, rule: &str) -> (String, bool, bool) { + /// Generates an exclude pattern for filter() from a gitignore rule + fn generate_pattern(&self, rule: &str) -> (String, bool, bool) { let mut negate = false; let mut pattern = String::new(); @@ -82,6 +79,10 @@ impl BaseExcludeFilter { let glob_regex = Glob::to_regex(rule); let rule_regex = &glob_regex[2..glob_regex.len() - 2]; - (format!("{}{}(?=$|/)", pattern, rule_regex), negate, false) + ( + format!("{{{}{}(?=$|/)}}", pattern, rule_regex), + negate, + false, + ) } } diff --git a/crates/shirabe/src/package/base_package.rs b/crates/shirabe/src/package/base_package.rs index 4cbd11c..eb6cc90 100644 --- a/crates/shirabe/src/package/base_package.rs +++ b/crates/shirabe/src/package/base_package.rs @@ -58,55 +58,43 @@ pub static SUPPORTED_LINK_TYPES: LazyLock<IndexMap<&'static str, SupportedLinkTy pub static STABILITIES: LazyLock<IndexMap<&'static str, i64>> = LazyLock::new(|| { let mut m = IndexMap::new(); - m.insert("stable", BasePackage::STABILITY_STABLE); - m.insert("RC", BasePackage::STABILITY_RC); - m.insert("beta", BasePackage::STABILITY_BETA); - m.insert("alpha", BasePackage::STABILITY_ALPHA); - m.insert("dev", BasePackage::STABILITY_DEV); + m.insert("stable", 0i64); + m.insert("RC", 5i64); + m.insert("beta", 10i64); + m.insert("alpha", 15i64); + m.insert("dev", 20i64); m }); -#[derive(Debug)] -pub struct BasePackage { - pub id: i64, - pub(crate) name: String, - pub(crate) pretty_name: String, - pub(crate) repository: Option<Box<dyn RepositoryInterface>>, -} - -impl std::fmt::Display for BasePackage { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.get_unique_name()) - } -} +pub trait BasePackage: PackageInterface + std::fmt::Display { + const STABILITY_STABLE: i64 = 0; + const STABILITY_RC: i64 = 5; + const STABILITY_BETA: i64 = 10; + const STABILITY_ALPHA: i64 = 15; + const STABILITY_DEV: i64 = 20; -impl BasePackage { - pub const STABILITY_STABLE: i64 = 0; - pub const STABILITY_RC: i64 = 5; - pub const STABILITY_BETA: i64 = 10; - pub const STABILITY_ALPHA: i64 = 15; - pub const STABILITY_DEV: i64 = 20; + fn id(&self) -> i64; + fn id_mut(&mut self) -> &mut i64; + fn name(&self) -> &str; + fn name_mut(&mut self) -> &mut String; + fn pretty_name(&self) -> &str; + fn pretty_name_mut(&mut self) -> &mut String; + fn repository_opt(&self) -> Option<&dyn RepositoryInterface>; + fn set_repository_box(&mut self, repository: Box<dyn RepositoryInterface>); + fn take_repository(&mut self) -> Option<Box<dyn RepositoryInterface>>; - pub fn new(name: String) -> Self { - let pretty_name = name.clone(); - let name = name.to_lowercase(); - Self { - id: -1, - name, - pretty_name, - repository: None, - } - } + fn as_any(&self) -> &dyn std::any::Any; + fn clone_box(&self) -> Box<dyn BasePackage>; - pub fn get_name(&self) -> &str { - &self.name + fn get_name(&self) -> &str { + self.name() } - pub fn get_pretty_name(&self) -> &str { - &self.pretty_name + fn get_pretty_name(&self) -> &str { + self.pretty_name() } - pub fn get_names(&self, provides: bool) -> Vec<String> { + fn get_names(&self, provides: bool) -> Vec<String> { let mut names: IndexMap<String, bool> = IndexMap::new(); names.insert(self.get_name().to_string(), true); @@ -123,19 +111,16 @@ impl BasePackage { names.into_keys().collect() } - pub fn set_id(&mut self, id: i64) { - self.id = id; + fn set_id(&mut self, id: i64) { + *self.id_mut() = id; } - pub fn get_id(&self) -> i64 { - self.id + fn get_id(&self) -> i64 { + self.id() } - pub fn set_repository( - &mut self, - repository: Box<dyn RepositoryInterface>, - ) -> anyhow::Result<()> { - if let Some(ref existing) = self.repository { + fn set_repository(&mut self, repository: Box<dyn RepositoryInterface>) -> anyhow::Result<()> { + if let Some(existing) = self.repository_opt() { // TODO(phase-b): proper reference identity check before raising error return Err(anyhow::anyhow!(LogicException { message: format!( @@ -147,40 +132,35 @@ impl BasePackage { code: 0, })); } - self.repository = Some(repository); + self.set_repository_box(repository); Ok(()) } - pub fn get_repository(&self) -> Option<&dyn RepositoryInterface> { - self.repository.as_deref() + fn get_repository(&self) -> Option<&dyn RepositoryInterface> { + self.repository_opt() } - pub fn is_platform(&self) -> bool { - self.repository - .as_ref() + fn is_platform(&self) -> bool { + self.repository_opt() .and_then(|r| r.as_any().downcast_ref::<PlatformRepository>()) .is_some() } - pub fn get_unique_name(&self) -> String { + fn get_unique_name(&self) -> String { format!("{}-{}", self.get_name(), self.get_version()) } - pub fn equals(&self, _package: &dyn PackageInterface) -> bool { + fn equals(&self, _package: &dyn PackageInterface) -> bool { // TODO(phase-b): implement via reference identity (requires Rc/Arc) // PHP uses === which is reference equality; unwraps AliasPackage on both sides todo!("equals requires reference identity which needs Rc/Arc") } - pub fn get_pretty_string(&self) -> String { + fn get_pretty_string(&self) -> String { format!("{} {}", self.get_pretty_name(), self.get_pretty_version()) } - pub fn get_full_pretty_version( - &self, - truncate: bool, - display_mode: i64, - ) -> anyhow::Result<String> { + fn get_full_pretty_version(&self, truncate: bool, display_mode: i64) -> anyhow::Result<String> { const DISPLAY_SOURCE_REF_IF_DEV: i64 = PackageInterface::DISPLAY_SOURCE_REF_IF_DEV; const DISPLAY_SOURCE_REF: i64 = PackageInterface::DISPLAY_SOURCE_REF; const DISPLAY_DIST_REF: i64 = PackageInterface::DISPLAY_DIST_REF; @@ -224,75 +204,33 @@ impl BasePackage { Ok(format!("{} {}", self.get_pretty_version(), reference)) } - pub fn get_stability_priority(&self) -> i64 { + fn get_stability_priority(&self) -> i64 { *STABILITIES .get(self.get_stability()) .unwrap_or(&Self::STABILITY_STABLE) } - pub fn php_clone(&mut self) { - self.repository = None; - self.id = -1; + fn php_clone(&mut self) { + self.take_repository(); + *self.id_mut() = -1; } - pub fn package_name_to_regexp(allow_pattern: &str, wrap: &str) -> String { + fn package_name_to_regexp(allow_pattern: &str, wrap: &str) -> String + where + Self: Sized, + { let cleaned = preg_quote(allow_pattern, None).replace("\\*", ".*"); wrap.replace("%s", &cleaned) } - pub fn package_names_to_regexp(package_names: &[String], wrap: &str) -> String { + fn package_names_to_regexp(package_names: &[String], wrap: &str) -> String + where + Self: Sized, + { let patterns: Vec<String> = package_names .iter() .map(|name| Self::package_name_to_regexp(name, "%s")) .collect(); wrap.replace("%s", &patterns.join("|")) } - - // Methods below are defined in Package/CompletePackage subclasses in PHP. - // Called via $this polymorphism from BasePackage methods. - // TODO(phase-b): resolve via trait dispatch or field access in concrete types. - - pub fn get_provides(&self) -> IndexMap<String, Link> { - todo!("defined in Package subclass") - } - - pub fn get_replaces(&self) -> IndexMap<String, Link> { - todo!("defined in Package subclass") - } - - pub fn get_version(&self) -> &str { - todo!("defined in Package subclass") - } - - pub fn get_pretty_version(&self) -> &str { - todo!("defined in Package subclass") - } - - pub fn is_dev(&self) -> bool { - todo!("defined in Package subclass") - } - - pub fn get_source_type(&self) -> Option<&str> { - todo!("defined in Package subclass") - } - - pub fn get_source_reference(&self) -> Option<&str> { - todo!("defined in Package subclass") - } - - pub fn get_dist_reference(&self) -> Option<&str> { - todo!("defined in Package subclass") - } - - pub fn get_stability(&self) -> &str { - todo!("defined in Package subclass") - } - - pub fn as_any(&self) -> &dyn std::any::Any { - self - } - - pub fn clone_box(&self) -> Box<BasePackage> { - todo!("clone_box needs resolution in Phase B") - } } diff --git a/crates/shirabe/src/repository/vcs/vcs_driver.rs b/crates/shirabe/src/repository/vcs/vcs_driver.rs index a16930f..3195822 100644 --- a/crates/shirabe/src/repository/vcs/vcs_driver.rs +++ b/crates/shirabe/src/repository/vcs/vcs_driver.rs @@ -11,72 +11,48 @@ use crate::config::Config; use crate::downloader::transport_exception::TransportException; use crate::io::io_interface::IOInterface; use crate::json::json_file::JsonFile; +use crate::repository::vcs::vcs_driver_interface::VcsDriverInterface; use crate::util::filesystem::Filesystem; use crate::util::http::response::Response; use crate::util::http_downloader::HttpDownloader; use crate::util::process_executor::ProcessExecutor; -#[derive(Debug)] -pub struct VcsDriver { - pub(crate) url: String, - pub(crate) origin_url: String, - pub(crate) repo_config: IndexMap<String, PhpMixed>, - pub(crate) io: Box<dyn IOInterface>, - pub(crate) config: Config, - pub(crate) process: ProcessExecutor, - pub(crate) http_downloader: HttpDownloader, - pub(crate) info_cache: IndexMap<String, Option<IndexMap<String, PhpMixed>>>, - pub(crate) cache: Option<Cache>, -} - -impl VcsDriver { - pub fn new( - mut repo_config: IndexMap<String, PhpMixed>, - io: Box<dyn IOInterface>, - config: Config, - http_downloader: HttpDownloader, - process: ProcessExecutor, - ) -> Self { - if let Some(PhpMixed::String(url)) = repo_config.get("url").cloned() { - if Filesystem::is_local_path(&url) { - let platform_path = Filesystem::get_platform_path(&url); - repo_config.insert("url".to_string(), PhpMixed::String(platform_path)); - } - } - - let url = repo_config - .get("url") - .and_then(|v| v.as_string()) - .unwrap_or("") - .to_string(); - - Self { - origin_url: url.clone(), - url, - repo_config, - io, - config, - http_downloader, - process, - info_cache: IndexMap::new(), - cache: None, - } - } +// TODO(phase-b): the constructor is `final` in PHP; concrete implementations must replicate the +// initialization logic (local-path normalization etc.) from the original new() body. +pub trait VcsDriver: VcsDriverInterface { + fn url(&self) -> &str; + fn url_mut(&mut self) -> &mut String; + fn origin_url(&self) -> &str; + fn origin_url_mut(&mut self) -> &mut String; + fn repo_config(&self) -> &IndexMap<String, PhpMixed>; + fn repo_config_mut(&mut self) -> &mut IndexMap<String, PhpMixed>; + 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 http_downloader(&self) -> &HttpDownloader; + fn http_downloader_mut(&mut self) -> &mut 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>; + fn cache_mut(&mut self) -> Option<&mut Cache>; - pub(crate) fn should_cache(&self, identifier: &str) -> bool { - self.cache.is_some() && Preg::is_match("{^[a-f0-9]{40}$}iD", identifier).unwrap_or(false) + fn should_cache(&self, identifier: &str) -> bool { + self.cache().is_some() && Preg::is_match("{^[a-f0-9]{40}$}iD", identifier).unwrap_or(false) } - pub fn get_composer_information( + fn get_composer_information( &mut self, identifier: &str, ) -> anyhow::Result<Option<IndexMap<String, PhpMixed>>> { - if !self.info_cache.contains_key(identifier) { + if !self.info_cache().contains_key(identifier) { if self.should_cache(identifier) { - if let Some(res) = self.cache.as_ref().and_then(|c| c.read(identifier)) { + if let Some(res) = self.cache().and_then(|c| c.read(identifier)) { let parsed = JsonFile::parse_json(&res, None)?; - self.info_cache.insert(identifier.to_string(), parsed); - return Ok(self.info_cache.get(identifier).and_then(|v| v.clone())); + self.info_cache_mut().insert(identifier.to_string(), parsed); + return Ok(self.info_cache().get(identifier).and_then(|v| v.clone())); } } @@ -88,17 +64,18 @@ impl VcsDriver { composer_map, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES, ); - self.cache.as_ref().map(|c| c.write(identifier, &encoded)); + self.cache().map(|c| c.write(identifier, &encoded)); } } - self.info_cache.insert(identifier.to_string(), composer); + self.info_cache_mut() + .insert(identifier.to_string(), composer); } - Ok(self.info_cache.get(identifier).and_then(|v| v.clone())) + Ok(self.info_cache().get(identifier).and_then(|v| v.clone())) } - pub(crate) fn get_base_composer_information( + fn get_base_composer_information( &mut self, identifier: &str, ) -> anyhow::Result<Option<IndexMap<String, PhpMixed>>> { @@ -137,44 +114,28 @@ impl VcsDriver { Ok(Some(composer)) } - pub fn has_composer_file(&mut self, identifier: &str) -> bool { + fn has_composer_file(&mut self, identifier: &str) -> bool { match self.get_composer_information(identifier) { Ok(Some(_)) => true, _ => false, } } - pub(crate) fn get_scheme(&self) -> &str { + fn get_scheme(&self) -> &str { if extension_loaded("openssl") { return "https"; } "http" } - pub(crate) fn get_contents(&self, url: &str) -> anyhow::Result<Response, TransportException> { + fn get_contents(&self, url: &str) -> anyhow::Result<Response, TransportException> { let options = self - .repo_config + .repo_config() .get("options") .cloned() .unwrap_or(PhpMixed::Array(IndexMap::new())); - self.http_downloader.get(url, &options) + self.http_downloader().get(url, &options) } - pub fn cleanup(&self) {} - - // abstract methods to be implemented by subclasses (via VcsDriverInterface trait) - pub(crate) fn get_file_content( - &self, - file: &str, - identifier: &str, - ) -> anyhow::Result<Option<String>> { - todo!() - } - - pub(crate) fn get_change_date( - &self, - identifier: &str, - ) -> anyhow::Result<Option<chrono::DateTime<chrono::Utc>>> { - todo!() - } + fn cleanup(&self) {} } |
