aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-05-15 23:19:12 +0900
committernsfisis <nsfisis@gmail.com>2026-05-16 10:00:40 +0900
commit148586086312fa32887e580f232be30a7125a1a0 (patch)
tree0ca9156d06256c605bedab02ea5c8ec5dc422ad3 /crates
parent7011204a8267137967e55b3e6016b88f2e4a600d (diff)
downloadphp-shirabe-148586086312fa32887e580f232be30a7125a1a0.tar.gz
php-shirabe-148586086312fa32887e580f232be30a7125a1a0.tar.zst
php-shirabe-148586086312fa32887e580f232be30a7125a1a0.zip
feat(port): port PharArchiver.php
Diffstat (limited to 'crates')
-rw-r--r--crates/shirabe-php-shim/src/lib.rs56
-rw-r--r--crates/shirabe/src/package/archiver/phar_archiver.rs146
2 files changed, 202 insertions, 0 deletions
diff --git a/crates/shirabe-php-shim/src/lib.rs b/crates/shirabe-php-shim/src/lib.rs
index 072d458..ffd6edf 100644
--- a/crates/shirabe-php-shim/src/lib.rs
+++ b/crates/shirabe-php-shim/src/lib.rs
@@ -240,6 +240,11 @@ pub struct Phar {
}
impl Phar {
+ pub const ZIP: i64 = 1;
+ pub const TAR: i64 = 2;
+ pub const GZ: i64 = 4096;
+ pub const BZ2: i64 = 8192;
+
pub fn new(a: String) -> Self {
todo!()
}
@@ -276,6 +281,14 @@ impl PharData {
todo!()
}
+ pub fn new_with_format(path: String, flags: i64, alias: &str, format: i64) -> Self {
+ todo!()
+ }
+
+ pub fn can_compress(algo: i64) -> bool {
+ todo!()
+ }
+
pub fn valid(&self) -> bool {
todo!()
}
@@ -296,6 +309,14 @@ impl PharData {
pub fn add_empty_dir(&self, a: &str) {
todo!()
}
+
+ pub fn build_from_iterator(&self, iter: &mut dyn Iterator<Item = std::path::PathBuf>, base: &str) {
+ todo!()
+ }
+
+ pub fn compress(&self, algo: i64) {
+ todo!()
+ }
}
#[derive(Debug)]
@@ -525,3 +546,38 @@ pub fn hash_file(algo: &str, filename: &str) -> Option<String> {
pub fn strnatcasecmp(s1: &str, s2: &str) -> i64 {
todo!()
}
+
+pub fn file_exists(path: &str) -> bool {
+ todo!()
+}
+
+pub fn unlink(path: &str) -> bool {
+ todo!()
+}
+
+pub fn file_put_contents(path: &str, data: &[u8]) -> Option<i64> {
+ todo!()
+}
+
+pub fn str_repeat(s: &str, count: usize) -> String {
+ todo!()
+}
+
+pub fn strrpos(haystack: &str, needle: &str) -> Option<usize> {
+ todo!()
+}
+
+pub fn gzcompress(data: &[u8]) -> Option<Vec<u8>> {
+ todo!()
+}
+
+pub fn bzcompress(data: &[u8]) -> Option<Vec<u8>> {
+ todo!()
+}
+
+pub struct FilesystemIterator;
+
+impl FilesystemIterator {
+ pub const KEY_AS_PATHNAME: i64 = 256;
+ pub const CURRENT_AS_FILEINFO: i64 = 0;
+}
diff --git a/crates/shirabe/src/package/archiver/phar_archiver.rs b/crates/shirabe/src/package/archiver/phar_archiver.rs
index 1ac14bf..24cd738 100644
--- a/crates/shirabe/src/package/archiver/phar_archiver.rs
+++ b/crates/shirabe/src/package/archiver/phar_archiver.rs
@@ -1 +1,147 @@
//! ref: composer/src/Composer/Package/Archiver/PharArchiver.php
+
+use indexmap::IndexMap;
+use shirabe_php_shim::{
+ bzcompress, file_exists, file_put_contents, function_exists, gzcompress, pack, str_repeat,
+ strrpos, unlink, FilesystemIterator, Phar, PharData, PhpMixed, RuntimeException,
+};
+
+use crate::package::archiver::archivable_files_filter::ArchivableFilesFilter;
+use crate::package::archiver::archivable_files_finder::ArchivableFilesFinder;
+use crate::package::archiver::archiver_interface::ArchiverInterface;
+
+fn formats() -> IndexMap<&'static str, i64> {
+ let mut m = IndexMap::new();
+ m.insert("zip", Phar::ZIP);
+ m.insert("tar", Phar::TAR);
+ m.insert("tar.gz", Phar::TAR);
+ m.insert("tar.bz2", Phar::TAR);
+ m
+}
+
+fn compress_formats() -> IndexMap<&'static str, i64> {
+ let mut m = IndexMap::new();
+ m.insert("tar.gz", Phar::GZ);
+ m.insert("tar.bz2", Phar::BZ2);
+ m
+}
+
+#[derive(Debug)]
+pub struct PharArchiver;
+
+impl ArchiverInterface for PharArchiver {
+ fn archive(
+ &self,
+ sources: String,
+ target: String,
+ format: String,
+ excludes: Vec<String>,
+ ignore_filters: bool,
+ ) -> anyhow::Result<String> {
+ let sources = shirabe_php_shim::realpath(&sources).unwrap_or(sources);
+ let formats = formats();
+ let compress_formats = compress_formats();
+
+ if file_exists(&target) {
+ unlink(&target);
+ }
+
+ let inner = (|| -> anyhow::Result<String> {
+ let pos = strrpos(&target, &format).unwrap_or(target.len());
+ let filename = target[..pos.saturating_sub(1)].to_string();
+
+ let target = if compress_formats.contains_key(format.as_str()) {
+ format!("{}.tar", filename)
+ } else {
+ target
+ };
+
+ let phar = PharData::new_with_format(
+ target.clone(),
+ FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO,
+ "",
+ *formats.get(format.as_str()).unwrap_or(&Phar::TAR),
+ );
+ let files = ArchivableFilesFinder::new(&sources, excludes, ignore_filters)?;
+ let mut files_only = ArchivableFilesFilter::new(files);
+ phar.build_from_iterator(&mut files_only, &sources);
+ files_only.add_empty_dir(&phar, &sources);
+
+ if !file_exists(&target) {
+ let target = format!("{}.{}", filename, format);
+ drop(phar);
+
+ if format == "tar" {
+ // create an empty tar file (=10240 null bytes) if the tar file is empty and PharData thus did not write it to disk
+ file_put_contents(&target, &str_repeat("\0", 10240).into_bytes());
+ } else if format == "zip" {
+ // create minimal valid ZIP file (Empty Central Directory + End of Central Directory record)
+ let eocd = pack(
+ "VvvvvVVv",
+ &[
+ PhpMixed::Int(0x06054b50), // End of central directory signature
+ PhpMixed::Int(0), // Number of this disk
+ PhpMixed::Int(0), // Disk where central directory starts
+ PhpMixed::Int(0), // Number of central directory records on this disk
+ PhpMixed::Int(0), // Total number of central directory records
+ PhpMixed::Int(0), // Size of central directory (bytes)
+ PhpMixed::Int(0), // Offset of start of central directory
+ PhpMixed::Int(0), // Comment length
+ ],
+ );
+ file_put_contents(&target, &eocd);
+ } else if format == "tar.gz" || format == "tar.bz2" {
+ let compress_algo = *compress_formats.get(format.as_str()).unwrap();
+ if !PharData::can_compress(compress_algo) {
+ return Err(RuntimeException {
+ message: format!("Can not compress to {} format", format),
+ code: 0,
+ }
+ .into());
+ }
+ if format == "tar.gz" && function_exists("gzcompress") {
+ let data = gzcompress(&str_repeat("\0", 10240).into_bytes()).unwrap_or_default();
+ file_put_contents(&target, &data);
+ } else if format == "tar.bz2" && function_exists("bzcompress") {
+ let data = bzcompress(&str_repeat("\0", 10240).into_bytes()).unwrap_or_default();
+ file_put_contents(&target, &data);
+ }
+ }
+
+ return Ok(target);
+ }
+
+ if compress_formats.contains_key(format.as_str()) {
+ let compress_algo = *compress_formats.get(format.as_str()).unwrap();
+ if !PharData::can_compress(compress_algo) {
+ return Err(RuntimeException {
+ message: format!("Can not compress to {} format", format),
+ code: 0,
+ }
+ .into());
+ }
+
+ unlink(&target);
+
+ phar.compress(compress_algo);
+
+ let target = format!("{}.{}", filename, format);
+ return Ok(target);
+ }
+
+ Ok(target)
+ })();
+
+ inner.map_err(|e| {
+ let message = format!(
+ "Could not create archive '{}' from '{}': {}",
+ target, sources, e
+ );
+ anyhow::anyhow!(RuntimeException { message, code: 0 })
+ })
+ }
+
+ fn supports(&self, format: String, _source_type: Option<String>) -> bool {
+ formats().contains_key(format.as_str())
+ }
+}