| 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 | |