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