1use super::PathSubcommandArguments;
2use nu_engine::command_prelude::*;
3use nu_path::{canonicalize_with, expand_path_with};
4use nu_protocol::engine::StateWorkingSet;
5use std::path::Path;
6
7struct Arguments {
8 strict: bool,
9 cwd: String,
10 not_follow_symlink: bool,
11}
12
13impl PathSubcommandArguments for Arguments {}
14
15#[derive(Clone)]
16pub struct PathExpand;
17
18impl Command for PathExpand {
19 fn name(&self) -> &str {
20 "path expand"
21 }
22
23 fn signature(&self) -> Signature {
24 Signature::build("path expand")
25 .input_output_types(vec![
26 (Type::String, Type::String),
27 (
28 Type::List(Box::new(Type::String)),
29 Type::List(Box::new(Type::String)),
30 ),
31 ])
32 .switch(
33 "strict",
34 "Throw an error if the path could not be expanded",
35 Some('s'),
36 )
37 .switch("no-symlink", "Do not resolve symbolic links", Some('n'))
38 .category(Category::Path)
39 }
40
41 fn description(&self) -> &str {
42 "Try to expand a path to its absolute form."
43 }
44
45 fn is_const(&self) -> bool {
46 true
47 }
48
49 fn run(
50 &self,
51 engine_state: &EngineState,
52 stack: &mut Stack,
53 call: &Call,
54 input: PipelineData,
55 ) -> Result<PipelineData, ShellError> {
56 let head = call.head;
57 let args = Arguments {
58 strict: call.has_flag(engine_state, stack, "strict")?,
59 cwd: engine_state.cwd_as_string(Some(stack))?,
60 not_follow_symlink: call.has_flag(engine_state, stack, "no-symlink")?,
61 };
62 if let PipelineData::Empty = input {
64 return Err(ShellError::PipelineEmpty { dst_span: head });
65 }
66 input.map(
67 move |value| super::operate(&expand, &args, value, head),
68 engine_state.signals(),
69 )
70 }
71
72 fn run_const(
73 &self,
74 working_set: &StateWorkingSet,
75 call: &Call,
76 input: PipelineData,
77 ) -> Result<PipelineData, ShellError> {
78 let head = call.head;
79 #[allow(deprecated)]
80 let args = Arguments {
81 strict: call.has_flag_const(working_set, "strict")?,
82 cwd: working_set.permanent_state.cwd_as_string(None)?,
83 not_follow_symlink: call.has_flag_const(working_set, "no-symlink")?,
84 };
85 if let PipelineData::Empty = input {
87 return Err(ShellError::PipelineEmpty { dst_span: head });
88 }
89 input.map(
90 move |value| super::operate(&expand, &args, value, head),
91 working_set.permanent().signals(),
92 )
93 }
94
95 #[cfg(windows)]
96 fn examples(&self) -> Vec<Example<'_>> {
97 vec![
98 Example {
99 description: "Expand an absolute path",
100 example: r"'C:\Users\joe\foo\..\bar' | path expand",
101 result: Some(Value::test_string(r"C:\Users\joe\bar")),
102 },
103 Example {
104 description: "Expand a relative path",
105 example: r"'foo\..\bar' | path expand",
106 result: None,
107 },
108 Example {
109 description: "Expand a list of paths",
110 example: r"[ C:\foo\..\bar, C:\foo\..\baz ] | path expand",
111 result: Some(Value::test_list(vec![
112 Value::test_string(r"C:\bar"),
113 Value::test_string(r"C:\baz"),
114 ])),
115 },
116 ]
117 }
118
119 #[cfg(not(windows))]
120 fn examples(&self) -> Vec<Example<'_>> {
121 vec![
122 Example {
123 description: "Expand an absolute path",
124 example: "'/home/joe/foo/../bar' | path expand",
125 result: Some(Value::test_string("/home/joe/bar")),
126 },
127 Example {
128 description: "Expand a relative path",
129 example: "'foo/../bar' | path expand",
130 result: None,
131 },
132 Example {
133 description: "Expand a list of paths",
134 example: "[ /foo/../bar, /foo/../baz ] | path expand",
135 result: Some(Value::test_list(vec![
136 Value::test_string("/bar"),
137 Value::test_string("/baz"),
138 ])),
139 },
140 ]
141 }
142}
143
144fn expand(path: &Path, span: Span, args: &Arguments) -> Value {
145 if args.strict {
146 match canonicalize_with(path, &args.cwd) {
147 Ok(p) => {
148 if args.not_follow_symlink {
149 Value::string(
150 expand_path_with(path, &args.cwd, true).to_string_lossy(),
151 span,
152 )
153 } else {
154 Value::string(p.to_string_lossy(), span)
155 }
156 }
157 Err(_) => Value::error(
158 ShellError::GenericError {
159 error: "Could not expand path".into(),
160 msg: "could not be expanded (path might not exist, non-final \
161 component is not a directory, or other cause)"
162 .into(),
163 span: Some(span),
164 help: None,
165 inner: vec![],
166 },
167 span,
168 ),
169 }
170 } else if args.not_follow_symlink {
171 Value::string(
172 expand_path_with(path, &args.cwd, true).to_string_lossy(),
173 span,
174 )
175 } else {
176 canonicalize_with(path, &args.cwd)
177 .map(|p| Value::string(p.to_string_lossy(), span))
178 .unwrap_or_else(|_| {
179 Value::string(
180 expand_path_with(path, &args.cwd, true).to_string_lossy(),
181 span,
182 )
183 })
184 }
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(PathExpand {})
196 }
197}