nu_command/path/
exists.rs

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