1 //! Helpers for include/exclude lists.
3 use bitflags
::bitflags
;
5 use crate::PatternFlag
;
9 /// These flags influence what kind of paths should be matched.
10 pub struct MatchFlag
: u16 {
11 /// Match only a complete entry. The pattern `bar` will not match `/foo/bar`.
12 const ANCHORED
= 0x00_01;
14 const MATCH_DIRECTORIES
= 0x01_00;
15 const MATCH_REGULAR_FILES
= 0x02_00;
16 const MATCH_SYMLINKS
= 0x04_00;
17 const MATCH_SOCKETS
= 0x08_00;
18 const MATCH_FIFOS
= 0x10_00;
19 const MATCH_CHARDEVS
= 0x20_00;
20 const MATCH_BLOCKDEVS
= 0x40_00;
22 MatchFlag
::MATCH_CHARDEVS
.bits() | MatchFlag
::MATCH_BLOCKDEVS
.bits();
24 /// This is the default.
26 MatchFlag
::MATCH_DIRECTORIES
.bits()
27 | MatchFlag
::MATCH_REGULAR_FILES
.bits()
28 | MatchFlag
::MATCH_SYMLINKS
.bits()
29 | MatchFlag
::MATCH_SOCKETS
.bits()
30 | MatchFlag
::MATCH_FIFOS
.bits()
31 | MatchFlag
::MATCH_CHARDEVS
.bits()
32 | MatchFlag
::MATCH_BLOCKDEVS
.bits();
36 impl Default
for MatchFlag
{
37 fn default() -> Self {
42 /// A pattern entry. For now this only contains glob patterns, but we may want to add regex
43 /// patterns or user defined callback functions later on as well.
45 /// For regex we'd likely use the POSIX extended REs via `regexec(3)`, since we're targetting
46 /// command line interfaces and want something command line users are used to.
47 #[derive(Clone, Debug)]
48 pub enum MatchPattern
{
50 Pattern(crate::Pattern
),
56 impl From
<crate::Pattern
> for MatchPattern
{
57 fn from(pattern
: crate::Pattern
) -> Self {
58 MatchPattern
::Pattern(pattern
)
63 pub fn literal(literal
: impl Into
<Vec
<u8>>) -> Self {
64 MatchPattern
::Literal(literal
.into())
68 /// A pattern can be used as an include or an exclude pattern. In a list of `MatchEntry`s, later
69 /// patterns take precedence over earlier patterns and the order of includes vs excludes makes a
71 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
77 /// Convenience helpers
79 pub fn is_include(self) -> bool
{
80 self == MatchType
::Include
83 pub fn is_exclude(self) -> bool
{
84 self == MatchType
::Exclude
88 impl std
::ops
::Not
for MatchType
{
89 type Output
= MatchType
;
91 fn not(self) -> Self::Output
{
93 MatchType
::Include
=> MatchType
::Exclude
,
94 MatchType
::Exclude
=> MatchType
::Include
,
99 /// A single entry in a `MatchList`.
100 #[derive(Clone, Debug)]
101 pub struct MatchEntry
{
102 pattern
: MatchPattern
,
108 /// Create a new match entry.
109 pub fn new
<T
: Into
<MatchPattern
>>(pattern
: T
, ty
: MatchType
) -> Self {
111 pattern
: pattern
.into(),
113 flags
: MatchFlag
::default(),
117 /// Create a new include-type match entry with default flags.
118 pub fn include
<T
: Into
<MatchPattern
>>(pattern
: T
) -> Self {
119 Self::new(pattern
.into(), MatchType
::Include
)
122 /// Create a new exclude-type match entry with default flags.
123 pub fn exclude
<T
: Into
<MatchPattern
>>(pattern
: T
) -> Self {
124 Self::new(pattern
.into(), MatchType
::Exclude
)
127 /// Builder method to set the match flags to a specific value.
128 pub fn flags(mut self, flags
: MatchFlag
) -> Self {
133 /// Builder method to add flag bits to the already present ones.
134 pub fn add_flags(mut self, flags
: MatchFlag
) -> Self {
135 self.flags
.insert(flags
);
139 /// Builder method to remove match flag bits.
140 pub fn remove_flags(mut self, flags
: MatchFlag
) -> Self {
141 self.flags
.remove(flags
);
145 /// Builder method to toggle flag bits.
146 pub fn toggle_flags(mut self, flags
: MatchFlag
) -> Self {
147 self.flags
.toggle(flags
);
152 pub fn match_type(&self) -> MatchType
{
156 /// Non-Builder method to change the match type.
157 pub fn match_type_mut(&mut self) -> &mut MatchType
{
161 /// Directly access the pattern.
162 pub fn pattern(&self) -> &MatchPattern
{
166 /// Non-Builder method to change the pattern.
167 pub fn pattern_mut(&mut self) -> &mut MatchPattern
{
171 /// Directly access the match flags.
172 pub fn match_flags(&self) -> MatchFlag
{
176 /// Non-Builder method to change the flags.
177 pub fn match_flags_mut(&mut self) -> &mut MatchFlag
{
181 /// Parse a pattern into a `MatchEntry` while interpreting a leading exclamation mark as
182 /// inversion and trailing slashes to match only directories.
183 pub fn parse_pattern
<T
: AsRef
<[u8]>>(
185 pattern_flags
: PatternFlag
,
187 ) -> Result
<Self, crate::ParseError
> {
188 Self::parse_pattern_do(pattern
.as_ref(), pattern_flags
, ty
)
193 pattern_flags
: PatternFlag
,
195 ) -> Result
<Self, crate::ParseError
> {
196 let (pattern
, ty
) = if pattern
.get(0).copied() == Some(b'
!'
) {
202 let (pattern
, flags
) = match pattern
.iter().rposition(|&b
| b
!= b'
/'
) {
203 Some(pos
) if (pos
+ 1) == pattern
.len() => (pattern
, MatchFlag
::default()),
204 Some(pos
) => (&pattern
[..=pos
], MatchFlag
::MATCH_DIRECTORIES
),
205 None
=> (b
"/".as_ref(), MatchFlag
::MATCH_DIRECTORIES
),
208 Ok(Self::new(crate::Pattern
::new(pattern
, pattern_flags
)?
, ty
).flags(flags
))
211 /// Test this entry's file type restrictions against a file mode retrieved from `stat()`.
212 pub fn matches_mode(&self, file_mode
: u32) -> bool
{
213 // bitflags' `.contains` means ALL bits must be set, if they are all set we don't
214 // need to check the mode...
215 if self.flags
.contains(MatchFlag
::ANY_FILE_TYPE
) {
219 let flag
= match file_mode
& libc
::S_IFMT
{
220 libc
::S_IFDIR
=> MatchFlag
::MATCH_DIRECTORIES
,
221 libc
::S_IFREG
=> MatchFlag
::MATCH_REGULAR_FILES
,
222 libc
::S_IFLNK
=> MatchFlag
::MATCH_SYMLINKS
,
223 libc
::S_IFSOCK
=> MatchFlag
::MATCH_SOCKETS
,
224 libc
::S_IFIFO
=> MatchFlag
::MATCH_FIFOS
,
225 libc
::S_IFCHR
=> MatchFlag
::MATCH_CHARDEVS
,
226 libc
::S_IFBLK
=> MatchFlag
::MATCH_BLOCKDEVS
,
227 _unknown
=> return false,
229 self.flags
.intersects(flag
)
232 /// Test whether this entry's pattern matches any complete suffix of a path.
234 /// For the path `/foo/bar/baz`, this tests whether `baz`, `bar/baz` or `foo/bar/baz` is
236 pub fn matches_path_suffix
<T
: AsRef
<[u8]>>(&self, path
: T
) -> bool
{
237 self.matches_path_suffix_do(path
.as_ref())
240 fn matches_path_suffix_do(&self, path
: &[u8]) -> bool
{
241 if self.flags
.intersects(MatchFlag
::ANCHORED
) {
242 return self.matches_path_exact(path
);
249 for start
in (0..path
.len()).rev() {
250 if path
[start
] == b'
/'
&& self.matches_path_exact(&path
[(start
+ 1)..]) {
255 // and try the whole string as well:
256 self.matches_path_exact(path
)
259 /// Test whether this entry's pattern matches a path exactly.
260 pub fn matches_path_exact
<T
: AsRef
<[u8]>>(&self, path
: T
) -> bool
{
261 self.matches_path_exact_do(path
.as_ref())
264 fn matches_path_exact_do(&self, path
: &[u8]) -> bool
{
265 match &self.pattern
{
266 MatchPattern
::Pattern(pattern
) => pattern
.matches(path
),
267 MatchPattern
::Literal(literal
) => path
== &literal
[..],
271 /// Check whether the path contains a matching suffix and the file mode match the expected file modes.
272 /// This is a combination of using `.matches_mode()` and `.matches_path_suffix()`.
273 pub fn matches
<T
: AsRef
<[u8]>>(&self, path
: T
, file_mode
: Option
<u32>) -> bool
{
274 self.matches_do(path
.as_ref(), file_mode
)
277 fn matches_do(&self, path
: &[u8], file_mode
: Option
<u32>) -> bool
{
278 if let Some(mode
) = file_mode
{
279 if !self.matches_mode(mode
) {
284 self.matches_path_suffix(path
)
287 /// Check whether the path contains a matching suffix and the file mode match the expected file modes.
288 /// This is a combination of using `.matches_mode()` and `.matches_path_exact()`.
289 pub fn matches_exact
<T
: AsRef
<[u8]>>(&self, path
: T
, file_mode
: Option
<u32>) -> bool
{
290 self.matches_exact_do(path
.as_ref(), file_mode
)
293 fn matches_exact_do(&self, path
: &[u8], file_mode
: Option
<u32>) -> bool
{
294 if let Some(mode
) = file_mode
{
295 if !self.matches_mode(mode
) {
300 self.matches_path_exact(path
)
305 pub trait MatchListEntry
{
306 fn entry_matches(&self, path
: &[u8], file_mode
: Option
<u32>) -> Option
<MatchType
>;
307 fn entry_matches_exact(&self, path
: &[u8], file_mode
: Option
<u32>) -> Option
<MatchType
>;
310 impl MatchListEntry
for &'_ MatchEntry
{
311 fn entry_matches(&self, path
: &[u8], file_mode
: Option
<u32>) -> Option
<MatchType
> {
312 if self.matches(path
, file_mode
) {
313 Some(self.match_type())
319 fn entry_matches_exact(&self, path
: &[u8], file_mode
: Option
<u32>) -> Option
<MatchType
> {
320 if self.matches_exact(path
, file_mode
) {
321 Some(self.match_type())
328 impl MatchListEntry
for &'_
&'_ MatchEntry
{
329 fn entry_matches(&self, path
: &[u8], file_mode
: Option
<u32>) -> Option
<MatchType
> {
330 if self.matches(path
, file_mode
) {
331 Some(self.match_type())
337 fn entry_matches_exact(&self, path
: &[u8], file_mode
: Option
<u32>) -> Option
<MatchType
> {
338 if self.matches_exact(path
, file_mode
) {
339 Some(self.match_type())
346 /// This provides `matches` and `matches_exact` methods to lists of `MatchEntry`s.
348 /// Technically this is implemented for anything you can turn into a `DoubleEndedIterator` over
349 /// `MatchEntry` or `&MatchEntry`.
351 /// In practice this means you can use it with slices or references to `Vec` or `VecDeque` etc.
352 /// This makes it easier to use slices over entries or references to entries.
353 pub trait MatchList
{
354 /// Check whether this list contains anything matching a prefix of the specified path, and the
355 /// specified file mode.
356 fn matches
<T
: AsRef
<[u8]>>(&self, path
: T
, file_mode
: Option
<u32>) -> Option
<MatchType
> {
357 self.matches_do(path
.as_ref(), file_mode
)
360 fn matches_do(&self, path
: &[u8], file_mode
: Option
<u32>) -> Option
<MatchType
>;
362 /// Check whether this list contains anything exactly matching the path and mode.
363 fn matches_exact
<T
: AsRef
<[u8]>>(&self, path
: T
, file_mode
: Option
<u32>) -> Option
<MatchType
> {
364 self.matches_exact_do(path
.as_ref(), file_mode
)
367 fn matches_exact_do(&self, path
: &[u8], file_mode
: Option
<u32>) -> Option
<MatchType
>;
370 impl<'a
, T
> MatchList
for T
374 <&'a T
as IntoIterator
>::IntoIter
: DoubleEndedIterator
,
375 <&'a T
as IntoIterator
>::Item
: MatchListEntry
,
377 fn matches_do(&self, path
: &[u8], file_mode
: Option
<u32>) -> Option
<MatchType
> {
378 // This is an &self method on a `T where T: 'a`.
379 let this
: &'a
Self = unsafe { std::mem::transmute(self) }
;
381 for m
in this
.into_iter().rev() {
382 if let Some(mt
) = m
.entry_matches(path
, file_mode
) {
390 fn matches_exact_do(&self, path
: &[u8], file_mode
: Option
<u32>) -> Option
<MatchType
> {
391 // This is an &self method on a `T where T: 'a`.
392 let this
: &'a
Self = unsafe { std::mem::transmute(self) }
;
394 for m
in this
.into_iter().rev() {
395 if let Some(mt
) = m
.entry_matches_exact(path
, file_mode
) {
405 fn assert_containers_implement_match_list() {
406 use std
::iter
::FromIterator
;
408 let vec
= vec
![MatchEntry
::include(crate::Pattern
::path("a*").unwrap())];
409 assert_eq
!(vec
.matches("asdf", None
), Some(MatchType
::Include
));
411 // FIXME: ideally we can make this work as well!
412 let vd
= std
::collections
::VecDeque
::<MatchEntry
>::from_iter(vec
.clone());
413 assert_eq
!(vd
.matches("asdf", None
), Some(MatchType
::Include
));
415 let list
: &[MatchEntry
] = &vec
[..];
416 assert_eq
!(list
.matches("asdf", None
), Some(MatchType
::Include
));
418 let list
: Vec
<&MatchEntry
> = vec
.iter().collect();
419 assert_eq
!(list
.matches("asdf", None
), Some(MatchType
::Include
));
421 let list
: &[&MatchEntry
] = &list
[..];
422 assert_eq
!(list
.matches("asdf", None
), Some(MatchType
::Include
));
426 fn test_file_type_matches() {
427 let matchlist
= vec
![
428 MatchEntry
::parse_pattern("a_dir/", PatternFlag
::PATH_NAME
, MatchType
::Include
).unwrap(),
429 MatchEntry
::parse_pattern("!a_file", PatternFlag
::PATH_NAME
, MatchType
::Include
)
431 .flags(MatchFlag
::MATCH_REGULAR_FILES
),
432 MatchEntry
::parse_pattern("!another_dir//", PatternFlag
::PATH_NAME
, MatchType
::Include
)
436 matchlist
.matches("a_dir", Some(libc
::S_IFDIR
)),
437 Some(MatchType
::Include
)
440 matchlist
.matches("/a_dir", Some(libc
::S_IFDIR
)),
441 Some(MatchType
::Include
)
443 assert_eq
!(matchlist
.matches("/a_dir", Some(libc
::S_IFREG
)), None
);
446 matchlist
.matches("/a_file", Some(libc
::S_IFREG
)),
447 Some(MatchType
::Exclude
)
449 assert_eq
!(matchlist
.matches("/a_file", Some(libc
::S_IFDIR
)), None
);
452 matchlist
.matches("/another_dir", Some(libc
::S_IFDIR
)),
453 Some(MatchType
::Exclude
)
455 assert_eq
!(matchlist
.matches("/another_dir", Some(libc
::S_IFREG
)), None
);
459 fn test_anchored_matches() {
462 let matchlist
= vec
![
463 MatchEntry
::new(Pattern
::path("file-a").unwrap(), MatchType
::Include
),
464 MatchEntry
::new(Pattern
::path("some/path").unwrap(), MatchType
::Include
)
465 .flags(MatchFlag
::ANCHORED
),
468 assert_eq
!(matchlist
.matches("file-a", None
), Some(MatchType
::Include
));
470 matchlist
.matches("another/file-a", None
),
471 Some(MatchType
::Include
)
474 assert_eq
!(matchlist
.matches("some", None
), None
);
475 assert_eq
!(matchlist
.matches("path", None
), None
);
477 matchlist
.matches("some/path", None
),
478 Some(MatchType
::Include
)
480 assert_eq
!(matchlist
.matches("another/some/path", None
), None
);
484 fn test_literal_matches() {
485 let matchlist
= vec
![MatchEntry
::new(
486 MatchPattern
::Literal(b
"/bin/mv".to_vec()),
489 assert_eq
!(matchlist
.matches("/bin/mv", None
), Some(MatchType
::Include
));
493 fn test_path_relativity() {
495 let matchlist
= vec
![
496 MatchEntry
::new(Pattern
::path("noslash").unwrap(), MatchType
::Include
),
497 MatchEntry
::new(Pattern
::path("noslash-a").unwrap(), MatchType
::Include
)
498 .flags(MatchFlag
::ANCHORED
),
499 MatchEntry
::new(Pattern
::path("/slash").unwrap(), MatchType
::Include
),
500 MatchEntry
::new(Pattern
::path("/slash-a").unwrap(), MatchType
::Include
)
501 .flags(MatchFlag
::ANCHORED
),
503 assert_eq
!(matchlist
.matches("noslash", None
), Some(MatchType
::Include
));
505 matchlist
.matches("noslash-a", None
),
506 Some(MatchType
::Include
)
508 assert_eq
!(matchlist
.matches("slash", None
), None
);
509 assert_eq
!(matchlist
.matches("/slash", None
), Some(MatchType
::Include
));
510 assert_eq
!(matchlist
.matches("slash-a", None
), None
);
512 matchlist
.matches("/slash-a", None
),
513 Some(MatchType
::Include
)
517 matchlist
.matches("foo/noslash", None
),
518 Some(MatchType
::Include
)
520 assert_eq
!(matchlist
.matches("foo/noslash-a", None
), None
);
521 assert_eq
!(matchlist
.matches("foo/slash", None
), None
);
522 assert_eq
!(matchlist
.matches("foo/slash-a", None
), None
);