predicates/path/
ft.rs

1// Copyright (c) 2018 The predicates-rs Project Developers.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use std::fmt;
10use std::fs;
11use std::io;
12use std::path;
13
14use crate::reflection;
15use crate::Predicate;
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18enum FileType {
19    File,
20    Dir,
21    Symlink,
22}
23
24impl FileType {
25    fn from_path(path: &path::Path, follow: bool) -> io::Result<FileType> {
26        let file_type = if follow {
27            path.metadata()
28        } else {
29            path.symlink_metadata()
30        }?
31        .file_type();
32        if file_type.is_dir() {
33            return Ok(FileType::Dir);
34        }
35        if file_type.is_file() {
36            return Ok(FileType::File);
37        }
38        Ok(FileType::Symlink)
39    }
40
41    fn eval(self, ft: fs::FileType) -> bool {
42        match self {
43            FileType::File => ft.is_file(),
44            FileType::Dir => ft.is_dir(),
45            FileType::Symlink => ft.is_symlink(),
46        }
47    }
48}
49
50impl fmt::Display for FileType {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        let t = match *self {
53            FileType::File => "file",
54            FileType::Dir => "dir",
55            FileType::Symlink => "symlink",
56        };
57        write!(f, "{t}")
58    }
59}
60
61/// Predicate that checks the `std::fs::FileType`.
62///
63/// This is created by the `predicate::path::is_file`, `predicate::path::is_dir`, and `predicate::path::is_symlink`.
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub struct FileTypePredicate {
66    ft: FileType,
67    follow: bool,
68}
69
70impl FileTypePredicate {
71    /// Follow symbolic links.
72    ///
73    /// When yes is true, symbolic links are followed as if they were normal directories and files.
74    ///
75    /// Default: disabled.
76    pub fn follow_links(mut self, yes: bool) -> Self {
77        self.follow = yes;
78        self
79    }
80
81    /// Allow to create an `FileTypePredicate` from a `path`
82    pub fn from_path(path: &path::Path) -> io::Result<FileTypePredicate> {
83        Ok(FileTypePredicate {
84            ft: FileType::from_path(path, true)?,
85            follow: true,
86        })
87    }
88}
89
90impl Predicate<path::Path> for FileTypePredicate {
91    fn eval(&self, path: &path::Path) -> bool {
92        let metadata = if self.follow {
93            path.metadata()
94        } else {
95            path.symlink_metadata()
96        };
97        metadata
98            .map(|m| self.ft.eval(m.file_type()))
99            .unwrap_or(false)
100    }
101
102    fn find_case<'a>(
103        &'a self,
104        expected: bool,
105        variable: &path::Path,
106    ) -> Option<reflection::Case<'a>> {
107        let actual_type = FileType::from_path(variable, self.follow);
108        match (expected, actual_type) {
109            (_, Ok(actual_type)) => {
110                let result = self.ft == actual_type;
111                if result == expected {
112                    Some(
113                        reflection::Case::new(Some(self), result)
114                            .add_product(reflection::Product::new("actual filetype", actual_type)),
115                    )
116                } else {
117                    None
118                }
119            }
120            (true, Err(_)) => None,
121            (false, Err(err)) => Some(
122                reflection::Case::new(Some(self), false)
123                    .add_product(reflection::Product::new("error", err)),
124            ),
125        }
126    }
127}
128
129impl reflection::PredicateReflection for FileTypePredicate {
130    fn parameters<'a>(&'a self) -> Box<dyn Iterator<Item = reflection::Parameter<'a>> + 'a> {
131        let params = vec![reflection::Parameter::new("follow", &self.follow)];
132        Box::new(params.into_iter())
133    }
134}
135
136impl fmt::Display for FileTypePredicate {
137    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138        let palette = crate::Palette::new(f.alternate());
139        write!(
140            f,
141            "{} {} {}",
142            palette.var("var"),
143            palette.description("is"),
144            palette.expected(self.ft)
145        )
146    }
147}
148
149/// Creates a new `Predicate` that ensures the path points to a file.
150///
151/// # Examples
152///
153/// ```
154/// use std::path::Path;
155/// use predicates::prelude::*;
156///
157/// let predicate_fn = predicate::path::is_file();
158/// assert_eq!(true, predicate_fn.eval(Path::new("Cargo.toml")));
159/// assert_eq!(false, predicate_fn.eval(Path::new("src")));
160/// assert_eq!(false, predicate_fn.eval(Path::new("non-existent-file.foo")));
161/// ```
162pub fn is_file() -> FileTypePredicate {
163    FileTypePredicate {
164        ft: FileType::File,
165        follow: false,
166    }
167}
168
169/// Creates a new `Predicate` that ensures the path points to a directory.
170///
171/// # Examples
172///
173/// ```
174/// use std::path::Path;
175/// use predicates::prelude::*;
176///
177/// let predicate_fn = predicate::path::is_dir();
178/// assert_eq!(false, predicate_fn.eval(Path::new("Cargo.toml")));
179/// assert_eq!(true, predicate_fn.eval(Path::new("src")));
180/// assert_eq!(false, predicate_fn.eval(Path::new("non-existent-file.foo")));
181/// ```
182pub fn is_dir() -> FileTypePredicate {
183    FileTypePredicate {
184        ft: FileType::Dir,
185        follow: false,
186    }
187}
188
189/// Creates a new `Predicate` that ensures the path points to a symlink.
190///
191/// # Examples
192///
193/// ```
194/// use std::path::Path;
195/// use predicates::prelude::*;
196///
197/// let predicate_fn = predicate::path::is_symlink();
198/// assert_eq!(false, predicate_fn.eval(Path::new("Cargo.toml")));
199/// assert_eq!(false, predicate_fn.eval(Path::new("src")));
200/// assert_eq!(false, predicate_fn.eval(Path::new("non-existent-file.foo")));
201/// ```
202pub fn is_symlink() -> FileTypePredicate {
203    FileTypePredicate {
204        ft: FileType::Symlink,
205        follow: false,
206    }
207}