Show More
@@ -1,178 +1,179 b'' | |||||
1 | // matchers.rs |
|
1 | // matchers.rs | |
2 | // |
|
2 | // | |
3 | // Copyright 2019 Raphaël Gomès <rgomes@octobus.net> |
|
3 | // Copyright 2019 Raphaël Gomès <rgomes@octobus.net> | |
4 | // |
|
4 | // | |
5 | // This software may be used and distributed according to the terms of the |
|
5 | // This software may be used and distributed according to the terms of the | |
6 | // GNU General Public License version 2 or any later version. |
|
6 | // GNU General Public License version 2 or any later version. | |
7 |
|
7 | |||
8 | //! Structs and types for matching files and directories. |
|
8 | //! Structs and types for matching files and directories. | |
9 |
|
9 | |||
10 | use crate::{utils::hg_path::HgPath, DirsMultiset, DirstateMapError}; |
|
10 | use crate::{utils::hg_path::HgPath, DirsMultiset, DirstateMapError}; | |
11 | use std::collections::HashSet; |
|
11 | use std::collections::HashSet; | |
12 | use std::iter::FromIterator; |
|
12 | use std::iter::FromIterator; | |
13 |
|
13 | |||
14 | pub enum VisitChildrenSet<'a> { |
|
14 | pub enum VisitChildrenSet<'a> { | |
15 | /// Don't visit anything |
|
15 | /// Don't visit anything | |
16 | Empty, |
|
16 | Empty, | |
17 | /// Only visit this directory |
|
17 | /// Only visit this directory | |
18 | This, |
|
18 | This, | |
19 | /// Visit this directory and these subdirectories |
|
19 | /// Visit this directory and these subdirectories | |
20 | /// TODO Should we implement a `NonEmptyHashSet`? |
|
20 | /// TODO Should we implement a `NonEmptyHashSet`? | |
21 | Set(HashSet<&'a HgPath>), |
|
21 | Set(HashSet<&'a HgPath>), | |
22 | /// Visit this directory and all subdirectories |
|
22 | /// Visit this directory and all subdirectories | |
23 | Recursive, |
|
23 | Recursive, | |
24 | } |
|
24 | } | |
25 |
|
25 | |||
26 | pub trait Matcher { |
|
26 | pub trait Matcher { | |
27 | /// Explicitly listed files |
|
27 | /// Explicitly listed files | |
28 | fn file_set(&self) -> Option<&HashSet<&HgPath>>; |
|
28 | fn file_set(&self) -> Option<&HashSet<&HgPath>>; | |
29 | /// Returns whether `filename` is in `file_set` |
|
29 | /// Returns whether `filename` is in `file_set` | |
30 | fn exact_match(&self, filename: impl AsRef<HgPath>) -> bool; |
|
30 | fn exact_match(&self, filename: impl AsRef<HgPath>) -> bool; | |
31 | /// Returns whether `filename` is matched by this matcher |
|
31 | /// Returns whether `filename` is matched by this matcher | |
32 | fn matches(&self, filename: impl AsRef<HgPath>) -> bool; |
|
32 | fn matches(&self, filename: impl AsRef<HgPath>) -> bool; | |
33 | /// Decides whether a directory should be visited based on whether it |
|
33 | /// Decides whether a directory should be visited based on whether it | |
34 | /// has potential matches in it or one of its subdirectories, and |
|
34 | /// has potential matches in it or one of its subdirectories, and | |
35 | /// potentially lists which subdirectories of that directory should be |
|
35 | /// potentially lists which subdirectories of that directory should be | |
36 | /// visited. This is based on the match's primary, included, and excluded |
|
36 | /// visited. This is based on the match's primary, included, and excluded | |
37 | /// patterns. |
|
37 | /// patterns. | |
38 | /// |
|
38 | /// | |
39 | /// # Example |
|
39 | /// # Example | |
40 | /// |
|
40 | /// | |
41 | /// Assume matchers `['path:foo/bar', 'rootfilesin:qux']`, we would |
|
41 | /// Assume matchers `['path:foo/bar', 'rootfilesin:qux']`, we would | |
42 | /// return the following values (assuming the implementation of |
|
42 | /// return the following values (assuming the implementation of | |
43 | /// visit_children_set is capable of recognizing this; some implementations |
|
43 | /// visit_children_set is capable of recognizing this; some implementations | |
44 | /// are not). |
|
44 | /// are not). | |
45 | /// |
|
45 | /// | |
|
46 | /// ```text | |||
46 | /// ```ignore |
|
47 | /// ```ignore | |
47 | /// '' -> {'foo', 'qux'} |
|
48 | /// '' -> {'foo', 'qux'} | |
48 | /// 'baz' -> set() |
|
49 | /// 'baz' -> set() | |
49 | /// 'foo' -> {'bar'} |
|
50 | /// 'foo' -> {'bar'} | |
50 | /// // Ideally this would be `Recursive`, but since the prefix nature of |
|
51 | /// // Ideally this would be `Recursive`, but since the prefix nature of | |
51 | /// // matchers is applied to the entire matcher, we have to downgrade this |
|
52 | /// // matchers is applied to the entire matcher, we have to downgrade this | |
52 | /// // to `This` due to the (yet to be implemented in Rust) non-prefix |
|
53 | /// // to `This` due to the (yet to be implemented in Rust) non-prefix | |
53 | /// // `RootFilesIn'-kind matcher being mixed in. |
|
54 | /// // `RootFilesIn'-kind matcher being mixed in. | |
54 | /// 'foo/bar' -> 'this' |
|
55 | /// 'foo/bar' -> 'this' | |
55 | /// 'qux' -> 'this' |
|
56 | /// 'qux' -> 'this' | |
56 | /// ``` |
|
57 | /// ``` | |
57 | /// # Important |
|
58 | /// # Important | |
58 | /// |
|
59 | /// | |
59 | /// Most matchers do not know if they're representing files or |
|
60 | /// Most matchers do not know if they're representing files or | |
60 | /// directories. They see `['path:dir/f']` and don't know whether `f` is a |
|
61 | /// directories. They see `['path:dir/f']` and don't know whether `f` is a | |
61 | /// file or a directory, so `visit_children_set('dir')` for most matchers |
|
62 | /// file or a directory, so `visit_children_set('dir')` for most matchers | |
62 | /// will return `HashSet{ HgPath { "f" } }`, but if the matcher knows it's |
|
63 | /// will return `HashSet{ HgPath { "f" } }`, but if the matcher knows it's | |
63 | /// a file (like the yet to be implemented in Rust `ExactMatcher` does), |
|
64 | /// a file (like the yet to be implemented in Rust `ExactMatcher` does), | |
64 | /// it may return `VisitChildrenSet::This`. |
|
65 | /// it may return `VisitChildrenSet::This`. | |
65 | /// Do not rely on the return being a `HashSet` indicating that there are |
|
66 | /// Do not rely on the return being a `HashSet` indicating that there are | |
66 | /// no files in this dir to investigate (or equivalently that if there are |
|
67 | /// no files in this dir to investigate (or equivalently that if there are | |
67 | /// files to investigate in 'dir' that it will always return |
|
68 | /// files to investigate in 'dir' that it will always return | |
68 | /// `VisitChildrenSet::This`). |
|
69 | /// `VisitChildrenSet::This`). | |
69 | fn visit_children_set( |
|
70 | fn visit_children_set( | |
70 | &self, |
|
71 | &self, | |
71 | directory: impl AsRef<HgPath>, |
|
72 | directory: impl AsRef<HgPath>, | |
72 | ) -> VisitChildrenSet; |
|
73 | ) -> VisitChildrenSet; | |
73 | /// Matcher will match everything and `files_set()` will be empty: |
|
74 | /// Matcher will match everything and `files_set()` will be empty: | |
74 | /// optimization might be possible. |
|
75 | /// optimization might be possible. | |
75 | fn matches_everything(&self) -> bool; |
|
76 | fn matches_everything(&self) -> bool; | |
76 | /// Matcher will match exactly the files in `files_set()`: optimization |
|
77 | /// Matcher will match exactly the files in `files_set()`: optimization | |
77 | /// might be possible. |
|
78 | /// might be possible. | |
78 | fn is_exact(&self) -> bool; |
|
79 | fn is_exact(&self) -> bool; | |
79 | } |
|
80 | } | |
80 |
|
81 | |||
81 | /// Matches everything. |
|
82 | /// Matches everything. | |
82 | ///``` |
|
83 | ///``` | |
83 | /// use hg::{ matchers::{Matcher, AlwaysMatcher}, utils::hg_path::HgPath }; |
|
84 | /// use hg::{ matchers::{Matcher, AlwaysMatcher}, utils::hg_path::HgPath }; | |
84 | /// |
|
85 | /// | |
85 | /// let matcher = AlwaysMatcher; |
|
86 | /// let matcher = AlwaysMatcher; | |
86 | /// |
|
87 | /// | |
87 | /// assert_eq!(matcher.matches(HgPath::new(b"whatever")), true); |
|
88 | /// assert_eq!(matcher.matches(HgPath::new(b"whatever")), true); | |
88 | /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), true); |
|
89 | /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), true); | |
89 | /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), true); |
|
90 | /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), true); | |
90 | /// assert_eq!(matcher.matches(HgPath::new(br"re:.*\.c$")), true); |
|
91 | /// assert_eq!(matcher.matches(HgPath::new(br"re:.*\.c$")), true); | |
91 | /// ``` |
|
92 | /// ``` | |
92 | #[derive(Debug)] |
|
93 | #[derive(Debug)] | |
93 | pub struct AlwaysMatcher; |
|
94 | pub struct AlwaysMatcher; | |
94 |
|
95 | |||
95 | impl Matcher for AlwaysMatcher { |
|
96 | impl Matcher for AlwaysMatcher { | |
96 | fn file_set(&self) -> Option<&HashSet<&HgPath>> { |
|
97 | fn file_set(&self) -> Option<&HashSet<&HgPath>> { | |
97 | None |
|
98 | None | |
98 | } |
|
99 | } | |
99 | fn exact_match(&self, _filename: impl AsRef<HgPath>) -> bool { |
|
100 | fn exact_match(&self, _filename: impl AsRef<HgPath>) -> bool { | |
100 | false |
|
101 | false | |
101 | } |
|
102 | } | |
102 | fn matches(&self, _filename: impl AsRef<HgPath>) -> bool { |
|
103 | fn matches(&self, _filename: impl AsRef<HgPath>) -> bool { | |
103 | true |
|
104 | true | |
104 | } |
|
105 | } | |
105 | fn visit_children_set( |
|
106 | fn visit_children_set( | |
106 | &self, |
|
107 | &self, | |
107 | _directory: impl AsRef<HgPath>, |
|
108 | _directory: impl AsRef<HgPath>, | |
108 | ) -> VisitChildrenSet { |
|
109 | ) -> VisitChildrenSet { | |
109 | VisitChildrenSet::Recursive |
|
110 | VisitChildrenSet::Recursive | |
110 | } |
|
111 | } | |
111 | fn matches_everything(&self) -> bool { |
|
112 | fn matches_everything(&self) -> bool { | |
112 | true |
|
113 | true | |
113 | } |
|
114 | } | |
114 | fn is_exact(&self) -> bool { |
|
115 | fn is_exact(&self) -> bool { | |
115 | false |
|
116 | false | |
116 | } |
|
117 | } | |
117 | } |
|
118 | } | |
118 |
|
119 | |||
119 | /// Matches the input files exactly. They are interpreted as paths, not |
|
120 | /// Matches the input files exactly. They are interpreted as paths, not | |
120 | /// patterns. |
|
121 | /// patterns. | |
121 | /// |
|
122 | /// | |
122 | ///``` |
|
123 | ///``` | |
123 | /// use hg::{ matchers::{Matcher, FileMatcher}, utils::hg_path::HgPath }; |
|
124 | /// use hg::{ matchers::{Matcher, FileMatcher}, utils::hg_path::HgPath }; | |
124 | /// |
|
125 | /// | |
125 | /// let files = [HgPath::new(b"a.txt"), HgPath::new(br"re:.*\.c$")]; |
|
126 | /// let files = [HgPath::new(b"a.txt"), HgPath::new(br"re:.*\.c$")]; | |
126 | /// let matcher = FileMatcher::new(&files).unwrap(); |
|
127 | /// let matcher = FileMatcher::new(&files).unwrap(); | |
127 | /// |
|
128 | /// | |
128 | /// assert_eq!(matcher.matches(HgPath::new(b"a.txt")), true); |
|
129 | /// assert_eq!(matcher.matches(HgPath::new(b"a.txt")), true); | |
129 | /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), false); |
|
130 | /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), false); | |
130 | /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), false); |
|
131 | /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), false); | |
131 | /// assert_eq!(matcher.matches(HgPath::new(br"re:.*\.c$")), true); |
|
132 | /// assert_eq!(matcher.matches(HgPath::new(br"re:.*\.c$")), true); | |
132 | /// ``` |
|
133 | /// ``` | |
133 | #[derive(Debug)] |
|
134 | #[derive(Debug)] | |
134 | pub struct FileMatcher<'a> { |
|
135 | pub struct FileMatcher<'a> { | |
135 | files: HashSet<&'a HgPath>, |
|
136 | files: HashSet<&'a HgPath>, | |
136 | dirs: DirsMultiset, |
|
137 | dirs: DirsMultiset, | |
137 | } |
|
138 | } | |
138 |
|
139 | |||
139 | impl<'a> FileMatcher<'a> { |
|
140 | impl<'a> FileMatcher<'a> { | |
140 | pub fn new( |
|
141 | pub fn new( | |
141 | files: &'a [impl AsRef<HgPath>], |
|
142 | files: &'a [impl AsRef<HgPath>], | |
142 | ) -> Result<Self, DirstateMapError> { |
|
143 | ) -> Result<Self, DirstateMapError> { | |
143 | Ok(Self { |
|
144 | Ok(Self { | |
144 | files: HashSet::from_iter(files.iter().map(|f| f.as_ref())), |
|
145 | files: HashSet::from_iter(files.iter().map(|f| f.as_ref())), | |
145 | dirs: DirsMultiset::from_manifest(files)?, |
|
146 | dirs: DirsMultiset::from_manifest(files)?, | |
146 | }) |
|
147 | }) | |
147 | } |
|
148 | } | |
148 | fn inner_matches(&self, filename: impl AsRef<HgPath>) -> bool { |
|
149 | fn inner_matches(&self, filename: impl AsRef<HgPath>) -> bool { | |
149 | self.files.contains(filename.as_ref()) |
|
150 | self.files.contains(filename.as_ref()) | |
150 | } |
|
151 | } | |
151 | } |
|
152 | } | |
152 |
|
153 | |||
153 | impl<'a> Matcher for FileMatcher<'a> { |
|
154 | impl<'a> Matcher for FileMatcher<'a> { | |
154 | fn file_set(&self) -> Option<&HashSet<&HgPath>> { |
|
155 | fn file_set(&self) -> Option<&HashSet<&HgPath>> { | |
155 | Some(&self.files) |
|
156 | Some(&self.files) | |
156 | } |
|
157 | } | |
157 | fn exact_match(&self, filename: impl AsRef<HgPath>) -> bool { |
|
158 | fn exact_match(&self, filename: impl AsRef<HgPath>) -> bool { | |
158 | self.inner_matches(filename) |
|
159 | self.inner_matches(filename) | |
159 | } |
|
160 | } | |
160 | fn matches(&self, filename: impl AsRef<HgPath>) -> bool { |
|
161 | fn matches(&self, filename: impl AsRef<HgPath>) -> bool { | |
161 | self.inner_matches(filename) |
|
162 | self.inner_matches(filename) | |
162 | } |
|
163 | } | |
163 | fn visit_children_set( |
|
164 | fn visit_children_set( | |
164 | &self, |
|
165 | &self, | |
165 | _directory: impl AsRef<HgPath>, |
|
166 | _directory: impl AsRef<HgPath>, | |
166 | ) -> VisitChildrenSet { |
|
167 | ) -> VisitChildrenSet { | |
167 | // TODO implement once we have `status.traverse` |
|
168 | // TODO implement once we have `status.traverse` | |
168 | // This is useless until unknown files are taken into account |
|
169 | // This is useless until unknown files are taken into account | |
169 | // Which will not need to happen before the `IncludeMatcher`. |
|
170 | // Which will not need to happen before the `IncludeMatcher`. | |
170 | unimplemented!() |
|
171 | unimplemented!() | |
171 | } |
|
172 | } | |
172 | fn matches_everything(&self) -> bool { |
|
173 | fn matches_everything(&self) -> bool { | |
173 | false |
|
174 | false | |
174 | } |
|
175 | } | |
175 | fn is_exact(&self) -> bool { |
|
176 | fn is_exact(&self) -> bool { | |
176 | true |
|
177 | true | |
177 | } |
|
178 | } | |
178 | } |
|
179 | } |
General Comments 0
You need to be logged in to leave comments.
Login now