jpx_core/extensions/
path.rs1use 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
12pub 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(
28 runtime,
29 "path_is_absolute",
30 enabled,
31 Box::new(PathIsAbsoluteFn::new()),
32 );
33 register_if_enabled(
34 runtime,
35 "path_is_relative",
36 enabled,
37 Box::new(PathIsRelativeFn::new()),
38 );
39 register_if_enabled(runtime, "path_join", enabled, Box::new(PathJoinFn::new()));
40 register_if_enabled(runtime, "path_stem", enabled, Box::new(PathStemFn::new()));
41}
42
43defn!(PathBasenameFn, vec![arg!(string)], None);
48
49impl Function for PathBasenameFn {
50 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
51 self.signature.validate(args, ctx)?;
52 let path = args[0].as_str().unwrap();
53 let basename = std::path::Path::new(path)
54 .file_name()
55 .and_then(|s| s.to_str())
56 .unwrap_or("");
57 Ok(Value::String(basename.to_string()))
58 }
59}
60
61defn!(PathDirnameFn, vec![arg!(string)], None);
66
67impl Function for PathDirnameFn {
68 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
69 self.signature.validate(args, ctx)?;
70 let path = args[0].as_str().unwrap();
71 let dirname = std::path::Path::new(path)
72 .parent()
73 .and_then(|s| s.to_str())
74 .unwrap_or("");
75 Ok(Value::String(dirname.to_string()))
76 }
77}
78
79defn!(PathExtFn, vec![arg!(string)], None);
84
85impl Function for PathExtFn {
86 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
87 self.signature.validate(args, ctx)?;
88 let path = args[0].as_str().unwrap();
89 let ext = std::path::Path::new(path)
90 .extension()
91 .and_then(|s| s.to_str())
92 .map(|s| format!(".{}", s))
93 .unwrap_or_default();
94 Ok(Value::String(ext))
95 }
96}
97
98defn!(PathIsAbsoluteFn, vec![arg!(string)], None);
103
104impl Function for PathIsAbsoluteFn {
105 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
106 self.signature.validate(args, ctx)?;
107 let path = args[0].as_str().unwrap();
108 Ok(Value::Bool(std::path::Path::new(path).is_absolute()))
109 }
110}
111
112defn!(PathIsRelativeFn, vec![arg!(string)], None);
117
118impl Function for PathIsRelativeFn {
119 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
120 self.signature.validate(args, ctx)?;
121 let path = args[0].as_str().unwrap();
122 Ok(Value::Bool(std::path::Path::new(path).is_relative()))
123 }
124}
125
126defn!(PathJoinFn, vec![arg!(array)], None);
131
132impl Function for PathJoinFn {
133 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
134 self.signature.validate(args, ctx)?;
135 let arr = args[0].as_array().unwrap();
136 let mut path = std::path::PathBuf::new();
137 for part in arr {
138 if let Some(s) = part.as_str() {
139 path.push(s);
140 }
141 }
142 let result = path.to_str().unwrap_or("").to_string();
143 Ok(Value::String(result))
144 }
145}
146
147defn!(PathStemFn, vec![arg!(string)], None);
152
153impl Function for PathStemFn {
154 fn evaluate(&self, args: &[Value], ctx: &mut Context<'_>) -> SearchResult {
155 self.signature.validate(args, ctx)?;
156 let path = args[0].as_str().unwrap();
157 let stem = std::path::Path::new(path)
158 .file_stem()
159 .and_then(|s| s.to_str());
160 match stem {
161 Some(s) => Ok(Value::String(s.to_string())),
162 None => Ok(Value::Null),
163 }
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use crate::Runtime;
170 use serde_json::json;
171
172 fn setup_runtime() -> Runtime {
173 Runtime::builder()
174 .with_standard()
175 .with_all_extensions()
176 .build()
177 }
178
179 #[test]
180 fn test_path_basename() {
181 let runtime = setup_runtime();
182 let expr = runtime.compile("path_basename(@)").unwrap();
183 let data = json!("/path/to/file.txt");
184 let result = expr.search(&data).unwrap();
185 assert_eq!(result.as_str().unwrap(), "file.txt");
186 }
187
188 #[test]
189 fn test_path_dirname() {
190 let runtime = setup_runtime();
191 let expr = runtime.compile("path_dirname(@)").unwrap();
192 let data = json!("/path/to/file.txt");
193 let result = expr.search(&data).unwrap();
194 assert_eq!(result.as_str().unwrap(), "/path/to");
195 }
196
197 #[test]
198 fn test_path_ext() {
199 let runtime = setup_runtime();
200 let expr = runtime.compile("path_ext(@)").unwrap();
201 let data = json!("/path/to/file.txt");
202 let result = expr.search(&data).unwrap();
203 assert_eq!(result.as_str().unwrap(), ".txt");
204 }
205
206 #[test]
207 fn test_path_is_absolute() {
208 let runtime = setup_runtime();
209 let expr = runtime.compile("path_is_absolute(@)").unwrap();
210
211 assert_eq!(expr.search(&json!("/foo/bar")).unwrap(), json!(true));
212 assert_eq!(expr.search(&json!("foo/bar")).unwrap(), json!(false));
213 assert_eq!(expr.search(&json!("file.txt")).unwrap(), json!(false));
214 }
215
216 #[test]
217 fn test_path_is_relative() {
218 let runtime = setup_runtime();
219 let expr = runtime.compile("path_is_relative(@)").unwrap();
220
221 assert_eq!(expr.search(&json!("foo/bar")).unwrap(), json!(true));
222 assert_eq!(expr.search(&json!("file.txt")).unwrap(), json!(true));
223 assert_eq!(expr.search(&json!("/foo/bar")).unwrap(), json!(false));
224 }
225
226 #[test]
227 fn test_path_stem() {
228 let runtime = setup_runtime();
229 let expr = runtime.compile("path_stem(@)").unwrap();
230
231 assert_eq!(expr.search(&json!("file.txt")).unwrap(), json!("file"));
232 assert_eq!(
233 expr.search(&json!("/foo/bar.tar.gz")).unwrap(),
234 json!("bar.tar")
235 );
236 assert_eq!(expr.search(&json!("noext")).unwrap(), json!("noext"));
237 assert_eq!(expr.search(&json!("/foo/bar/")).unwrap(), json!("bar"));
238 }
239}