From fcef25f6ef36287a4984ffdaab39df84f5aceeba Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sun, 21 Jun 2026 15:49:24 +0900 Subject: refactor(php-shim): split lib.rs --- crates/shirabe-php-shim/src/fs.rs | 492 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 492 insertions(+) create mode 100644 crates/shirabe-php-shim/src/fs.rs (limited to 'crates/shirabe-php-shim/src/fs.rs') diff --git a/crates/shirabe-php-shim/src/fs.rs b/crates/shirabe-php-shim/src/fs.rs new file mode 100644 index 0000000..df7d3b3 --- /dev/null +++ b/crates/shirabe-php-shim/src/fs.rs @@ -0,0 +1,492 @@ +use crate::PhpMixed; +use crate::PhpResource; +use crate::UnexpectedValueException; +use indexmap::IndexMap; + +pub const PHP_EOL: &str = "\n"; + +pub const FILE_APPEND: i64 = 8; + +pub const STDIN: PhpResource = PhpResource::Stdin; + +pub const PATHINFO_FILENAME: i64 = 64; +pub const PATHINFO_EXTENSION: i64 = 4; +pub const PATHINFO_DIRNAME: i64 = 1; +pub const PATHINFO_BASENAME: i64 = 2; + +pub const PATH_SEPARATOR: &str = ":"; +pub const DIRECTORY_SEPARATOR: &str = "/"; + +pub const FILE_IGNORE_NEW_LINES: i64 = 2; + +pub const SEEK_SET: i64 = 0; +pub const SEEK_CUR: i64 = 1; +pub const SEEK_END: i64 = 2; + +pub const SKIP_DOTS: i64 = 4096; +pub const CHILD_FIRST: i64 = 16; +pub const SELF_FIRST: i64 = 0; + +pub struct FilesystemIterator; + +impl FilesystemIterator { + pub const KEY_AS_PATHNAME: i64 = 256; + pub const CURRENT_AS_FILEINFO: i64 = 0; +} + +#[derive(Debug)] +pub struct DirectoryIteratorEntry; +impl DirectoryIteratorEntry { + pub fn get_basename(&self) -> String { + todo!() + } + pub fn is_file(&self) -> bool { + todo!() + } + pub fn get_extension(&self) -> String { + todo!() + } +} + +#[derive(Debug)] +pub struct RecursiveDirectoryIterator; + +impl RecursiveDirectoryIterator { + pub const SKIP_DOTS: i64 = 4096; + pub const FOLLOW_SYMLINKS: i64 = 512; +} + +#[derive(Debug)] +pub struct RecursiveIteratorIterator; + +impl RecursiveIteratorIterator { + pub const SELF_FIRST: i64 = 0; + pub const CHILD_FIRST: i64 = 16; + + pub fn get_sub_pathname(&self) -> String { + todo!() + } +} + +impl IntoIterator for &RecursiveIteratorIterator { + type Item = RecursiveIteratorFileInfo; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + todo!() + } +} + +#[derive(Debug)] +pub struct RecursiveIteratorFileInfo; + +impl RecursiveIteratorFileInfo { + pub fn is_dir(&self) -> bool { + todo!() + } + + pub fn is_file(&self) -> bool { + todo!() + } + + pub fn is_link(&self) -> bool { + todo!() + } + + pub fn get_pathname(&self) -> String { + todo!() + } + + pub fn get_size(&self) -> i64 { + todo!() + } +} + +pub fn recursive_directory_iterator( + _path: impl AsRef, + _flags: i64, +) -> Result { + todo!() +} + +pub fn recursive_iterator_iterator( + _iter: RecursiveDirectoryIterator, + _mode: i64, +) -> RecursiveIteratorIterator { + todo!() +} + +pub fn directory_iterator(_path: &str) -> Vec { + todo!() +} + +pub fn fopen(_file: &str, _mode: &str) -> PhpMixed { + todo!() +} + +pub fn fwrite(_file: PhpMixed, _data: &str, _length: i64) -> Option { + todo!() +} + +pub fn fread(_handle: PhpMixed, _length: i64) -> Option { + todo!() +} + +pub fn feof(_stream: PhpMixed) -> bool { + todo!() +} + +pub fn fclose(_file: PhpMixed) { + todo!() +} + +pub fn fgets(_handle: PhpMixed) -> Option { + todo!() +} + +pub fn fgetc(_resource: &PhpResource) -> Option { + todo!() +} + +pub fn ftell(_resource: &PhpResource) -> i64 { + todo!() +} + +pub fn fseek(_stream: PhpMixed, _offset: i64) -> i64 { + todo!() +} + +pub fn rewind(_stream: PhpMixed) -> bool { + todo!() +} + +pub fn fstat(_stream: PhpResource) -> PhpMixed { + todo!() +} + +pub fn lstat(_filename: &str) -> Option> { + todo!() +} + +/// PHP `ftell()` over a PhpMixed stream resource. (`ftell` itself is already defined for the +/// `PhpResource`-typed stream API used elsewhere.) +pub fn ftell_stream(_stream: &PhpMixed) -> i64 { + todo!() +} + +pub fn fseek3(_stream: PhpMixed, _offset: i64, _whence: i64) -> i64 { + todo!() +} + +pub fn touch(_path: &str) -> bool { + todo!() +} + +pub fn fflush_resource(resource: &PhpResource) { + use std::io::Write; + match resource { + PhpResource::Stdin => {} + PhpResource::Stdout => { + let _ = std::io::stdout().flush(); + } + PhpResource::Stderr => { + let _ = std::io::stderr().flush(); + } + PhpResource::File(file) => { + let _ = file.borrow_mut().flush(); + } + } +} + +pub fn fwrite_resource(resource: &PhpResource, data: &str) { + use std::io::Write; + let bytes = data.as_bytes(); + match resource { + PhpResource::Stdin => {} + PhpResource::Stdout => { + let _ = std::io::stdout().write_all(bytes); + } + PhpResource::Stderr => { + let _ = std::io::stderr().write_all(bytes); + } + PhpResource::File(file) => { + let _ = file.borrow_mut().write_all(bytes); + } + } +} + +pub fn touch2(_path: &str, _mtime: i64) -> bool { + todo!() +} + +pub fn touch3(_path: &str, _mtime: i64, _atime: i64) -> bool { + todo!() +} + +pub fn chmod(_path: &str, _mode: u32) -> bool { + todo!() +} + +pub fn fileperms(_path: &str) -> i64 { + todo!() +} + +pub fn filesize(path: impl AsRef) -> Option { + std::fs::metadata(path).ok().map(|m| m.len() as i64) +} + +pub fn file_exists(path: impl AsRef) -> bool { + path.as_ref().exists() +} + +// TODO(phase-c): PHP's is_writable() resolves to access(2) with W_OK, honoring the effective +// user/group and ACLs. This std-only approximation only inspects the permission bits, so it can +// diverge for files the current user does not own. Refine with a syscall (libc/rustix) crate later. +pub fn is_writable(_path: &str) -> bool { + match std::fs::metadata(_path) { + Ok(meta) => !meta.permissions().readonly(), + Err(_) => false, + } +} + +pub fn is_readable(_path: &str) -> bool { + let path = std::path::Path::new(_path); + match std::fs::metadata(path) { + Ok(meta) => { + if meta.is_dir() { + std::fs::read_dir(path).is_ok() + } else { + std::fs::File::open(path).is_ok() + } + } + Err(_) => false, + } +} + +pub fn is_executable(_path: &str) -> bool { + todo!() +} + +pub fn is_file(path: impl AsRef) -> bool { + path.as_ref().is_file() +} + +pub fn is_link(path: impl AsRef) -> bool { + std::fs::symlink_metadata(path) + .map(|m| m.file_type().is_symlink()) + .unwrap_or(false) +} + +pub fn is_dir(path: impl AsRef) -> bool { + path.as_ref().is_dir() +} + +pub fn fileatime(_filename: &str) -> Option { + 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 filemtime(_filename: &str) -> Option { + 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) +} + +pub fn fileowner(_filename: &str) -> Option { + todo!() +} + +pub fn unlink(path: impl AsRef) -> bool { + std::fs::remove_file(path).is_ok() +} + +pub fn unlink_silent(_path: &str) -> bool { + todo!() +} + +pub fn file_put_contents(_path: &str, _data: &[u8]) -> Option { + std::fs::write(_path, _data) + .ok() + .map(|_| _data.len() as i64) +} + +pub fn file_put_contents3(_filename: &str, _data: &str, _flags: i64) -> Option { + todo!() +} + +pub fn file_get_contents(_path: &str) -> Option { + std::fs::read(_path) + .ok() + .map(|bytes| String::from_utf8_lossy(&bytes).into_owned()) +} + +pub fn file_get_contents5( + _path: &str, + _use_include_path: bool, + _context: PhpMixed, + _offset: i64, + _length: Option, +) -> Option { + todo!() +} + +pub fn getcwd() -> Option { + std::env::current_dir() + .ok() + .map(|p| p.to_string_lossy().into_owned()) +} + +pub fn chdir(_path: &str) -> anyhow::Result<()> { + Ok(std::env::set_current_dir(_path)?) +} + +pub fn glob(_pattern: &str) -> Vec { + todo!() +} + +pub fn file(_filename: &str, _flags: i64) -> Option> { + todo!() +} + +pub fn umask() -> u32 { + todo!() +} + +pub fn mkdir(_pathname: &str, _mode: u32, _recursive: bool) -> bool { + todo!() +} + +pub fn rmdir(dir: impl AsRef) -> bool { + std::fs::remove_dir(dir).is_ok() +} + +pub fn rename( + old_name: impl AsRef, + new_name: impl AsRef, +) -> bool { + std::fs::rename(old_name, new_name).is_ok() +} + +pub fn copy(_source: &str, _dest: &str) -> bool { + std::fs::copy(_source, _dest).is_ok() +} + +pub fn ftruncate(_stream: &PhpMixed, _size: i64) -> bool { + todo!() +} + +pub fn symlink(_target: &str, _link: &str) -> bool { + todo!() +} + +pub fn sys_get_temp_dir() -> String { + std::env::temp_dir().to_string_lossy().into_owned() +} + +pub fn tempnam(_dir: &str, _prefix: &str) -> Option { + todo!() +} + +pub fn opendir(_path: &str) -> Option { + todo!() +} + +pub fn pathinfo(path: PhpMixed, option: i64) -> PhpMixed { + let path = path.as_string().unwrap_or(""); + let component = match option { + PATHINFO_DIRNAME => dirname(path), + PATHINFO_BASENAME => basename(path), + PATHINFO_EXTENSION => { + let base = basename(path); + match base.rfind('.') { + Some(index) => base[index + 1..].to_string(), + None => String::new(), + } + } + PATHINFO_FILENAME => { + let base = basename(path); + match base.rfind('.') { + Some(index) => base[..index].to_string(), + None => base, + } + } + _ => unreachable!("pathinfo called with an unsupported single-component option"), + }; + PhpMixed::String(component) +} + +// TODO(phase-c): takes &Path and returns Option +pub fn realpath(path: &str) -> Option { + std::path::Path::new(path) + .canonicalize() + .ok() + .and_then(|p| p.to_str().map(ToOwned::to_owned)) +} + +pub fn dirname(path: &str) -> String { + if path.is_empty() { + return String::new(); + } + match std::path::Path::new(path).parent() { + // No parent: the root itself, or a path made up solely of slashes. + None => "/".to_string(), + // Path::parent yields an empty path where PHP's dirname returns ".". + Some(parent) if parent.as_os_str().is_empty() => ".".to_string(), + Some(parent) => parent.to_str().expect("input was valid UTF-8").to_string(), + } +} + +pub fn dirname_levels(path: &str, levels: i64) -> String { + let mut result = path.to_string(); + for _ in 0..levels { + result = dirname(&result); + } + result +} + +pub fn basename(path: &str) -> String { + // PHP basename(): the trailing name component, after stripping trailing directory separators. + let trimmed = path.trim_end_matches(['/', '\\']); + match trimmed.rfind(['/', '\\']) { + Some(index) => trimmed[index + 1..].to_string(), + None => trimmed.to_string(), + } +} + +pub fn basename_with_suffix(path: &str, suffix: &str) -> String { + let base = basename(path); + // PHP strips the suffix only when it is a proper trailing part of the name, + // never when it equals the whole basename. + if base != suffix && base.ends_with(suffix) { + base[..base.len() - suffix.len()].to_string() + } else { + base + } +} + +pub fn clearstatcache() { + // 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) { + // Rust performs a fresh syscall for every metadata query; there is no stat + // cache to invalidate. +} + +pub fn disk_free_space(_directory: &str) -> Option { + todo!() +} + +pub const GLOB_MARK: i64 = 8; +pub const GLOB_ONLYDIR: i64 = 1024; +pub const GLOB_BRACE: i64 = 4096; + +pub fn glob_with_flags(_pattern: &str, _flags: i64) -> Vec { + todo!() +} -- cgit v1.3.1