1use crate::compiler::prelude::*;
2use std::path::Path;
3
4fn split_path(path_str: &str) -> Value {
5 let path = Path::new(path_str);
6
7 let split_path: Vec<_> = path
8 .components()
9 .map(|comp| comp.as_os_str().to_string_lossy().into_owned())
10 .collect();
11 split_path.into()
12}
13
14#[derive(Clone, Copy, Debug)]
15pub struct SplitPath;
16
17impl Function for SplitPath {
18 fn identifier(&self) -> &'static str {
19 "split_path"
20 }
21
22 fn usage(&self) -> &'static str {
23 "Splits the given `path` into its constituent components, returning an array of strings. Each component represents a part of the file system path hierarchy."
24 }
25
26 fn category(&self) -> &'static str {
27 Category::String.as_ref()
28 }
29
30 fn internal_failure_reasons(&self) -> &'static [&'static str] {
31 &["`value` is not a valid string."]
32 }
33
34 fn return_kind(&self) -> u16 {
35 kind::ARRAY
36 }
37
38 fn parameters(&self) -> &'static [Parameter] {
39 const PARAMETERS: &[Parameter] = &[Parameter::required(
40 "value",
41 kind::BYTES,
42 "The path to split into components.",
43 )];
44 PARAMETERS
45 }
46
47 fn compile(
48 &self,
49 _state: &state::TypeState,
50 _ctx: &mut FunctionCompileContext,
51 arguments: ArgumentList,
52 ) -> Compiled {
53 let value = arguments.required("value");
54
55 Ok(SplitPathFn { value }.as_expr())
56 }
57
58 fn examples(&self) -> &'static [Example] {
59 &[
60 example! {
61 title: "Split path with trailing slash",
62 source: r#"split_path("/home/user/")"#,
63 result: Ok(r#"["/", "home", "user"]"#),
64 },
65 example! {
66 title: "Split path from file path",
67 source: r#"split_path("/home/user")"#,
68 result: Ok(r#"["/", "home", "user"]"#),
69 },
70 example! {
71 title: "Split path from root",
72 source: r#"split_path("/")"#,
73 result: Ok(r#"["/"]"#),
74 },
75 example! {
76 title: "Empty path returns empty array",
77 source: r#"split_path("")"#,
78 result: Ok("[]"),
79 },
80 ]
81 }
82}
83
84#[derive(Debug, Clone)]
85struct SplitPathFn {
86 value: Box<dyn Expression>,
87}
88
89impl FunctionExpression for SplitPathFn {
90 fn resolve(&self, ctx: &mut Context) -> Resolved {
91 let value = self.value.resolve(ctx)?;
92 let path_str = value.try_bytes_utf8_lossy()?;
93 Ok(split_path(&path_str))
94 }
95
96 fn type_def(&self, _: &state::TypeState) -> TypeDef {
97 TypeDef::array(Collection::any())
98 }
99}
100#[cfg(test)]
101mod tests {
102 use super::*;
103 use crate::value;
104
105 fn tdef() -> TypeDef {
106 TypeDef::array(Collection::any())
107 }
108
109 test_function![
110 split_path => SplitPath;
111
112 home_user_trailing_slash {
113 args: func_args![value: "/home/user/"],
114 want: Ok(value!(["/", "home", "user"])),
115 tdef: tdef(),
116 }
117
118 home_user_no_trailing_slash {
119 args: func_args![value: "/home/user"],
120 want: Ok(value!(["/", "home", "user"])),
121 tdef: tdef(),
122 }
123
124 root {
125 args: func_args![value: "/"],
126 want: Ok(value!(["/"])),
127 tdef: tdef(),
128 }
129
130 empty {
131 args: func_args![value: ""],
132 want: Ok(value!([])),
133 tdef: tdef(),
134 }
135
136 ];
137}