Skip to main content

jpx_core/extensions/
path.rs

1//! File path manipulation functions.
2
3use std::collections::HashSet;
4
5use serde_json::Value;
6
7use crate::functions::Function;
8use crate::interpreter::SearchResult;
9use crate::registry::register_if_enabled;
10use crate::{Context, Runtime, arg, defn};
11
12/// Register path functions filtered by the enabled set.
13pub fn register_filtered(runtime: &mut Runtime, enabled: &HashSet<&str>) {
14    register_if_enabled(
15        runtime,
16        "path_basename",
17        enabled,
18        Box::new(PathBasenameFn::new()),
19    );
20    register_if_enabled(
21        runtime,
22        "path_dirname",
23        enabled,
24        Box::new(PathDirnameFn::new()),
25    );
26    register_if_enabled(runtime, "path_ext", enabled, Box::new(PathExtFn::new()));
27    register_if_enabled(runtime, "path_join", enabled, Box::new(PathJoinFn::new()));
28}
29
30// =============================================================================
31// path_basename(string) -> string
32// =============================================================================
33
34defn!(PathBasenameFn, vec![arg!(string)], None);
35
36impl Function for PathBasenameFn {
37    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
38        self.signature.validate(args, ctx)?;
39        let path = args[0].as_str().unwrap();
40        let basename = std::path::Path::new(path)
41            .file_name()
42            .and_then(|s| s.to_str())
43            .unwrap_or("");
44        Ok(Value::String(basename.to_string()))
45    }
46}
47
48// =============================================================================
49// path_dirname(string) -> string
50// =============================================================================
51
52defn!(PathDirnameFn, vec![arg!(string)], None);
53
54impl Function for PathDirnameFn {
55    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
56        self.signature.validate(args, ctx)?;
57        let path = args[0].as_str().unwrap();
58        let dirname = std::path::Path::new(path)
59            .parent()
60            .and_then(|s| s.to_str())
61            .unwrap_or("");
62        Ok(Value::String(dirname.to_string()))
63    }
64}
65
66// =============================================================================
67// path_ext(string) -> string
68// =============================================================================
69
70defn!(PathExtFn, vec![arg!(string)], None);
71
72impl Function for PathExtFn {
73    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
74        self.signature.validate(args, ctx)?;
75        let path = args[0].as_str().unwrap();
76        let ext = std::path::Path::new(path)
77            .extension()
78            .and_then(|s| s.to_str())
79            .map(|s| format!(".{}", s))
80            .unwrap_or_default();
81        Ok(Value::String(ext))
82    }
83}
84
85// =============================================================================
86// path_join(array) -> string
87// =============================================================================
88
89defn!(PathJoinFn, vec![arg!(array)], None);
90
91impl Function for PathJoinFn {
92    fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
93        self.signature.validate(args, ctx)?;
94        let arr = args[0].as_array().unwrap();
95        let mut path = std::path::PathBuf::new();
96        for part in arr {
97            if let Some(s) = part.as_str() {
98                path.push(s);
99            }
100        }
101        let result = path.to_str().unwrap_or("").to_string();
102        Ok(Value::String(result))
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use crate::Runtime;
109    use serde_json::json;
110
111    fn setup_runtime() -> Runtime {
112        Runtime::builder()
113            .with_standard()
114            .with_all_extensions()
115            .build()
116    }
117
118    #[test]
119    fn test_path_basename() {
120        let runtime = setup_runtime();
121        let expr = runtime.compile("path_basename(@)").unwrap();
122        let data = json!("/path/to/file.txt");
123        let result = expr.search(&data).unwrap();
124        assert_eq!(result.as_str().unwrap(), "file.txt");
125    }
126
127    #[test]
128    fn test_path_dirname() {
129        let runtime = setup_runtime();
130        let expr = runtime.compile("path_dirname(@)").unwrap();
131        let data = json!("/path/to/file.txt");
132        let result = expr.search(&data).unwrap();
133        assert_eq!(result.as_str().unwrap(), "/path/to");
134    }
135
136    #[test]
137    fn test_path_ext() {
138        let runtime = setup_runtime();
139        let expr = runtime.compile("path_ext(@)").unwrap();
140        let data = json!("/path/to/file.txt");
141        let result = expr.search(&data).unwrap();
142        assert_eq!(result.as_str().unwrap(), ".txt");
143    }
144}