nu_command/path/
expand.rs

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        // This doesn't match explicit nulls
68        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        // This doesn't match explicit nulls
91        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}