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