fusabi_stdlib_ext/
path.rs

1//! Path manipulation module.
2//!
3//! Provides functions for path manipulation operations.
4
5use std::path::{Path, PathBuf};
6
7use fusabi_host::ExecutionContext;
8use fusabi_host::Value;
9
10/// Join path components.
11pub fn join(
12    args: &[Value],
13    _ctx: &ExecutionContext,
14) -> fusabi_host::Result<Value> {
15    if args.is_empty() {
16        return Err(fusabi_host::Error::host_function("path.join: no arguments"));
17    }
18
19    let mut result = PathBuf::new();
20
21    for arg in args {
22        let part = arg
23            .as_str()
24            .ok_or_else(|| fusabi_host::Error::host_function("path.join: argument must be string"))?;
25        result.push(part);
26    }
27
28    Ok(Value::String(result.to_string_lossy().into_owned()))
29}
30
31/// Get the directory name of a path.
32pub fn dirname(
33    args: &[Value],
34    _ctx: &ExecutionContext,
35) -> fusabi_host::Result<Value> {
36    let path_str = args
37        .first()
38        .and_then(|v| v.as_str())
39        .ok_or_else(|| fusabi_host::Error::host_function("path.dirname: missing path argument"))?;
40
41    let path = Path::new(path_str);
42
43    match path.parent() {
44        Some(parent) => Ok(Value::String(parent.to_string_lossy().into_owned())),
45        None => Ok(Value::Null),
46    }
47}
48
49/// Get the base name of a path.
50pub fn basename(
51    args: &[Value],
52    _ctx: &ExecutionContext,
53) -> fusabi_host::Result<Value> {
54    let path_str = args
55        .first()
56        .and_then(|v| v.as_str())
57        .ok_or_else(|| fusabi_host::Error::host_function("path.basename: missing path argument"))?;
58
59    let path = Path::new(path_str);
60
61    match path.file_name() {
62        Some(name) => Ok(Value::String(name.to_string_lossy().into_owned())),
63        None => Ok(Value::Null),
64    }
65}
66
67/// Get the file extension.
68pub fn extension(
69    args: &[Value],
70    _ctx: &ExecutionContext,
71) -> fusabi_host::Result<Value> {
72    let path_str = args
73        .first()
74        .and_then(|v| v.as_str())
75        .ok_or_else(|| fusabi_host::Error::host_function("path.extension: missing path argument"))?;
76
77    let path = Path::new(path_str);
78
79    match path.extension() {
80        Some(ext) => Ok(Value::String(ext.to_string_lossy().into_owned())),
81        None => Ok(Value::Null),
82    }
83}
84
85/// Normalize a path.
86pub fn normalize(
87    args: &[Value],
88    _ctx: &ExecutionContext,
89) -> fusabi_host::Result<Value> {
90    let path_str = args
91        .first()
92        .and_then(|v| v.as_str())
93        .ok_or_else(|| fusabi_host::Error::host_function("path.normalize: missing path argument"))?;
94
95    // Simple normalization - in real implementation would handle . and ..
96    let path = Path::new(path_str);
97    let normalized = path.components().collect::<PathBuf>();
98
99    Ok(Value::String(normalized.to_string_lossy().into_owned()))
100}
101
102/// Check if a path is absolute.
103pub fn is_absolute(
104    args: &[Value],
105    _ctx: &ExecutionContext,
106) -> fusabi_host::Result<Value> {
107    let path_str = args
108        .first()
109        .and_then(|v| v.as_str())
110        .ok_or_else(|| fusabi_host::Error::host_function("path.is_absolute: missing path argument"))?;
111
112    let path = Path::new(path_str);
113    Ok(Value::Bool(path.is_absolute()))
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119    use fusabi_host::Capabilities;
120    use fusabi_host::{Sandbox, SandboxConfig};
121    use fusabi_host::Limits;
122
123    fn create_test_ctx() -> ExecutionContext {
124        let sandbox = Sandbox::new(SandboxConfig::default()).unwrap();
125        ExecutionContext::new(1, Capabilities::none(), Limits::default(), sandbox)
126    }
127
128    #[test]
129    fn test_join() {
130        let ctx = create_test_ctx();
131        let result = join(&[
132            Value::String("/home".into()),
133            Value::String("user".into()),
134            Value::String("file.txt".into()),
135        ], &ctx).unwrap();
136
137        let path = result.as_str().unwrap();
138        assert!(path.contains("home"));
139        assert!(path.contains("user"));
140        assert!(path.contains("file.txt"));
141    }
142
143    #[test]
144    fn test_dirname() {
145        let ctx = create_test_ctx();
146        let result = dirname(&[Value::String("/home/user/file.txt".into())], &ctx).unwrap();
147        assert_eq!(result.as_str().unwrap(), "/home/user");
148    }
149
150    #[test]
151    fn test_basename() {
152        let ctx = create_test_ctx();
153        let result = basename(&[Value::String("/home/user/file.txt".into())], &ctx).unwrap();
154        assert_eq!(result.as_str().unwrap(), "file.txt");
155    }
156
157    #[test]
158    fn test_extension() {
159        let ctx = create_test_ctx();
160        let result = extension(&[Value::String("/home/user/file.txt".into())], &ctx).unwrap();
161        assert_eq!(result.as_str().unwrap(), "txt");
162    }
163
164    #[test]
165    fn test_is_absolute() {
166        let ctx = create_test_ctx();
167
168        let result = is_absolute(&[Value::String("/absolute/path".into())], &ctx).unwrap();
169        assert_eq!(result.as_bool().unwrap(), true);
170
171        let result = is_absolute(&[Value::String("relative/path".into())], &ctx).unwrap();
172        assert_eq!(result.as_bool().unwrap(), false);
173    }
174}