Skip to main content

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