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