1use super::PathSubcommandArguments;
2use nu_engine::command_prelude::*;
3use nu_protocol::engine::StateWorkingSet;
4use std::path::Path;
5
6struct Arguments {
7 replace: Option<Spanned<String>>,
8 num_levels: Option<i64>,
9}
10
11impl PathSubcommandArguments for Arguments {}
12
13#[derive(Clone)]
14pub struct PathDirname;
15
16impl Command for PathDirname {
17 fn name(&self) -> &str {
18 "path dirname"
19 }
20
21 fn signature(&self) -> Signature {
22 Signature::build("path dirname")
23 .input_output_types(vec![
24 (Type::String, Type::String),
25 (
26 Type::List(Box::new(Type::String)),
27 Type::List(Box::new(Type::String)),
28 ),
29 ])
30 .named(
31 "replace",
32 SyntaxShape::String,
33 "Return original path with dirname replaced by this string",
34 Some('r'),
35 )
36 .named(
37 "num-levels",
38 SyntaxShape::Int,
39 "Number of directories to walk up",
40 Some('n'),
41 )
42 .category(Category::Path)
43 }
44
45 fn description(&self) -> &str {
46 "Get the parent directory of a path."
47 }
48
49 fn is_const(&self) -> bool {
50 true
51 }
52
53 fn run(
54 &self,
55 engine_state: &EngineState,
56 stack: &mut Stack,
57 call: &Call,
58 input: PipelineData,
59 ) -> Result<PipelineData, ShellError> {
60 let head = call.head;
61 let args = Arguments {
62 replace: call.get_flag(engine_state, stack, "replace")?,
63 num_levels: call.get_flag(engine_state, stack, "num-levels")?,
64 };
65
66 if matches!(input, PipelineData::Empty) {
68 return Err(ShellError::PipelineEmpty { dst_span: head });
69 }
70 input.map(
71 move |value| super::operate(&get_dirname, &args, value, head),
72 engine_state.signals(),
73 )
74 }
75
76 fn run_const(
77 &self,
78 working_set: &StateWorkingSet,
79 call: &Call,
80 input: PipelineData,
81 ) -> Result<PipelineData, ShellError> {
82 let head = call.head;
83 let args = Arguments {
84 replace: call.get_flag_const(working_set, "replace")?,
85 num_levels: call.get_flag_const(working_set, "num-levels")?,
86 };
87
88 if matches!(input, PipelineData::Empty) {
90 return Err(ShellError::PipelineEmpty { dst_span: head });
91 }
92 input.map(
93 move |value| super::operate(&get_dirname, &args, value, head),
94 working_set.permanent().signals(),
95 )
96 }
97
98 #[cfg(windows)]
99 fn examples(&self) -> Vec<Example> {
100 vec![
101 Example {
102 description: "Get dirname of a path",
103 example: "'C:\\Users\\joe\\code\\test.txt' | path dirname",
104 result: Some(Value::test_string("C:\\Users\\joe\\code")),
105 },
106 Example {
107 description: "Get dirname of a list of paths",
108 example: r"[ C:\Users\joe\test.txt, C:\Users\doe\test.txt ] | path dirname",
109 result: Some(Value::test_list(vec![
110 Value::test_string(r"C:\Users\joe"),
111 Value::test_string(r"C:\Users\doe"),
112 ])),
113 },
114 Example {
115 description: "Walk up two levels",
116 example: "'C:\\Users\\joe\\code\\test.txt' | path dirname --num-levels 2",
117 result: Some(Value::test_string("C:\\Users\\joe")),
118 },
119 Example {
120 description: "Replace the part that would be returned with a custom path",
121 example: "'C:\\Users\\joe\\code\\test.txt' | path dirname --num-levels 2 --replace C:\\Users\\viking",
122 result: Some(Value::test_string("C:\\Users\\viking\\code\\test.txt")),
123 },
124 ]
125 }
126
127 #[cfg(not(windows))]
128 fn examples(&self) -> Vec<Example> {
129 vec![
130 Example {
131 description: "Get dirname of a path",
132 example: "'/home/joe/code/test.txt' | path dirname",
133 result: Some(Value::test_string("/home/joe/code")),
134 },
135 Example {
136 description: "Get dirname of a list of paths",
137 example: "[ /home/joe/test.txt, /home/doe/test.txt ] | path dirname",
138 result: Some(Value::test_list(vec![
139 Value::test_string("/home/joe"),
140 Value::test_string("/home/doe"),
141 ])),
142 },
143 Example {
144 description: "Walk up two levels",
145 example: "'/home/joe/code/test.txt' | path dirname --num-levels 2",
146 result: Some(Value::test_string("/home/joe")),
147 },
148 Example {
149 description: "Replace the part that would be returned with a custom path",
150 example: "'/home/joe/code/test.txt' | path dirname --num-levels 2 --replace /home/viking",
151 result: Some(Value::test_string("/home/viking/code/test.txt")),
152 },
153 ]
154 }
155}
156
157fn get_dirname(path: &Path, span: Span, args: &Arguments) -> Value {
158 let num_levels = args.num_levels.as_ref().map_or(1, |val| *val);
159
160 let mut dirname = path;
161 let mut reached_top = false;
162 for _ in 0..num_levels {
163 dirname = dirname.parent().unwrap_or_else(|| {
164 reached_top = true;
165 dirname
166 });
167 if reached_top {
168 break;
169 }
170 }
171
172 let path = match args.replace {
173 Some(ref newdir) => {
174 let remainder = path.strip_prefix(dirname).unwrap_or(dirname);
175 if !remainder.as_os_str().is_empty() {
176 Path::new(&newdir.item).join(remainder)
177 } else {
178 Path::new(&newdir.item).to_path_buf()
179 }
180 }
181 None => dirname.to_path_buf(),
182 };
183
184 Value::string(path.to_string_lossy(), span)
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 #[test]
192 fn test_examples() {
193 use crate::test_examples;
194
195 test_examples(PathDirname {})
196 }
197}