nu_command/path/
split.rs

1use super::PathSubcommandArguments;
2use nu_engine::command_prelude::*;
3use nu_protocol::engine::StateWorkingSet;
4use std::path::{Component, Path};
5
6struct Arguments;
7
8impl PathSubcommandArguments for Arguments {}
9
10#[derive(Clone)]
11pub struct PathSplit;
12
13impl Command for PathSplit {
14    fn name(&self) -> &str {
15        "path split"
16    }
17
18    fn signature(&self) -> Signature {
19        Signature::build("path split")
20            .input_output_types(vec![
21                (Type::String, Type::List(Box::new(Type::String))),
22                (
23                    Type::List(Box::new(Type::String)),
24                    Type::List(Box::new(Type::List(Box::new(Type::String)))),
25                ),
26            ])
27            .category(Category::Path)
28    }
29
30    fn description(&self) -> &str {
31        "Split a path into a list based on the system's path separator."
32    }
33
34    fn is_const(&self) -> bool {
35        true
36    }
37
38    fn run(
39        &self,
40        engine_state: &EngineState,
41        _stack: &mut Stack,
42        call: &Call,
43        input: PipelineData,
44    ) -> Result<PipelineData, ShellError> {
45        let head = call.head;
46        let args = Arguments;
47
48        // This doesn't match explicit nulls
49        if matches!(input, PipelineData::Empty) {
50            return Err(ShellError::PipelineEmpty { dst_span: head });
51        }
52        input.map(
53            move |value| super::operate(&split, &args, value, head),
54            engine_state.signals(),
55        )
56    }
57
58    fn run_const(
59        &self,
60        working_set: &StateWorkingSet,
61        call: &Call,
62        input: PipelineData,
63    ) -> Result<PipelineData, ShellError> {
64        let head = call.head;
65        let args = Arguments;
66
67        // This doesn't match explicit nulls
68        if matches!(input, PipelineData::Empty) {
69            return Err(ShellError::PipelineEmpty { dst_span: head });
70        }
71        input.map(
72            move |value| super::operate(&split, &args, value, head),
73            working_set.permanent().signals(),
74        )
75    }
76
77    #[cfg(windows)]
78    fn examples(&self) -> Vec<Example> {
79        vec![
80            Example {
81                description: "Split a path into parts",
82                example: r"'C:\Users\viking\spam.txt' | path split",
83                result: Some(Value::list(
84                    vec![
85                        Value::test_string(r"C:\"),
86                        Value::test_string("Users"),
87                        Value::test_string("viking"),
88                        Value::test_string("spam.txt"),
89                    ],
90                    Span::test_data(),
91                )),
92            },
93            Example {
94                description: "Split paths in list into parts",
95                example: r"[ C:\Users\viking\spam.txt C:\Users\viking\eggs.txt ] | path split",
96                result: Some(Value::list(
97                    vec![
98                        Value::test_list(vec![
99                            Value::test_string(r"C:\"),
100                            Value::test_string("Users"),
101                            Value::test_string("viking"),
102                            Value::test_string("spam.txt"),
103                        ]),
104                        Value::test_list(vec![
105                            Value::test_string(r"C:\"),
106                            Value::test_string("Users"),
107                            Value::test_string("viking"),
108                            Value::test_string("eggs.txt"),
109                        ]),
110                    ],
111                    Span::test_data(),
112                )),
113            },
114        ]
115    }
116
117    #[cfg(not(windows))]
118    fn examples(&self) -> Vec<Example> {
119        vec![
120            Example {
121                description: "Split a path into parts",
122                example: r"'/home/viking/spam.txt' | path split",
123                result: Some(Value::list(
124                    vec![
125                        Value::test_string("/"),
126                        Value::test_string("home"),
127                        Value::test_string("viking"),
128                        Value::test_string("spam.txt"),
129                    ],
130                    Span::test_data(),
131                )),
132            },
133            Example {
134                description: "Split paths in list into parts",
135                example: r"[ /home/viking/spam.txt /home/viking/eggs.txt ] | path split",
136                result: Some(Value::list(
137                    vec![
138                        Value::test_list(vec![
139                            Value::test_string("/"),
140                            Value::test_string("home"),
141                            Value::test_string("viking"),
142                            Value::test_string("spam.txt"),
143                        ]),
144                        Value::test_list(vec![
145                            Value::test_string("/"),
146                            Value::test_string("home"),
147                            Value::test_string("viking"),
148                            Value::test_string("eggs.txt"),
149                        ]),
150                    ],
151                    Span::test_data(),
152                )),
153            },
154        ]
155    }
156}
157
158fn split(path: &Path, span: Span, _: &Arguments) -> Value {
159    Value::list(
160        path.components()
161            .filter_map(|comp| {
162                let comp = process_component(comp);
163                comp.map(|s| Value::string(s, span))
164            })
165            .collect(),
166        span,
167    )
168}
169
170#[cfg(windows)]
171fn process_component(comp: Component) -> Option<String> {
172    match comp {
173        Component::RootDir => None,
174        Component::Prefix(_) => {
175            let mut s = comp.as_os_str().to_string_lossy().to_string();
176            s.push('\\');
177            Some(s)
178        }
179        comp => Some(comp.as_os_str().to_string_lossy().to_string()),
180    }
181}
182
183#[cfg(not(windows))]
184fn process_component(comp: Component) -> Option<String> {
185    Some(comp.as_os_str().to_string_lossy().to_string())
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191
192    #[test]
193    fn test_examples() {
194        use crate::test_examples;
195
196        test_examples(PathSplit {})
197    }
198}