vrl/stdlib/
basename.rs

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