From a40d5170b75a793badc7dbc59e6b1983c37fa7af Mon Sep 17 00:00:00 2001 From: nsfisis Date: Thu, 11 Jun 2026 19:54:41 +0900 Subject: feat(php-shim): implement trivial PHP-compatible stub functions Co-Authored-By: Claude Opus 4.8 (1M context) --- crates/shirabe-php-shim/src/lib.rs | 222 +++++++++++++++++++++++------------- crates/shirabe/src/factory.rs | 4 +- crates/shirabe/src/util/platform.rs | 5 - 3 files changed, 142 insertions(+), 89 deletions(-) (limited to 'crates') diff --git a/crates/shirabe-php-shim/src/lib.rs b/crates/shirabe-php-shim/src/lib.rs index 7aa3c4b..9255ee7 100644 --- a/crates/shirabe-php-shim/src/lib.rs +++ b/crates/shirabe-php-shim/src/lib.rs @@ -417,19 +417,22 @@ impl std::fmt::Display for ErrorException { impl std::error::Error for ErrorException {} pub fn is_bool(_value: &PhpMixed) -> bool { - todo!() + matches!(_value, PhpMixed::Bool(_)) } pub fn is_string(_value: &PhpMixed) -> bool { - todo!() + matches!(_value, PhpMixed::String(_)) } pub fn is_int(_value: &PhpMixed) -> bool { - todo!() + matches!(_value, PhpMixed::Int(_)) } pub fn is_scalar(_value: &PhpMixed) -> bool { - todo!() + matches!( + _value, + PhpMixed::Bool(_) | PhpMixed::Int(_) | PhpMixed::Float(_) | PhpMixed::String(_) + ) } pub fn is_numeric(_value: &PhpMixed) -> bool { @@ -441,7 +444,7 @@ pub fn strtotime(_time: &str) -> Option { } pub fn strcasecmp(_s1: &str, _s2: &str) -> i64 { - todo!() + _s1.to_ascii_lowercase().cmp(&_s2.to_ascii_lowercase()) as i64 } pub fn sprintf(_format: &str, _args: &[PhpMixed]) -> String { @@ -449,11 +452,11 @@ pub fn sprintf(_format: &str, _args: &[PhpMixed]) -> String { } pub fn array_values(_array: &IndexMap) -> Vec { - todo!() + _array.values().cloned().collect() } pub fn array_keys(_array: &IndexMap) -> Vec { - todo!() + _array.keys().cloned().collect() } pub fn str_replace(_search: &str, _replace: &str, _subject: &str) -> String { @@ -477,15 +480,16 @@ pub fn spl_autoload_functions() -> Vec { } pub fn array_push(_array: &mut Vec, _value: String) -> i64 { - todo!() + _array.push(_value); + _array.len() as i64 } pub fn array_search_in_vec(_needle: &str, _haystack: &[String]) -> Option { - todo!() + _haystack.iter().position(|s| s.as_str() == _needle) } pub fn array_map_str_fn String>(_callback: F, _array: &[String]) -> Vec { - todo!() + _array.iter().map(|s| _callback(s)).collect() } pub fn is_callable(_value: &PhpMixed) -> bool { @@ -493,7 +497,7 @@ pub fn is_callable(_value: &PhpMixed) -> bool { } pub fn is_object(_value: &PhpMixed) -> bool { - todo!() + matches!(_value, PhpMixed::Object(_)) } pub fn is_a(_object_or_class: &PhpMixed, _class: &str, _allow_string: bool) -> bool { @@ -501,27 +505,27 @@ pub fn is_a(_object_or_class: &PhpMixed, _class: &str, _allow_string: bool) -> b } pub fn str_contains(_haystack: &str, _needle: &str) -> bool { - todo!() + _haystack.contains(_needle) } pub fn str_starts_with(_haystack: &str, _needle: &str) -> bool { - todo!() + _haystack.starts_with(_needle) } pub fn str_ends_with(_haystack: &str, _needle: &str) -> bool { - todo!() + _haystack.ends_with(_needle) } pub fn strpos(_haystack: &str, _needle: &str) -> Option { - todo!() + _haystack.find(_needle) } pub fn strtoupper(_s: &str) -> String { - todo!() + _s.to_ascii_uppercase() } pub fn strlen(_s: &str) -> i64 { - todo!() + _s.len() as i64 } pub fn krsort(_array: &mut IndexMap) { @@ -529,7 +533,7 @@ pub fn krsort(_array: &mut IndexMap) { } pub fn max_i64(_a: i64, _b: i64) -> i64 { - todo!() + _a.max(_b) } pub fn count_mixed(_value: &PhpMixed) -> i64 { @@ -653,7 +657,7 @@ pub fn strtr(_str: &str, _from: &str, _to: &str) -> String { } pub fn implode(_glue: &str, _pieces: &[String]) -> String { - todo!() + _pieces.join(_glue) } pub fn version_compare(_v1: &str, _v2: &str, _op: &str) -> bool { @@ -1190,7 +1194,7 @@ pub fn hash_file(_algo: &str, _filename: &str) -> Option { } pub fn filesize(_path: &str) -> Option { - todo!() + std::fs::metadata(_path).ok().map(|m| m.len() as i64) } pub fn random_int(_min: i64, _max: i64) -> i64 { @@ -1204,7 +1208,7 @@ pub fn json_encode_ex(_value: &T, _flags: i64) -> pub const JSON_INVALID_UTF8_IGNORE: i64 = 1048576; pub fn is_array(_value: &PhpMixed) -> bool { - todo!() + matches!(_value, PhpMixed::List(_) | PhpMixed::Array(_)) } pub fn strnatcasecmp(_s1: &str, _s2: &str) -> i64 { @@ -1212,7 +1216,7 @@ pub fn strnatcasecmp(_s1: &str, _s2: &str) -> i64 { } pub fn file_exists(_path: &str) -> bool { - todo!() + std::path::Path::new(_path).exists() } pub fn is_writable(_path: &str) -> bool { @@ -1220,19 +1224,21 @@ pub fn is_writable(_path: &str) -> bool { } pub fn unlink(_path: &str) -> bool { - todo!() + std::fs::remove_file(_path).is_ok() } pub fn file_put_contents(_path: &str, _data: &[u8]) -> Option { - todo!() + std::fs::write(_path, _data) + .ok() + .map(|_| _data.len() as i64) } pub fn str_repeat(_s: &str, _count: usize) -> String { - todo!() + _s.repeat(_count) } pub fn strrpos(_haystack: &str, _needle: &str) -> Option { - todo!() + _haystack.rfind(_needle) } pub fn gzcompress(_data: &[u8]) -> Option> { @@ -1244,11 +1250,13 @@ pub fn bzcompress(_data: &[u8]) -> Option> { } pub fn getcwd() -> Option { - todo!() + std::env::current_dir() + .ok() + .map(|p| p.to_string_lossy().into_owned()) } pub fn chdir(_path: &str) -> anyhow::Result<()> { - todo!() + Ok(std::env::set_current_dir(_path)?) } pub fn glob(_pattern: &str) -> Vec { @@ -1439,7 +1447,7 @@ pub fn stream_get_contents_with_max(stream: PhpMixed, max_length: Option) - } pub fn bin2hex(_data: &[u8]) -> String { - todo!() + _data.iter().map(|b| format!("{:02x}", b)).collect() } pub fn random_bytes(_length: usize) -> Vec { @@ -1447,7 +1455,7 @@ pub fn random_bytes(_length: usize) -> Vec { } pub fn is_dir(_path: &str) -> bool { - todo!() + std::path::Path::new(_path).is_dir() } pub fn file_get_contents(_path: &str) -> Option { @@ -1465,15 +1473,15 @@ pub fn file_get_contents5( } pub fn strtolower(_s: &str) -> String { - todo!() + _s.to_ascii_lowercase() } pub fn ctype_alnum(_s: &str) -> bool { - todo!() + !_s.is_empty() && _s.bytes().all(|b| b.is_ascii_alphanumeric()) } pub fn ord(_c: &str) -> i64 { - todo!() + _c.as_bytes().first().copied().unwrap_or(0) as i64 } pub fn gethostname() -> String { @@ -1503,11 +1511,15 @@ pub fn get_current_user() -> String { pub const FILE_IGNORE_NEW_LINES: i64 = 2; pub fn array_diff(_array1: &[String], _array2: &[String]) -> Vec { - todo!() + _array1 + .iter() + .filter(|&x| !_array2.contains(x)) + .cloned() + .collect() } pub fn copy(_source: &str, _dest: &str) -> bool { - todo!() + std::fs::copy(_source, _dest).is_ok() } pub fn exec( @@ -1547,11 +1559,11 @@ pub fn iterator_to_array(iter: I) -> Vec where I: IntoIterator, { - todo!() + iter.into_iter().collect() } pub fn end_arr(_array: &IndexMap) -> Option { - todo!() + _array.values().last().cloned() } pub fn fileowner(_filename: &str) -> Option { @@ -1575,7 +1587,7 @@ pub fn key(_value: PhpMixed) -> Option { } pub fn reset(_array: &[T]) -> Option { - todo!() + _array.first().cloned() } pub const OPENSSL_ALGO_SHA384: i64 = 9; @@ -1584,11 +1596,15 @@ pub fn array_intersect_key( _array1: &IndexMap, _array2: &IndexMap, ) -> IndexMap { - todo!() + _array1 + .iter() + .filter(|(k, _)| _array2.contains_key(k.as_str())) + .map(|(k, v)| (k.clone(), v.clone())) + .collect() } pub fn is_file(_path: &str) -> bool { - todo!() + std::path::Path::new(_path).is_file() } pub fn spl_object_hash(_object: &T) -> String { @@ -1607,7 +1623,9 @@ pub fn stream_context_create( } pub fn stripos(_haystack: &str, _needle: &str) -> Option { - todo!() + _haystack + .to_ascii_lowercase() + .find(_needle.to_ascii_lowercase().as_str()) } pub fn php_uname(_mode: &str) -> String { @@ -1651,7 +1669,10 @@ pub fn glob_with_flags(_pattern: &str, _flags: i64) -> Vec { } pub fn time() -> i64 { - todo!() + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs() as i64 } pub fn date(_format: &str, _timestamp: Option) -> String { @@ -1663,7 +1684,7 @@ pub fn trigger_error(_message: &str, _error_level: i64) { } pub fn sys_get_temp_dir() -> String { - todo!() + std::env::temp_dir().to_string_lossy().into_owned() } pub fn json_decode(_s: &str, _assoc: bool) -> anyhow::Result { @@ -1705,7 +1726,7 @@ pub fn array_search(_needle: &str, _haystack: &IndexMap) -> Opti } pub fn strcmp(_s1: &str, _s2: &str) -> i64 { - todo!() + _s1.cmp(_s2) as i64 } pub fn rtrim(_s: &str, _chars: Option<&str>) -> String { @@ -1713,11 +1734,13 @@ pub fn rtrim(_s: &str, _chars: Option<&str>) -> String { } pub fn rmdir(_dir: &str) -> bool { - todo!() + std::fs::remove_dir(_dir).is_ok() } pub fn is_link(_path: &str) -> bool { - todo!() + std::fs::symlink_metadata(_path) + .map(|m| m.file_type().is_symlink()) + .unwrap_or(false) } pub fn str_pad(_input: &str, _length: usize, _pad_string: &str, _pad_type: i64) -> String { @@ -1729,7 +1752,7 @@ pub const STR_PAD_RIGHT: i64 = 1; pub const STR_PAD_BOTH: i64 = 2; pub fn abs(_value: i64) -> i64 { - todo!() + _value.abs() } pub fn ucfirst(_s: &str) -> String { @@ -1741,7 +1764,7 @@ pub fn strval(_value: &PhpMixed) -> String { } pub fn usleep(_microseconds: u64) { - todo!() + std::thread::sleep(std::time::Duration::from_micros(_microseconds)); } pub fn mb_strlen(_s: &str, _encoding: &str) -> i64 { @@ -1773,7 +1796,7 @@ pub fn fstat(_stream: PhpMixed) -> PhpMixed { } pub fn getenv(_name: &str) -> Option { - todo!() + std::env::var(_name).ok() } pub fn putenv(_setting: &str) -> bool { @@ -1827,26 +1850,30 @@ pub fn count(_value: &PhpMixed) -> i64 { } pub fn array_shift(_array: &mut Vec) -> Option { - todo!() + if _array.is_empty() { + None + } else { + Some(_array.remove(0)) + } } pub fn array_pop(_array: &mut Vec) -> Option { - todo!() + _array.pop() } pub fn array_unshift(_array: &mut Vec, _value: T) { - todo!() + _array.insert(0, _value); } pub fn array_reverse(_array: &[T], _preserve_keys: bool) -> Vec { - todo!() + _array.iter().rev().cloned().collect() } pub fn array_filter(_array: &[T], _callback: F) -> Vec where F: Fn(&T) -> bool, { - todo!() + _array.iter().filter(|&x| _callback(x)).cloned().collect() } pub fn array_filter_map( @@ -1856,32 +1883,40 @@ pub fn array_filter_map( where F: Fn(&PhpMixed) -> bool, { - todo!() + _array + .iter() + .filter(|(_, v)| _callback(v.as_ref())) + .map(|(k, v)| (k.clone(), v.as_ref().clone())) + .collect() } pub fn array_all(_array: &[T], _callback: F) -> bool where F: Fn(&T) -> bool, { - todo!() + _array.iter().all(|x| _callback(x)) } pub fn array_any(_array: &[T], _callback: F) -> bool where F: Fn(&T) -> bool, { - todo!() + _array.iter().any(|x| _callback(x)) } pub fn array_reduce(_array: &[T], _callback: F, _initial: U) -> U where F: Fn(U, &T) -> U, { - todo!() + _array.iter().fold(_initial, |acc, x| _callback(acc, x)) } pub fn array_intersect(_array1: &[T], _array2: &[T]) -> Vec { - todo!() + _array1 + .iter() + .filter(|&x| _array2.contains(x)) + .cloned() + .collect() } pub fn mkdir(_pathname: &str, _mode: u32, _recursive: bool) -> bool { @@ -1889,15 +1924,17 @@ pub fn mkdir(_pathname: &str, _mode: u32, _recursive: bool) -> bool { } pub fn rename(_old_name: &str, _new_name: &str) -> bool { - todo!() + std::fs::rename(_old_name, _new_name).is_ok() } pub fn clearstatcache() { - todo!() + // Rust performs a fresh syscall for every metadata query; there is no stat + // cache to invalidate. } pub fn clearstatcache2(_clear_realpath_cache: bool, _filename: &str) { - todo!() + // Rust performs a fresh syscall for every metadata query; there is no stat + // cache to invalidate. } pub fn disk_free_space(_directory: &str) -> Option { @@ -1905,7 +1942,11 @@ pub fn disk_free_space(_directory: &str) -> Option { } pub fn filemtime(_filename: &str) -> Option { - todo!() + std::fs::metadata(_filename) + .ok() + .and_then(|m| m.modified().ok()) + .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok()) + .map(|d| d.as_secs() as i64) } /// Equivalent to PHP's __DIR__ magic constant @@ -1923,15 +1964,19 @@ pub fn array_flip(_array: &PhpMixed) -> PhpMixed { } pub fn array_flip_strings(_array: &[String]) -> IndexMap { - todo!() + _array + .iter() + .enumerate() + .map(|(i, s)| (s.clone(), PhpMixed::Int(i as i64))) + .collect() } pub fn max(_a: i64, _b: i64) -> i64 { - todo!() + _a.max(_b) } pub fn array_key_exists(_key: &str, _array: &IndexMap) -> bool { - todo!() + _array.contains_key(_key) } pub fn fgets(_handle: PhpMixed) -> Option { @@ -1955,7 +2000,7 @@ pub fn ltrim(_s: &str, _chars: Option<&str>) -> String { } pub fn floor(_value: f64) -> f64 { - todo!() + _value.floor() } pub fn chr(_value: u8) -> String { @@ -1993,7 +2038,7 @@ pub fn json_last_error() -> i64 { } pub fn sort(_array: &mut Vec) { - todo!() + _array.sort(); } pub fn sort_with_flags(_array: &mut Vec, _flags: i64) { @@ -2010,7 +2055,8 @@ pub fn usort(_array: &mut Vec, _compare: F) where F: FnMut(&T, &T) -> i64, { - todo!() + let mut compare = _compare; + _array.sort_by(|a, b| compare(a, b).cmp(&0)); } pub fn ksort(_array: &mut IndexMap) { @@ -2018,7 +2064,7 @@ pub fn ksort(_array: &mut IndexMap) { } pub fn is_null(_value: &PhpMixed) -> bool { - todo!() + matches!(_value, PhpMixed::Null) } pub fn r#eval(_code: &str) -> PhpMixed { @@ -2043,7 +2089,7 @@ pub fn array_pop_first(_array: &mut Vec) -> Option { } pub fn reset_first(_array: &[T]) -> Option { - todo!() + _array.first().cloned() } pub fn call_user_func(_callback: &str, _args: &[PhpMixed]) -> T @@ -2085,7 +2131,7 @@ pub fn array_map(_callback: F, _array: &[T]) -> Vec where F: Fn(&T) -> U, { - todo!() + _array.iter().map(|x| _callback(x)).collect() } impl Phar { @@ -2229,7 +2275,7 @@ pub fn date_default_timezone_set(_tz: &str) -> bool { } pub fn getmypid() -> i64 { - todo!() + std::process::id() as i64 } pub fn ini_set(_varname: &str, _value: &str) -> Option { @@ -2268,7 +2314,11 @@ pub fn array_filter_use_key( _array: &IndexMap, _callback: Box bool>, ) -> IndexMap { - todo!() + _array + .iter() + .filter(|(k, _)| _callback(k.as_str())) + .map(|(k, v)| (k.clone(), v.clone())) + .collect() } pub fn escapeshellcmd(_command: &str) -> String { @@ -2280,7 +2330,7 @@ pub fn system(_command: &str, _result_code: Option<&mut i64>) -> Option } pub fn array_chunk(_array: &[T], _size: i64, _preserve_keys: bool) -> Vec> { - todo!() + _array.chunks(_size as usize).map(|c| c.to_vec()).collect() } pub fn number_format( @@ -2297,15 +2347,16 @@ pub fn is_executable(_path: &str) -> bool { } pub fn gc_collect_cycles() -> i64 { - todo!() + // Rust has no cycle collector; nothing is collected. + 0 } pub fn gc_disable() { - todo!() + // Rust has no cycle collector to disable. } pub fn gc_enable() { - todo!() + // Rust has no cycle collector to enable. } pub fn addcslashes(_string: &str, _charlist: &str) -> String { @@ -2324,11 +2375,15 @@ where } pub fn end(_array: &[V]) -> Option { - todo!() + _array.last().cloned() } pub fn fileatime(_filename: &str) -> Option { - todo!() + std::fs::metadata(_filename) + .ok() + .and_then(|m| m.accessed().ok()) + .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok()) + .map(|d| d.as_secs() as i64) } pub fn fread(_handle: PhpMixed, _length: i64) -> Option { @@ -2351,11 +2406,14 @@ pub fn array_diff_key( _array1: IndexMap, _array2: &IndexMap, ) -> IndexMap { - todo!() + _array1 + .into_iter() + .filter(|(k, _)| !_array2.contains_key(k.as_str())) + .collect() } pub fn min(_a: i64, _b: i64) -> i64 { - todo!() + _a.min(_b) } pub fn escapeshellarg(_arg: &str) -> String { diff --git a/crates/shirabe/src/factory.rs b/crates/shirabe/src/factory.rs index ab0d389..6d788f4 100644 --- a/crates/shirabe/src/factory.rs +++ b/crates/shirabe/src/factory.rs @@ -10,7 +10,7 @@ use shirabe_php_shim::{ RuntimeException, UnexpectedValueException, ZipArchive, array_keys, array_replace_recursive, class_exists, dirname, extension_loaded, file_exists, file_get_contents, file_put_contents, implode, in_array, is_array, is_dir, is_file, is_string, json_decode, mkdir, pathinfo, - realpath, str_replace, strpos, strtr, substr, trim, + realpath, rename, str_replace, strpos, strtr, substr, trim, }; use crate::autoload::AutoloadGenerator; @@ -182,7 +182,7 @@ impl Factory { { let from = format!("{}/cache", home); let to = format!("{}/Library/Caches/composer", user_dir); - let _ = Silencer::call(|| Ok::(Platform::rename(&from, &to))); + let _ = Silencer::call(|| Ok::(rename(&from, &to))); } return Ok(format!("{}/Library/Caches/composer", user_dir)); diff --git a/crates/shirabe/src/util/platform.rs b/crates/shirabe/src/util/platform.rs index 6a0c2d0..08f5007 100644 --- a/crates/shirabe/src/util/platform.rs +++ b/crates/shirabe/src/util/platform.rs @@ -425,9 +425,4 @@ impl Platform { "/dev/null".to_string() } - - /// PHP: rename($from, $to) — wrap the std rename so callers can use Platform::rename. - pub fn rename(from: &str, to: &str) -> bool { - std::fs::rename(from, to).is_ok() - } } -- cgit v1.3.1