aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-05-15 23:12:39 +0900
committernsfisis <nsfisis@gmail.com>2026-05-16 10:00:39 +0900
commit4ac20be1cb04443d193d9e99a0d1037563ee0d13 (patch)
treed006e9f75668de519aab3348fea4750d67b5badd
parent52c28540c4cc55290e24ff62bc7d36fc65e18e84 (diff)
downloadphp-shirabe-4ac20be1cb04443d193d9e99a0d1037563ee0d13.tar.gz
php-shirabe-4ac20be1cb04443d193d9e99a0d1037563ee0d13.tar.zst
php-shirabe-4ac20be1cb04443d193d9e99a0d1037563ee0d13.zip
feat(port): port ArtifactRepository.php
-rw-r--r--crates/shirabe-php-shim/src/lib.rs4
-rw-r--r--crates/shirabe/src/package/loader/json_loader.rs2
-rw-r--r--crates/shirabe/src/package/loader/loader_interface.rs2
-rw-r--r--crates/shirabe/src/repository/artifact_repository.rs201
4 files changed, 207 insertions, 2 deletions
diff --git a/crates/shirabe-php-shim/src/lib.rs b/crates/shirabe-php-shim/src/lib.rs
index ad408ec..4a8d035 100644
--- a/crates/shirabe-php-shim/src/lib.rs
+++ b/crates/shirabe-php-shim/src/lib.rs
@@ -517,3 +517,7 @@ pub fn ob_get_clean() -> Option<String> {
pub fn html_entity_decode(s: &str) -> String {
todo!()
}
+
+pub fn hash_file(algo: &str, filename: &str) -> Option<String> {
+ todo!()
+}
diff --git a/crates/shirabe/src/package/loader/json_loader.rs b/crates/shirabe/src/package/loader/json_loader.rs
index 4e7faf9..5e5829a 100644
--- a/crates/shirabe/src/package/loader/json_loader.rs
+++ b/crates/shirabe/src/package/loader/json_loader.rs
@@ -31,6 +31,6 @@ impl JsonLoader {
}
};
- Ok(self.loader.load(config, None))
+ self.loader.load(config, None)
}
}
diff --git a/crates/shirabe/src/package/loader/loader_interface.rs b/crates/shirabe/src/package/loader/loader_interface.rs
index b95345e..9f1e6da 100644
--- a/crates/shirabe/src/package/loader/loader_interface.rs
+++ b/crates/shirabe/src/package/loader/loader_interface.rs
@@ -5,5 +5,5 @@ use shirabe_php_shim::PhpMixed;
use crate::package::base_package::BasePackage;
pub trait LoaderInterface {
- fn load(&self, config: IndexMap<String, PhpMixed>, class: Option<String>) -> Box<BasePackage>;
+ fn load(&self, config: IndexMap<String, PhpMixed>, class: Option<String>) -> anyhow::Result<Box<BasePackage>>;
}
diff --git a/crates/shirabe/src/repository/artifact_repository.rs b/crates/shirabe/src/repository/artifact_repository.rs
index a0d7757..988e456 100644
--- a/crates/shirabe/src/repository/artifact_repository.rs
+++ b/crates/shirabe/src/repository/artifact_repository.rs
@@ -1 +1,202 @@
//! ref: composer/src/Composer/Repository/ArtifactRepository.php
+
+use std::path::Path;
+
+use indexmap::IndexMap;
+use shirabe_php_shim::{extension_loaded, hash_file, PhpMixed, RuntimeException, UnexpectedValueException};
+
+use crate::io::io_interface::IOInterface;
+use crate::json::json_file::JsonFile;
+use crate::package::base_package::BasePackage;
+use crate::package::loader::array_loader::ArrayLoader;
+use crate::package::loader::loader_interface::LoaderInterface;
+use crate::repository::array_repository::ArrayRepository;
+use crate::repository::configurable_repository_interface::ConfigurableRepositoryInterface;
+use crate::util::platform::Platform;
+use crate::util::tar::Tar;
+use crate::util::zip::Zip;
+
+pub struct ArtifactRepository {
+ inner: ArrayRepository,
+ pub(crate) loader: Box<dyn LoaderInterface>,
+ pub(crate) lookup: String,
+ pub(crate) repo_config: IndexMap<String, PhpMixed>,
+ io: Box<dyn IOInterface>,
+}
+
+impl std::fmt::Debug for ArtifactRepository {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("ArtifactRepository")
+ .field("lookup", &self.lookup)
+ .field("repo_config", &self.repo_config)
+ .finish()
+ }
+}
+
+impl ArtifactRepository {
+ pub fn new(repo_config: IndexMap<String, PhpMixed>, io: Box<dyn IOInterface>) -> anyhow::Result<Self> {
+ if !extension_loaded("zip") {
+ return Err(RuntimeException {
+ message: "The artifact repository requires PHP's zip extension".to_string(),
+ code: 0,
+ }
+ .into());
+ }
+
+ let url = repo_config["url"].as_string().unwrap_or("").to_string();
+ let lookup = Platform::expand_path(&url);
+ Ok(Self {
+ inner: ArrayRepository::new(),
+ loader: Box::new(ArrayLoader::new()),
+ lookup,
+ repo_config,
+ io,
+ })
+ }
+
+ pub fn get_repo_name(&self) -> String {
+ format!("artifact repo ({})", self.lookup)
+ }
+
+ fn initialize(&mut self) -> anyhow::Result<()> {
+ self.inner.initialize()?;
+ let lookup = self.lookup.clone();
+ self.scan_directory(&lookup)
+ }
+
+ fn scan_directory(&mut self, path: &str) -> anyhow::Result<()> {
+ let entries = std::fs::read_dir(path)?;
+ for entry in entries {
+ let entry = entry?;
+ let file_path = entry.path();
+
+ if file_path.is_symlink() {
+ let resolved = std::fs::canonicalize(&file_path)?;
+ if resolved.is_dir() {
+ self.scan_directory(resolved.to_str().unwrap_or(""))?;
+ continue;
+ }
+ }
+
+ if file_path.is_dir() {
+ self.scan_directory(file_path.to_str().unwrap_or(""))?;
+ continue;
+ }
+
+ if !file_path.is_file() {
+ continue;
+ }
+
+ let ext = file_path
+ .extension()
+ .and_then(|e| e.to_str())
+ .unwrap_or("")
+ .to_lowercase();
+ if !matches!(ext.as_str(), "zip" | "tar" | "gz" | "tgz") {
+ continue;
+ }
+
+ let package = self.get_composer_information(&file_path)?;
+ let basename = file_path.file_name().and_then(|n| n.to_str()).unwrap_or("");
+ match package {
+ None => {
+ self.io.write_error(
+ &format!("File <comment>{}</comment> doesn't seem to hold a package", basename),
+ true,
+ IOInterface::VERBOSE,
+ );
+ }
+ Some(package) => {
+ self.io.write_error(
+ &format!(
+ "Found package <info>{}</info> (<comment>{}</comment>) in file <info>{}</info>",
+ package.get_name(),
+ package.get_pretty_version(),
+ basename,
+ ),
+ true,
+ IOInterface::VERBOSE,
+ );
+ self.inner.add_package(package);
+ }
+ }
+ }
+ Ok(())
+ }
+
+ fn get_composer_information(&self, file: &Path) -> anyhow::Result<Option<Box<BasePackage>>> {
+ let mut json: Option<String> = None;
+ let file_extension = file
+ .extension()
+ .and_then(|e| e.to_str())
+ .unwrap_or("")
+ .to_lowercase();
+
+ let file_type: &str;
+ if matches!(file_extension.as_str(), "gz" | "tar" | "tgz") {
+ file_type = "tar";
+ } else if file_extension == "zip" {
+ file_type = "zip";
+ } else {
+ return Err(RuntimeException {
+ message: format!(
+ "Files with \"{}\" extensions aren't supported. Only ZIP and TAR/TAR.GZ/TGZ archives are supported.",
+ file_extension
+ ),
+ code: 0,
+ }
+ .into());
+ }
+
+ let pathname = file.to_str().unwrap_or("");
+ let get_result = if file_type == "tar" {
+ Tar::get_composer_json(pathname)
+ } else {
+ Zip::get_composer_json(pathname)
+ };
+ match get_result {
+ Ok(j) => json = j,
+ Err(exception) => {
+ self.io.write(
+ &format!("Failed loading package {}: {}", pathname, exception),
+ false,
+ IOInterface::VERBOSE,
+ );
+ }
+ }
+
+ if json.is_none() {
+ return Ok(None);
+ }
+
+ let mut package = JsonFile::parse_json(&json.unwrap(), &format!("{}#composer.json", pathname))?;
+ let url_normalized = pathname.replace('\\', '/');
+ let real_path = file
+ .canonicalize()
+ .ok()
+ .and_then(|p| p.to_str().map(|s| s.to_string()))
+ .unwrap_or_default();
+ let shasum = hash_file("sha1", &real_path).unwrap_or_default();
+
+ let mut dist = IndexMap::new();
+ dist.insert("type".to_string(), Box::new(PhpMixed::String(file_type.to_string())));
+ dist.insert("url".to_string(), Box::new(PhpMixed::String(url_normalized)));
+ dist.insert("shasum".to_string(), Box::new(PhpMixed::String(shasum)));
+ package.insert("dist".to_string(), Box::new(PhpMixed::Array(dist)));
+
+ match self.loader.load(package, None) {
+ Ok(package) => Ok(Some(package)),
+ Err(exception) => Err(UnexpectedValueException {
+ message: format!("Failed loading package in {}: {}", pathname, exception),
+ code: 0,
+ }
+ .into()),
+ }
+ }
+}
+
+impl ConfigurableRepositoryInterface for ArtifactRepository {
+ fn get_repo_config(&self) -> IndexMap<String, PhpMixed> {
+ self.repo_config.clone()
+ }
+}