##// END OF EJS Templates
match: remove matchessubrepo method (API)...
Hannes Oldenburg -
r29803:dab7069f default
parent child Browse files
Show More
@@ -1,716 +1,712 b''
1 1 # match.py - filename matching
2 2 #
3 3 # Copyright 2008, 2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import copy
11 11 import os
12 12 import re
13 13
14 14 from .i18n import _
15 15 from . import (
16 16 error,
17 17 pathutil,
18 18 util,
19 19 )
20 20
21 21 propertycache = util.propertycache
22 22
23 23 def _rematcher(regex):
24 24 '''compile the regexp with the best available regexp engine and return a
25 25 matcher function'''
26 26 m = util.re.compile(regex)
27 27 try:
28 28 # slightly faster, provided by facebook's re2 bindings
29 29 return m.test_match
30 30 except AttributeError:
31 31 return m.match
32 32
33 33 def _expandsets(kindpats, ctx, listsubrepos):
34 34 '''Returns the kindpats list with the 'set' patterns expanded.'''
35 35 fset = set()
36 36 other = []
37 37
38 38 for kind, pat, source in kindpats:
39 39 if kind == 'set':
40 40 if not ctx:
41 41 raise error.Abort(_("fileset expression with no context"))
42 42 s = ctx.getfileset(pat)
43 43 fset.update(s)
44 44
45 45 if listsubrepos:
46 46 for subpath in ctx.substate:
47 47 s = ctx.sub(subpath).getfileset(pat)
48 48 fset.update(subpath + '/' + f for f in s)
49 49
50 50 continue
51 51 other.append((kind, pat, source))
52 52 return fset, other
53 53
54 54 def _expandsubinclude(kindpats, root):
55 55 '''Returns the list of subinclude matchers and the kindpats without the
56 56 subincludes in it.'''
57 57 relmatchers = []
58 58 other = []
59 59
60 60 for kind, pat, source in kindpats:
61 61 if kind == 'subinclude':
62 62 sourceroot = pathutil.dirname(util.normpath(source))
63 63 pat = util.pconvert(pat)
64 64 path = pathutil.join(sourceroot, pat)
65 65
66 66 newroot = pathutil.dirname(path)
67 67 relmatcher = match(newroot, '', [], ['include:%s' % path])
68 68
69 69 prefix = pathutil.canonpath(root, root, newroot)
70 70 if prefix:
71 71 prefix += '/'
72 72 relmatchers.append((prefix, relmatcher))
73 73 else:
74 74 other.append((kind, pat, source))
75 75
76 76 return relmatchers, other
77 77
78 78 def _kindpatsalwaysmatch(kindpats):
79 79 """"Checks whether the kindspats match everything, as e.g.
80 80 'relpath:.' does.
81 81 """
82 82 for kind, pat, source in kindpats:
83 83 if pat != '' or kind not in ['relpath', 'glob']:
84 84 return False
85 85 return True
86 86
87 87 class match(object):
88 88 def __init__(self, root, cwd, patterns, include=[], exclude=[],
89 89 default='glob', exact=False, auditor=None, ctx=None,
90 90 listsubrepos=False, warn=None, badfn=None):
91 91 """build an object to match a set of file patterns
92 92
93 93 arguments:
94 94 root - the canonical root of the tree you're matching against
95 95 cwd - the current working directory, if relevant
96 96 patterns - patterns to find
97 97 include - patterns to include (unless they are excluded)
98 98 exclude - patterns to exclude (even if they are included)
99 99 default - if a pattern in patterns has no explicit type, assume this one
100 100 exact - patterns are actually filenames (include/exclude still apply)
101 101 warn - optional function used for printing warnings
102 102 badfn - optional bad() callback for this matcher instead of the default
103 103
104 104 a pattern is one of:
105 105 'glob:<glob>' - a glob relative to cwd
106 106 're:<regexp>' - a regular expression
107 107 'path:<path>' - a path relative to repository root
108 108 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
109 109 'relpath:<path>' - a path relative to cwd
110 110 'relre:<regexp>' - a regexp that needn't match the start of a name
111 111 'set:<fileset>' - a fileset expression
112 112 'include:<path>' - a file of patterns to read and include
113 113 'subinclude:<path>' - a file of patterns to match against files under
114 114 the same directory
115 115 '<something>' - a pattern of the specified default type
116 116 """
117 117
118 118 self._root = root
119 119 self._cwd = cwd
120 120 self._files = [] # exact files and roots of patterns
121 121 self._anypats = bool(include or exclude)
122 122 self._always = False
123 123 self._pathrestricted = bool(include or exclude or patterns)
124 124 self._warn = warn
125 125 self._includeroots = set()
126 126 self._includedirs = set(['.'])
127 127 self._excluderoots = set()
128 128
129 129 if badfn is not None:
130 130 self.bad = badfn
131 131
132 132 matchfns = []
133 133 if include:
134 134 kindpats = self._normalize(include, 'glob', root, cwd, auditor)
135 135 self.includepat, im = _buildmatch(ctx, kindpats, '(?:/|$)',
136 136 listsubrepos, root)
137 137 self._includeroots.update(_roots(kindpats))
138 138 self._includedirs.update(util.dirs(self._includeroots))
139 139 matchfns.append(im)
140 140 if exclude:
141 141 kindpats = self._normalize(exclude, 'glob', root, cwd, auditor)
142 142 self.excludepat, em = _buildmatch(ctx, kindpats, '(?:/|$)',
143 143 listsubrepos, root)
144 144 if not _anypats(kindpats):
145 145 self._excluderoots.update(_roots(kindpats))
146 146 matchfns.append(lambda f: not em(f))
147 147 if exact:
148 148 if isinstance(patterns, list):
149 149 self._files = patterns
150 150 else:
151 151 self._files = list(patterns)
152 152 matchfns.append(self.exact)
153 153 elif patterns:
154 154 kindpats = self._normalize(patterns, default, root, cwd, auditor)
155 155 if not _kindpatsalwaysmatch(kindpats):
156 156 self._files = _roots(kindpats)
157 157 self._anypats = self._anypats or _anypats(kindpats)
158 158 self.patternspat, pm = _buildmatch(ctx, kindpats, '$',
159 159 listsubrepos, root)
160 160 matchfns.append(pm)
161 161
162 162 if not matchfns:
163 163 m = util.always
164 164 self._always = True
165 165 elif len(matchfns) == 1:
166 166 m = matchfns[0]
167 167 else:
168 168 def m(f):
169 169 for matchfn in matchfns:
170 170 if not matchfn(f):
171 171 return False
172 172 return True
173 173
174 174 self.matchfn = m
175 175 self._fileroots = set(self._files)
176 176
177 177 def __call__(self, fn):
178 178 return self.matchfn(fn)
179 179 def __iter__(self):
180 180 for f in self._files:
181 181 yield f
182 182
183 183 # Callbacks related to how the matcher is used by dirstate.walk.
184 184 # Subscribers to these events must monkeypatch the matcher object.
185 185 def bad(self, f, msg):
186 186 '''Callback from dirstate.walk for each explicit file that can't be
187 187 found/accessed, with an error message.'''
188 188 pass
189 189
190 190 # If an explicitdir is set, it will be called when an explicitly listed
191 191 # directory is visited.
192 192 explicitdir = None
193 193
194 194 # If an traversedir is set, it will be called when a directory discovered
195 195 # by recursive traversal is visited.
196 196 traversedir = None
197 197
198 198 def abs(self, f):
199 199 '''Convert a repo path back to path that is relative to the root of the
200 200 matcher.'''
201 201 return f
202 202
203 203 def rel(self, f):
204 204 '''Convert repo path back to path that is relative to cwd of matcher.'''
205 205 return util.pathto(self._root, self._cwd, f)
206 206
207 207 def uipath(self, f):
208 208 '''Convert repo path to a display path. If patterns or -I/-X were used
209 209 to create this matcher, the display path will be relative to cwd.
210 210 Otherwise it is relative to the root of the repo.'''
211 211 return (self._pathrestricted and self.rel(f)) or self.abs(f)
212 212
213 213 def files(self):
214 214 '''Explicitly listed files or patterns or roots:
215 215 if no patterns or .always(): empty list,
216 216 if exact: list exact files,
217 217 if not .anypats(): list all files and dirs,
218 218 else: optimal roots'''
219 219 return self._files
220 220
221 221 @propertycache
222 222 def _dirs(self):
223 223 return set(util.dirs(self._fileroots)) | set(['.'])
224 224
225 225 def visitdir(self, dir):
226 226 '''Decides whether a directory should be visited based on whether it
227 227 has potential matches in it or one of its subdirectories. This is
228 228 based on the match's primary, included, and excluded patterns.
229 229
230 230 Returns the string 'all' if the given directory and all subdirectories
231 231 should be visited. Otherwise returns True or False indicating whether
232 232 the given directory should be visited.
233 233
234 234 This function's behavior is undefined if it has returned False for
235 235 one of the dir's parent directories.
236 236 '''
237 237 if self.prefix() and dir in self._fileroots:
238 238 return 'all'
239 239 if dir in self._excluderoots:
240 240 return False
241 241 if (self._includeroots and
242 242 '.' not in self._includeroots and
243 243 dir not in self._includeroots and
244 244 dir not in self._includedirs and
245 245 not any(parent in self._includeroots
246 246 for parent in util.finddirs(dir))):
247 247 return False
248 248 return (not self._fileroots or
249 249 '.' in self._fileroots or
250 250 dir in self._fileroots or
251 251 dir in self._dirs or
252 252 any(parentdir in self._fileroots
253 253 for parentdir in util.finddirs(dir)))
254 254
255 255 def exact(self, f):
256 256 '''Returns True if f is in .files().'''
257 257 return f in self._fileroots
258 258
259 259 def anypats(self):
260 260 '''Matcher uses patterns or include/exclude.'''
261 261 return self._anypats
262 262
263 263 def always(self):
264 264 '''Matcher will match everything and .files() will be empty
265 265 - optimization might be possible and necessary.'''
266 266 return self._always
267 267
268 268 def ispartial(self):
269 269 '''True if the matcher won't always match.
270 270
271 271 Although it's just the inverse of _always in this implementation,
272 272 an extension such as narrowhg might make it return something
273 273 slightly different.'''
274 274 return not self._always
275 275
276 276 def isexact(self):
277 277 return self.matchfn == self.exact
278 278
279 279 def prefix(self):
280 280 return not self.always() and not self.isexact() and not self.anypats()
281 281
282 282 def _normalize(self, patterns, default, root, cwd, auditor):
283 283 '''Convert 'kind:pat' from the patterns list to tuples with kind and
284 284 normalized and rooted patterns and with listfiles expanded.'''
285 285 kindpats = []
286 286 for kind, pat in [_patsplit(p, default) for p in patterns]:
287 287 if kind in ('glob', 'relpath'):
288 288 pat = pathutil.canonpath(root, cwd, pat, auditor)
289 289 elif kind in ('relglob', 'path'):
290 290 pat = util.normpath(pat)
291 291 elif kind in ('listfile', 'listfile0'):
292 292 try:
293 293 files = util.readfile(pat)
294 294 if kind == 'listfile0':
295 295 files = files.split('\0')
296 296 else:
297 297 files = files.splitlines()
298 298 files = [f for f in files if f]
299 299 except EnvironmentError:
300 300 raise error.Abort(_("unable to read file list (%s)") % pat)
301 301 for k, p, source in self._normalize(files, default, root, cwd,
302 302 auditor):
303 303 kindpats.append((k, p, pat))
304 304 continue
305 305 elif kind == 'include':
306 306 try:
307 307 fullpath = os.path.join(root, util.localpath(pat))
308 308 includepats = readpatternfile(fullpath, self._warn)
309 309 for k, p, source in self._normalize(includepats, default,
310 310 root, cwd, auditor):
311 311 kindpats.append((k, p, source or pat))
312 312 except error.Abort as inst:
313 313 raise error.Abort('%s: %s' % (pat, inst[0]))
314 314 except IOError as inst:
315 315 if self._warn:
316 316 self._warn(_("skipping unreadable pattern file "
317 317 "'%s': %s\n") % (pat, inst.strerror))
318 318 continue
319 319 # else: re or relre - which cannot be normalized
320 320 kindpats.append((kind, pat, ''))
321 321 return kindpats
322 322
323 def matchessubrepo(self, subpath):
324 return (self.exact(subpath)
325 or any(f.startswith(subpath + '/') for f in self.files()))
326
327 323 def exact(root, cwd, files, badfn=None):
328 324 return match(root, cwd, files, exact=True, badfn=badfn)
329 325
330 326 def always(root, cwd):
331 327 return match(root, cwd, [])
332 328
333 329 def badmatch(match, badfn):
334 330 """Make a copy of the given matcher, replacing its bad method with the given
335 331 one.
336 332 """
337 333 m = copy.copy(match)
338 334 m.bad = badfn
339 335 return m
340 336
341 337 class subdirmatcher(match):
342 338 """Adapt a matcher to work on a subdirectory only.
343 339
344 340 The paths are remapped to remove/insert the path as needed:
345 341
346 342 >>> m1 = match('root', '', ['a.txt', 'sub/b.txt'])
347 343 >>> m2 = subdirmatcher('sub', m1)
348 344 >>> bool(m2('a.txt'))
349 345 False
350 346 >>> bool(m2('b.txt'))
351 347 True
352 348 >>> bool(m2.matchfn('a.txt'))
353 349 False
354 350 >>> bool(m2.matchfn('b.txt'))
355 351 True
356 352 >>> m2.files()
357 353 ['b.txt']
358 354 >>> m2.exact('b.txt')
359 355 True
360 356 >>> util.pconvert(m2.rel('b.txt'))
361 357 'sub/b.txt'
362 358 >>> def bad(f, msg):
363 359 ... print "%s: %s" % (f, msg)
364 360 >>> m1.bad = bad
365 361 >>> m2.bad('x.txt', 'No such file')
366 362 sub/x.txt: No such file
367 363 >>> m2.abs('c.txt')
368 364 'sub/c.txt'
369 365 """
370 366
371 367 def __init__(self, path, matcher):
372 368 self._root = matcher._root
373 369 self._cwd = matcher._cwd
374 370 self._path = path
375 371 self._matcher = matcher
376 372 self._always = matcher._always
377 373 self._pathrestricted = matcher._pathrestricted
378 374
379 375 self._files = [f[len(path) + 1:] for f in matcher._files
380 376 if f.startswith(path + "/")]
381 377
382 378 # If the parent repo had a path to this subrepo and no patterns are
383 379 # specified, this submatcher always matches.
384 380 if not self._always and not matcher._anypats:
385 381 self._always = any(f == path for f in matcher._files)
386 382
387 383 self._anypats = matcher._anypats
388 384 # Some information is lost in the superclass's constructor, so we
389 385 # can not accurately create the matching function for the subdirectory
390 386 # from the inputs. Instead, we override matchfn() and visitdir() to
391 387 # call the original matcher with the subdirectory path prepended.
392 388 self.matchfn = lambda fn: matcher.matchfn(self._path + "/" + fn)
393 389 def visitdir(dir):
394 390 if dir == '.':
395 391 return matcher.visitdir(self._path)
396 392 return matcher.visitdir(self._path + "/" + dir)
397 393 self.visitdir = visitdir
398 394 self._fileroots = set(self._files)
399 395
400 396 def abs(self, f):
401 397 return self._matcher.abs(self._path + "/" + f)
402 398
403 399 def bad(self, f, msg):
404 400 self._matcher.bad(self._path + "/" + f, msg)
405 401
406 402 def rel(self, f):
407 403 return self._matcher.rel(self._path + "/" + f)
408 404
409 405 class icasefsmatcher(match):
410 406 """A matcher for wdir on case insensitive filesystems, which normalizes the
411 407 given patterns to the case in the filesystem.
412 408 """
413 409
414 410 def __init__(self, root, cwd, patterns, include, exclude, default, auditor,
415 411 ctx, listsubrepos=False, badfn=None):
416 412 init = super(icasefsmatcher, self).__init__
417 413 self._dirstate = ctx.repo().dirstate
418 414 self._dsnormalize = self._dirstate.normalize
419 415
420 416 init(root, cwd, patterns, include, exclude, default, auditor=auditor,
421 417 ctx=ctx, listsubrepos=listsubrepos, badfn=badfn)
422 418
423 419 # m.exact(file) must be based off of the actual user input, otherwise
424 420 # inexact case matches are treated as exact, and not noted without -v.
425 421 if self._files:
426 422 self._fileroots = set(_roots(self._kp))
427 423
428 424 def _normalize(self, patterns, default, root, cwd, auditor):
429 425 self._kp = super(icasefsmatcher, self)._normalize(patterns, default,
430 426 root, cwd, auditor)
431 427 kindpats = []
432 428 for kind, pats, source in self._kp:
433 429 if kind not in ('re', 'relre'): # regex can't be normalized
434 430 p = pats
435 431 pats = self._dsnormalize(pats)
436 432
437 433 # Preserve the original to handle a case only rename.
438 434 if p != pats and p in self._dirstate:
439 435 kindpats.append((kind, p, source))
440 436
441 437 kindpats.append((kind, pats, source))
442 438 return kindpats
443 439
444 440 def patkind(pattern, default=None):
445 441 '''If pattern is 'kind:pat' with a known kind, return kind.'''
446 442 return _patsplit(pattern, default)[0]
447 443
448 444 def _patsplit(pattern, default):
449 445 """Split a string into the optional pattern kind prefix and the actual
450 446 pattern."""
451 447 if ':' in pattern:
452 448 kind, pat = pattern.split(':', 1)
453 449 if kind in ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
454 450 'listfile', 'listfile0', 'set', 'include', 'subinclude'):
455 451 return kind, pat
456 452 return default, pattern
457 453
458 454 def _globre(pat):
459 455 r'''Convert an extended glob string to a regexp string.
460 456
461 457 >>> print _globre(r'?')
462 458 .
463 459 >>> print _globre(r'*')
464 460 [^/]*
465 461 >>> print _globre(r'**')
466 462 .*
467 463 >>> print _globre(r'**/a')
468 464 (?:.*/)?a
469 465 >>> print _globre(r'a/**/b')
470 466 a\/(?:.*/)?b
471 467 >>> print _globre(r'[a*?!^][^b][!c]')
472 468 [a*?!^][\^b][^c]
473 469 >>> print _globre(r'{a,b}')
474 470 (?:a|b)
475 471 >>> print _globre(r'.\*\?')
476 472 \.\*\?
477 473 '''
478 474 i, n = 0, len(pat)
479 475 res = ''
480 476 group = 0
481 477 escape = util.re.escape
482 478 def peek():
483 479 return i < n and pat[i]
484 480 while i < n:
485 481 c = pat[i]
486 482 i += 1
487 483 if c not in '*?[{},\\':
488 484 res += escape(c)
489 485 elif c == '*':
490 486 if peek() == '*':
491 487 i += 1
492 488 if peek() == '/':
493 489 i += 1
494 490 res += '(?:.*/)?'
495 491 else:
496 492 res += '.*'
497 493 else:
498 494 res += '[^/]*'
499 495 elif c == '?':
500 496 res += '.'
501 497 elif c == '[':
502 498 j = i
503 499 if j < n and pat[j] in '!]':
504 500 j += 1
505 501 while j < n and pat[j] != ']':
506 502 j += 1
507 503 if j >= n:
508 504 res += '\\['
509 505 else:
510 506 stuff = pat[i:j].replace('\\','\\\\')
511 507 i = j + 1
512 508 if stuff[0] == '!':
513 509 stuff = '^' + stuff[1:]
514 510 elif stuff[0] == '^':
515 511 stuff = '\\' + stuff
516 512 res = '%s[%s]' % (res, stuff)
517 513 elif c == '{':
518 514 group += 1
519 515 res += '(?:'
520 516 elif c == '}' and group:
521 517 res += ')'
522 518 group -= 1
523 519 elif c == ',' and group:
524 520 res += '|'
525 521 elif c == '\\':
526 522 p = peek()
527 523 if p:
528 524 i += 1
529 525 res += escape(p)
530 526 else:
531 527 res += escape(c)
532 528 else:
533 529 res += escape(c)
534 530 return res
535 531
536 532 def _regex(kind, pat, globsuffix):
537 533 '''Convert a (normalized) pattern of any kind into a regular expression.
538 534 globsuffix is appended to the regexp of globs.'''
539 535 if not pat:
540 536 return ''
541 537 if kind == 're':
542 538 return pat
543 539 if kind == 'path':
544 540 if pat == '.':
545 541 return ''
546 542 return '^' + util.re.escape(pat) + '(?:/|$)'
547 543 if kind == 'relglob':
548 544 return '(?:|.*/)' + _globre(pat) + globsuffix
549 545 if kind == 'relpath':
550 546 return util.re.escape(pat) + '(?:/|$)'
551 547 if kind == 'relre':
552 548 if pat.startswith('^'):
553 549 return pat
554 550 return '.*' + pat
555 551 return _globre(pat) + globsuffix
556 552
557 553 def _buildmatch(ctx, kindpats, globsuffix, listsubrepos, root):
558 554 '''Return regexp string and a matcher function for kindpats.
559 555 globsuffix is appended to the regexp of globs.'''
560 556 matchfuncs = []
561 557
562 558 subincludes, kindpats = _expandsubinclude(kindpats, root)
563 559 if subincludes:
564 560 def matchsubinclude(f):
565 561 for prefix, mf in subincludes:
566 562 if f.startswith(prefix) and mf(f[len(prefix):]):
567 563 return True
568 564 return False
569 565 matchfuncs.append(matchsubinclude)
570 566
571 567 fset, kindpats = _expandsets(kindpats, ctx, listsubrepos)
572 568 if fset:
573 569 matchfuncs.append(fset.__contains__)
574 570
575 571 regex = ''
576 572 if kindpats:
577 573 regex, mf = _buildregexmatch(kindpats, globsuffix)
578 574 matchfuncs.append(mf)
579 575
580 576 if len(matchfuncs) == 1:
581 577 return regex, matchfuncs[0]
582 578 else:
583 579 return regex, lambda f: any(mf(f) for mf in matchfuncs)
584 580
585 581 def _buildregexmatch(kindpats, globsuffix):
586 582 """Build a match function from a list of kinds and kindpats,
587 583 return regexp string and a matcher function."""
588 584 try:
589 585 regex = '(?:%s)' % '|'.join([_regex(k, p, globsuffix)
590 586 for (k, p, s) in kindpats])
591 587 if len(regex) > 20000:
592 588 raise OverflowError
593 589 return regex, _rematcher(regex)
594 590 except OverflowError:
595 591 # We're using a Python with a tiny regex engine and we
596 592 # made it explode, so we'll divide the pattern list in two
597 593 # until it works
598 594 l = len(kindpats)
599 595 if l < 2:
600 596 raise
601 597 regexa, a = _buildregexmatch(kindpats[:l//2], globsuffix)
602 598 regexb, b = _buildregexmatch(kindpats[l//2:], globsuffix)
603 599 return regex, lambda s: a(s) or b(s)
604 600 except re.error:
605 601 for k, p, s in kindpats:
606 602 try:
607 603 _rematcher('(?:%s)' % _regex(k, p, globsuffix))
608 604 except re.error:
609 605 if s:
610 606 raise error.Abort(_("%s: invalid pattern (%s): %s") %
611 607 (s, k, p))
612 608 else:
613 609 raise error.Abort(_("invalid pattern (%s): %s") % (k, p))
614 610 raise error.Abort(_("invalid pattern"))
615 611
616 612 def _roots(kindpats):
617 613 '''return roots and exact explicitly listed files from patterns
618 614
619 615 >>> _roots([('glob', 'g/*', ''), ('glob', 'g', ''), ('glob', 'g*', '')])
620 616 ['g', 'g', '.']
621 617 >>> _roots([('relpath', 'r', ''), ('path', 'p/p', ''), ('path', '', '')])
622 618 ['r', 'p/p', '.']
623 619 >>> _roots([('relglob', 'rg*', ''), ('re', 're/', ''), ('relre', 'rr', '')])
624 620 ['.', '.', '.']
625 621 '''
626 622 r = []
627 623 for kind, pat, source in kindpats:
628 624 if kind == 'glob': # find the non-glob prefix
629 625 root = []
630 626 for p in pat.split('/'):
631 627 if '[' in p or '{' in p or '*' in p or '?' in p:
632 628 break
633 629 root.append(p)
634 630 r.append('/'.join(root) or '.')
635 631 elif kind in ('relpath', 'path'):
636 632 r.append(pat or '.')
637 633 else: # relglob, re, relre
638 634 r.append('.')
639 635 return r
640 636
641 637 def _anypats(kindpats):
642 638 for kind, pat, source in kindpats:
643 639 if kind in ('glob', 're', 'relglob', 'relre', 'set'):
644 640 return True
645 641
646 642 _commentre = None
647 643
648 644 def readpatternfile(filepath, warn, sourceinfo=False):
649 645 '''parse a pattern file, returning a list of
650 646 patterns. These patterns should be given to compile()
651 647 to be validated and converted into a match function.
652 648
653 649 trailing white space is dropped.
654 650 the escape character is backslash.
655 651 comments start with #.
656 652 empty lines are skipped.
657 653
658 654 lines can be of the following formats:
659 655
660 656 syntax: regexp # defaults following lines to non-rooted regexps
661 657 syntax: glob # defaults following lines to non-rooted globs
662 658 re:pattern # non-rooted regular expression
663 659 glob:pattern # non-rooted glob
664 660 pattern # pattern of the current default type
665 661
666 662 if sourceinfo is set, returns a list of tuples:
667 663 (pattern, lineno, originalline). This is useful to debug ignore patterns.
668 664 '''
669 665
670 666 syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:',
671 667 'include': 'include', 'subinclude': 'subinclude'}
672 668 syntax = 'relre:'
673 669 patterns = []
674 670
675 671 fp = open(filepath)
676 672 for lineno, line in enumerate(fp, start=1):
677 673 if "#" in line:
678 674 global _commentre
679 675 if not _commentre:
680 676 _commentre = util.re.compile(r'((?:^|[^\\])(?:\\\\)*)#.*')
681 677 # remove comments prefixed by an even number of escapes
682 678 m = _commentre.search(line)
683 679 if m:
684 680 line = line[:m.end(1)]
685 681 # fixup properly escaped comments that survived the above
686 682 line = line.replace("\\#", "#")
687 683 line = line.rstrip()
688 684 if not line:
689 685 continue
690 686
691 687 if line.startswith('syntax:'):
692 688 s = line[7:].strip()
693 689 try:
694 690 syntax = syntaxes[s]
695 691 except KeyError:
696 692 if warn:
697 693 warn(_("%s: ignoring invalid syntax '%s'\n") %
698 694 (filepath, s))
699 695 continue
700 696
701 697 linesyntax = syntax
702 698 for s, rels in syntaxes.iteritems():
703 699 if line.startswith(rels):
704 700 linesyntax = rels
705 701 line = line[len(rels):]
706 702 break
707 703 elif line.startswith(s+':'):
708 704 linesyntax = rels
709 705 line = line[len(s) + 1:]
710 706 break
711 707 if sourceinfo:
712 708 patterns.append((linesyntax + line, lineno, line))
713 709 else:
714 710 patterns.append(linesyntax + line)
715 711 fp.close()
716 712 return patterns
General Comments 0
You need to be logged in to leave comments. Login now