//! ref: composer/src/Composer/Repository/RepositoryManager.php use indexmap::IndexMap; use shirabe_php_shim::{InvalidArgumentException, PhpMixed, json_encode}; use shirabe_semver::constraint::AnyConstraint; use crate::config::Config; use crate::event_dispatcher::EventDispatcher; use crate::io::IOInterface; use crate::package::PackageInterface; use crate::repository::FilterRepository; use crate::repository::InstalledRepositoryInterface; use crate::repository::RepositoryInterface; use crate::util::HttpDownloader; use crate::util::ProcessExecutor; #[derive(Debug)] pub struct RepositoryManager { local_repository: Option>, repositories: Vec>, repository_classes: IndexMap, io: Box, config: std::rc::Rc>, http_downloader: std::rc::Rc>, event_dispatcher: Option>>, process: std::rc::Rc>, } impl RepositoryManager { pub fn new( io: &dyn IOInterface, config: std::rc::Rc>, http_downloader: std::rc::Rc>, event_dispatcher: Option>>, process: Option>>, ) -> Self { let process = process .unwrap_or_else(|| std::rc::Rc::new(std::cell::RefCell::new(ProcessExecutor::new(io)))); Self { local_repository: None, repositories: vec![], repository_classes: IndexMap::new(), io: io.clone_box(), config, http_downloader, event_dispatcher, process, } } pub fn find_package( &self, name: &str, constraint: &AnyConstraint, ) -> Option> { for repository in &self.repositories { if let Some(package) = repository.find_package( name, crate::repository::FindPackageConstraint::Constraint(constraint.clone()), ) { return Some(package.clone_package_box()); } } None } pub fn find_packages( &self, name: &str, constraint: &AnyConstraint, ) -> Vec> { let mut packages: Vec> = vec![]; for repository in self.get_repositories() { for p in repository.find_packages( name, Some(crate::repository::FindPackageConstraint::Constraint( constraint.clone(), )), ) { packages.push(p.clone_package_box()); } } packages } pub fn add_repository(&mut self, repository: Box) { self.repositories.push(repository); } pub fn prepend_repository(&mut self, repository: Box) { self.repositories.insert(0, repository); } pub fn create_repository( &self, r#type: &str, config: IndexMap, name: Option<&str>, ) -> anyhow::Result> { if !self.repository_classes.contains_key(r#type) { return Err(InvalidArgumentException { message: format!("Repository type is not registered: {}", r#type), code: 0, } .into()); } if config.get("packagist").and_then(|v| v.as_bool()) == Some(false) { let config_json = json_encode(&PhpMixed::Array( config .iter() .map(|(k, v)| (k.clone(), Box::new(v.clone()))) .collect(), )) .unwrap_or_default(); self.io.write_error(&format!("Repository \"{}\" ({}) has a packagist key which should be in its own repository definition", name.unwrap_or(""), config_json)); } let class = self.repository_classes[r#type].clone(); let has_filter = config.contains_key("only") || config.contains_key("exclude") || config.contains_key("canonical"); let filter_config = if has_filter { Some(config.clone()) } else { None }; let mut cleaned_config = config; cleaned_config.remove("only"); cleaned_config.remove("exclude"); cleaned_config.remove("canonical"); // Phase B: implement dynamic class instantiation by class name let repository = self.create_repository_by_class(&class, cleaned_config)?; if let Some(filter_config) = filter_config { return Ok(Box::new(FilterRepository::new(repository, filter_config)?)); } Ok(repository) } fn create_repository_by_class( &self, _class: &str, _config: IndexMap, ) -> anyhow::Result> { todo!("Phase B: dynamic class instantiation by class name") } pub fn set_repository_class(&mut self, r#type: &str, class: &str) { self.repository_classes .insert(r#type.to_string(), class.to_string()); } pub fn get_repositories(&self) -> &Vec> { &self.repositories } pub fn set_local_repository(&mut self, repository: Box) { self.local_repository = Some(repository); } pub fn get_local_repository(&self) -> &dyn InstalledRepositoryInterface { self.local_repository.as_ref().unwrap().as_ref() } }