##// END OF EJS Templates
rustfilepatterns: refactor the pattern of removing a prefix from a &[u8]...
Valentin Gatien-Baron -
r43129:62eabdf9 default
parent child Browse files
Show More
@@ -1,383 +1,384 b''
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::{files::get_path_from_bytes, SliceExt},
11 utils::{files::get_path_from_bytes, SliceExt},
12 LineNumber, PatternError, PatternFileError,
12 LineNumber, PatternError, PatternFileError,
13 };
13 };
14 use lazy_static::lazy_static;
14 use lazy_static::lazy_static;
15 use regex::bytes::{NoExpand, Regex};
15 use regex::bytes::{NoExpand, Regex};
16 use std::collections::HashMap;
16 use std::collections::HashMap;
17 use std::fs::File;
17 use std::fs::File;
18 use std::io::Read;
18 use std::io::Read;
19 use std::vec::Vec;
19 use std::vec::Vec;
20
20
21 lazy_static! {
21 lazy_static! {
22 static ref RE_ESCAPE: Vec<Vec<u8>> = {
22 static ref RE_ESCAPE: Vec<Vec<u8>> = {
23 let mut v: Vec<Vec<u8>> = (0..=255).map(|byte| vec![byte]).collect();
23 let mut v: Vec<Vec<u8>> = (0..=255).map(|byte| vec![byte]).collect();
24 let to_escape = b"()[]{}?*+-|^$\\.&~# \t\n\r\x0b\x0c";
24 let to_escape = b"()[]{}?*+-|^$\\.&~# \t\n\r\x0b\x0c";
25 for byte in to_escape {
25 for byte in to_escape {
26 v[*byte as usize].insert(0, b'\\');
26 v[*byte as usize].insert(0, b'\\');
27 }
27 }
28 v
28 v
29 };
29 };
30 }
30 }
31
31
32 /// These are matched in order
32 /// These are matched in order
33 const GLOB_REPLACEMENTS: &[(&[u8], &[u8])] =
33 const GLOB_REPLACEMENTS: &[(&[u8], &[u8])] =
34 &[(b"*/", b"(?:.*/)?"), (b"*", b".*"), (b"", b"[^/]*")];
34 &[(b"*/", b"(?:.*/)?"), (b"*", b".*"), (b"", b"[^/]*")];
35
35
36 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
36 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
37 pub enum PatternSyntax {
37 pub enum PatternSyntax {
38 Regexp,
38 Regexp,
39 /// Glob that matches at the front of the path
39 /// Glob that matches at the front of the path
40 RootGlob,
40 RootGlob,
41 /// Glob that matches at any suffix of the path (still anchored at
41 /// Glob that matches at any suffix of the path (still anchored at
42 /// slashes)
42 /// slashes)
43 Glob,
43 Glob,
44 Path,
44 Path,
45 RelPath,
45 RelPath,
46 RelGlob,
46 RelGlob,
47 RelRegexp,
47 RelRegexp,
48 RootFiles,
48 RootFiles,
49 }
49 }
50
50
51 /// Transforms a glob pattern into a regex
51 /// Transforms a glob pattern into a regex
52 fn glob_to_re(pat: &[u8]) -> Vec<u8> {
52 fn glob_to_re(pat: &[u8]) -> Vec<u8> {
53 let mut input = pat;
53 let mut input = pat;
54 let mut res: Vec<u8> = vec![];
54 let mut res: Vec<u8> = vec![];
55 let mut group_depth = 0;
55 let mut group_depth = 0;
56
56
57 while let Some((c, rest)) = input.split_first() {
57 while let Some((c, rest)) = input.split_first() {
58 input = rest;
58 input = rest;
59
59
60 match c {
60 match c {
61 b'*' => {
61 b'*' => {
62 for (source, repl) in GLOB_REPLACEMENTS {
62 for (source, repl) in GLOB_REPLACEMENTS {
63 if input.starts_with(source) {
63 if let Some(rest) = input.drop_prefix(source) {
64 input = &input[source.len()..];
64 input = rest;
65 res.extend(*repl);
65 res.extend(*repl);
66 break;
66 break;
67 }
67 }
68 }
68 }
69 }
69 }
70 b'?' => res.extend(b"."),
70 b'?' => res.extend(b"."),
71 b'[' => {
71 b'[' => {
72 match input.iter().skip(1).position(|b| *b == b']') {
72 match input.iter().skip(1).position(|b| *b == b']') {
73 None => res.extend(b"\\["),
73 None => res.extend(b"\\["),
74 Some(end) => {
74 Some(end) => {
75 // Account for the one we skipped
75 // Account for the one we skipped
76 let end = end + 1;
76 let end = end + 1;
77
77
78 res.extend(b"[");
78 res.extend(b"[");
79
79
80 for (i, b) in input[..end].iter().enumerate() {
80 for (i, b) in input[..end].iter().enumerate() {
81 if *b == b'!' && i == 0 {
81 if *b == b'!' && i == 0 {
82 res.extend(b"^")
82 res.extend(b"^")
83 } else if *b == b'^' && i == 0 {
83 } else if *b == b'^' && i == 0 {
84 res.extend(b"\\^")
84 res.extend(b"\\^")
85 } else if *b == b'\\' {
85 } else if *b == b'\\' {
86 res.extend(b"\\\\")
86 res.extend(b"\\\\")
87 } else {
87 } else {
88 res.push(*b)
88 res.push(*b)
89 }
89 }
90 }
90 }
91 res.extend(b"]");
91 res.extend(b"]");
92 input = &input[end + 1..];
92 input = &input[end + 1..];
93 }
93 }
94 }
94 }
95 }
95 }
96 b'{' => {
96 b'{' => {
97 group_depth += 1;
97 group_depth += 1;
98 res.extend(b"(?:")
98 res.extend(b"(?:")
99 }
99 }
100 b'}' if group_depth > 0 => {
100 b'}' if group_depth > 0 => {
101 group_depth -= 1;
101 group_depth -= 1;
102 res.extend(b")");
102 res.extend(b")");
103 }
103 }
104 b',' if group_depth > 0 => res.extend(b"|"),
104 b',' if group_depth > 0 => res.extend(b"|"),
105 b'\\' => {
105 b'\\' => {
106 let c = {
106 let c = {
107 if let Some((c, rest)) = input.split_first() {
107 if let Some((c, rest)) = input.split_first() {
108 input = rest;
108 input = rest;
109 c
109 c
110 } else {
110 } else {
111 c
111 c
112 }
112 }
113 };
113 };
114 res.extend(&RE_ESCAPE[*c as usize])
114 res.extend(&RE_ESCAPE[*c as usize])
115 }
115 }
116 _ => res.extend(&RE_ESCAPE[*c as usize]),
116 _ => res.extend(&RE_ESCAPE[*c as usize]),
117 }
117 }
118 }
118 }
119 res
119 res
120 }
120 }
121
121
122 fn escape_pattern(pattern: &[u8]) -> Vec<u8> {
122 fn escape_pattern(pattern: &[u8]) -> Vec<u8> {
123 pattern
123 pattern
124 .iter()
124 .iter()
125 .flat_map(|c| RE_ESCAPE[*c as usize].clone())
125 .flat_map(|c| RE_ESCAPE[*c as usize].clone())
126 .collect()
126 .collect()
127 }
127 }
128
128
129 fn parse_pattern_syntax(kind: &[u8]) -> Result<PatternSyntax, PatternError> {
129 fn parse_pattern_syntax(kind: &[u8]) -> Result<PatternSyntax, PatternError> {
130 match kind {
130 match kind {
131 b"re" => Ok(PatternSyntax::Regexp),
131 b"re" => Ok(PatternSyntax::Regexp),
132 b"path" => Ok(PatternSyntax::Path),
132 b"path" => Ok(PatternSyntax::Path),
133 b"relpath" => Ok(PatternSyntax::RelPath),
133 b"relpath" => Ok(PatternSyntax::RelPath),
134 b"rootfilesin" => Ok(PatternSyntax::RootFiles),
134 b"rootfilesin" => Ok(PatternSyntax::RootFiles),
135 b"relglob" => Ok(PatternSyntax::RelGlob),
135 b"relglob" => Ok(PatternSyntax::RelGlob),
136 b"relre" => Ok(PatternSyntax::RelRegexp),
136 b"relre" => Ok(PatternSyntax::RelRegexp),
137 b"glob" => Ok(PatternSyntax::Glob),
137 b"glob" => Ok(PatternSyntax::Glob),
138 b"rootglob" => Ok(PatternSyntax::RootGlob),
138 b"rootglob" => Ok(PatternSyntax::RootGlob),
139 _ => Err(PatternError::UnsupportedSyntax(
139 _ => Err(PatternError::UnsupportedSyntax(
140 String::from_utf8_lossy(kind).to_string(),
140 String::from_utf8_lossy(kind).to_string(),
141 )),
141 )),
142 }
142 }
143 }
143 }
144
144
145 /// Builds the regex that corresponds to the given pattern.
145 /// Builds the regex that corresponds to the given pattern.
146 /// If within a `syntax: regexp` context, returns the pattern,
146 /// If within a `syntax: regexp` context, returns the pattern,
147 /// otherwise, returns the corresponding regex.
147 /// otherwise, returns the corresponding regex.
148 fn _build_single_regex(
148 fn _build_single_regex(
149 syntax: PatternSyntax,
149 syntax: PatternSyntax,
150 pattern: &[u8],
150 pattern: &[u8],
151 globsuffix: &[u8],
151 globsuffix: &[u8],
152 ) -> Vec<u8> {
152 ) -> Vec<u8> {
153 if pattern.is_empty() {
153 if pattern.is_empty() {
154 return vec![];
154 return vec![];
155 }
155 }
156 match syntax {
156 match syntax {
157 PatternSyntax::Regexp => pattern.to_owned(),
157 PatternSyntax::Regexp => pattern.to_owned(),
158 PatternSyntax::RelRegexp => {
158 PatternSyntax::RelRegexp => {
159 if pattern[0] == b'^' {
159 if pattern[0] == b'^' {
160 return pattern.to_owned();
160 return pattern.to_owned();
161 }
161 }
162 let mut res = b".*".to_vec();
162 let mut res = b".*".to_vec();
163 res.extend(pattern);
163 res.extend(pattern);
164 res
164 res
165 }
165 }
166 PatternSyntax::Path | PatternSyntax::RelPath => {
166 PatternSyntax::Path | PatternSyntax::RelPath => {
167 if pattern == b"." {
167 if pattern == b"." {
168 return vec![];
168 return vec![];
169 }
169 }
170 let mut pattern = escape_pattern(pattern);
170 let mut pattern = escape_pattern(pattern);
171 pattern.extend(b"(?:/|$)");
171 pattern.extend(b"(?:/|$)");
172 pattern
172 pattern
173 }
173 }
174 PatternSyntax::RootFiles => {
174 PatternSyntax::RootFiles => {
175 let mut res = if pattern == b"." {
175 let mut res = if pattern == b"." {
176 vec![]
176 vec![]
177 } else {
177 } else {
178 // Pattern is a directory name.
178 // Pattern is a directory name.
179 let mut as_vec: Vec<u8> = escape_pattern(pattern);
179 let mut as_vec: Vec<u8> = escape_pattern(pattern);
180 as_vec.push(b'/');
180 as_vec.push(b'/');
181 as_vec
181 as_vec
182 };
182 };
183
183
184 // Anything after the pattern must be a non-directory.
184 // Anything after the pattern must be a non-directory.
185 res.extend(b"[^/]+$");
185 res.extend(b"[^/]+$");
186 res
186 res
187 }
187 }
188 PatternSyntax::Glob
188 PatternSyntax::Glob
189 | PatternSyntax::RelGlob
189 | PatternSyntax::RelGlob
190 | PatternSyntax::RootGlob => {
190 | PatternSyntax::RootGlob => {
191 let mut res: Vec<u8> = vec![];
191 let mut res: Vec<u8> = vec![];
192 if syntax == PatternSyntax::RelGlob {
192 if syntax == PatternSyntax::RelGlob {
193 res.extend(b"(?:|.*/)");
193 res.extend(b"(?:|.*/)");
194 }
194 }
195
195
196 res.extend(glob_to_re(pattern));
196 res.extend(glob_to_re(pattern));
197 res.extend(globsuffix.iter());
197 res.extend(globsuffix.iter());
198 res
198 res
199 }
199 }
200 }
200 }
201 }
201 }
202
202
203 const GLOB_SPECIAL_CHARACTERS: [u8; 7] =
203 const GLOB_SPECIAL_CHARACTERS: [u8; 7] =
204 [b'*', b'?', b'[', b']', b'{', b'}', b'\\'];
204 [b'*', b'?', b'[', b']', b'{', b'}', b'\\'];
205
205
206 /// Wrapper function to `_build_single_regex` that short-circuits 'exact' globs
206 /// Wrapper function to `_build_single_regex` that short-circuits 'exact' globs
207 /// that don't need to be transformed into a regex.
207 /// that don't need to be transformed into a regex.
208 pub fn build_single_regex(
208 pub fn build_single_regex(
209 kind: &[u8],
209 kind: &[u8],
210 pat: &[u8],
210 pat: &[u8],
211 globsuffix: &[u8],
211 globsuffix: &[u8],
212 ) -> Result<Vec<u8>, PatternError> {
212 ) -> Result<Vec<u8>, PatternError> {
213 let enum_kind = parse_pattern_syntax(kind)?;
213 let enum_kind = parse_pattern_syntax(kind)?;
214 if enum_kind == PatternSyntax::RootGlob
214 if enum_kind == PatternSyntax::RootGlob
215 && !pat.iter().any(|b| GLOB_SPECIAL_CHARACTERS.contains(b))
215 && !pat.iter().any(|b| GLOB_SPECIAL_CHARACTERS.contains(b))
216 {
216 {
217 let mut escaped = escape_pattern(pat);
217 let mut escaped = escape_pattern(pat);
218 escaped.extend(b"(?:/|$)");
218 escaped.extend(b"(?:/|$)");
219 Ok(escaped)
219 Ok(escaped)
220 } else {
220 } else {
221 Ok(_build_single_regex(enum_kind, pat, globsuffix))
221 Ok(_build_single_regex(enum_kind, pat, globsuffix))
222 }
222 }
223 }
223 }
224
224
225 lazy_static! {
225 lazy_static! {
226 static ref SYNTAXES: HashMap<&'static [u8], &'static [u8]> = {
226 static ref SYNTAXES: HashMap<&'static [u8], &'static [u8]> = {
227 let mut m = HashMap::new();
227 let mut m = HashMap::new();
228
228
229 m.insert(b"re".as_ref(), b"relre:".as_ref());
229 m.insert(b"re".as_ref(), b"relre:".as_ref());
230 m.insert(b"regexp".as_ref(), b"relre:".as_ref());
230 m.insert(b"regexp".as_ref(), b"relre:".as_ref());
231 m.insert(b"glob".as_ref(), b"relglob:".as_ref());
231 m.insert(b"glob".as_ref(), b"relglob:".as_ref());
232 m.insert(b"rootglob".as_ref(), b"rootglob:".as_ref());
232 m.insert(b"rootglob".as_ref(), b"rootglob:".as_ref());
233 m.insert(b"include".as_ref(), b"include".as_ref());
233 m.insert(b"include".as_ref(), b"include".as_ref());
234 m.insert(b"subinclude".as_ref(), b"subinclude".as_ref());
234 m.insert(b"subinclude".as_ref(), b"subinclude".as_ref());
235 m
235 m
236 };
236 };
237 }
237 }
238
238
239 pub type PatternTuple = (Vec<u8>, LineNumber, Vec<u8>);
239 pub type PatternTuple = (Vec<u8>, LineNumber, Vec<u8>);
240 type WarningTuple = (Vec<u8>, Vec<u8>);
240 type WarningTuple = (Vec<u8>, Vec<u8>);
241
241
242 pub fn parse_pattern_file_contents(
242 pub fn parse_pattern_file_contents(
243 lines: &[u8],
243 lines: &[u8],
244 file_path: &[u8],
244 file_path: &[u8],
245 warn: bool,
245 warn: bool,
246 ) -> (Vec<PatternTuple>, Vec<WarningTuple>) {
246 ) -> (Vec<PatternTuple>, Vec<WarningTuple>) {
247 let comment_regex = Regex::new(r"((?:^|[^\\])(?:\\\\)*)#.*").unwrap();
247 let comment_regex = Regex::new(r"((?:^|[^\\])(?:\\\\)*)#.*").unwrap();
248 let comment_escape_regex = Regex::new(r"\\#").unwrap();
248 let comment_escape_regex = Regex::new(r"\\#").unwrap();
249 let mut inputs: Vec<PatternTuple> = vec![];
249 let mut inputs: Vec<PatternTuple> = vec![];
250 let mut warnings: Vec<WarningTuple> = vec![];
250 let mut warnings: Vec<WarningTuple> = vec![];
251
251
252 let mut current_syntax = b"relre:".as_ref();
252 let mut current_syntax = b"relre:".as_ref();
253
253
254 for (line_number, mut line) in lines.split(|c| *c == b'\n').enumerate() {
254 for (line_number, mut line) in lines.split(|c| *c == b'\n').enumerate() {
255 let line_number = line_number + 1;
255 let line_number = line_number + 1;
256
256
257 let line_buf;
257 let line_buf;
258 if line.contains(&b'#') {
258 if line.contains(&b'#') {
259 if let Some(cap) = comment_regex.captures(line) {
259 if let Some(cap) = comment_regex.captures(line) {
260 line = &line[..cap.get(1).unwrap().end()]
260 line = &line[..cap.get(1).unwrap().end()]
261 }
261 }
262 line_buf = comment_escape_regex.replace_all(line, NoExpand(b"#"));
262 line_buf = comment_escape_regex.replace_all(line, NoExpand(b"#"));
263 line = &line_buf;
263 line = &line_buf;
264 }
264 }
265
265
266 let mut line = line.trim_end();
266 let mut line = line.trim_end();
267
267
268 if line.is_empty() {
268 if line.is_empty() {
269 continue;
269 continue;
270 }
270 }
271
271
272 if line.starts_with(b"syntax:") {
272 if let Some(syntax) = line.drop_prefix(b"syntax:") {
273 let syntax = line[b"syntax:".len()..].trim();
273 let syntax = syntax.trim();
274
274
275 if let Some(rel_syntax) = SYNTAXES.get(syntax) {
275 if let Some(rel_syntax) = SYNTAXES.get(syntax) {
276 current_syntax = rel_syntax;
276 current_syntax = rel_syntax;
277 } else if warn {
277 } else if warn {
278 warnings.push((file_path.to_owned(), syntax.to_owned()));
278 warnings.push((file_path.to_owned(), syntax.to_owned()));
279 }
279 }
280 continue;
280 continue;
281 }
281 }
282
282
283 let mut line_syntax: &[u8] = &current_syntax;
283 let mut line_syntax: &[u8] = &current_syntax;
284
284
285 for (s, rels) in SYNTAXES.iter() {
285 for (s, rels) in SYNTAXES.iter() {
286 if line.starts_with(rels) {
286 if let Some(rest) = line.drop_prefix(rels) {
287 line_syntax = rels;
287 line_syntax = rels;
288 line = &line[rels.len()..];
288 line = rest;
289 break;
289 break;
290 } else if line.starts_with(&[s, b":".as_ref()].concat()) {
290 }
291 if let Some(rest) = line.drop_prefix(&[s, &b":"[..]].concat()) {
291 line_syntax = rels;
292 line_syntax = rels;
292 line = &line[s.len() + 1..];
293 line = rest;
293 break;
294 break;
294 }
295 }
295 }
296 }
296
297
297 inputs.push((
298 inputs.push((
298 [line_syntax, line].concat(),
299 [line_syntax, line].concat(),
299 line_number,
300 line_number,
300 line.to_owned(),
301 line.to_owned(),
301 ));
302 ));
302 }
303 }
303 (inputs, warnings)
304 (inputs, warnings)
304 }
305 }
305
306
306 pub fn read_pattern_file(
307 pub fn read_pattern_file(
307 file_path: &[u8],
308 file_path: &[u8],
308 warn: bool,
309 warn: bool,
309 ) -> Result<(Vec<PatternTuple>, Vec<WarningTuple>), PatternFileError> {
310 ) -> Result<(Vec<PatternTuple>, Vec<WarningTuple>), PatternFileError> {
310 let mut f = File::open(get_path_from_bytes(file_path))?;
311 let mut f = File::open(get_path_from_bytes(file_path))?;
311 let mut contents = Vec::new();
312 let mut contents = Vec::new();
312
313
313 f.read_to_end(&mut contents)?;
314 f.read_to_end(&mut contents)?;
314
315
315 Ok(parse_pattern_file_contents(&contents, file_path, warn))
316 Ok(parse_pattern_file_contents(&contents, file_path, warn))
316 }
317 }
317
318
318 #[cfg(test)]
319 #[cfg(test)]
319 mod tests {
320 mod tests {
320 use super::*;
321 use super::*;
321
322
322 #[test]
323 #[test]
323 fn escape_pattern_test() {
324 fn escape_pattern_test() {
324 let untouched = br#"!"%',/0123456789:;<=>@ABCDEFGHIJKLMNOPQRSTUVWXYZ_`abcdefghijklmnopqrstuvwxyz"#;
325 let untouched = br#"!"%',/0123456789:;<=>@ABCDEFGHIJKLMNOPQRSTUVWXYZ_`abcdefghijklmnopqrstuvwxyz"#;
325 assert_eq!(escape_pattern(untouched), untouched.to_vec());
326 assert_eq!(escape_pattern(untouched), untouched.to_vec());
326 // All escape codes
327 // All escape codes
327 assert_eq!(
328 assert_eq!(
328 escape_pattern(br#"()[]{}?*+-|^$\\.&~# \t\n\r\v\f"#),
329 escape_pattern(br#"()[]{}?*+-|^$\\.&~# \t\n\r\v\f"#),
329 br#"\(\)\[\]\{\}\?\*\+\-\|\^\$\\\\\.\&\~\#\ \\t\\n\\r\\v\\f"#
330 br#"\(\)\[\]\{\}\?\*\+\-\|\^\$\\\\\.\&\~\#\ \\t\\n\\r\\v\\f"#
330 .to_vec()
331 .to_vec()
331 );
332 );
332 }
333 }
333
334
334 #[test]
335 #[test]
335 fn glob_test() {
336 fn glob_test() {
336 assert_eq!(glob_to_re(br#"?"#), br#"."#);
337 assert_eq!(glob_to_re(br#"?"#), br#"."#);
337 assert_eq!(glob_to_re(br#"*"#), br#"[^/]*"#);
338 assert_eq!(glob_to_re(br#"*"#), br#"[^/]*"#);
338 assert_eq!(glob_to_re(br#"**"#), br#".*"#);
339 assert_eq!(glob_to_re(br#"**"#), br#".*"#);
339 assert_eq!(glob_to_re(br#"**/a"#), br#"(?:.*/)?a"#);
340 assert_eq!(glob_to_re(br#"**/a"#), br#"(?:.*/)?a"#);
340 assert_eq!(glob_to_re(br#"a/**/b"#), br#"a/(?:.*/)?b"#);
341 assert_eq!(glob_to_re(br#"a/**/b"#), br#"a/(?:.*/)?b"#);
341 assert_eq!(glob_to_re(br#"[a*?!^][^b][!c]"#), br#"[a*?!^][\^b][^c]"#);
342 assert_eq!(glob_to_re(br#"[a*?!^][^b][!c]"#), br#"[a*?!^][\^b][^c]"#);
342 assert_eq!(glob_to_re(br#"{a,b}"#), br#"(?:a|b)"#);
343 assert_eq!(glob_to_re(br#"{a,b}"#), br#"(?:a|b)"#);
343 assert_eq!(glob_to_re(br#".\*\?"#), br#"\.\*\?"#);
344 assert_eq!(glob_to_re(br#".\*\?"#), br#"\.\*\?"#);
344 }
345 }
345
346
346 #[test]
347 #[test]
347 fn test_parse_pattern_file_contents() {
348 fn test_parse_pattern_file_contents() {
348 let lines = b"syntax: glob\n*.elc";
349 let lines = b"syntax: glob\n*.elc";
349
350
350 assert_eq!(
351 assert_eq!(
351 vec![(b"relglob:*.elc".to_vec(), 2, b"*.elc".to_vec())],
352 vec![(b"relglob:*.elc".to_vec(), 2, b"*.elc".to_vec())],
352 parse_pattern_file_contents(lines, b"file_path", false).0,
353 parse_pattern_file_contents(lines, b"file_path", false).0,
353 );
354 );
354
355
355 let lines = b"syntax: include\nsyntax: glob";
356 let lines = b"syntax: include\nsyntax: glob";
356
357
357 assert_eq!(
358 assert_eq!(
358 parse_pattern_file_contents(lines, b"file_path", false).0,
359 parse_pattern_file_contents(lines, b"file_path", false).0,
359 vec![]
360 vec![]
360 );
361 );
361 let lines = b"glob:**.o";
362 let lines = b"glob:**.o";
362 assert_eq!(
363 assert_eq!(
363 parse_pattern_file_contents(lines, b"file_path", false).0,
364 parse_pattern_file_contents(lines, b"file_path", false).0,
364 vec![(b"relglob:**.o".to_vec(), 1, b"**.o".to_vec())]
365 vec![(b"relglob:**.o".to_vec(), 1, b"**.o".to_vec())]
365 );
366 );
366 }
367 }
367
368
368 #[test]
369 #[test]
369 fn test_build_single_regex_shortcut() {
370 fn test_build_single_regex_shortcut() {
370 assert_eq!(
371 assert_eq!(
371 br"(?:/|$)".to_vec(),
372 br"(?:/|$)".to_vec(),
372 build_single_regex(b"rootglob", b"", b"").unwrap()
373 build_single_regex(b"rootglob", b"", b"").unwrap()
373 );
374 );
374 assert_eq!(
375 assert_eq!(
375 br"whatever(?:/|$)".to_vec(),
376 br"whatever(?:/|$)".to_vec(),
376 build_single_regex(b"rootglob", b"whatever", b"").unwrap()
377 build_single_regex(b"rootglob", b"whatever", b"").unwrap()
377 );
378 );
378 assert_eq!(
379 assert_eq!(
379 br"[^/]*\.o".to_vec(),
380 br"[^/]*\.o".to_vec(),
380 build_single_regex(b"rootglob", b"*.o", b"").unwrap()
381 build_single_regex(b"rootglob", b"*.o", b"").unwrap()
381 );
382 );
382 }
383 }
383 }
384 }
@@ -1,83 +1,92 b''
1 // utils module
1 // utils module
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 //! Contains useful functions, traits, structs, etc. for use in core.
8 //! Contains useful functions, traits, structs, etc. for use in core.
9
9
10 pub mod files;
10 pub mod files;
11
11
12 /// Replaces the `from` slice with the `to` slice inside the `buf` slice.
12 /// Replaces the `from` slice with the `to` slice inside the `buf` slice.
13 ///
13 ///
14 /// # Examples
14 /// # Examples
15 ///
15 ///
16 /// ```
16 /// ```
17 /// use crate::hg::utils::replace_slice;
17 /// use crate::hg::utils::replace_slice;
18 /// let mut line = b"I hate writing tests!".to_vec();
18 /// let mut line = b"I hate writing tests!".to_vec();
19 /// replace_slice(&mut line, b"hate", b"love");
19 /// replace_slice(&mut line, b"hate", b"love");
20 /// assert_eq!(
20 /// assert_eq!(
21 /// line,
21 /// line,
22 /// b"I love writing tests!".to_vec()
22 /// b"I love writing tests!".to_vec()
23 /// );
23 /// );
24 /// ```
24 /// ```
25 pub fn replace_slice<T>(buf: &mut [T], from: &[T], to: &[T])
25 pub fn replace_slice<T>(buf: &mut [T], from: &[T], to: &[T])
26 where
26 where
27 T: Clone + PartialEq,
27 T: Clone + PartialEq,
28 {
28 {
29 if buf.len() < from.len() || from.len() != to.len() {
29 if buf.len() < from.len() || from.len() != to.len() {
30 return;
30 return;
31 }
31 }
32 for i in 0..=buf.len() - from.len() {
32 for i in 0..=buf.len() - from.len() {
33 if buf[i..].starts_with(from) {
33 if buf[i..].starts_with(from) {
34 buf[i..(i + from.len())].clone_from_slice(to);
34 buf[i..(i + from.len())].clone_from_slice(to);
35 }
35 }
36 }
36 }
37 }
37 }
38
38
39 pub trait SliceExt {
39 pub trait SliceExt {
40 fn trim_end(&self) -> &Self;
40 fn trim_end(&self) -> &Self;
41 fn trim_start(&self) -> &Self;
41 fn trim_start(&self) -> &Self;
42 fn trim(&self) -> &Self;
42 fn trim(&self) -> &Self;
43 fn drop_prefix(&self, needle: &Self) -> Option<&Self>;
43 }
44 }
44
45
45 fn is_not_whitespace(c: &u8) -> bool {
46 fn is_not_whitespace(c: &u8) -> bool {
46 !(*c as char).is_whitespace()
47 !(*c as char).is_whitespace()
47 }
48 }
48
49
49 impl SliceExt for [u8] {
50 impl SliceExt for [u8] {
50 fn trim_end(&self) -> &[u8] {
51 fn trim_end(&self) -> &[u8] {
51 if let Some(last) = self.iter().rposition(is_not_whitespace) {
52 if let Some(last) = self.iter().rposition(is_not_whitespace) {
52 &self[..last + 1]
53 &self[..last + 1]
53 } else {
54 } else {
54 &[]
55 &[]
55 }
56 }
56 }
57 }
57 fn trim_start(&self) -> &[u8] {
58 fn trim_start(&self) -> &[u8] {
58 if let Some(first) = self.iter().position(is_not_whitespace) {
59 if let Some(first) = self.iter().position(is_not_whitespace) {
59 &self[first..]
60 &self[first..]
60 } else {
61 } else {
61 &[]
62 &[]
62 }
63 }
63 }
64 }
64
65
65 /// ```
66 /// ```
66 /// use hg::utils::SliceExt;
67 /// use hg::utils::SliceExt;
67 /// assert_eq!(
68 /// assert_eq!(
68 /// b" to trim ".trim(),
69 /// b" to trim ".trim(),
69 /// b"to trim"
70 /// b"to trim"
70 /// );
71 /// );
71 /// assert_eq!(
72 /// assert_eq!(
72 /// b"to trim ".trim(),
73 /// b"to trim ".trim(),
73 /// b"to trim"
74 /// b"to trim"
74 /// );
75 /// );
75 /// assert_eq!(
76 /// assert_eq!(
76 /// b" to trim".trim(),
77 /// b" to trim".trim(),
77 /// b"to trim"
78 /// b"to trim"
78 /// );
79 /// );
79 /// ```
80 /// ```
80 fn trim(&self) -> &[u8] {
81 fn trim(&self) -> &[u8] {
81 self.trim_start().trim_end()
82 self.trim_start().trim_end()
82 }
83 }
84
85 fn drop_prefix(&self, needle: &Self) -> Option<&Self> {
86 if self.starts_with(needle) {
87 Some(&self[needle.len()..])
88 } else {
89 None
90 }
91 }
83 }
92 }
General Comments 0
You need to be logged in to leave comments. Login now