//! ref: composer/src/Composer/Package/Package.php use std::rc::Rc; use chrono::{DateTime, Utc}; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::Preg; use shirabe_external_packages::composer::util::ComposerMirror; use shirabe_php_shim::{E_USER_DEPRECATED, LogicException, PhpMixed, strpos, trigger_error}; use crate::package::BasePackage; use crate::package::Link; use crate::package::PackageInterface; use crate::package::version::VersionParser; use crate::repository::RepositoryInterfaceHandle; use crate::repository::RepositoryInterfaceWeakHandle; /// Mirror entry, e.g. `['url' => 'https://...', 'preferred' => true]`. #[derive(Debug, Clone)] pub struct Mirror { pub url: String, pub preferred: bool, } /// Core package definitions that are needed to resolve dependencies and install packages #[derive(Debug)] pub struct Package { id: i64, name: String, pretty_name: String, /// Back-reference to the owning repository. `Weak` breaks the repository -> packages cycle. repository: Option, pub(crate) r#type: Option, pub(crate) target_dir: Option, /// `'source'` | `'dist'` | `null` pub(crate) installation_source: Option, pub(crate) source_type: Option, pub(crate) source_url: Option, pub(crate) source_reference: Option, pub(crate) source_mirrors: Option>, pub(crate) dist_type: Option, pub(crate) dist_url: Option, pub(crate) dist_reference: Option, pub(crate) dist_sha1_checksum: Option, pub(crate) dist_mirrors: Option>, pub(crate) version: String, pub(crate) pretty_version: String, pub(crate) release_date: Option>, pub(crate) extra: IndexMap, pub(crate) binaries: Vec, pub(crate) dev: bool, /// `'stable'` | `'RC'` | `'beta'` | `'alpha'` | `'dev'` pub(crate) stability: String, pub(crate) notification_url: Option, pub(crate) requires: IndexMap, pub(crate) conflicts: IndexMap, pub(crate) provides: IndexMap, pub(crate) replaces: IndexMap, pub(crate) dev_requires: IndexMap, pub(crate) suggests: IndexMap, pub(crate) autoload: IndexMap, pub(crate) dev_autoload: IndexMap, pub(crate) include_paths: Vec, pub(crate) is_default_branch: bool, pub(crate) transport_options: IndexMap, pub(crate) php_ext: Option>, } impl Package { /// Creates a new in memory package. pub fn new(name: String, version: String, pretty_version: String) -> Self { let stability = VersionParser::parse_stability(&version).to_string(); let dev = stability == "dev"; Self { id: -1, name: name.to_lowercase(), pretty_name: name, repository: None, r#type: None, target_dir: None, installation_source: None, source_type: None, source_url: None, source_reference: None, source_mirrors: None, dist_type: None, dist_url: None, dist_reference: None, dist_sha1_checksum: None, dist_mirrors: None, version, pretty_version, release_date: None, extra: IndexMap::new(), binaries: Vec::new(), dev, stability, notification_url: None, requires: IndexMap::new(), conflicts: IndexMap::new(), provides: IndexMap::new(), replaces: IndexMap::new(), dev_requires: IndexMap::new(), suggests: IndexMap::new(), autoload: IndexMap::new(), dev_autoload: IndexMap::new(), include_paths: Vec::new(), is_default_branch: false, transport_options: IndexMap::new(), php_ext: None, } } pub fn is_dev(&self) -> bool { self.dev } pub fn set_type(&mut self, r#type: String) { self.r#type = Some(r#type); } pub fn get_type(&self) -> String { self.r#type .clone() .filter(|s| !s.is_empty()) .unwrap_or_else(|| "library".to_string()) } pub fn get_stability(&self) -> &str { &self.stability } pub fn set_target_dir(&mut self, target_dir: Option) { self.target_dir = target_dir; } pub fn get_target_dir(&self) -> Option { let target_dir = self.target_dir.as_ref()?; let replaced = Preg::replace( "{ (?:^|[\\\\/]+) \\.\\.? (?:[\\\\/]+|$) (?:\\.\\.? (?:[\\\\/]+|$) )*}x", "/", target_dir, ) .unwrap_or_else(|_| target_dir.clone()); Some(replaced.trim_start_matches('/').to_string()) } pub fn set_extra(&mut self, extra: IndexMap) { self.extra = extra; } pub fn get_extra(&self) -> &IndexMap { &self.extra } pub fn set_binaries(&mut self, binaries: Vec) { self.binaries = binaries; } pub fn get_binaries(&self) -> &Vec { &self.binaries } pub fn set_installation_source(&mut self, r#type: Option) { self.installation_source = r#type; } pub fn get_installation_source(&self) -> Option<&str> { self.installation_source.as_deref() } pub fn set_source_type(&mut self, r#type: Option) { self.source_type = r#type; } pub fn get_source_type(&self) -> Option<&str> { self.source_type.as_deref() } pub fn set_source_url(&mut self, url: Option) { self.source_url = url; } pub fn get_source_url(&self) -> Option<&str> { self.source_url.as_deref() } pub fn set_source_reference(&mut self, reference: Option) { self.source_reference = reference; } pub fn get_source_reference(&self) -> Option<&str> { self.source_reference.as_deref() } pub fn set_source_mirrors(&mut self, mirrors: Option>) { self.source_mirrors = mirrors; } pub fn get_source_mirrors(&self) -> Option<&Vec> { self.source_mirrors.as_ref() } pub fn get_source_urls(&self) -> Vec { self.get_urls( self.source_url.as_deref(), self.source_mirrors.as_ref(), self.source_reference.as_deref(), self.source_type.as_deref(), "source", ) } pub fn set_dist_type(&mut self, r#type: Option) { self.dist_type = match r#type.as_deref() { Some("") => None, _ => r#type, }; } pub fn get_dist_type(&self) -> Option<&str> { self.dist_type.as_deref() } pub fn set_dist_url(&mut self, url: Option) { self.dist_url = match url.as_deref() { Some("") => None, _ => url, }; } pub fn get_dist_url(&self) -> Option<&str> { self.dist_url.as_deref() } pub fn set_dist_reference(&mut self, reference: Option) { self.dist_reference = reference; } pub fn get_dist_reference(&self) -> Option<&str> { self.dist_reference.as_deref() } pub fn set_dist_sha1_checksum(&mut self, sha1checksum: Option) { self.dist_sha1_checksum = sha1checksum; } pub fn get_dist_sha1_checksum(&self) -> Option<&str> { self.dist_sha1_checksum.as_deref() } pub fn set_dist_mirrors(&mut self, mirrors: Option>) { self.dist_mirrors = mirrors; } pub fn get_dist_mirrors(&self) -> Option<&Vec> { self.dist_mirrors.as_ref() } pub fn get_dist_urls(&self) -> Vec { self.get_urls( self.dist_url.as_deref(), self.dist_mirrors.as_ref(), self.dist_reference.as_deref(), self.dist_type.as_deref(), "dist", ) } pub fn get_transport_options(&self) -> &IndexMap { &self.transport_options } pub fn set_transport_options(&mut self, options: IndexMap) { self.transport_options = options; } pub fn get_version(&self) -> &str { &self.version } pub fn get_pretty_version(&self) -> &str { &self.pretty_version } pub fn set_release_date(&mut self, release_date: Option>) { self.release_date = release_date; } pub fn get_release_date(&self) -> Option<&chrono::DateTime> { self.release_date.as_ref() } /// Set the required packages pub fn set_requires(&mut self, mut requires: IndexMap) { if requires.contains_key("0") { requires = self.convert_links_to_map(requires, "setRequires"); } self.requires = requires; } pub fn get_requires(&self) -> &IndexMap { &self.requires } pub fn set_conflicts(&mut self, mut conflicts: IndexMap) { if conflicts.contains_key("0") { conflicts = self.convert_links_to_map(conflicts, "setConflicts"); } self.conflicts = conflicts; } pub fn get_conflicts(&self) -> &IndexMap { &self.conflicts } pub fn set_provides(&mut self, mut provides: IndexMap) { if provides.contains_key("0") { provides = self.convert_links_to_map(provides, "setProvides"); } self.provides = provides; } pub fn get_provides(&self) -> &IndexMap { &self.provides } pub fn set_replaces(&mut self, mut replaces: IndexMap) { if replaces.contains_key("0") { replaces = self.convert_links_to_map(replaces, "setReplaces"); } self.replaces = replaces; } pub fn get_replaces(&self) -> &IndexMap { &self.replaces } pub fn set_dev_requires(&mut self, mut dev_requires: IndexMap) { if dev_requires.contains_key("0") { dev_requires = self.convert_links_to_map(dev_requires, "setDevRequires"); } self.dev_requires = dev_requires; } pub fn get_dev_requires(&self) -> &IndexMap { &self.dev_requires } pub fn set_suggests(&mut self, suggests: IndexMap) { self.suggests = suggests; } pub fn get_suggests(&self) -> &IndexMap { &self.suggests } pub fn set_autoload(&mut self, autoload: IndexMap) { self.autoload = autoload; } pub fn get_autoload(&self) -> &IndexMap { &self.autoload } pub fn set_dev_autoload(&mut self, dev_autoload: IndexMap) { self.dev_autoload = dev_autoload; } pub fn get_dev_autoload(&self) -> &IndexMap { &self.dev_autoload } pub fn set_include_paths(&mut self, include_paths: Vec) { self.include_paths = include_paths; } pub fn get_include_paths(&self) -> &Vec { &self.include_paths } pub fn set_php_ext(&mut self, php_ext: Option>) { self.php_ext = php_ext; } pub fn get_php_ext(&self) -> Option<&IndexMap> { self.php_ext.as_ref() } pub fn set_notification_url(&mut self, notification_url: String) { self.notification_url = Some(notification_url); } pub fn get_notification_url(&self) -> Option<&str> { self.notification_url.as_deref() } pub fn set_is_default_branch(&mut self, default_branch: bool) { self.is_default_branch = default_branch; } pub fn is_default_branch(&self) -> bool { self.is_default_branch } pub fn set_source_dist_references(&mut self, reference: String) { self.set_source_reference(Some(reference.clone())); // only bitbucket, github and gitlab have auto generated dist URLs that easily allow replacing the reference in the dist URL // TODO generalize this a bit for self-managed/on-prem versions? Some kind of replace token in dist urls which allow this? if self.get_dist_url().is_some() && Preg::is_match( "{^https?://(?:(?:www\\.)?bitbucket\\.org|(api\\.)?github\\.com|(?:www\\.)?gitlab\\.com)/}i", self.get_dist_url().unwrap_or(""), ) .unwrap_or(false) { self.set_dist_reference(Some(reference.clone())); self.set_dist_url(Some( Preg::replace( "{(?<=/|sha=)[a-f0-9]{40}(?=/|$)}i", &reference, self.get_dist_url().unwrap_or(""), ) .unwrap_or_default(), )); } else if self.get_dist_reference().is_some() { // update the dist reference if there was one, but if none was provided ignore it self.set_dist_reference(Some(reference)); } } /// Replaces current version and pretty version with passed values. /// It also sets stability. pub fn replace_version(&mut self, version: String, pretty_version: String) { self.version = version; self.pretty_version = pretty_version; self.stability = VersionParser::parse_stability(&self.version).to_string(); self.dev = self.stability == "dev"; } fn get_urls( &self, url: Option<&str>, mirrors: Option<&Vec>, r#ref: Option<&str>, r#type: Option<&str>, url_type: &str, ) -> Vec { let url = match url { Some(u) if !u.is_empty() => u, _ => return Vec::new(), }; let url = if url_type == "dist" && strpos(url, "%").is_some() { ComposerMirror::process_url( url, &self.name, &self.version, r#ref, r#type, Some(self.pretty_version.as_str()), ) } else { url.to_string() }; let mut urls: Vec = vec![url.clone()]; if let Some(mirrors) = mirrors { for mirror in mirrors { let mirror_url = if url_type == "dist" { ComposerMirror::process_url( &mirror.url, &self.name, &self.version, r#ref, r#type, Some(self.pretty_version.as_str()), ) } else if url_type == "source" && r#type == Some("git") { ComposerMirror::process_git_url( &mirror.url, &self.name, &url, r#type.unwrap_or(""), ) } else if url_type == "source" && r#type == Some("hg") { ComposerMirror::process_hg_url( &mirror.url, &self.name, &url, r#type.unwrap_or(""), ) } else { continue; }; if !urls.contains(&mirror_url) { if mirror.preferred { urls.insert(0, mirror_url); } else { urls.push(mirror_url); } } } } urls } fn convert_links_to_map( &self, links: IndexMap, source: &str, ) -> IndexMap { trigger_error( &format!( "Package::{} must be called with a map of lowercased package name => Link object, got a indexed array, this is deprecated and you should fix your usage.", source ), E_USER_DEPRECATED, ); let mut new_links: IndexMap = IndexMap::new(); for (_k, link) in links { new_links.insert(link.get_target().to_string(), link); } new_links } } impl BasePackage for Package { fn id(&self) -> i64 { self.id } fn id_mut(&mut self) -> &mut i64 { &mut self.id } fn name(&self) -> &str { &self.name } fn name_mut(&mut self) -> &mut String { &mut self.name } fn pretty_name(&self) -> &str { &self.pretty_name } fn pretty_name_mut(&mut self) -> &mut String { &mut self.pretty_name } fn repository_opt(&self) -> Option { self.repository .as_ref() .and_then(|w| w.upgrade()) .map(RepositoryInterfaceHandle::from_rc) } fn set_repository_box(&mut self, repository: RepositoryInterfaceHandle) { self.repository = Some(repository.downgrade()); } fn take_repository(&mut self) -> Option { self.repository .take() .and_then(|w| w.upgrade()) .map(RepositoryInterfaceHandle::from_rc) } } impl std::fmt::Display for Package { fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { todo!() } } impl PackageInterface for Package { fn get_name(&self) -> &str { todo!() } fn get_pretty_name(&self) -> &str { todo!() } fn get_names(&self, _provides: bool) -> Vec { todo!() } fn set_id(&mut self, _id: i64) { todo!() } fn get_id(&self) -> i64 { todo!() } fn is_dev(&self) -> bool { todo!() } fn get_type(&self) -> &str { todo!() } fn get_target_dir(&self) -> Option<&str> { todo!() } fn get_extra(&self) -> IndexMap { todo!() } fn set_installation_source(&mut self, _type: Option) { todo!() } fn get_installation_source(&self) -> Option<&str> { todo!() } fn get_source_type(&self) -> Option<&str> { todo!() } fn get_source_url(&self) -> Option<&str> { todo!() } fn get_source_urls(&self) -> Vec { todo!() } fn get_source_reference(&self) -> Option<&str> { todo!() } fn get_source_mirrors(&self) -> Option>> { todo!() } fn set_source_mirrors(&mut self, _mirrors: Option>>) { todo!() } fn get_dist_type(&self) -> Option<&str> { todo!() } fn get_dist_url(&self) -> Option<&str> { todo!() } fn get_dist_urls(&self) -> Vec { todo!() } fn get_dist_reference(&self) -> Option<&str> { todo!() } fn get_dist_sha1_checksum(&self) -> Option<&str> { todo!() } fn get_dist_mirrors(&self) -> Option>> { todo!() } fn set_dist_mirrors(&mut self, _mirrors: Option>>) { todo!() } fn get_version(&self) -> &str { todo!() } fn get_pretty_version(&self) -> &str { todo!() } fn get_full_pretty_version(&self, _truncate: bool, _display_mode: i64) -> String { todo!() } fn get_release_date(&self) -> Option> { todo!() } fn get_stability(&self) -> &str { todo!() } fn get_requires(&self) -> IndexMap { todo!() } fn get_conflicts(&self) -> IndexMap { todo!() } fn get_provides(&self) -> IndexMap { todo!() } fn get_replaces(&self) -> IndexMap { todo!() } fn get_dev_requires(&self) -> IndexMap { todo!() } fn get_suggests(&self) -> IndexMap { todo!() } fn get_autoload(&self) -> IndexMap { todo!() } fn get_dev_autoload(&self) -> IndexMap { todo!() } fn get_include_paths(&self) -> Vec { todo!() } fn get_php_ext(&self) -> Option> { todo!() } fn set_repository(&mut self, repository: RepositoryInterfaceHandle) -> anyhow::Result<()> { if let Some(existing) = self.repository.as_ref().and_then(|w| w.upgrade()) { if !Rc::ptr_eq(&existing, repository.as_rc()) { return Err(LogicException { message: "A package can only be added to one repository".to_string(), code: 0, } .into()); } } self.repository = Some(repository.downgrade()); Ok(()) } fn get_repository(&self) -> Option { self.repository .as_ref() .and_then(|w| w.upgrade()) .map(RepositoryInterfaceHandle::from_rc) } fn get_binaries(&self) -> Vec { todo!() } fn get_unique_name(&self) -> String { todo!() } fn get_notification_url(&self) -> Option<&str> { todo!() } fn get_pretty_string(&self) -> String { todo!() } fn is_default_branch(&self) -> bool { todo!() } fn get_transport_options(&self) -> IndexMap { todo!() } fn set_transport_options(&mut self, _options: IndexMap) { todo!() } fn set_source_reference(&mut self, _reference: Option) { todo!() } fn set_dist_url(&mut self, _url: Option) { todo!() } fn set_dist_type(&mut self, _type: Option) { todo!() } fn set_dist_reference(&mut self, _reference: Option) { todo!() } fn set_source_dist_references(&mut self, _reference: &str) { todo!() } }