nu_command/path/
expand.rs

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