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 { |
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 path.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 | |
50 | impl 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)] |
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. |
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 | |
90 | impl 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 | |
129 | impl 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 | |
136 | impl fmt::Display for FileTypePredicate { |
137 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
138 | let palette = crate::Palette::current(); |
139 | write!( |
140 | f, |
141 | "{} {} {}" , |
142 | palette.var.paint("var" ), |
143 | palette.description.paint("is" ), |
144 | palette.expected.paint(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 | /// ``` |
162 | 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 | /// ``` |
182 | 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 | /// ``` |
202 | pub fn is_symlink() -> FileTypePredicate { |
203 | FileTypePredicate { |
204 | ft: FileType::Symlink, |
205 | follow: false, |
206 | } |
207 | } |
208 | |