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