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