diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-06-29 21:56:39 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-06-29 21:58:35 +0900 |
| commit | 91ccd49cf68bca199e7d41fa65a4a3b0d1368127 (patch) | |
| tree | 77206f45d192ba27f2d322d2cb1df89f0e4c6dc3 /crates/shirabe-php-shim | |
| parent | ff90c6b259f597a17d73aaf5c3a437bfdda3e68e (diff) | |
| download | php-shirabe-91ccd49cf68bca199e7d41fa65a4a3b0d1368127.tar.gz php-shirabe-91ccd49cf68bca199e7d41fa65a4a3b0d1368127.tar.zst php-shirabe-91ccd49cf68bca199e7d41fa65a4a3b0d1368127.zip | |
feat(php-shim): implement DirectoryIterator in fs shim
Give DirectoryIteratorEntry a backing path (mirroring
RecursiveIteratorFileInfo) and make directory_iterator enumerate entries,
returning Result to match PHP DirectoryIterator's UnexpectedValueException
on an unopenable directory. Wire the new Result through DumpCompletionCommand's
get_supported_shells.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe-php-shim')
| -rw-r--r-- | crates/shirabe-php-shim/Cargo.toml | 3 | ||||
| -rw-r--r-- | crates/shirabe-php-shim/src/fs.rs | 83 |
2 files changed, 74 insertions, 12 deletions
diff --git a/crates/shirabe-php-shim/Cargo.toml b/crates/shirabe-php-shim/Cargo.toml index 4226234..f8092ec 100644 --- a/crates/shirabe-php-shim/Cargo.toml +++ b/crates/shirabe-php-shim/Cargo.toml @@ -19,5 +19,8 @@ sha2.workspace = true twox-hash.workspace = true zip.workspace = true +[dev-dependencies] +tempfile.workspace = true + [lints] workspace = true diff --git a/crates/shirabe-php-shim/src/fs.rs b/crates/shirabe-php-shim/src/fs.rs index bd882c2..ba6eef6 100644 --- a/crates/shirabe-php-shim/src/fs.rs +++ b/crates/shirabe-php-shim/src/fs.rs @@ -38,20 +38,29 @@ impl FilesystemIterator { pub const CURRENT_AS_FILEINFO: i64 = 0; } -#[derive(Debug)] -pub struct DirectoryIteratorEntry; +#[derive(Debug, Clone)] +pub struct DirectoryIteratorEntry { + path: std::path::PathBuf, +} + impl DirectoryIteratorEntry { - // TODO(phase-d): DirectoryIterator is a unit struct carrying no entry data; giving it real - // behavior requires the same field redesign as RecursiveIteratorFileInfo. It has no callers, so - // it is left unimplemented. + // SplFileInfo::getBasename(): the trailing name component. pub fn get_basename(&self) -> String { - todo!() + basename(&self.path.to_string_lossy()) } + // SplFileInfo::isFile() follows symlinks. pub fn is_file(&self) -> bool { - todo!() + std::fs::metadata(&self.path) + .map(|m| m.is_file()) + .unwrap_or(false) } + // SplFileInfo::getExtension(): substring after the basename's last '.'. pub fn get_extension(&self) -> String { - todo!() + let base = self.get_basename(); + match base.rfind('.') { + Some(index) => base[index + 1..].to_string(), + None => String::new(), + } } } @@ -232,10 +241,30 @@ fn rii_walk( } } -pub fn directory_iterator(_path: &str) -> Vec<DirectoryIteratorEntry> { - // TODO(phase-d): see DirectoryIteratorEntry; the entry type carries no data yet and there are no - // callers. - todo!() +pub fn directory_iterator( + path: &str, +) -> Result<Vec<DirectoryIteratorEntry>, UnexpectedValueException> { + let base = std::path::Path::new(path); + let rd = std::fs::read_dir(base).map_err(|_| UnexpectedValueException { + message: format!( + "DirectoryIterator::__construct({}): Failed to open directory", + path + ), + code: 0, + })?; + // PHP's DirectoryIterator yields the "." and ".." entries before the real ones. + let mut entries = vec![ + DirectoryIteratorEntry { + path: base.join("."), + }, + DirectoryIteratorEntry { + path: base.join(".."), + }, + ]; + for entry in rd.flatten() { + entries.push(DirectoryIteratorEntry { path: entry.path() }); + } + Ok(entries) } /// PHP `fopen()`. Returns a stream resource, or an error mirroring PHP's `false`-on-failure @@ -1428,4 +1457,34 @@ mod tests { fn fopen_missing_file_is_err() { assert!(fopen("/nonexistent/shirabe/test/path", "r").is_err()); } + + #[test] + fn directory_iterator_lists_entries() { + let dir = tempfile::tempdir().unwrap(); + std::fs::write(dir.path().join("completion.bash"), b"").unwrap(); + std::fs::create_dir(dir.path().join("sub")).unwrap(); + + let entries = directory_iterator(dir.path().to_str().unwrap()).unwrap(); + let basenames: Vec<String> = entries.iter().map(|e| e.get_basename()).collect(); + // PHP's DirectoryIterator includes the "." and ".." entries. + assert!(basenames.contains(&".".to_string())); + assert!(basenames.contains(&"..".to_string())); + assert!(basenames.contains(&"completion.bash".to_string())); + assert!(basenames.contains(&"sub".to_string())); + + let file = entries + .iter() + .find(|e| e.get_basename() == "completion.bash") + .unwrap(); + assert!(file.is_file()); + assert_eq!(file.get_extension(), "bash"); + + let sub = entries.iter().find(|e| e.get_basename() == "sub").unwrap(); + assert!(!sub.is_file()); + } + + #[test] + fn directory_iterator_missing_dir_is_err() { + assert!(directory_iterator("/nonexistent/shirabe/test/dir").is_err()); + } } |
