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 
9 use std::fmt;
10 use std::fs;
11 use std::io;
12 use std::path;
13 
14 use crate::reflection;
15 use crate::Predicate;
16 
17 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
18 enum FileType {
19     File,
20     Dir,
21     Symlink,
22 }
23 
24 impl FileType {
from_path(path: &path::Path, follow: bool) -> io::Result<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 
eval(self, ft: fs::FileType) -> bool41     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 
50 impl fmt::Display for FileType {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result51     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)]
65 pub struct FileTypePredicate {
66     ft: FileType,
67     follow: bool,
68 }
69 
70 impl 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.
follow_links(mut self, yes: bool) -> Self76     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`
from_path(path: &path::Path) -> io::Result<FileTypePredicate>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 
90 impl Predicate<path::Path> for FileTypePredicate {
eval(&self, path: &path::Path) -> bool91     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 
find_case<'a>( &'a self, expected: bool, variable: &path::Path, ) -> Option<reflection::Case<'a>>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 
129 impl reflection::PredicateReflection for FileTypePredicate {
parameters<'a>(&'a self) -> Box<dyn Iterator<Item = reflection::Parameter<'a>> + 'a>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 
136 impl fmt::Display for FileTypePredicate {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result137     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 /// ```
is_file() -> FileTypePredicate162 pub 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 /// ```
is_dir() -> FileTypePredicate182 pub 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 /// ```
is_symlink() -> FileTypePredicate202 pub fn is_symlink() -> FileTypePredicate {
203     FileTypePredicate {
204         ft: FileType::Symlink,
205         follow: false,
206     }
207 }
208