nu_command/path/
exists.rs

1use super::PathSubcommandArguments;
2use nu_engine::command_prelude::*;
3use nu_path::expand_path_with;
4use nu_protocol::{engine::StateWorkingSet, shell_error::io::IoError};
5use std::path::{Path, PathBuf};
6
7struct Arguments {
8    pwd: PathBuf,
9    not_follow_symlink: bool,
10}
11
12impl PathSubcommandArguments for Arguments {}
13
14#[derive(Clone)]
15pub struct PathExists;
16
17impl Command for PathExists {
18    fn name(&self) -> &str {
19        "path exists"
20    }
21
22    fn signature(&self) -> Signature {
23        Signature::build("path exists")
24            .input_output_types(vec![
25                (Type::String, Type::Bool),
26                (
27                    Type::List(Box::new(Type::String)),
28                    Type::List(Box::new(Type::Bool)),
29                ),
30            ])
31            .switch("no-symlink", "Do not resolve symbolic links", Some('n'))
32            .category(Category::Path)
33    }
34
35    fn description(&self) -> &str {
36        "Check whether a path exists."
37    }
38
39    fn extra_description(&self) -> &str {
40        r#"This only checks if it is possible to either `open` or `cd` to the given path.
41If you need to distinguish dirs and files, please use `path type`.
42Also note that if you don't have a permission to a directory of a path, false will be returned"#
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            pwd: engine_state.cwd(Some(stack))?.into_std_path_buf(),
59            not_follow_symlink: call.has_flag(engine_state, stack, "no-symlink")?,
60        };
61        // This doesn't match explicit nulls
62        if let PipelineData::Empty = input {
63            return Err(ShellError::PipelineEmpty { dst_span: head });
64        }
65        input.map(
66            move |value| super::operate(&exists, &args, value, head),
67            engine_state.signals(),
68        )
69    }
70
71    fn run_const(
72        &self,
73        working_set: &StateWorkingSet,
74        call: &Call,
75        input: PipelineData,
76    ) -> Result<PipelineData, ShellError> {
77        let head = call.head;
78        let args = Arguments {
79            pwd: working_set.permanent_state.cwd(None)?.into_std_path_buf(),
80            not_follow_symlink: call.has_flag_const(working_set, "no-symlink")?,
81        };
82        // This doesn't match explicit nulls
83        if let PipelineData::Empty = input {
84            return Err(ShellError::PipelineEmpty { dst_span: head });
85        }
86        input.map(
87            move |value| super::operate(&exists, &args, value, head),
88            working_set.permanent().signals(),
89        )
90    }
91
92    #[cfg(windows)]
93    fn examples(&self) -> Vec<Example<'_>> {
94        vec![
95            Example {
96                description: "Check if a file exists",
97                example: "'C:\\Users\\joe\\todo.txt' | path exists",
98                result: Some(Value::test_bool(false)),
99            },
100            Example {
101                description: "Check if files in list exist",
102                example: r"[ C:\joe\todo.txt, C:\Users\doe\todo.txt ] | path exists",
103                result: Some(Value::test_list(vec![
104                    Value::test_bool(false),
105                    Value::test_bool(false),
106                ])),
107            },
108        ]
109    }
110
111    #[cfg(not(windows))]
112    fn examples(&self) -> Vec<Example<'_>> {
113        vec![
114            Example {
115                description: "Check if a file exists",
116                example: "'/home/joe/todo.txt' | path exists",
117                result: Some(Value::test_bool(false)),
118            },
119            Example {
120                description: "Check if files in list exist",
121                example: "[ /home/joe/todo.txt, /home/doe/todo.txt ] | path exists",
122                result: Some(Value::test_list(vec![
123                    Value::test_bool(false),
124                    Value::test_bool(false),
125                ])),
126            },
127        ]
128    }
129}
130
131fn exists(path: &Path, span: Span, args: &Arguments) -> Value {
132    if path.as_os_str().is_empty() {
133        return Value::bool(false, span);
134    }
135    let path = expand_path_with(path, &args.pwd, true);
136    let exists = if args.not_follow_symlink {
137        // symlink_metadata returns true if the file/folder exists
138        // whether it is a symbolic link or not. Sorry, but returns Err
139        // in every other scenario including the NotFound
140        std::fs::symlink_metadata(&path).map_or_else(
141            |e| match e.kind() {
142                std::io::ErrorKind::NotFound => Ok(false),
143                _ => Err(e),
144            },
145            |_| Ok(true),
146        )
147    } else {
148        Ok(path.exists())
149    };
150    Value::bool(
151        match exists {
152            Ok(exists) => exists,
153            Err(err) => return Value::error(IoError::new(err, span, path).into(), span),
154        },
155        span,
156    )
157}
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162
163    #[test]
164    fn test_examples() {
165        use crate::test_examples;
166
167        test_examples(PathExists {})
168    }
169}