diff --git a/docs/_docs/user-guide/eldritch.md b/docs/_docs/user-guide/eldritch.md index 5a78ec33a..620752c3d 100644 --- a/docs/_docs/user-guide/eldritch.md +++ b/docs/_docs/user-guide/eldritch.md @@ -797,6 +797,54 @@ The **file.mkdir** method will make a new directory at `path`. If the parent dir The **file.move** method moves a file or directory from `src` to `dst`. If the `dst` directory or file exists it will be deleted before being replaced to ensure consistency across systems. +### file.path_abs + +Returns an absolute representation of path. + +```python +file.path_abs(path: str) -> str +``` + +### file.path_base + +Returns the last element of path. + +```python +file.path_base(path: str) -> str +``` + +### file.path_clean + +Returns the shortest path name equivalent to path by purely lexical processing. + +```python +file.path_clean(path: str) -> str +``` + +### file.path_dir + +Returns all but the last element of path, typically the path's directory. + +```python +file.path_dir(path: str) -> str +``` + +### file.path_join + +Joins two path elements into a single path, separating them with an OS specific separator. + +```python +file.path_join(base: str, target: str) -> str +``` + +### file.path_separator + +Returns the operating system-specific path separator. + +```python +file.path_separator() -> str +``` + ### file.parent_dir `file.parent_dir(path: str) -> str` diff --git a/implants/lib/eldritch/stdlib/eldritch-libfile/src/fake.rs b/implants/lib/eldritch/stdlib/eldritch-libfile/src/fake.rs index 7e6fa23ab..5ab124220 100644 --- a/implants/lib/eldritch/stdlib/eldritch-libfile/src/fake.rs +++ b/implants/lib/eldritch/stdlib/eldritch-libfile/src/fake.rs @@ -436,6 +436,37 @@ impl FileLibrary for FileLibraryFake { Err("Parent path not found".to_string()) } + fn path_abs(&self, path: String) -> Result { + if path.is_empty() { + return Ok("/home/user".into()); + } + if path.starts_with("/") { + return self.path_clean(path); + } + let joined = alloc::format!("/home/user/{}", path); + self.path_clean(joined) + } + + fn path_clean(&self, path: String) -> Result { + crate::std::path_clean_impl::path_clean(path) + } + + fn path_base(&self, path: String) -> Result { + crate::std::path_base_impl::path_base(path) + } + + fn path_join(&self, base: String, target: String) -> Result { + crate::std::path_join_impl::path_join(base, target) + } + + fn path_dir(&self, path: String) -> Result { + crate::std::path_dir_impl::path_dir(path) + } + + fn path_separator(&self) -> Result { + Ok("/".to_string()) + } + fn find( &self, _path: String, diff --git a/implants/lib/eldritch/stdlib/eldritch-libfile/src/lib.rs b/implants/lib/eldritch/stdlib/eldritch-libfile/src/lib.rs index a0fe1f88f..5ee73ed52 100644 --- a/implants/lib/eldritch/stdlib/eldritch-libfile/src/lib.rs +++ b/implants/lib/eldritch/stdlib/eldritch-libfile/src/lib.rs @@ -431,6 +431,67 @@ pub trait FileLibrary { /// /// **Errors** /// - Returns an error string if the search encounters issues. + #[eldritch_method] + /// Returns an absolute representation of path. + /// + /// **Parameters** + /// - `path` (`str`): The path to convert. + /// + /// **Returns** + /// - `str`: The absolute path. + /// + /// **Errors** + /// - Returns an error string if the current working directory cannot be determined. + fn path_abs(&self, path: String) -> Result; + + #[eldritch_method] + /// Returns the shortest path name equivalent to path by purely lexical processing. + /// + /// **Parameters** + /// - `path` (`str`): The path to clean. + /// + /// **Returns** + /// - `str`: The cleaned path. + fn path_clean(&self, path: String) -> Result; + + #[eldritch_method] + /// Returns the last element of path. + /// + /// **Parameters** + /// - `path` (`str`): The path. + /// + /// **Returns** + /// - `str`: The base name of the path. + fn path_base(&self, path: String) -> Result; + + #[eldritch_method] + /// Joins two path elements into a single path, separating them with an OS specific separator. + /// + /// **Parameters** + /// - `base` (`str`): The base path. + /// - `target` (`str`): The target path. + /// + /// **Returns** + /// - `str`: The joined path. + fn path_join(&self, base: String, target: String) -> Result; + + #[eldritch_method] + /// Returns all but the last element of path, typically the path's directory. + /// + /// **Parameters** + /// - `path` (`str`): The path. + /// + /// **Returns** + /// - `str`: The directory of the path. + fn path_dir(&self, path: String) -> Result; + + #[eldritch_method] + /// Returns the operating system-specific path separator. + /// + /// **Returns** + /// - `str`: The path separator ("/" or "\"). + fn path_separator(&self) -> Result; + fn find( &self, path: String, diff --git a/implants/lib/eldritch/stdlib/eldritch-libfile/src/std/mod.rs b/implants/lib/eldritch/stdlib/eldritch-libfile/src/std/mod.rs index 28d8554e6..6d7e1e707 100644 --- a/implants/lib/eldritch/stdlib/eldritch-libfile/src/std/mod.rs +++ b/implants/lib/eldritch/stdlib/eldritch-libfile/src/std/mod.rs @@ -19,6 +19,12 @@ pub mod list_recent_impl; pub mod mkdir_impl; pub mod move_impl; pub mod parent_dir_impl; +pub mod path_abs_impl; +pub mod path_base_impl; +pub mod path_clean_impl; +pub mod path_dir_impl; +pub mod path_join_impl; +pub mod path_separator_impl; pub mod pwd_impl; pub mod read_binary_impl; pub mod read_impl; @@ -166,6 +172,30 @@ impl FileLibrary for StdFileLibrary { write_binary_impl::write_binary(path, content) } + fn path_abs(&self, path: String) -> Result { + path_abs_impl::path_abs(path) + } + + fn path_clean(&self, path: String) -> Result { + path_clean_impl::path_clean(path) + } + + fn path_base(&self, path: String) -> Result { + path_base_impl::path_base(path) + } + + fn path_join(&self, base: String, target: String) -> Result { + path_join_impl::path_join(base, target) + } + + fn path_dir(&self, path: String) -> Result { + path_dir_impl::path_dir(path) + } + + fn path_separator(&self) -> Result { + path_separator_impl::path_separator() + } + fn find( &self, path: String, diff --git a/implants/lib/eldritch/stdlib/eldritch-libfile/src/std/path_abs_impl.rs b/implants/lib/eldritch/stdlib/eldritch-libfile/src/std/path_abs_impl.rs new file mode 100644 index 000000000..60da95c94 --- /dev/null +++ b/implants/lib/eldritch/stdlib/eldritch-libfile/src/std/path_abs_impl.rs @@ -0,0 +1,20 @@ +use alloc::string::String; +use alloc::string::ToString; + +pub fn path_abs(path: String) -> Result { + if path.is_empty() { + return Ok(std::env::current_dir() + .map_err(|e| e.to_string())? + .to_string_lossy() + .into_owned()); + } + + let p = std::path::Path::new(&path); + if p.is_absolute() { + return super::path_clean_impl::path_clean(path); + } + + let cwd = std::env::current_dir().map_err(|e| e.to_string())?; + let joined = cwd.join(p); + super::path_clean_impl::path_clean(joined.to_string_lossy().into_owned()) +} diff --git a/implants/lib/eldritch/stdlib/eldritch-libfile/src/std/path_base_impl.rs b/implants/lib/eldritch/stdlib/eldritch-libfile/src/std/path_base_impl.rs new file mode 100644 index 000000000..ca2c648e2 --- /dev/null +++ b/implants/lib/eldritch/stdlib/eldritch-libfile/src/std/path_base_impl.rs @@ -0,0 +1,23 @@ +use alloc::string::String; + +pub fn path_base(path: String) -> Result { + if path.is_empty() { + return Ok(".".into()); + } + + let separator = std::path::MAIN_SEPARATOR; + let mut p = path.as_str(); + while p.len() > 1 && p.ends_with(separator) { + p = &p[..p.len() - 1]; + } + + if p == separator.to_string() { + return Ok(separator.to_string()); + } + + if let Some(idx) = p.rfind(separator) { + Ok(p[idx + 1..].into()) + } else { + Ok(p.into()) + } +} diff --git a/implants/lib/eldritch/stdlib/eldritch-libfile/src/std/path_clean_impl.rs b/implants/lib/eldritch/stdlib/eldritch-libfile/src/std/path_clean_impl.rs new file mode 100644 index 000000000..7f31faf69 --- /dev/null +++ b/implants/lib/eldritch/stdlib/eldritch-libfile/src/std/path_clean_impl.rs @@ -0,0 +1,47 @@ +use alloc::string::String; +use alloc::vec::Vec; + +pub fn path_clean(path: String) -> Result { + if path.is_empty() { + return Ok(".".into()); + } + + let separator = std::path::MAIN_SEPARATOR; + let is_absolute = path.starts_with(separator); + let mut out: Vec<&str> = Vec::new(); + + let segments = path.split(separator); + + for segment in segments { + match segment { + "" | "." => continue, + ".." => { + if let Some(last) = out.last() { + if *last != ".." { + out.pop(); + continue; + } + } + if !is_absolute { + out.push(".."); + } + } + _ => out.push(segment), + } + } + + if out.is_empty() && is_absolute { + return Ok(separator.to_string()); + } + + if out.is_empty() { + return Ok(".".into()); + } + + let joined = out.join(&separator.to_string()); + if is_absolute { + Ok(alloc::format!("{}{}", separator, joined)) + } else { + Ok(joined) + } +} diff --git a/implants/lib/eldritch/stdlib/eldritch-libfile/src/std/path_dir_impl.rs b/implants/lib/eldritch/stdlib/eldritch-libfile/src/std/path_dir_impl.rs new file mode 100644 index 000000000..cf2faa396 --- /dev/null +++ b/implants/lib/eldritch/stdlib/eldritch-libfile/src/std/path_dir_impl.rs @@ -0,0 +1,31 @@ +use alloc::string::String; + +pub fn path_dir(path: String) -> Result { + if path.is_empty() { + return Ok(".".into()); + } + + let separator = std::path::MAIN_SEPARATOR; + let mut p = path.as_str(); + while p.len() > 1 && p.ends_with(separator) { + p = &p[..p.len() - 1]; + } + + if p == separator.to_string() { + return Ok(separator.to_string()); + } + + if let Some(idx) = p.rfind(separator) { + if idx == 0 { + Ok(separator.to_string()) + } else { + let mut dir = &p[..idx]; + while dir.len() > 1 && dir.ends_with(separator) { + dir = &dir[..dir.len() - 1]; + } + Ok(dir.into()) + } + } else { + Ok(".".into()) + } +} diff --git a/implants/lib/eldritch/stdlib/eldritch-libfile/src/std/path_join_impl.rs b/implants/lib/eldritch/stdlib/eldritch-libfile/src/std/path_join_impl.rs new file mode 100644 index 000000000..cd7783aaa --- /dev/null +++ b/implants/lib/eldritch/stdlib/eldritch-libfile/src/std/path_join_impl.rs @@ -0,0 +1,17 @@ +use alloc::string::String; + +pub fn path_join(base: String, target: String) -> Result { + if base.is_empty() && target.is_empty() { + return Ok("".into()); + } + if base.is_empty() { + return super::path_clean_impl::path_clean(target); + } + if target.is_empty() { + return super::path_clean_impl::path_clean(base); + } + + let separator = std::path::MAIN_SEPARATOR; + let joined = alloc::format!("{}{}{}", base, separator, target); + super::path_clean_impl::path_clean(joined) +} diff --git a/implants/lib/eldritch/stdlib/eldritch-libfile/src/std/path_separator_impl.rs b/implants/lib/eldritch/stdlib/eldritch-libfile/src/std/path_separator_impl.rs new file mode 100644 index 000000000..bafd517ee --- /dev/null +++ b/implants/lib/eldritch/stdlib/eldritch-libfile/src/std/path_separator_impl.rs @@ -0,0 +1,5 @@ +use alloc::string::String; + +pub fn path_separator() -> Result { + Ok(std::path::MAIN_SEPARATOR.to_string()) +} diff --git a/tavern/internal/www/src/assets/eldritch-docs.json b/tavern/internal/www/src/assets/eldritch-docs.json index d14058bf2..bc2c9a436 100644 --- a/tavern/internal/www/src/assets/eldritch-docs.json +++ b/tavern/internal/www/src/assets/eldritch-docs.json @@ -339,6 +339,30 @@ "signature": "file.move(src: str, dst: str) -> None", "description": "The **file.move** method moves a file or directory from `src` to `dst`. If the `dst` directory or file exists it will be deleted before being replaced to ensure consistency across systems." }, + "file.path_abs": { + "signature": "file.path_abs(path: str) -> str", + "description": "Returns an absolute representation of path." + }, + "file.path_base": { + "signature": "file.path_base(path: str) -> str", + "description": "Returns the last element of path." + }, + "file.path_clean": { + "signature": "file.path_clean(path: str) -> str", + "description": "Returns the shortest path name equivalent to path by purely lexical processing." + }, + "file.path_dir": { + "signature": "file.path_dir(path: str) -> str", + "description": "Returns all but the last element of path, typically the path's directory." + }, + "file.path_join": { + "signature": "file.path_join(base: str, target: str) -> str", + "description": "Joins two path elements into a single path, separating them with an OS specific separator." + }, + "file.path_separator": { + "signature": "file.path_separator() -> str", + "description": "Returns the operating system-specific path separator." + }, "file.parent_dir": { "signature": "file.parent_dir(path: str) -> str", "description": "The **file.parent_dir** method returns the parent directory of a give path. Eg `/etc/ssh/sshd_config` -> `/etc/ssh`"