##// END OF EJS Templates
fileset: move import of match module to top...
Yuya Nishihara -
r35757:9eb5c400 default
parent child Browse files
Show More
@@ -1,643 +1,642
1 1 # fileset.py - file set queries for mercurial
2 2 #
3 3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
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 re
11 11
12 12 from .i18n import _
13 13 from . import (
14 14 error,
15 match as matchmod,
15 16 merge,
16 17 parser,
17 18 pycompat,
18 19 registrar,
19 20 scmutil,
20 21 util,
21 22 )
22 23
23 24 elements = {
24 25 # token-type: binding-strength, primary, prefix, infix, suffix
25 26 "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None),
26 27 "-": (5, None, ("negate", 19), ("minus", 5), None),
27 28 "not": (10, None, ("not", 10), None, None),
28 29 "!": (10, None, ("not", 10), None, None),
29 30 "and": (5, None, None, ("and", 5), None),
30 31 "&": (5, None, None, ("and", 5), None),
31 32 "or": (4, None, None, ("or", 4), None),
32 33 "|": (4, None, None, ("or", 4), None),
33 34 "+": (4, None, None, ("or", 4), None),
34 35 ",": (2, None, None, ("list", 2), None),
35 36 ")": (0, None, None, None, None),
36 37 "symbol": (0, "symbol", None, None, None),
37 38 "string": (0, "string", None, None, None),
38 39 "end": (0, None, None, None, None),
39 40 }
40 41
41 42 keywords = {'and', 'or', 'not'}
42 43
43 44 globchars = ".*{}[]?/\\_"
44 45
45 46 def tokenize(program):
46 47 pos, l = 0, len(program)
47 48 program = pycompat.bytestr(program)
48 49 while pos < l:
49 50 c = program[pos]
50 51 if c.isspace(): # skip inter-token whitespace
51 52 pass
52 53 elif c in "(),-|&+!": # handle simple operators
53 54 yield (c, None, pos)
54 55 elif (c in '"\'' or c == 'r' and
55 56 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
56 57 if c == 'r':
57 58 pos += 1
58 59 c = program[pos]
59 60 decode = lambda x: x
60 61 else:
61 62 decode = parser.unescapestr
62 63 pos += 1
63 64 s = pos
64 65 while pos < l: # find closing quote
65 66 d = program[pos]
66 67 if d == '\\': # skip over escaped characters
67 68 pos += 2
68 69 continue
69 70 if d == c:
70 71 yield ('string', decode(program[s:pos]), s)
71 72 break
72 73 pos += 1
73 74 else:
74 75 raise error.ParseError(_("unterminated string"), s)
75 76 elif c.isalnum() or c in globchars or ord(c) > 127:
76 77 # gather up a symbol/keyword
77 78 s = pos
78 79 pos += 1
79 80 while pos < l: # find end of symbol
80 81 d = program[pos]
81 82 if not (d.isalnum() or d in globchars or ord(d) > 127):
82 83 break
83 84 pos += 1
84 85 sym = program[s:pos]
85 86 if sym in keywords: # operator keywords
86 87 yield (sym, None, s)
87 88 else:
88 89 yield ('symbol', sym, s)
89 90 pos -= 1
90 91 else:
91 92 raise error.ParseError(_("syntax error"), pos)
92 93 pos += 1
93 94 yield ('end', None, pos)
94 95
95 96 def parse(expr):
96 97 p = parser.parser(elements)
97 98 tree, pos = p.parse(tokenize(expr))
98 99 if pos != len(expr):
99 100 raise error.ParseError(_("invalid token"), pos)
100 101 return tree
101 102
102 103 def getsymbol(x):
103 104 if x and x[0] == 'symbol':
104 105 return x[1]
105 106 raise error.ParseError(_('not a symbol'))
106 107
107 108 def getstring(x, err):
108 109 if x and (x[0] == 'string' or x[0] == 'symbol'):
109 110 return x[1]
110 111 raise error.ParseError(err)
111 112
112 113 def getset(mctx, x):
113 114 if not x:
114 115 raise error.ParseError(_("missing argument"))
115 116 return methods[x[0]](mctx, *x[1:])
116 117
117 118 def stringset(mctx, x):
118 119 m = mctx.matcher([x])
119 120 return [f for f in mctx.subset if m(f)]
120 121
121 122 def andset(mctx, x, y):
122 123 return getset(mctx.narrow(getset(mctx, x)), y)
123 124
124 125 def orset(mctx, x, y):
125 126 # needs optimizing
126 127 xl = getset(mctx, x)
127 128 yl = getset(mctx, y)
128 129 return xl + [f for f in yl if f not in xl]
129 130
130 131 def notset(mctx, x):
131 132 s = set(getset(mctx, x))
132 133 return [r for r in mctx.subset if r not in s]
133 134
134 135 def minusset(mctx, x, y):
135 136 xl = getset(mctx, x)
136 137 yl = set(getset(mctx, y))
137 138 return [f for f in xl if f not in yl]
138 139
139 140 def negateset(mctx, x):
140 141 raise error.ParseError(_("can't use negate operator in this context"))
141 142
142 143 def listset(mctx, a, b):
143 144 raise error.ParseError(_("can't use a list in this context"),
144 145 hint=_('see hg help "filesets.x or y"'))
145 146
146 147 # symbols are callable like:
147 148 # fun(mctx, x)
148 149 # with:
149 150 # mctx - current matchctx instance
150 151 # x - argument in tree form
151 152 symbols = {}
152 153
153 154 # filesets using matchctx.status()
154 155 _statuscallers = set()
155 156
156 157 # filesets using matchctx.existing()
157 158 _existingcallers = set()
158 159
159 160 predicate = registrar.filesetpredicate()
160 161
161 162 @predicate('modified()', callstatus=True)
162 163 def modified(mctx, x):
163 164 """File that is modified according to :hg:`status`.
164 165 """
165 166 # i18n: "modified" is a keyword
166 167 getargs(x, 0, 0, _("modified takes no arguments"))
167 168 s = set(mctx.status().modified)
168 169 return [f for f in mctx.subset if f in s]
169 170
170 171 @predicate('added()', callstatus=True)
171 172 def added(mctx, x):
172 173 """File that is added according to :hg:`status`.
173 174 """
174 175 # i18n: "added" is a keyword
175 176 getargs(x, 0, 0, _("added takes no arguments"))
176 177 s = set(mctx.status().added)
177 178 return [f for f in mctx.subset if f in s]
178 179
179 180 @predicate('removed()', callstatus=True)
180 181 def removed(mctx, x):
181 182 """File that is removed according to :hg:`status`.
182 183 """
183 184 # i18n: "removed" is a keyword
184 185 getargs(x, 0, 0, _("removed takes no arguments"))
185 186 s = set(mctx.status().removed)
186 187 return [f for f in mctx.subset if f in s]
187 188
188 189 @predicate('deleted()', callstatus=True)
189 190 def deleted(mctx, x):
190 191 """Alias for ``missing()``.
191 192 """
192 193 # i18n: "deleted" is a keyword
193 194 getargs(x, 0, 0, _("deleted takes no arguments"))
194 195 s = set(mctx.status().deleted)
195 196 return [f for f in mctx.subset if f in s]
196 197
197 198 @predicate('missing()', callstatus=True)
198 199 def missing(mctx, x):
199 200 """File that is missing according to :hg:`status`.
200 201 """
201 202 # i18n: "missing" is a keyword
202 203 getargs(x, 0, 0, _("missing takes no arguments"))
203 204 s = set(mctx.status().deleted)
204 205 return [f for f in mctx.subset if f in s]
205 206
206 207 @predicate('unknown()', callstatus=True)
207 208 def unknown(mctx, x):
208 209 """File that is unknown according to :hg:`status`. These files will only be
209 210 considered if this predicate is used.
210 211 """
211 212 # i18n: "unknown" is a keyword
212 213 getargs(x, 0, 0, _("unknown takes no arguments"))
213 214 s = set(mctx.status().unknown)
214 215 return [f for f in mctx.subset if f in s]
215 216
216 217 @predicate('ignored()', callstatus=True)
217 218 def ignored(mctx, x):
218 219 """File that is ignored according to :hg:`status`. These files will only be
219 220 considered if this predicate is used.
220 221 """
221 222 # i18n: "ignored" is a keyword
222 223 getargs(x, 0, 0, _("ignored takes no arguments"))
223 224 s = set(mctx.status().ignored)
224 225 return [f for f in mctx.subset if f in s]
225 226
226 227 @predicate('clean()', callstatus=True)
227 228 def clean(mctx, x):
228 229 """File that is clean according to :hg:`status`.
229 230 """
230 231 # i18n: "clean" is a keyword
231 232 getargs(x, 0, 0, _("clean takes no arguments"))
232 233 s = set(mctx.status().clean)
233 234 return [f for f in mctx.subset if f in s]
234 235
235 236 def func(mctx, a, b):
236 237 funcname = getsymbol(a)
237 238 if funcname in symbols:
238 239 enabled = mctx._existingenabled
239 240 mctx._existingenabled = funcname in _existingcallers
240 241 try:
241 242 return symbols[funcname](mctx, b)
242 243 finally:
243 244 mctx._existingenabled = enabled
244 245
245 246 keep = lambda fn: getattr(fn, '__doc__', None) is not None
246 247
247 248 syms = [s for (s, fn) in symbols.items() if keep(fn)]
248 249 raise error.UnknownIdentifier(funcname, syms)
249 250
250 251 def getlist(x):
251 252 if not x:
252 253 return []
253 254 if x[0] == 'list':
254 255 return getlist(x[1]) + [x[2]]
255 256 return [x]
256 257
257 258 def getargs(x, min, max, err):
258 259 l = getlist(x)
259 260 if len(l) < min or len(l) > max:
260 261 raise error.ParseError(err)
261 262 return l
262 263
263 264 @predicate('binary()', callexisting=True)
264 265 def binary(mctx, x):
265 266 """File that appears to be binary (contains NUL bytes).
266 267 """
267 268 # i18n: "binary" is a keyword
268 269 getargs(x, 0, 0, _("binary takes no arguments"))
269 270 return [f for f in mctx.existing() if mctx.ctx[f].isbinary()]
270 271
271 272 @predicate('exec()', callexisting=True)
272 273 def exec_(mctx, x):
273 274 """File that is marked as executable.
274 275 """
275 276 # i18n: "exec" is a keyword
276 277 getargs(x, 0, 0, _("exec takes no arguments"))
277 278 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'x']
278 279
279 280 @predicate('symlink()', callexisting=True)
280 281 def symlink(mctx, x):
281 282 """File that is marked as a symlink.
282 283 """
283 284 # i18n: "symlink" is a keyword
284 285 getargs(x, 0, 0, _("symlink takes no arguments"))
285 286 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'l']
286 287
287 288 @predicate('resolved()')
288 289 def resolved(mctx, x):
289 290 """File that is marked resolved according to :hg:`resolve -l`.
290 291 """
291 292 # i18n: "resolved" is a keyword
292 293 getargs(x, 0, 0, _("resolved takes no arguments"))
293 294 if mctx.ctx.rev() is not None:
294 295 return []
295 296 ms = merge.mergestate.read(mctx.ctx.repo())
296 297 return [f for f in mctx.subset if f in ms and ms[f] == 'r']
297 298
298 299 @predicate('unresolved()')
299 300 def unresolved(mctx, x):
300 301 """File that is marked unresolved according to :hg:`resolve -l`.
301 302 """
302 303 # i18n: "unresolved" is a keyword
303 304 getargs(x, 0, 0, _("unresolved takes no arguments"))
304 305 if mctx.ctx.rev() is not None:
305 306 return []
306 307 ms = merge.mergestate.read(mctx.ctx.repo())
307 308 return [f for f in mctx.subset if f in ms and ms[f] == 'u']
308 309
309 310 @predicate('hgignore()')
310 311 def hgignore(mctx, x):
311 312 """File that matches the active .hgignore pattern.
312 313 """
313 314 # i18n: "hgignore" is a keyword
314 315 getargs(x, 0, 0, _("hgignore takes no arguments"))
315 316 ignore = mctx.ctx.repo().dirstate._ignore
316 317 return [f for f in mctx.subset if ignore(f)]
317 318
318 319 @predicate('portable()')
319 320 def portable(mctx, x):
320 321 """File that has a portable name. (This doesn't include filenames with case
321 322 collisions.)
322 323 """
323 324 # i18n: "portable" is a keyword
324 325 getargs(x, 0, 0, _("portable takes no arguments"))
325 326 checkwinfilename = util.checkwinfilename
326 327 return [f for f in mctx.subset if checkwinfilename(f) is None]
327 328
328 329 @predicate('grep(regex)', callexisting=True)
329 330 def grep(mctx, x):
330 331 """File contains the given regular expression.
331 332 """
332 333 try:
333 334 # i18n: "grep" is a keyword
334 335 r = re.compile(getstring(x, _("grep requires a pattern")))
335 336 except re.error as e:
336 337 raise error.ParseError(_('invalid match pattern: %s') % e)
337 338 return [f for f in mctx.existing() if r.search(mctx.ctx[f].data())]
338 339
339 340 def _sizetomax(s):
340 341 try:
341 342 s = s.strip().lower()
342 343 for k, v in util._sizeunits:
343 344 if s.endswith(k):
344 345 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
345 346 n = s[:-len(k)]
346 347 inc = 1.0
347 348 if "." in n:
348 349 inc /= 10 ** len(n.split(".")[1])
349 350 return int((float(n) + inc) * v) - 1
350 351 # no extension, this is a precise value
351 352 return int(s)
352 353 except ValueError:
353 354 raise error.ParseError(_("couldn't parse size: %s") % s)
354 355
355 356 def sizematcher(x):
356 357 """Return a function(size) -> bool from the ``size()`` expression"""
357 358
358 359 # i18n: "size" is a keyword
359 360 expr = getstring(x, _("size requires an expression")).strip()
360 361 if '-' in expr: # do we have a range?
361 362 a, b = expr.split('-', 1)
362 363 a = util.sizetoint(a)
363 364 b = util.sizetoint(b)
364 365 return lambda x: x >= a and x <= b
365 366 elif expr.startswith("<="):
366 367 a = util.sizetoint(expr[2:])
367 368 return lambda x: x <= a
368 369 elif expr.startswith("<"):
369 370 a = util.sizetoint(expr[1:])
370 371 return lambda x: x < a
371 372 elif expr.startswith(">="):
372 373 a = util.sizetoint(expr[2:])
373 374 return lambda x: x >= a
374 375 elif expr.startswith(">"):
375 376 a = util.sizetoint(expr[1:])
376 377 return lambda x: x > a
377 378 elif expr[0].isdigit or expr[0] == '.':
378 379 a = util.sizetoint(expr)
379 380 b = _sizetomax(expr)
380 381 return lambda x: x >= a and x <= b
381 382 raise error.ParseError(_("couldn't parse size: %s") % expr)
382 383
383 384 @predicate('size(expression)', callexisting=True)
384 385 def size(mctx, x):
385 386 """File size matches the given expression. Examples:
386 387
387 388 - size('1k') - files from 1024 to 2047 bytes
388 389 - size('< 20k') - files less than 20480 bytes
389 390 - size('>= .5MB') - files at least 524288 bytes
390 391 - size('4k - 1MB') - files from 4096 bytes to 1048576 bytes
391 392 """
392 393 m = sizematcher(x)
393 394 return [f for f in mctx.existing() if m(mctx.ctx[f].size())]
394 395
395 396 @predicate('encoding(name)', callexisting=True)
396 397 def encoding(mctx, x):
397 398 """File can be successfully decoded with the given character
398 399 encoding. May not be useful for encodings other than ASCII and
399 400 UTF-8.
400 401 """
401 402
402 403 # i18n: "encoding" is a keyword
403 404 enc = getstring(x, _("encoding requires an encoding name"))
404 405
405 406 s = []
406 407 for f in mctx.existing():
407 408 d = mctx.ctx[f].data()
408 409 try:
409 410 d.decode(enc)
410 411 except LookupError:
411 412 raise error.Abort(_("unknown encoding '%s'") % enc)
412 413 except UnicodeDecodeError:
413 414 continue
414 415 s.append(f)
415 416
416 417 return s
417 418
418 419 @predicate('eol(style)', callexisting=True)
419 420 def eol(mctx, x):
420 421 """File contains newlines of the given style (dos, unix, mac). Binary
421 422 files are excluded, files with mixed line endings match multiple
422 423 styles.
423 424 """
424 425
425 426 # i18n: "eol" is a keyword
426 427 enc = getstring(x, _("eol requires a style name"))
427 428
428 429 s = []
429 430 for f in mctx.existing():
430 431 d = mctx.ctx[f].data()
431 432 if util.binary(d):
432 433 continue
433 434 if (enc == 'dos' or enc == 'win') and '\r\n' in d:
434 435 s.append(f)
435 436 elif enc == 'unix' and re.search('(?<!\r)\n', d):
436 437 s.append(f)
437 438 elif enc == 'mac' and re.search('\r(?!\n)', d):
438 439 s.append(f)
439 440 return s
440 441
441 442 @predicate('copied()')
442 443 def copied(mctx, x):
443 444 """File that is recorded as being copied.
444 445 """
445 446 # i18n: "copied" is a keyword
446 447 getargs(x, 0, 0, _("copied takes no arguments"))
447 448 s = []
448 449 for f in mctx.subset:
449 450 p = mctx.ctx[f].parents()
450 451 if p and p[0].path() != f:
451 452 s.append(f)
452 453 return s
453 454
454 455 @predicate('revs(revs, pattern)')
455 456 def revs(mctx, x):
456 457 """Evaluate set in the specified revisions. If the revset match multiple
457 458 revs, this will return file matching pattern in any of the revision.
458 459 """
459 460 # i18n: "revs" is a keyword
460 461 r, x = getargs(x, 2, 2, _("revs takes two arguments"))
461 462 # i18n: "revs" is a keyword
462 463 revspec = getstring(r, _("first argument to revs must be a revision"))
463 464 repo = mctx.ctx.repo()
464 465 revs = scmutil.revrange(repo, [revspec])
465 466
466 467 found = set()
467 468 result = []
468 469 for r in revs:
469 470 ctx = repo[r]
470 471 for f in getset(mctx.switch(ctx, _buildstatus(ctx, x)), x):
471 472 if f not in found:
472 473 found.add(f)
473 474 result.append(f)
474 475 return result
475 476
476 477 @predicate('status(base, rev, pattern)')
477 478 def status(mctx, x):
478 479 """Evaluate predicate using status change between ``base`` and
479 480 ``rev``. Examples:
480 481
481 482 - ``status(3, 7, added())`` - matches files added from "3" to "7"
482 483 """
483 484 repo = mctx.ctx.repo()
484 485 # i18n: "status" is a keyword
485 486 b, r, x = getargs(x, 3, 3, _("status takes three arguments"))
486 487 # i18n: "status" is a keyword
487 488 baseerr = _("first argument to status must be a revision")
488 489 baserevspec = getstring(b, baseerr)
489 490 if not baserevspec:
490 491 raise error.ParseError(baseerr)
491 492 reverr = _("second argument to status must be a revision")
492 493 revspec = getstring(r, reverr)
493 494 if not revspec:
494 495 raise error.ParseError(reverr)
495 496 basenode, node = scmutil.revpair(repo, [baserevspec, revspec])
496 497 basectx = repo[basenode]
497 498 ctx = repo[node]
498 499 return getset(mctx.switch(ctx, _buildstatus(ctx, x, basectx=basectx)), x)
499 500
500 501 @predicate('subrepo([pattern])')
501 502 def subrepo(mctx, x):
502 503 """Subrepositories whose paths match the given pattern.
503 504 """
504 505 # i18n: "subrepo" is a keyword
505 506 getargs(x, 0, 1, _("subrepo takes at most one argument"))
506 507 ctx = mctx.ctx
507 508 sstate = sorted(ctx.substate)
508 509 if x:
509 510 # i18n: "subrepo" is a keyword
510 511 pat = getstring(x, _("subrepo requires a pattern or no arguments"))
511
512 from . import match as matchmod # avoid circular import issues
513 512 fast = not matchmod.patkind(pat)
514 513 if fast:
515 514 def m(s):
516 515 return (s == pat)
517 516 else:
518 517 m = matchmod.match(ctx.repo().root, '', [pat], ctx=ctx)
519 518 return [sub for sub in sstate if m(sub)]
520 519 else:
521 520 return [sub for sub in sstate]
522 521
523 522 methods = {
524 523 'string': stringset,
525 524 'symbol': stringset,
526 525 'and': andset,
527 526 'or': orset,
528 527 'minus': minusset,
529 528 'negate': negateset,
530 529 'list': listset,
531 530 'group': getset,
532 531 'not': notset,
533 532 'func': func,
534 533 }
535 534
536 535 class matchctx(object):
537 536 def __init__(self, ctx, subset, status=None):
538 537 self.ctx = ctx
539 538 self.subset = subset
540 539 self._status = status
541 540 self._existingenabled = False
542 541 def status(self):
543 542 return self._status
544 543 def matcher(self, patterns):
545 544 return self.ctx.match(patterns)
546 545 def filter(self, files):
547 546 return [f for f in files if f in self.subset]
548 547 def existing(self):
549 548 assert self._existingenabled, 'unexpected existing() invocation'
550 549 if self._status is not None:
551 550 removed = set(self._status[3])
552 551 unknown = set(self._status[4] + self._status[5])
553 552 else:
554 553 removed = set()
555 554 unknown = set()
556 555 return (f for f in self.subset
557 556 if (f in self.ctx and f not in removed) or f in unknown)
558 557 def narrow(self, files):
559 558 return matchctx(self.ctx, self.filter(files), self._status)
560 559 def switch(self, ctx, status=None):
561 560 subset = self.filter(_buildsubset(ctx, status))
562 561 return matchctx(ctx, subset, status)
563 562
564 563 class fullmatchctx(matchctx):
565 564 """A match context where any files in any revisions should be valid"""
566 565
567 566 def __init__(self, ctx, status=None):
568 567 subset = _buildsubset(ctx, status)
569 568 super(fullmatchctx, self).__init__(ctx, subset, status)
570 569 def switch(self, ctx, status=None):
571 570 return fullmatchctx(ctx, status)
572 571
573 572 # filesets using matchctx.switch()
574 573 _switchcallers = [
575 574 'revs',
576 575 'status',
577 576 ]
578 577
579 578 def _intree(funcs, tree):
580 579 if isinstance(tree, tuple):
581 580 if tree[0] == 'func' and tree[1][0] == 'symbol':
582 581 if tree[1][1] in funcs:
583 582 return True
584 583 if tree[1][1] in _switchcallers:
585 584 # arguments won't be evaluated in the current context
586 585 return False
587 586 for s in tree[1:]:
588 587 if _intree(funcs, s):
589 588 return True
590 589 return False
591 590
592 591 def _buildsubset(ctx, status):
593 592 if status:
594 593 subset = []
595 594 for c in status:
596 595 subset.extend(c)
597 596 return subset
598 597 else:
599 598 return list(ctx.walk(ctx.match([])))
600 599
601 600 def getfileset(ctx, expr):
602 601 tree = parse(expr)
603 602 return getset(fullmatchctx(ctx, _buildstatus(ctx, tree)), tree)
604 603
605 604 def _buildstatus(ctx, tree, basectx=None):
606 605 # do we need status info?
607 606
608 607 # temporaty boolean to simplify the next conditional
609 608 purewdir = ctx.rev() is None and basectx is None
610 609
611 610 if (_intree(_statuscallers, tree) or
612 611 # Using matchctx.existing() on a workingctx requires us to check
613 612 # for deleted files.
614 613 (purewdir and _intree(_existingcallers, tree))):
615 614 unknown = _intree(['unknown'], tree)
616 615 ignored = _intree(['ignored'], tree)
617 616
618 617 r = ctx.repo()
619 618 if basectx is None:
620 619 basectx = ctx.p1()
621 620 return r.status(basectx, ctx,
622 621 unknown=unknown, ignored=ignored, clean=True)
623 622 else:
624 623 return None
625 624
626 625 def prettyformat(tree):
627 626 return parser.prettyformat(tree, ('string', 'symbol'))
628 627
629 628 def loadpredicate(ui, extname, registrarobj):
630 629 """Load fileset predicates from specified registrarobj
631 630 """
632 631 for name, func in registrarobj._table.iteritems():
633 632 symbols[name] = func
634 633 if func._callstatus:
635 634 _statuscallers.add(name)
636 635 if func._callexisting:
637 636 _existingcallers.add(name)
638 637
639 638 # load built-in predicates explicitly to setup _statuscallers/_existingcallers
640 639 loadpredicate(None, None, predicate)
641 640
642 641 # tell hggettext to extract docstrings from these functions:
643 642 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now