#[cfg(test)]
mod tests;
use std::fs;
use std::path::Path;
pub fn php_scandir(dirname: &str) -> Result<Vec<String>, String> {
let path = Path::new(dirname);
if !path.exists() {
return Err(format!("Directory does not exist: {dirname}"));
}
if !path.is_dir() {
return Err(format!("Not a directory: {dirname}"));
}
let entries = fs::read_dir(path).map_err(|e| format!("Failed to read directory: {e}"))?;
let mut files = Vec::new();
for entry in entries {
let entry = entry.map_err(|e| format!("Failed to read entry: {e}"))?;
if let Some(name) = entry.file_name().to_str() {
files.push(name.to_string());
}
}
files.sort();
Ok(files)
}
pub fn php_file_exists(filename: &str) -> bool {
Path::new(filename).exists()
}
pub fn php_is_dir(path: &str) -> bool {
Path::new(path).is_dir()
}
pub fn php_is_file(path: &str) -> bool {
Path::new(path).is_file()
}
pub fn php_filesize(filename: &str) -> Result<u64, String> {
let metadata =
fs::metadata(filename).map_err(|e| format!("Failed to get file metadata: {e}"))?;
Ok(metadata.len())
}
pub fn php_file_get_contents(filename: &str) -> Result<String, String> {
fs::read_to_string(filename).map_err(|e| format!("Failed to read file: {e}"))
}
pub fn php_file_put_contents(filename: &str, data: &str) -> Result<usize, String> {
fs::write(filename, data).map_err(|e| format!("Failed to write file: {e}"))?;
Ok(data.len())
}
pub fn php_file_append_contents(filename: &str, data: &str) -> Result<usize, String> {
use std::io::Write;
let mut file = fs::OpenOptions::new()
.create(true)
.append(true)
.open(filename)
.map_err(|e| format!("Failed to open file for append: {e}"))?;
file.write_all(data.as_bytes())
.map_err(|e| format!("Failed to append to file: {e}"))?;
Ok(data.len())
}
pub fn php_mkdir(pathname: &str, recursive: bool) -> Result<(), String> {
let path = Path::new(pathname);
if recursive {
fs::create_dir_all(path).map_err(|e| format!("Failed to create directory: {e}"))
} else {
fs::create_dir(path).map_err(|e| format!("Failed to create directory: {e}"))
}
}
pub fn php_rmdir(dirname: &str) -> Result<(), String> {
let path = Path::new(dirname);
if !path.is_dir() {
return Err(format!("Not a directory: {dirname}"));
}
fs::remove_dir(path).map_err(|e| format!("Failed to remove directory: {e}"))
}
pub fn php_copy(source: &str, dest: &str) -> Result<u64, String> {
fs::copy(source, dest).map_err(|e| format!("Failed to copy file: {e}"))
}
pub fn php_rename(oldname: &str, newname: &str) -> Result<(), String> {
fs::rename(oldname, newname).map_err(|e| format!("Failed to rename: {e}"))
}
pub fn php_unlink(filename: &str) -> Result<(), String> {
fs::remove_file(filename).map_err(|e| format!("Failed to delete file: {e}"))
}
pub fn php_realpath(path: &str) -> Result<String, String> {
fs::canonicalize(path)
.map(|p| p.to_string_lossy().to_string())
.map_err(|e| format!("Failed to resolve path: {e}"))
}
pub fn php_basename(path: &str) -> String {
Path::new(path)
.file_name()
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_default()
}
pub fn php_dirname(path: &str) -> String {
Path::new(path)
.parent()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_else(|| ".".to_string())
}
pub fn php_pathinfo_extension(path: &str) -> Option<String> {
Path::new(path)
.extension()
.map(|e| e.to_string_lossy().to_string())
}
pub fn php_is_readable(path: &str) -> bool {
fs::metadata(path).is_ok()
}
pub fn php_is_writable(path: &str) -> bool {
let p = Path::new(path);
if p.exists() {
fs::OpenOptions::new().write(true).open(path).is_ok()
} else {
p.parent()
.map(|_parent| {
fs::OpenOptions::new()
.write(true)
.create(true)
.open(path)
.map(|_| {
let _ = fs::remove_file(path);
true
})
.unwrap_or(false)
})
.unwrap_or(false)
}
}
pub fn php_tempnam(dir: &str, prefix: &str) -> Result<String, String> {
let dir_path = Path::new(dir);
if !dir_path.is_dir() {
return Err(format!("Not a directory: {dir}"));
}
let filename = format!(
"{prefix}{}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0)
);
let full_path = dir_path.join(filename);
fs::File::create(&full_path).map_err(|e| format!("Failed to create temp file: {e}"))?;
Ok(full_path.to_string_lossy().to_string())
}
pub fn php_glob(pattern: &str) -> Result<Vec<String>, String> {
let path = Path::new(pattern);
let dir = match path.parent() {
Some(p) if p.as_os_str().is_empty() => Path::new("."),
Some(p) => p,
None => Path::new("."),
};
let file_pattern = path
.file_name()
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_else(|| "*".to_string());
if !dir.is_dir() {
return Ok(Vec::new());
}
let entries = fs::read_dir(dir).map_err(|e| format!("Failed to read directory: {e}"))?;
let mut matches = Vec::new();
for entry in entries {
let entry = entry.map_err(|e| format!("Failed to read entry: {e}"))?;
let name = entry.file_name().to_string_lossy().to_string();
if glob_match(&file_pattern, &name) {
matches.push(
dir.join(&name).to_string_lossy().to_string(),
);
}
}
matches.sort();
Ok(matches)
}
fn glob_match(pattern: &str, text: &str) -> bool {
let pat: Vec<char> = pattern.chars().collect();
let txt: Vec<char> = text.chars().collect();
glob_match_impl(&pat, &txt, 0, 0)
}
fn glob_match_impl(pat: &[char], txt: &[char], pi: usize, ti: usize) -> bool {
if pi == pat.len() && ti == txt.len() {
return true;
}
if pi == pat.len() {
return false;
}
if pat[pi] == '*' {
for i in ti..=txt.len() {
if glob_match_impl(pat, txt, pi + 1, i) {
return true;
}
}
return false;
}
if ti == txt.len() {
return false;
}
if pat[pi] == '?' || pat[pi] == txt[ti] {
return glob_match_impl(pat, txt, pi + 1, ti + 1);
}
false
}