vrl/stdlib/
dirname.rs

1use crate::compiler::prelude::*;
2use std::path::Path;
3
4fn dirname(path_str: &str) -> &str {
5    if path_str == "/" {
6        return "/";
7    }
8
9    let path = Path::new(path_str);
10    match path.parent() {
11        Some(parent) => {
12            if parent.as_os_str().is_empty() {
13                "."
14            } else {
15                parent.to_str().unwrap_or(".")
16            }
17        }
18        None => ".",
19    }
20}
21
22#[derive(Clone, Copy, Debug)]
23pub struct DirName;
24
25impl Function for DirName {
26    fn identifier(&self) -> &'static str {
27        "dirname"
28    }
29
30    fn usage(&self) -> &'static str {
31        "Returns the directory component of the given `path`. This is similar to the Unix `dirname` command. The directory component is the path with the final component removed."
32    }
33
34    fn category(&self) -> &'static str {
35        Category::String.as_ref()
36    }
37
38    fn internal_failure_reasons(&self) -> &'static [&'static str] {
39        &["`value` is not a valid string."]
40    }
41
42    fn return_kind(&self) -> u16 {
43        kind::BYTES
44    }
45
46    fn parameters(&self) -> &'static [Parameter] {
47        const PARAMETERS: &[Parameter] = &[Parameter::required(
48            "value",
49            kind::BYTES,
50            "The path from which to extract the directory name.",
51        )];
52        PARAMETERS
53    }
54
55    fn compile(
56        &self,
57        _state: &state::TypeState,
58        _ctx: &mut FunctionCompileContext,
59        arguments: ArgumentList,
60    ) -> Compiled {
61        let value = arguments.required("value");
62
63        Ok(DirNameFn { value }.as_expr())
64    }
65
66    fn examples(&self) -> &'static [Example] {
67        &[
68            example! {
69                title: "Extract dirname from file path",
70                source: r#"dirname!("/usr/local/bin/vrl")"#,
71                result: Ok("\"/usr/local/bin\""),
72            },
73            example! {
74                title: "Extract dirname from file path with extension",
75                source: r#"dirname!("/home/user/file.txt")"#,
76                result: Ok("\"/home/user\""),
77            },
78            example! {
79                title: "Extract dirname from directory path",
80                source: r#"dirname!("/home/user/")"#,
81                result: Ok("\"/home\""),
82            },
83            example! {
84                title: "Root directory dirname is itself",
85                source: r#"dirname!("/")"#,
86                result: Ok("\"/\""),
87            },
88            example! {
89                title: "Relative files have current directory as dirname",
90                source: r#"dirname!("file.txt")"#,
91                result: Ok("\".\""),
92            },
93        ]
94    }
95}
96
97#[derive(Debug, Clone)]
98struct DirNameFn {
99    value: Box<dyn Expression>,
100}
101
102impl FunctionExpression for DirNameFn {
103    fn resolve(&self, ctx: &mut Context) -> Resolved {
104        let value = self.value.resolve(ctx)?;
105        let path_str_cow = value.try_bytes_utf8_lossy()?;
106        let path_str = path_str_cow.as_ref();
107        Ok(Value::from(dirname(path_str)))
108    }
109
110    fn type_def(&self, _: &state::TypeState) -> TypeDef {
111        TypeDef::bytes().fallible()
112    }
113}
114#[cfg(test)]
115mod tests {
116    use super::*;
117
118    fn tdef() -> TypeDef {
119        TypeDef::bytes().fallible()
120    }
121
122    test_function![
123        dirname => DirName;
124
125        home_user_trailing_slash {
126            args: func_args![value: "/home/user/"],
127            want: Ok("/home"),
128            tdef: tdef(),
129        }
130
131        home_user_no_trailing_slash {
132            args: func_args![value: "/home/user"],
133            want: Ok("/home"),
134            tdef: tdef(),
135        }
136
137        root {
138            args: func_args![value: "/"],
139            want: Ok("/"),
140            tdef: tdef(),
141        }
142
143        current_dir {
144            args: func_args![value: "."],
145            want: Ok("."),
146            tdef: tdef(),
147        }
148
149        parent_dir {
150            args: func_args![value: ".."],
151            want: Ok("."),
152            tdef: tdef(),
153        }
154
155        file_in_current_dir {
156            args: func_args![value: "file"],
157            want: Ok("."),
158            tdef: tdef(),
159        }
160
161        hidden_file {
162            args: func_args![value: ".file"],
163            want: Ok("."),
164            tdef: tdef(),
165        }
166
167        empty_string {
168            args: func_args![value: ""],
169            want: Ok("."),
170            tdef: tdef(),
171        }
172
173        double_extension {
174            args: func_args![value: "file.tar.gz"],
175            want: Ok("."),
176            tdef: tdef(),
177        }
178
179        path_with_extension {
180            args: func_args![value: "/home/user/file.txt"],
181            want: Ok("/home/user"),
182            tdef: tdef(),
183        }
184    ];
185}