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 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
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::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/// ```
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}
208