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 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 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}