##// END OF EJS Templates
rust-filepatterns: don't `Box` subincludes unnecessarily...
Raphaël Gomès -
r50823:ccb6cfb0 default
parent child Browse files
Show More
@@ -1,772 +1,772
1 // filepatterns.rs
1 // filepatterns.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 //! Handling of Mercurial-specific patterns.
8 //! Handling of Mercurial-specific patterns.
9
9
10 use crate::{
10 use crate::{
11 utils::{
11 utils::{
12 files::{canonical_path, get_bytes_from_path, get_path_from_bytes},
12 files::{canonical_path, get_bytes_from_path, get_path_from_bytes},
13 hg_path::{path_to_hg_path_buf, HgPathBuf, HgPathError},
13 hg_path::{path_to_hg_path_buf, HgPathBuf, HgPathError},
14 SliceExt,
14 SliceExt,
15 },
15 },
16 FastHashMap, PatternError,
16 FastHashMap, PatternError,
17 };
17 };
18 use lazy_static::lazy_static;
18 use lazy_static::lazy_static;
19 use regex::bytes::{NoExpand, Regex};
19 use regex::bytes::{NoExpand, Regex};
20 use std::ops::Deref;
20 use std::ops::Deref;
21 use std::path::{Path, PathBuf};
21 use std::path::{Path, PathBuf};
22 use std::vec::Vec;
22 use std::vec::Vec;
23
23
24 lazy_static! {
24 lazy_static! {
25 static ref RE_ESCAPE: Vec<Vec<u8>> = {
25 static ref RE_ESCAPE: Vec<Vec<u8>> = {
26 let mut v: Vec<Vec<u8>> = (0..=255).map(|byte| vec![byte]).collect();
26 let mut v: Vec<Vec<u8>> = (0..=255).map(|byte| vec![byte]).collect();
27 let to_escape = b"()[]{}?*+-|^$\\.&~# \t\n\r\x0b\x0c";
27 let to_escape = b"()[]{}?*+-|^$\\.&~# \t\n\r\x0b\x0c";
28 for byte in to_escape {
28 for byte in to_escape {
29 v[*byte as usize].insert(0, b'\\');
29 v[*byte as usize].insert(0, b'\\');
30 }
30 }
31 v
31 v
32 };
32 };
33 }
33 }
34
34
35 /// These are matched in order
35 /// These are matched in order
36 const GLOB_REPLACEMENTS: &[(&[u8], &[u8])] =
36 const GLOB_REPLACEMENTS: &[(&[u8], &[u8])] =
37 &[(b"*/", b"(?:.*/)?"), (b"*", b".*"), (b"", b"[^/]*")];
37 &[(b"*/", b"(?:.*/)?"), (b"*", b".*"), (b"", b"[^/]*")];
38
38
39 /// Appended to the regexp of globs
39 /// Appended to the regexp of globs
40 const GLOB_SUFFIX: &[u8; 7] = b"(?:/|$)";
40 const GLOB_SUFFIX: &[u8; 7] = b"(?:/|$)";
41
41
42 #[derive(Debug, Clone, PartialEq, Eq)]
42 #[derive(Debug, Clone, PartialEq, Eq)]
43 pub enum PatternSyntax {
43 pub enum PatternSyntax {
44 /// A regular expression
44 /// A regular expression
45 Regexp,
45 Regexp,
46 /// Glob that matches at the front of the path
46 /// Glob that matches at the front of the path
47 RootGlob,
47 RootGlob,
48 /// Glob that matches at any suffix of the path (still anchored at
48 /// Glob that matches at any suffix of the path (still anchored at
49 /// slashes)
49 /// slashes)
50 Glob,
50 Glob,
51 /// a path relative to repository root, which is matched recursively
51 /// a path relative to repository root, which is matched recursively
52 Path,
52 Path,
53 /// A path relative to cwd
53 /// A path relative to cwd
54 RelPath,
54 RelPath,
55 /// an unrooted glob (*.rs matches Rust files in all dirs)
55 /// an unrooted glob (*.rs matches Rust files in all dirs)
56 RelGlob,
56 RelGlob,
57 /// A regexp that needn't match the start of a name
57 /// A regexp that needn't match the start of a name
58 RelRegexp,
58 RelRegexp,
59 /// A path relative to repository root, which is matched non-recursively
59 /// A path relative to repository root, which is matched non-recursively
60 /// (will not match subdirectories)
60 /// (will not match subdirectories)
61 RootFiles,
61 RootFiles,
62 /// A file of patterns to read and include
62 /// A file of patterns to read and include
63 Include,
63 Include,
64 /// A file of patterns to match against files under the same directory
64 /// A file of patterns to match against files under the same directory
65 SubInclude,
65 SubInclude,
66 /// SubInclude with the result of parsing the included file
66 /// SubInclude with the result of parsing the included file
67 ///
67 ///
68 /// Note: there is no ExpandedInclude because that expansion can be done
68 /// Note: there is no ExpandedInclude because that expansion can be done
69 /// in place by replacing the Include pattern by the included patterns.
69 /// in place by replacing the Include pattern by the included patterns.
70 /// SubInclude requires more handling.
70 /// SubInclude requires more handling.
71 ///
71 ///
72 /// Note: `Box` is used to minimize size impact on other enum variants
72 /// Note: `Box` is used to minimize size impact on other enum variants
73 ExpandedSubInclude(Box<SubInclude>),
73 ExpandedSubInclude(Box<SubInclude>),
74 }
74 }
75
75
76 /// Transforms a glob pattern into a regex
76 /// Transforms a glob pattern into a regex
77 fn glob_to_re(pat: &[u8]) -> Vec<u8> {
77 fn glob_to_re(pat: &[u8]) -> Vec<u8> {
78 let mut input = pat;
78 let mut input = pat;
79 let mut res: Vec<u8> = vec![];
79 let mut res: Vec<u8> = vec![];
80 let mut group_depth = 0;
80 let mut group_depth = 0;
81
81
82 while let Some((c, rest)) = input.split_first() {
82 while let Some((c, rest)) = input.split_first() {
83 input = rest;
83 input = rest;
84
84
85 match c {
85 match c {
86 b'*' => {
86 b'*' => {
87 for (source, repl) in GLOB_REPLACEMENTS {
87 for (source, repl) in GLOB_REPLACEMENTS {
88 if let Some(rest) = input.drop_prefix(source) {
88 if let Some(rest) = input.drop_prefix(source) {
89 input = rest;
89 input = rest;
90 res.extend(*repl);
90 res.extend(*repl);
91 break;
91 break;
92 }
92 }
93 }
93 }
94 }
94 }
95 b'?' => res.extend(b"."),
95 b'?' => res.extend(b"."),
96 b'[' => {
96 b'[' => {
97 match input.iter().skip(1).position(|b| *b == b']') {
97 match input.iter().skip(1).position(|b| *b == b']') {
98 None => res.extend(b"\\["),
98 None => res.extend(b"\\["),
99 Some(end) => {
99 Some(end) => {
100 // Account for the one we skipped
100 // Account for the one we skipped
101 let end = end + 1;
101 let end = end + 1;
102
102
103 res.extend(b"[");
103 res.extend(b"[");
104
104
105 for (i, b) in input[..end].iter().enumerate() {
105 for (i, b) in input[..end].iter().enumerate() {
106 if *b == b'!' && i == 0 {
106 if *b == b'!' && i == 0 {
107 res.extend(b"^")
107 res.extend(b"^")
108 } else if *b == b'^' && i == 0 {
108 } else if *b == b'^' && i == 0 {
109 res.extend(b"\\^")
109 res.extend(b"\\^")
110 } else if *b == b'\\' {
110 } else if *b == b'\\' {
111 res.extend(b"\\\\")
111 res.extend(b"\\\\")
112 } else {
112 } else {
113 res.push(*b)
113 res.push(*b)
114 }
114 }
115 }
115 }
116 res.extend(b"]");
116 res.extend(b"]");
117 input = &input[end + 1..];
117 input = &input[end + 1..];
118 }
118 }
119 }
119 }
120 }
120 }
121 b'{' => {
121 b'{' => {
122 group_depth += 1;
122 group_depth += 1;
123 res.extend(b"(?:")
123 res.extend(b"(?:")
124 }
124 }
125 b'}' if group_depth > 0 => {
125 b'}' if group_depth > 0 => {
126 group_depth -= 1;
126 group_depth -= 1;
127 res.extend(b")");
127 res.extend(b")");
128 }
128 }
129 b',' if group_depth > 0 => res.extend(b"|"),
129 b',' if group_depth > 0 => res.extend(b"|"),
130 b'\\' => {
130 b'\\' => {
131 let c = {
131 let c = {
132 if let Some((c, rest)) = input.split_first() {
132 if let Some((c, rest)) = input.split_first() {
133 input = rest;
133 input = rest;
134 c
134 c
135 } else {
135 } else {
136 c
136 c
137 }
137 }
138 };
138 };
139 res.extend(&RE_ESCAPE[*c as usize])
139 res.extend(&RE_ESCAPE[*c as usize])
140 }
140 }
141 _ => res.extend(&RE_ESCAPE[*c as usize]),
141 _ => res.extend(&RE_ESCAPE[*c as usize]),
142 }
142 }
143 }
143 }
144 res
144 res
145 }
145 }
146
146
147 fn escape_pattern(pattern: &[u8]) -> Vec<u8> {
147 fn escape_pattern(pattern: &[u8]) -> Vec<u8> {
148 pattern
148 pattern
149 .iter()
149 .iter()
150 .flat_map(|c| RE_ESCAPE[*c as usize].clone())
150 .flat_map(|c| RE_ESCAPE[*c as usize].clone())
151 .collect()
151 .collect()
152 }
152 }
153
153
154 pub fn parse_pattern_syntax(
154 pub fn parse_pattern_syntax(
155 kind: &[u8],
155 kind: &[u8],
156 ) -> Result<PatternSyntax, PatternError> {
156 ) -> Result<PatternSyntax, PatternError> {
157 match kind {
157 match kind {
158 b"re:" => Ok(PatternSyntax::Regexp),
158 b"re:" => Ok(PatternSyntax::Regexp),
159 b"path:" => Ok(PatternSyntax::Path),
159 b"path:" => Ok(PatternSyntax::Path),
160 b"relpath:" => Ok(PatternSyntax::RelPath),
160 b"relpath:" => Ok(PatternSyntax::RelPath),
161 b"rootfilesin:" => Ok(PatternSyntax::RootFiles),
161 b"rootfilesin:" => Ok(PatternSyntax::RootFiles),
162 b"relglob:" => Ok(PatternSyntax::RelGlob),
162 b"relglob:" => Ok(PatternSyntax::RelGlob),
163 b"relre:" => Ok(PatternSyntax::RelRegexp),
163 b"relre:" => Ok(PatternSyntax::RelRegexp),
164 b"glob:" => Ok(PatternSyntax::Glob),
164 b"glob:" => Ok(PatternSyntax::Glob),
165 b"rootglob:" => Ok(PatternSyntax::RootGlob),
165 b"rootglob:" => Ok(PatternSyntax::RootGlob),
166 b"include:" => Ok(PatternSyntax::Include),
166 b"include:" => Ok(PatternSyntax::Include),
167 b"subinclude:" => Ok(PatternSyntax::SubInclude),
167 b"subinclude:" => Ok(PatternSyntax::SubInclude),
168 _ => Err(PatternError::UnsupportedSyntax(
168 _ => Err(PatternError::UnsupportedSyntax(
169 String::from_utf8_lossy(kind).to_string(),
169 String::from_utf8_lossy(kind).to_string(),
170 )),
170 )),
171 }
171 }
172 }
172 }
173
173
174 lazy_static! {
174 lazy_static! {
175 static ref FLAG_RE: Regex = Regex::new(r"^\(\?[aiLmsux]+\)").unwrap();
175 static ref FLAG_RE: Regex = Regex::new(r"^\(\?[aiLmsux]+\)").unwrap();
176 }
176 }
177
177
178 /// Builds the regex that corresponds to the given pattern.
178 /// Builds the regex that corresponds to the given pattern.
179 /// If within a `syntax: regexp` context, returns the pattern,
179 /// If within a `syntax: regexp` context, returns the pattern,
180 /// otherwise, returns the corresponding regex.
180 /// otherwise, returns the corresponding regex.
181 fn _build_single_regex(entry: &IgnorePattern) -> Vec<u8> {
181 fn _build_single_regex(entry: &IgnorePattern) -> Vec<u8> {
182 let IgnorePattern {
182 let IgnorePattern {
183 syntax, pattern, ..
183 syntax, pattern, ..
184 } = entry;
184 } = entry;
185 if pattern.is_empty() {
185 if pattern.is_empty() {
186 return vec![];
186 return vec![];
187 }
187 }
188 match syntax {
188 match syntax {
189 PatternSyntax::Regexp => pattern.to_owned(),
189 PatternSyntax::Regexp => pattern.to_owned(),
190 PatternSyntax::RelRegexp => {
190 PatternSyntax::RelRegexp => {
191 // The `regex` crate accepts `**` while `re2` and Python's `re`
191 // The `regex` crate accepts `**` while `re2` and Python's `re`
192 // do not. Checking for `*` correctly triggers the same error all
192 // do not. Checking for `*` correctly triggers the same error all
193 // engines.
193 // engines.
194 if pattern[0] == b'^'
194 if pattern[0] == b'^'
195 || pattern[0] == b'*'
195 || pattern[0] == b'*'
196 || pattern.starts_with(b".*")
196 || pattern.starts_with(b".*")
197 {
197 {
198 return pattern.to_owned();
198 return pattern.to_owned();
199 }
199 }
200 match FLAG_RE.find(pattern) {
200 match FLAG_RE.find(pattern) {
201 Some(mat) => {
201 Some(mat) => {
202 let s = mat.start();
202 let s = mat.start();
203 let e = mat.end();
203 let e = mat.end();
204 [
204 [
205 &b"(?"[..],
205 &b"(?"[..],
206 &pattern[s + 2..e - 1],
206 &pattern[s + 2..e - 1],
207 &b":"[..],
207 &b":"[..],
208 if pattern[e] == b'^'
208 if pattern[e] == b'^'
209 || pattern[e] == b'*'
209 || pattern[e] == b'*'
210 || pattern[e..].starts_with(b".*")
210 || pattern[e..].starts_with(b".*")
211 {
211 {
212 &b""[..]
212 &b""[..]
213 } else {
213 } else {
214 &b".*"[..]
214 &b".*"[..]
215 },
215 },
216 &pattern[e..],
216 &pattern[e..],
217 &b")"[..],
217 &b")"[..],
218 ]
218 ]
219 .concat()
219 .concat()
220 }
220 }
221 None => [&b".*"[..], pattern].concat(),
221 None => [&b".*"[..], pattern].concat(),
222 }
222 }
223 }
223 }
224 PatternSyntax::Path | PatternSyntax::RelPath => {
224 PatternSyntax::Path | PatternSyntax::RelPath => {
225 if pattern == b"." {
225 if pattern == b"." {
226 return vec![];
226 return vec![];
227 }
227 }
228 [escape_pattern(pattern).as_slice(), b"(?:/|$)"].concat()
228 [escape_pattern(pattern).as_slice(), b"(?:/|$)"].concat()
229 }
229 }
230 PatternSyntax::RootFiles => {
230 PatternSyntax::RootFiles => {
231 let mut res = if pattern == b"." {
231 let mut res = if pattern == b"." {
232 vec![]
232 vec![]
233 } else {
233 } else {
234 // Pattern is a directory name.
234 // Pattern is a directory name.
235 [escape_pattern(pattern).as_slice(), b"/"].concat()
235 [escape_pattern(pattern).as_slice(), b"/"].concat()
236 };
236 };
237
237
238 // Anything after the pattern must be a non-directory.
238 // Anything after the pattern must be a non-directory.
239 res.extend(b"[^/]+$");
239 res.extend(b"[^/]+$");
240 res
240 res
241 }
241 }
242 PatternSyntax::RelGlob => {
242 PatternSyntax::RelGlob => {
243 let glob_re = glob_to_re(pattern);
243 let glob_re = glob_to_re(pattern);
244 if let Some(rest) = glob_re.drop_prefix(b"[^/]*") {
244 if let Some(rest) = glob_re.drop_prefix(b"[^/]*") {
245 [b".*", rest, GLOB_SUFFIX].concat()
245 [b".*", rest, GLOB_SUFFIX].concat()
246 } else {
246 } else {
247 [b"(?:.*/)?", glob_re.as_slice(), GLOB_SUFFIX].concat()
247 [b"(?:.*/)?", glob_re.as_slice(), GLOB_SUFFIX].concat()
248 }
248 }
249 }
249 }
250 PatternSyntax::Glob | PatternSyntax::RootGlob => {
250 PatternSyntax::Glob | PatternSyntax::RootGlob => {
251 [glob_to_re(pattern).as_slice(), GLOB_SUFFIX].concat()
251 [glob_to_re(pattern).as_slice(), GLOB_SUFFIX].concat()
252 }
252 }
253 PatternSyntax::Include
253 PatternSyntax::Include
254 | PatternSyntax::SubInclude
254 | PatternSyntax::SubInclude
255 | PatternSyntax::ExpandedSubInclude(_) => unreachable!(),
255 | PatternSyntax::ExpandedSubInclude(_) => unreachable!(),
256 }
256 }
257 }
257 }
258
258
259 const GLOB_SPECIAL_CHARACTERS: [u8; 7] =
259 const GLOB_SPECIAL_CHARACTERS: [u8; 7] =
260 [b'*', b'?', b'[', b']', b'{', b'}', b'\\'];
260 [b'*', b'?', b'[', b']', b'{', b'}', b'\\'];
261
261
262 /// TODO support other platforms
262 /// TODO support other platforms
263 #[cfg(unix)]
263 #[cfg(unix)]
264 pub fn normalize_path_bytes(bytes: &[u8]) -> Vec<u8> {
264 pub fn normalize_path_bytes(bytes: &[u8]) -> Vec<u8> {
265 if bytes.is_empty() {
265 if bytes.is_empty() {
266 return b".".to_vec();
266 return b".".to_vec();
267 }
267 }
268 let sep = b'/';
268 let sep = b'/';
269
269
270 let mut initial_slashes = bytes.iter().take_while(|b| **b == sep).count();
270 let mut initial_slashes = bytes.iter().take_while(|b| **b == sep).count();
271 if initial_slashes > 2 {
271 if initial_slashes > 2 {
272 // POSIX allows one or two initial slashes, but treats three or more
272 // POSIX allows one or two initial slashes, but treats three or more
273 // as single slash.
273 // as single slash.
274 initial_slashes = 1;
274 initial_slashes = 1;
275 }
275 }
276 let components = bytes
276 let components = bytes
277 .split(|b| *b == sep)
277 .split(|b| *b == sep)
278 .filter(|c| !(c.is_empty() || c == b"."))
278 .filter(|c| !(c.is_empty() || c == b"."))
279 .fold(vec![], |mut acc, component| {
279 .fold(vec![], |mut acc, component| {
280 if component != b".."
280 if component != b".."
281 || (initial_slashes == 0 && acc.is_empty())
281 || (initial_slashes == 0 && acc.is_empty())
282 || (!acc.is_empty() && acc[acc.len() - 1] == b"..")
282 || (!acc.is_empty() && acc[acc.len() - 1] == b"..")
283 {
283 {
284 acc.push(component)
284 acc.push(component)
285 } else if !acc.is_empty() {
285 } else if !acc.is_empty() {
286 acc.pop();
286 acc.pop();
287 }
287 }
288 acc
288 acc
289 });
289 });
290 let mut new_bytes = components.join(&sep);
290 let mut new_bytes = components.join(&sep);
291
291
292 if initial_slashes > 0 {
292 if initial_slashes > 0 {
293 let mut buf: Vec<_> = (0..initial_slashes).map(|_| sep).collect();
293 let mut buf: Vec<_> = (0..initial_slashes).map(|_| sep).collect();
294 buf.extend(new_bytes);
294 buf.extend(new_bytes);
295 new_bytes = buf;
295 new_bytes = buf;
296 }
296 }
297 if new_bytes.is_empty() {
297 if new_bytes.is_empty() {
298 b".".to_vec()
298 b".".to_vec()
299 } else {
299 } else {
300 new_bytes
300 new_bytes
301 }
301 }
302 }
302 }
303
303
304 /// Wrapper function to `_build_single_regex` that short-circuits 'exact' globs
304 /// Wrapper function to `_build_single_regex` that short-circuits 'exact' globs
305 /// that don't need to be transformed into a regex.
305 /// that don't need to be transformed into a regex.
306 pub fn build_single_regex(
306 pub fn build_single_regex(
307 entry: &IgnorePattern,
307 entry: &IgnorePattern,
308 ) -> Result<Option<Vec<u8>>, PatternError> {
308 ) -> Result<Option<Vec<u8>>, PatternError> {
309 let IgnorePattern {
309 let IgnorePattern {
310 pattern, syntax, ..
310 pattern, syntax, ..
311 } = entry;
311 } = entry;
312 let pattern = match syntax {
312 let pattern = match syntax {
313 PatternSyntax::RootGlob
313 PatternSyntax::RootGlob
314 | PatternSyntax::Path
314 | PatternSyntax::Path
315 | PatternSyntax::RelGlob
315 | PatternSyntax::RelGlob
316 | PatternSyntax::RootFiles => normalize_path_bytes(&pattern),
316 | PatternSyntax::RootFiles => normalize_path_bytes(&pattern),
317 PatternSyntax::Include | PatternSyntax::SubInclude => {
317 PatternSyntax::Include | PatternSyntax::SubInclude => {
318 return Err(PatternError::NonRegexPattern(entry.clone()))
318 return Err(PatternError::NonRegexPattern(entry.clone()))
319 }
319 }
320 _ => pattern.to_owned(),
320 _ => pattern.to_owned(),
321 };
321 };
322 if *syntax == PatternSyntax::RootGlob
322 if *syntax == PatternSyntax::RootGlob
323 && !pattern.iter().any(|b| GLOB_SPECIAL_CHARACTERS.contains(b))
323 && !pattern.iter().any(|b| GLOB_SPECIAL_CHARACTERS.contains(b))
324 {
324 {
325 Ok(None)
325 Ok(None)
326 } else {
326 } else {
327 let mut entry = entry.clone();
327 let mut entry = entry.clone();
328 entry.pattern = pattern;
328 entry.pattern = pattern;
329 Ok(Some(_build_single_regex(&entry)))
329 Ok(Some(_build_single_regex(&entry)))
330 }
330 }
331 }
331 }
332
332
333 lazy_static! {
333 lazy_static! {
334 static ref SYNTAXES: FastHashMap<&'static [u8], &'static [u8]> = {
334 static ref SYNTAXES: FastHashMap<&'static [u8], &'static [u8]> = {
335 let mut m = FastHashMap::default();
335 let mut m = FastHashMap::default();
336
336
337 m.insert(b"re".as_ref(), b"relre:".as_ref());
337 m.insert(b"re".as_ref(), b"relre:".as_ref());
338 m.insert(b"regexp".as_ref(), b"relre:".as_ref());
338 m.insert(b"regexp".as_ref(), b"relre:".as_ref());
339 m.insert(b"glob".as_ref(), b"relglob:".as_ref());
339 m.insert(b"glob".as_ref(), b"relglob:".as_ref());
340 m.insert(b"rootglob".as_ref(), b"rootglob:".as_ref());
340 m.insert(b"rootglob".as_ref(), b"rootglob:".as_ref());
341 m.insert(b"include".as_ref(), b"include:".as_ref());
341 m.insert(b"include".as_ref(), b"include:".as_ref());
342 m.insert(b"subinclude".as_ref(), b"subinclude:".as_ref());
342 m.insert(b"subinclude".as_ref(), b"subinclude:".as_ref());
343 m.insert(b"path".as_ref(), b"path:".as_ref());
343 m.insert(b"path".as_ref(), b"path:".as_ref());
344 m.insert(b"rootfilesin".as_ref(), b"rootfilesin:".as_ref());
344 m.insert(b"rootfilesin".as_ref(), b"rootfilesin:".as_ref());
345 m
345 m
346 };
346 };
347 }
347 }
348
348
349 #[derive(Debug)]
349 #[derive(Debug)]
350 pub enum PatternFileWarning {
350 pub enum PatternFileWarning {
351 /// (file path, syntax bytes)
351 /// (file path, syntax bytes)
352 InvalidSyntax(PathBuf, Vec<u8>),
352 InvalidSyntax(PathBuf, Vec<u8>),
353 /// File path
353 /// File path
354 NoSuchFile(PathBuf),
354 NoSuchFile(PathBuf),
355 }
355 }
356
356
357 pub fn parse_pattern_file_contents(
357 pub fn parse_pattern_file_contents(
358 lines: &[u8],
358 lines: &[u8],
359 file_path: &Path,
359 file_path: &Path,
360 default_syntax_override: Option<&[u8]>,
360 default_syntax_override: Option<&[u8]>,
361 warn: bool,
361 warn: bool,
362 ) -> Result<(Vec<IgnorePattern>, Vec<PatternFileWarning>), PatternError> {
362 ) -> Result<(Vec<IgnorePattern>, Vec<PatternFileWarning>), PatternError> {
363 let comment_regex = Regex::new(r"((?:^|[^\\])(?:\\\\)*)#.*").unwrap();
363 let comment_regex = Regex::new(r"((?:^|[^\\])(?:\\\\)*)#.*").unwrap();
364
364
365 #[allow(clippy::trivial_regex)]
365 #[allow(clippy::trivial_regex)]
366 let comment_escape_regex = Regex::new(r"\\#").unwrap();
366 let comment_escape_regex = Regex::new(r"\\#").unwrap();
367 let mut inputs: Vec<IgnorePattern> = vec![];
367 let mut inputs: Vec<IgnorePattern> = vec![];
368 let mut warnings: Vec<PatternFileWarning> = vec![];
368 let mut warnings: Vec<PatternFileWarning> = vec![];
369
369
370 let mut current_syntax =
370 let mut current_syntax =
371 default_syntax_override.unwrap_or(b"relre:".as_ref());
371 default_syntax_override.unwrap_or(b"relre:".as_ref());
372
372
373 for (line_number, mut line) in lines.split(|c| *c == b'\n').enumerate() {
373 for (line_number, mut line) in lines.split(|c| *c == b'\n').enumerate() {
374 let line_number = line_number + 1;
374 let line_number = line_number + 1;
375
375
376 let line_buf;
376 let line_buf;
377 if line.contains(&b'#') {
377 if line.contains(&b'#') {
378 if let Some(cap) = comment_regex.captures(line) {
378 if let Some(cap) = comment_regex.captures(line) {
379 line = &line[..cap.get(1).unwrap().end()]
379 line = &line[..cap.get(1).unwrap().end()]
380 }
380 }
381 line_buf = comment_escape_regex.replace_all(line, NoExpand(b"#"));
381 line_buf = comment_escape_regex.replace_all(line, NoExpand(b"#"));
382 line = &line_buf;
382 line = &line_buf;
383 }
383 }
384
384
385 let mut line = line.trim_end();
385 let mut line = line.trim_end();
386
386
387 if line.is_empty() {
387 if line.is_empty() {
388 continue;
388 continue;
389 }
389 }
390
390
391 if let Some(syntax) = line.drop_prefix(b"syntax:") {
391 if let Some(syntax) = line.drop_prefix(b"syntax:") {
392 let syntax = syntax.trim();
392 let syntax = syntax.trim();
393
393
394 if let Some(rel_syntax) = SYNTAXES.get(syntax) {
394 if let Some(rel_syntax) = SYNTAXES.get(syntax) {
395 current_syntax = rel_syntax;
395 current_syntax = rel_syntax;
396 } else if warn {
396 } else if warn {
397 warnings.push(PatternFileWarning::InvalidSyntax(
397 warnings.push(PatternFileWarning::InvalidSyntax(
398 file_path.to_owned(),
398 file_path.to_owned(),
399 syntax.to_owned(),
399 syntax.to_owned(),
400 ));
400 ));
401 }
401 }
402 continue;
402 continue;
403 }
403 }
404
404
405 let mut line_syntax: &[u8] = &current_syntax;
405 let mut line_syntax: &[u8] = &current_syntax;
406
406
407 for (s, rels) in SYNTAXES.iter() {
407 for (s, rels) in SYNTAXES.iter() {
408 if let Some(rest) = line.drop_prefix(rels) {
408 if let Some(rest) = line.drop_prefix(rels) {
409 line_syntax = rels;
409 line_syntax = rels;
410 line = rest;
410 line = rest;
411 break;
411 break;
412 }
412 }
413 if let Some(rest) = line.drop_prefix(&[s, &b":"[..]].concat()) {
413 if let Some(rest) = line.drop_prefix(&[s, &b":"[..]].concat()) {
414 line_syntax = rels;
414 line_syntax = rels;
415 line = rest;
415 line = rest;
416 break;
416 break;
417 }
417 }
418 }
418 }
419
419
420 inputs.push(IgnorePattern::new(
420 inputs.push(IgnorePattern::new(
421 parse_pattern_syntax(&line_syntax).map_err(|e| match e {
421 parse_pattern_syntax(&line_syntax).map_err(|e| match e {
422 PatternError::UnsupportedSyntax(syntax) => {
422 PatternError::UnsupportedSyntax(syntax) => {
423 PatternError::UnsupportedSyntaxInFile(
423 PatternError::UnsupportedSyntaxInFile(
424 syntax,
424 syntax,
425 file_path.to_string_lossy().into(),
425 file_path.to_string_lossy().into(),
426 line_number,
426 line_number,
427 )
427 )
428 }
428 }
429 _ => e,
429 _ => e,
430 })?,
430 })?,
431 &line,
431 &line,
432 file_path,
432 file_path,
433 ));
433 ));
434 }
434 }
435 Ok((inputs, warnings))
435 Ok((inputs, warnings))
436 }
436 }
437
437
438 pub fn read_pattern_file(
438 pub fn read_pattern_file(
439 file_path: &Path,
439 file_path: &Path,
440 warn: bool,
440 warn: bool,
441 inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
441 inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
442 ) -> Result<(Vec<IgnorePattern>, Vec<PatternFileWarning>), PatternError> {
442 ) -> Result<(Vec<IgnorePattern>, Vec<PatternFileWarning>), PatternError> {
443 match std::fs::read(file_path) {
443 match std::fs::read(file_path) {
444 Ok(contents) => {
444 Ok(contents) => {
445 inspect_pattern_bytes(file_path, &contents);
445 inspect_pattern_bytes(file_path, &contents);
446 parse_pattern_file_contents(&contents, file_path, None, warn)
446 parse_pattern_file_contents(&contents, file_path, None, warn)
447 }
447 }
448 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok((
448 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok((
449 vec![],
449 vec![],
450 vec![PatternFileWarning::NoSuchFile(file_path.to_owned())],
450 vec![PatternFileWarning::NoSuchFile(file_path.to_owned())],
451 )),
451 )),
452 Err(e) => Err(e.into()),
452 Err(e) => Err(e.into()),
453 }
453 }
454 }
454 }
455
455
456 /// Represents an entry in an "ignore" file.
456 /// Represents an entry in an "ignore" file.
457 #[derive(Debug, Eq, PartialEq, Clone)]
457 #[derive(Debug, Eq, PartialEq, Clone)]
458 pub struct IgnorePattern {
458 pub struct IgnorePattern {
459 pub syntax: PatternSyntax,
459 pub syntax: PatternSyntax,
460 pub pattern: Vec<u8>,
460 pub pattern: Vec<u8>,
461 pub source: PathBuf,
461 pub source: PathBuf,
462 }
462 }
463
463
464 impl IgnorePattern {
464 impl IgnorePattern {
465 pub fn new(syntax: PatternSyntax, pattern: &[u8], source: &Path) -> Self {
465 pub fn new(syntax: PatternSyntax, pattern: &[u8], source: &Path) -> Self {
466 Self {
466 Self {
467 syntax,
467 syntax,
468 pattern: pattern.to_owned(),
468 pattern: pattern.to_owned(),
469 source: source.to_owned(),
469 source: source.to_owned(),
470 }
470 }
471 }
471 }
472 }
472 }
473
473
474 pub type PatternResult<T> = Result<T, PatternError>;
474 pub type PatternResult<T> = Result<T, PatternError>;
475
475
476 /// Wrapper for `read_pattern_file` that also recursively expands `include:`
476 /// Wrapper for `read_pattern_file` that also recursively expands `include:`
477 /// and `subinclude:` patterns.
477 /// and `subinclude:` patterns.
478 ///
478 ///
479 /// The former are expanded in place, while `PatternSyntax::ExpandedSubInclude`
479 /// The former are expanded in place, while `PatternSyntax::ExpandedSubInclude`
480 /// is used for the latter to form a tree of patterns.
480 /// is used for the latter to form a tree of patterns.
481 pub fn get_patterns_from_file(
481 pub fn get_patterns_from_file(
482 pattern_file: &Path,
482 pattern_file: &Path,
483 root_dir: &Path,
483 root_dir: &Path,
484 inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
484 inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
485 ) -> PatternResult<(Vec<IgnorePattern>, Vec<PatternFileWarning>)> {
485 ) -> PatternResult<(Vec<IgnorePattern>, Vec<PatternFileWarning>)> {
486 let (patterns, mut warnings) =
486 let (patterns, mut warnings) =
487 read_pattern_file(pattern_file, true, inspect_pattern_bytes)?;
487 read_pattern_file(pattern_file, true, inspect_pattern_bytes)?;
488 let patterns = patterns
488 let patterns = patterns
489 .into_iter()
489 .into_iter()
490 .flat_map(|entry| -> PatternResult<_> {
490 .flat_map(|entry| -> PatternResult<_> {
491 Ok(match &entry.syntax {
491 Ok(match &entry.syntax {
492 PatternSyntax::Include => {
492 PatternSyntax::Include => {
493 let inner_include =
493 let inner_include =
494 root_dir.join(get_path_from_bytes(&entry.pattern));
494 root_dir.join(get_path_from_bytes(&entry.pattern));
495 let (inner_pats, inner_warnings) = get_patterns_from_file(
495 let (inner_pats, inner_warnings) = get_patterns_from_file(
496 &inner_include,
496 &inner_include,
497 root_dir,
497 root_dir,
498 inspect_pattern_bytes,
498 inspect_pattern_bytes,
499 )?;
499 )?;
500 warnings.extend(inner_warnings);
500 warnings.extend(inner_warnings);
501 inner_pats
501 inner_pats
502 }
502 }
503 PatternSyntax::SubInclude => {
503 PatternSyntax::SubInclude => {
504 let mut sub_include = SubInclude::new(
504 let mut sub_include = SubInclude::new(
505 &root_dir,
505 &root_dir,
506 &entry.pattern,
506 &entry.pattern,
507 &entry.source,
507 &entry.source,
508 )?;
508 )?;
509 let (inner_patterns, inner_warnings) =
509 let (inner_patterns, inner_warnings) =
510 get_patterns_from_file(
510 get_patterns_from_file(
511 &sub_include.path,
511 &sub_include.path,
512 &sub_include.root,
512 &sub_include.root,
513 inspect_pattern_bytes,
513 inspect_pattern_bytes,
514 )?;
514 )?;
515 sub_include.included_patterns = inner_patterns;
515 sub_include.included_patterns = inner_patterns;
516 warnings.extend(inner_warnings);
516 warnings.extend(inner_warnings);
517 vec![IgnorePattern {
517 vec![IgnorePattern {
518 syntax: PatternSyntax::ExpandedSubInclude(Box::new(
518 syntax: PatternSyntax::ExpandedSubInclude(Box::new(
519 sub_include,
519 sub_include,
520 )),
520 )),
521 ..entry
521 ..entry
522 }]
522 }]
523 }
523 }
524 _ => vec![entry],
524 _ => vec![entry],
525 })
525 })
526 })
526 })
527 .flatten()
527 .flatten()
528 .collect();
528 .collect();
529
529
530 Ok((patterns, warnings))
530 Ok((patterns, warnings))
531 }
531 }
532
532
533 /// Holds all the information needed to handle a `subinclude:` pattern.
533 /// Holds all the information needed to handle a `subinclude:` pattern.
534 #[derive(Debug, PartialEq, Eq, Clone)]
534 #[derive(Debug, PartialEq, Eq, Clone)]
535 pub struct SubInclude {
535 pub struct SubInclude {
536 /// Will be used for repository (hg) paths that start with this prefix.
536 /// Will be used for repository (hg) paths that start with this prefix.
537 /// It is relative to the current working directory, so comparing against
537 /// It is relative to the current working directory, so comparing against
538 /// repository paths is painless.
538 /// repository paths is painless.
539 pub prefix: HgPathBuf,
539 pub prefix: HgPathBuf,
540 /// The file itself, containing the patterns
540 /// The file itself, containing the patterns
541 pub path: PathBuf,
541 pub path: PathBuf,
542 /// Folder in the filesystem where this it applies
542 /// Folder in the filesystem where this it applies
543 pub root: PathBuf,
543 pub root: PathBuf,
544
544
545 pub included_patterns: Vec<IgnorePattern>,
545 pub included_patterns: Vec<IgnorePattern>,
546 }
546 }
547
547
548 impl SubInclude {
548 impl SubInclude {
549 pub fn new(
549 pub fn new(
550 root_dir: &Path,
550 root_dir: &Path,
551 pattern: &[u8],
551 pattern: &[u8],
552 source: &Path,
552 source: &Path,
553 ) -> Result<SubInclude, HgPathError> {
553 ) -> Result<SubInclude, HgPathError> {
554 let normalized_source =
554 let normalized_source =
555 normalize_path_bytes(&get_bytes_from_path(source));
555 normalize_path_bytes(&get_bytes_from_path(source));
556
556
557 let source_root = get_path_from_bytes(&normalized_source);
557 let source_root = get_path_from_bytes(&normalized_source);
558 let source_root =
558 let source_root =
559 source_root.parent().unwrap_or_else(|| source_root.deref());
559 source_root.parent().unwrap_or_else(|| source_root.deref());
560
560
561 let path = source_root.join(get_path_from_bytes(pattern));
561 let path = source_root.join(get_path_from_bytes(pattern));
562 let new_root = path.parent().unwrap_or_else(|| path.deref());
562 let new_root = path.parent().unwrap_or_else(|| path.deref());
563
563
564 let prefix = canonical_path(root_dir, root_dir, new_root)?;
564 let prefix = canonical_path(root_dir, root_dir, new_root)?;
565
565
566 Ok(Self {
566 Ok(Self {
567 prefix: path_to_hg_path_buf(prefix).and_then(|mut p| {
567 prefix: path_to_hg_path_buf(prefix).and_then(|mut p| {
568 if !p.is_empty() {
568 if !p.is_empty() {
569 p.push_byte(b'/');
569 p.push_byte(b'/');
570 }
570 }
571 Ok(p)
571 Ok(p)
572 })?,
572 })?,
573 path: path.to_owned(),
573 path: path.to_owned(),
574 root: new_root.to_owned(),
574 root: new_root.to_owned(),
575 included_patterns: Vec::new(),
575 included_patterns: Vec::new(),
576 })
576 })
577 }
577 }
578 }
578 }
579
579
580 /// Separate and pre-process subincludes from other patterns for the "ignore"
580 /// Separate and pre-process subincludes from other patterns for the "ignore"
581 /// phase.
581 /// phase.
582 pub fn filter_subincludes(
582 pub fn filter_subincludes(
583 ignore_patterns: Vec<IgnorePattern>,
583 ignore_patterns: Vec<IgnorePattern>,
584 ) -> Result<(Vec<Box<SubInclude>>, Vec<IgnorePattern>), HgPathError> {
584 ) -> Result<(Vec<SubInclude>, Vec<IgnorePattern>), HgPathError> {
585 let mut subincludes = vec![];
585 let mut subincludes = vec![];
586 let mut others = vec![];
586 let mut others = vec![];
587
587
588 for pattern in ignore_patterns {
588 for pattern in ignore_patterns {
589 if let PatternSyntax::ExpandedSubInclude(sub_include) = pattern.syntax
589 if let PatternSyntax::ExpandedSubInclude(sub_include) = pattern.syntax
590 {
590 {
591 subincludes.push(sub_include);
591 subincludes.push(*sub_include);
592 } else {
592 } else {
593 others.push(pattern)
593 others.push(pattern)
594 }
594 }
595 }
595 }
596 Ok((subincludes, others))
596 Ok((subincludes, others))
597 }
597 }
598
598
599 #[cfg(test)]
599 #[cfg(test)]
600 mod tests {
600 mod tests {
601 use super::*;
601 use super::*;
602 use pretty_assertions::assert_eq;
602 use pretty_assertions::assert_eq;
603
603
604 #[test]
604 #[test]
605 fn escape_pattern_test() {
605 fn escape_pattern_test() {
606 let untouched =
606 let untouched =
607 br#"!"%',/0123456789:;<=>@ABCDEFGHIJKLMNOPQRSTUVWXYZ_`abcdefghijklmnopqrstuvwxyz"#;
607 br#"!"%',/0123456789:;<=>@ABCDEFGHIJKLMNOPQRSTUVWXYZ_`abcdefghijklmnopqrstuvwxyz"#;
608 assert_eq!(escape_pattern(untouched), untouched.to_vec());
608 assert_eq!(escape_pattern(untouched), untouched.to_vec());
609 // All escape codes
609 // All escape codes
610 assert_eq!(
610 assert_eq!(
611 escape_pattern(br#"()[]{}?*+-|^$\\.&~# \t\n\r\v\f"#),
611 escape_pattern(br#"()[]{}?*+-|^$\\.&~# \t\n\r\v\f"#),
612 br#"\(\)\[\]\{\}\?\*\+\-\|\^\$\\\\\.\&\~\#\ \\t\\n\\r\\v\\f"#
612 br#"\(\)\[\]\{\}\?\*\+\-\|\^\$\\\\\.\&\~\#\ \\t\\n\\r\\v\\f"#
613 .to_vec()
613 .to_vec()
614 );
614 );
615 }
615 }
616
616
617 #[test]
617 #[test]
618 fn glob_test() {
618 fn glob_test() {
619 assert_eq!(glob_to_re(br#"?"#), br#"."#);
619 assert_eq!(glob_to_re(br#"?"#), br#"."#);
620 assert_eq!(glob_to_re(br#"*"#), br#"[^/]*"#);
620 assert_eq!(glob_to_re(br#"*"#), br#"[^/]*"#);
621 assert_eq!(glob_to_re(br#"**"#), br#".*"#);
621 assert_eq!(glob_to_re(br#"**"#), br#".*"#);
622 assert_eq!(glob_to_re(br#"**/a"#), br#"(?:.*/)?a"#);
622 assert_eq!(glob_to_re(br#"**/a"#), br#"(?:.*/)?a"#);
623 assert_eq!(glob_to_re(br#"a/**/b"#), br#"a/(?:.*/)?b"#);
623 assert_eq!(glob_to_re(br#"a/**/b"#), br#"a/(?:.*/)?b"#);
624 assert_eq!(glob_to_re(br#"[a*?!^][^b][!c]"#), br#"[a*?!^][\^b][^c]"#);
624 assert_eq!(glob_to_re(br#"[a*?!^][^b][!c]"#), br#"[a*?!^][\^b][^c]"#);
625 assert_eq!(glob_to_re(br#"{a,b}"#), br#"(?:a|b)"#);
625 assert_eq!(glob_to_re(br#"{a,b}"#), br#"(?:a|b)"#);
626 assert_eq!(glob_to_re(br#".\*\?"#), br#"\.\*\?"#);
626 assert_eq!(glob_to_re(br#".\*\?"#), br#"\.\*\?"#);
627 }
627 }
628
628
629 #[test]
629 #[test]
630 fn test_parse_pattern_file_contents() {
630 fn test_parse_pattern_file_contents() {
631 let lines = b"syntax: glob\n*.elc";
631 let lines = b"syntax: glob\n*.elc";
632
632
633 assert_eq!(
633 assert_eq!(
634 parse_pattern_file_contents(
634 parse_pattern_file_contents(
635 lines,
635 lines,
636 Path::new("file_path"),
636 Path::new("file_path"),
637 None,
637 None,
638 false
638 false
639 )
639 )
640 .unwrap()
640 .unwrap()
641 .0,
641 .0,
642 vec![IgnorePattern::new(
642 vec![IgnorePattern::new(
643 PatternSyntax::RelGlob,
643 PatternSyntax::RelGlob,
644 b"*.elc",
644 b"*.elc",
645 Path::new("file_path")
645 Path::new("file_path")
646 )],
646 )],
647 );
647 );
648
648
649 let lines = b"syntax: include\nsyntax: glob";
649 let lines = b"syntax: include\nsyntax: glob";
650
650
651 assert_eq!(
651 assert_eq!(
652 parse_pattern_file_contents(
652 parse_pattern_file_contents(
653 lines,
653 lines,
654 Path::new("file_path"),
654 Path::new("file_path"),
655 None,
655 None,
656 false
656 false
657 )
657 )
658 .unwrap()
658 .unwrap()
659 .0,
659 .0,
660 vec![]
660 vec![]
661 );
661 );
662 let lines = b"glob:**.o";
662 let lines = b"glob:**.o";
663 assert_eq!(
663 assert_eq!(
664 parse_pattern_file_contents(
664 parse_pattern_file_contents(
665 lines,
665 lines,
666 Path::new("file_path"),
666 Path::new("file_path"),
667 None,
667 None,
668 false
668 false
669 )
669 )
670 .unwrap()
670 .unwrap()
671 .0,
671 .0,
672 vec![IgnorePattern::new(
672 vec![IgnorePattern::new(
673 PatternSyntax::RelGlob,
673 PatternSyntax::RelGlob,
674 b"**.o",
674 b"**.o",
675 Path::new("file_path")
675 Path::new("file_path")
676 )]
676 )]
677 );
677 );
678 }
678 }
679
679
680 #[test]
680 #[test]
681 fn test_build_single_regex() {
681 fn test_build_single_regex() {
682 assert_eq!(
682 assert_eq!(
683 build_single_regex(&IgnorePattern::new(
683 build_single_regex(&IgnorePattern::new(
684 PatternSyntax::RelGlob,
684 PatternSyntax::RelGlob,
685 b"rust/target/",
685 b"rust/target/",
686 Path::new("")
686 Path::new("")
687 ))
687 ))
688 .unwrap(),
688 .unwrap(),
689 Some(br"(?:.*/)?rust/target(?:/|$)".to_vec()),
689 Some(br"(?:.*/)?rust/target(?:/|$)".to_vec()),
690 );
690 );
691 assert_eq!(
691 assert_eq!(
692 build_single_regex(&IgnorePattern::new(
692 build_single_regex(&IgnorePattern::new(
693 PatternSyntax::Regexp,
693 PatternSyntax::Regexp,
694 br"rust/target/\d+",
694 br"rust/target/\d+",
695 Path::new("")
695 Path::new("")
696 ))
696 ))
697 .unwrap(),
697 .unwrap(),
698 Some(br"rust/target/\d+".to_vec()),
698 Some(br"rust/target/\d+".to_vec()),
699 );
699 );
700 }
700 }
701
701
702 #[test]
702 #[test]
703 fn test_build_single_regex_shortcut() {
703 fn test_build_single_regex_shortcut() {
704 assert_eq!(
704 assert_eq!(
705 build_single_regex(&IgnorePattern::new(
705 build_single_regex(&IgnorePattern::new(
706 PatternSyntax::RootGlob,
706 PatternSyntax::RootGlob,
707 b"",
707 b"",
708 Path::new("")
708 Path::new("")
709 ))
709 ))
710 .unwrap(),
710 .unwrap(),
711 None,
711 None,
712 );
712 );
713 assert_eq!(
713 assert_eq!(
714 build_single_regex(&IgnorePattern::new(
714 build_single_regex(&IgnorePattern::new(
715 PatternSyntax::RootGlob,
715 PatternSyntax::RootGlob,
716 b"whatever",
716 b"whatever",
717 Path::new("")
717 Path::new("")
718 ))
718 ))
719 .unwrap(),
719 .unwrap(),
720 None,
720 None,
721 );
721 );
722 assert_eq!(
722 assert_eq!(
723 build_single_regex(&IgnorePattern::new(
723 build_single_regex(&IgnorePattern::new(
724 PatternSyntax::RootGlob,
724 PatternSyntax::RootGlob,
725 b"*.o",
725 b"*.o",
726 Path::new("")
726 Path::new("")
727 ))
727 ))
728 .unwrap(),
728 .unwrap(),
729 Some(br"[^/]*\.o(?:/|$)".to_vec()),
729 Some(br"[^/]*\.o(?:/|$)".to_vec()),
730 );
730 );
731 }
731 }
732
732
733 #[test]
733 #[test]
734 fn test_build_single_relregex() {
734 fn test_build_single_relregex() {
735 assert_eq!(
735 assert_eq!(
736 build_single_regex(&IgnorePattern::new(
736 build_single_regex(&IgnorePattern::new(
737 PatternSyntax::RelRegexp,
737 PatternSyntax::RelRegexp,
738 b"^ba{2}r",
738 b"^ba{2}r",
739 Path::new("")
739 Path::new("")
740 ))
740 ))
741 .unwrap(),
741 .unwrap(),
742 Some(b"^ba{2}r".to_vec()),
742 Some(b"^ba{2}r".to_vec()),
743 );
743 );
744 assert_eq!(
744 assert_eq!(
745 build_single_regex(&IgnorePattern::new(
745 build_single_regex(&IgnorePattern::new(
746 PatternSyntax::RelRegexp,
746 PatternSyntax::RelRegexp,
747 b"ba{2}r",
747 b"ba{2}r",
748 Path::new("")
748 Path::new("")
749 ))
749 ))
750 .unwrap(),
750 .unwrap(),
751 Some(b".*ba{2}r".to_vec()),
751 Some(b".*ba{2}r".to_vec()),
752 );
752 );
753 assert_eq!(
753 assert_eq!(
754 build_single_regex(&IgnorePattern::new(
754 build_single_regex(&IgnorePattern::new(
755 PatternSyntax::RelRegexp,
755 PatternSyntax::RelRegexp,
756 b"(?ia)ba{2}r",
756 b"(?ia)ba{2}r",
757 Path::new("")
757 Path::new("")
758 ))
758 ))
759 .unwrap(),
759 .unwrap(),
760 Some(b"(?ia:.*ba{2}r)".to_vec()),
760 Some(b"(?ia:.*ba{2}r)".to_vec()),
761 );
761 );
762 assert_eq!(
762 assert_eq!(
763 build_single_regex(&IgnorePattern::new(
763 build_single_regex(&IgnorePattern::new(
764 PatternSyntax::RelRegexp,
764 PatternSyntax::RelRegexp,
765 b"(?ia)^ba{2}r",
765 b"(?ia)^ba{2}r",
766 Path::new("")
766 Path::new("")
767 ))
767 ))
768 .unwrap(),
768 .unwrap(),
769 Some(b"(?ia:^ba{2}r)".to_vec()),
769 Some(b"(?ia:^ba{2}r)".to_vec()),
770 );
770 );
771 }
771 }
772 }
772 }
General Comments 0
You need to be logged in to leave comments. Login now