weavatrix_scan/ignore/repository/
matching.rs1use super::{
2 Cow, Error, IgnoreRules, IgnoreSourceEvidence, Path, PathBuf, RepositoryMatch,
3 RepositoryMatcher, Result, RuleAction, ScanWarning, io, is_hidden_with_hint,
4 match_prepared_rules, match_rules, normalized_relative_path,
5};
6
7impl RepositoryMatcher {
8 pub fn matched(
16 &mut self,
17 path: impl AsRef<Path>,
18 is_directory: bool,
19 ) -> Result<RepositoryMatch> {
20 let absolute = self.absolute_path(path.as_ref())?;
21 self.ensure_scan_scope(&absolute)?;
22 let parent = absolute.parent().unwrap_or(&self.match_root).to_path_buf();
23 self.prepare_directory_inner(&parent)?;
24 let scan_relative = absolute.strip_prefix(&self.scan_root).map_err(|_| {
25 Error::io(
26 &absolute,
27 io::Error::new(io::ErrorKind::InvalidInput, "path escapes matcher root"),
28 )
29 })?;
30 Ok(self.matched_prepared(
31 &normalized_relative_path(scan_relative),
32 &parent,
33 &absolute,
34 is_directory,
35 None,
36 false,
37 ))
38 }
39
40 pub fn is_ignored(&mut self, path: impl AsRef<Path>, is_directory: bool) -> Result<bool> {
46 self.matched(path, is_directory)
47 .map(RepositoryMatch::is_ignored)
48 }
49
50 pub fn normalize(&self, path: impl AsRef<Path>) -> Result<PathBuf> {
60 let absolute = self.absolute_path(path.as_ref())?;
61 self.ensure_scan_scope(&absolute)?;
62 absolute
63 .strip_prefix(&self.scan_root)
64 .map(Path::to_path_buf)
65 .map_err(|_| {
66 Error::io(
67 &absolute,
68 io::Error::new(io::ErrorKind::InvalidInput, "path escapes matcher root"),
69 )
70 })
71 }
72
73 pub fn prepare_directory(&mut self, directory: impl AsRef<Path>) -> Result<()> {
79 let absolute = self.absolute_path(directory.as_ref())?;
80 self.ensure_scan_scope(&absolute)?;
81 self.prepare_directory_inner(&absolute)
82 }
83
84 pub fn refresh(&mut self) -> Result<bool> {
92 let refreshed = Self::with_options(&self.scan_root, &self.options)?;
93 let changed = self.sources != refreshed.sources
94 || self.portable != refreshed.portable
95 || self.warnings != refreshed.warnings;
96 *self = refreshed;
97 Ok(changed)
98 }
99
100 #[must_use]
101 pub fn root(&self) -> &Path {
102 &self.scan_root
103 }
104
105 #[must_use]
106 pub fn sources(&self) -> &[IgnoreSourceEvidence] {
107 &self.sources
108 }
109
110 #[must_use]
111 pub fn warnings(&self) -> &[ScanWarning] {
112 &self.warnings
113 }
114
115 #[must_use]
116 pub const fn portable(&self) -> bool {
117 self.portable
118 }
119
120 pub(crate) fn matched_prepared(
121 &self,
122 scan_relative: &str,
123 parent: &Path,
124 absolute: &Path,
125 is_directory: bool,
126 hidden: Option<bool>,
127 ancestors_prepared: bool,
128 ) -> RepositoryMatch {
129 let rules = self.directories.get(parent).unwrap_or(&self.base_rules);
130 self.matched_with_rules(
131 scan_relative,
132 absolute,
133 is_directory,
134 hidden,
135 rules,
136 ancestors_prepared,
137 )
138 }
139
140 pub(crate) fn prepared_rules(&self, parent: &Path) -> &IgnoreRules {
141 self.directories.get(parent).unwrap_or(&self.base_rules)
142 }
143
144 pub(crate) fn matched_with_rules(
145 &self,
146 scan_relative: &str,
147 absolute: &Path,
148 is_directory: bool,
149 hidden: Option<bool>,
150 rules: &IgnoreRules,
151 ancestors_prepared: bool,
152 ) -> RepositoryMatch {
153 let override_match = self.overrides.matched(scan_relative, is_directory);
154 if override_match != RepositoryMatch::None {
155 return override_match;
156 }
157 let candidate = if self.scan_base.is_empty() {
158 Cow::Borrowed(scan_relative)
159 } else {
160 Cow::Owned(format!("{}/{scan_relative}", self.scan_base))
161 };
162 let matched = if ancestors_prepared {
163 match_prepared_rules(&candidate, is_directory, rules)
164 } else {
165 match_rules(&candidate, is_directory, rules)
166 };
167 match matched {
168 Some(RuleAction::Ignore) => RepositoryMatch::Ignore,
169 Some(RuleAction::Include) => RepositoryMatch::Include,
170 None if self.skip_hidden && is_hidden_with_hint(absolute, hidden) => {
171 RepositoryMatch::Hidden
172 }
173 None => RepositoryMatch::None,
174 }
175 }
176}