fileset.py
563 lines
| 17.1 KiB
| text/x-python
|
PythonLexer
/ mercurial / fileset.py
|
r14511 | # fileset.py - file set queries for mercurial | ||
# | ||||
# Copyright 2010 Matt Mackall <mpm@selenic.com> | ||||
# | ||||
# This software may be used and distributed according to the terms of the | ||||
# GNU General Public License version 2 or any later version. | ||||
|
r25938 | from __future__ import absolute_import | ||
|
r20034 | import re | ||
|
r25938 | |||
from .i18n import _ | ||||
from . import ( | ||||
error, | ||||
merge, | ||||
parser, | ||||
|
r28448 | registrar, | ||
|
r25938 | util, | ||
) | ||||
|
r14511 | |||
elements = { | ||||
|
r25815 | # token-type: binding-strength, primary, prefix, infix, suffix | ||
"(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None), | ||||
"-": (5, None, ("negate", 19), ("minus", 5), None), | ||||
"not": (10, None, ("not", 10), None, None), | ||||
"!": (10, None, ("not", 10), None, None), | ||||
"and": (5, None, None, ("and", 5), None), | ||||
"&": (5, None, None, ("and", 5), None), | ||||
"or": (4, None, None, ("or", 4), None), | ||||
"|": (4, None, None, ("or", 4), None), | ||||
"+": (4, None, None, ("or", 4), None), | ||||
",": (2, None, None, ("list", 2), None), | ||||
")": (0, None, None, None, None), | ||||
"symbol": (0, "symbol", None, None, None), | ||||
"string": (0, "string", None, None, None), | ||||
"end": (0, None, None, None, None), | ||||
|
r14511 | } | ||
keywords = set(['and', 'or', 'not']) | ||||
|
r19470 | globchars = ".*{}[]?/\\_" | ||
|
r14551 | |||
|
r14511 | def tokenize(program): | ||
pos, l = 0, len(program) | ||||
while pos < l: | ||||
c = program[pos] | ||||
if c.isspace(): # skip inter-token whitespace | ||||
pass | ||||
elif c in "(),-|&+!": # handle simple operators | ||||
yield (c, None, pos) | ||||
elif (c in '"\'' or c == 'r' and | ||||
program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings | ||||
if c == 'r': | ||||
pos += 1 | ||||
c = program[pos] | ||||
decode = lambda x: x | ||||
else: | ||||
|
r26233 | decode = parser.unescapestr | ||
|
r14511 | pos += 1 | ||
s = pos | ||||
while pos < l: # find closing quote | ||||
d = program[pos] | ||||
if d == '\\': # skip over escaped characters | ||||
pos += 2 | ||||
continue | ||||
if d == c: | ||||
yield ('string', decode(program[s:pos]), s) | ||||
break | ||||
pos += 1 | ||||
else: | ||||
raise error.ParseError(_("unterminated string"), s) | ||||
|
r14551 | elif c.isalnum() or c in globchars or ord(c) > 127: | ||
|
r14513 | # gather up a symbol/keyword | ||
|
r14511 | s = pos | ||
pos += 1 | ||||
while pos < l: # find end of symbol | ||||
d = program[pos] | ||||
|
r14551 | if not (d.isalnum() or d in globchars or ord(d) > 127): | ||
|
r14511 | break | ||
pos += 1 | ||||
sym = program[s:pos] | ||||
if sym in keywords: # operator keywords | ||||
yield (sym, None, s) | ||||
else: | ||||
yield ('symbol', sym, s) | ||||
pos -= 1 | ||||
else: | ||||
raise error.ParseError(_("syntax error"), pos) | ||||
pos += 1 | ||||
yield ('end', None, pos) | ||||
|
r20208 | def parse(expr): | ||
|
r25654 | p = parser.parser(elements) | ||
tree, pos = p.parse(tokenize(expr)) | ||||
|
r25252 | if pos != len(expr): | ||
raise error.ParseError(_("invalid token"), pos) | ||||
return tree | ||||
|
r14511 | |||
|
r14551 | def getstring(x, err): | ||
if x and (x[0] == 'string' or x[0] == 'symbol'): | ||||
return x[1] | ||||
raise error.ParseError(err) | ||||
def getset(mctx, x): | ||||
if not x: | ||||
raise error.ParseError(_("missing argument")) | ||||
return methods[x[0]](mctx, *x[1:]) | ||||
def stringset(mctx, x): | ||||
m = mctx.matcher([x]) | ||||
return [f for f in mctx.subset if m(f)] | ||||
def andset(mctx, x, y): | ||||
return getset(mctx.narrow(getset(mctx, x)), y) | ||||
def orset(mctx, x, y): | ||||
# needs optimizing | ||||
xl = getset(mctx, x) | ||||
yl = getset(mctx, y) | ||||
return xl + [f for f in yl if f not in xl] | ||||
def notset(mctx, x): | ||||
s = set(getset(mctx, x)) | ||||
return [r for r in mctx.subset if r not in s] | ||||
|
r17363 | def minusset(mctx, x, y): | ||
xl = getset(mctx, x) | ||||
yl = set(getset(mctx, y)) | ||||
return [f for f in xl if f not in yl] | ||||
|
r14551 | def listset(mctx, a, b): | ||
|
r27518 | raise error.ParseError(_("can't use a list in this context"), | ||
hint=_('see hg help "filesets.x or y"')) | ||||
|
r14551 | |||
|
r27460 | # symbols are callable like: | ||
# fun(mctx, x) | ||||
# with: | ||||
# mctx - current matchctx instance | ||||
# x - argument in tree form | ||||
symbols = {} | ||||
|
r27461 | # filesets using matchctx.status() | ||
|
r27463 | _statuscallers = set() | ||
|
r27461 | |||
|
r27462 | # filesets using matchctx.existing() | ||
|
r27463 | _existingcallers = set() | ||
|
r27462 | |||
|
r28448 | predicate = registrar.filesetpredicate() | ||
|
r27460 | |||
|
r27461 | @predicate('modified()', callstatus=True) | ||
|
r14677 | def modified(mctx, x): | ||
|
r27460 | """File that is modified according to :hg:`status`. | ||
|
r14681 | """ | ||
|
r14785 | # i18n: "modified" is a keyword | ||
|
r14677 | getargs(x, 0, 0, _("modified takes no arguments")) | ||
|
r22924 | s = mctx.status().modified | ||
|
r14677 | return [f for f in mctx.subset if f in s] | ||
|
r27461 | @predicate('added()', callstatus=True) | ||
|
r14677 | def added(mctx, x): | ||
|
r27460 | """File that is added according to :hg:`status`. | ||
|
r14681 | """ | ||
|
r14785 | # i18n: "added" is a keyword | ||
|
r14677 | getargs(x, 0, 0, _("added takes no arguments")) | ||
|
r22924 | s = mctx.status().added | ||
|
r14677 | return [f for f in mctx.subset if f in s] | ||
|
r27461 | @predicate('removed()', callstatus=True) | ||
|
r14677 | def removed(mctx, x): | ||
|
r27460 | """File that is removed according to :hg:`status`. | ||
|
r14681 | """ | ||
|
r14785 | # i18n: "removed" is a keyword | ||
|
r14677 | getargs(x, 0, 0, _("removed takes no arguments")) | ||
|
r22924 | s = mctx.status().removed | ||
|
r14677 | return [f for f in mctx.subset if f in s] | ||
|
r27461 | @predicate('deleted()', callstatus=True) | ||
|
r14677 | def deleted(mctx, x): | ||
|
r27460 | """Alias for ``missing()``. | ||
|
r14681 | """ | ||
|
r14785 | # i18n: "deleted" is a keyword | ||
|
r14677 | getargs(x, 0, 0, _("deleted takes no arguments")) | ||
|
r22924 | s = mctx.status().deleted | ||
|
r14677 | return [f for f in mctx.subset if f in s] | ||
|
r27461 | @predicate('missing()', callstatus=True) | ||
|
r27024 | def missing(mctx, x): | ||
|
r27460 | """File that is missing according to :hg:`status`. | ||
|
r27024 | """ | ||
# i18n: "missing" is a keyword | ||||
getargs(x, 0, 0, _("missing takes no arguments")) | ||||
s = mctx.status().deleted | ||||
return [f for f in mctx.subset if f in s] | ||||
|
r27461 | @predicate('unknown()', callstatus=True) | ||
|
r14677 | def unknown(mctx, x): | ||
|
r27460 | """File that is unknown according to :hg:`status`. These files will only be | ||
|
r14681 | considered if this predicate is used. | ||
""" | ||||
|
r14785 | # i18n: "unknown" is a keyword | ||
|
r14677 | getargs(x, 0, 0, _("unknown takes no arguments")) | ||
|
r22924 | s = mctx.status().unknown | ||
|
r14677 | return [f for f in mctx.subset if f in s] | ||
|
r27461 | @predicate('ignored()', callstatus=True) | ||
|
r14677 | def ignored(mctx, x): | ||
|
r27460 | """File that is ignored according to :hg:`status`. These files will only be | ||
|
r14681 | considered if this predicate is used. | ||
""" | ||||
|
r14785 | # i18n: "ignored" is a keyword | ||
|
r14677 | getargs(x, 0, 0, _("ignored takes no arguments")) | ||
|
r22924 | s = mctx.status().ignored | ||
|
r14677 | return [f for f in mctx.subset if f in s] | ||
|
r27461 | @predicate('clean()', callstatus=True) | ||
|
r14677 | def clean(mctx, x): | ||
|
r27460 | """File that is clean according to :hg:`status`. | ||
|
r14681 | """ | ||
|
r14785 | # i18n: "clean" is a keyword | ||
|
r14677 | getargs(x, 0, 0, _("clean takes no arguments")) | ||
|
r22924 | s = mctx.status().clean | ||
|
r14677 | return [f for f in mctx.subset if f in s] | ||
|
r14676 | def func(mctx, a, b): | ||
if a[0] == 'symbol' and a[1] in symbols: | ||||
|
r27464 | funcname = a[1] | ||
enabled = mctx._existingenabled | ||||
mctx._existingenabled = funcname in _existingcallers | ||||
try: | ||||
return symbols[funcname](mctx, b) | ||||
finally: | ||||
mctx._existingenabled = enabled | ||||
|
r25633 | |||
keep = lambda fn: getattr(fn, '__doc__', None) is not None | ||||
syms = [s for (s, fn) in symbols.items() if keep(fn)] | ||||
raise error.UnknownIdentifier(a[1], syms) | ||||
|
r14676 | |||
def getlist(x): | ||||
if not x: | ||||
return [] | ||||
if x[0] == 'list': | ||||
return getlist(x[1]) + [x[2]] | ||||
return [x] | ||||
def getargs(x, min, max, err): | ||||
l = getlist(x) | ||||
if len(l) < min or len(l) > max: | ||||
raise error.ParseError(err) | ||||
return l | ||||
|
r27462 | @predicate('binary()', callexisting=True) | ||
|
r14676 | def binary(mctx, x): | ||
|
r27460 | """File that appears to be binary (contains NUL bytes). | ||
|
r14681 | """ | ||
|
r14785 | # i18n: "binary" is a keyword | ||
|
r14676 | getargs(x, 0, 0, _("binary takes no arguments")) | ||
|
r15963 | return [f for f in mctx.existing() if util.binary(mctx.ctx[f].data())] | ||
|
r14676 | |||
|
r27462 | @predicate('exec()', callexisting=True) | ||
|
r14676 | def exec_(mctx, x): | ||
|
r27460 | """File that is marked as executable. | ||
|
r14681 | """ | ||
|
r14785 | # i18n: "exec" is a keyword | ||
|
r14676 | getargs(x, 0, 0, _("exec takes no arguments")) | ||
|
r15963 | return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'x'] | ||
|
r14676 | |||
|
r27462 | @predicate('symlink()', callexisting=True) | ||
|
r14676 | def symlink(mctx, x): | ||
|
r27460 | """File that is marked as a symlink. | ||
|
r14681 | """ | ||
|
r14785 | # i18n: "symlink" is a keyword | ||
|
r14676 | getargs(x, 0, 0, _("symlink takes no arguments")) | ||
|
r15963 | return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'l'] | ||
|
r14676 | |||
|
r27460 | @predicate('resolved()') | ||
|
r14679 | def resolved(mctx, x): | ||
|
r27460 | """File that is marked resolved according to :hg:`resolve -l`. | ||
|
r14681 | """ | ||
|
r14785 | # i18n: "resolved" is a keyword | ||
|
r14679 | getargs(x, 0, 0, _("resolved takes no arguments")) | ||
if mctx.ctx.rev() is not None: | ||||
return [] | ||||
|
r26995 | ms = merge.mergestate.read(mctx.ctx.repo()) | ||
|
r14679 | return [f for f in mctx.subset if f in ms and ms[f] == 'r'] | ||
|
r27460 | @predicate('unresolved()') | ||
|
r14679 | def unresolved(mctx, x): | ||
|
r27460 | """File that is marked unresolved according to :hg:`resolve -l`. | ||
|
r14681 | """ | ||
|
r14785 | # i18n: "unresolved" is a keyword | ||
|
r14679 | getargs(x, 0, 0, _("unresolved takes no arguments")) | ||
if mctx.ctx.rev() is not None: | ||||
return [] | ||||
|
r26995 | ms = merge.mergestate.read(mctx.ctx.repo()) | ||
|
r14679 | return [f for f in mctx.subset if f in ms and ms[f] == 'u'] | ||
|
r27460 | @predicate('hgignore()') | ||
|
r14680 | def hgignore(mctx, x): | ||
|
r27460 | """File that matches the active .hgignore pattern. | ||
|
r14681 | """ | ||
|
r23113 | # i18n: "hgignore" is a keyword | ||
|
r14680 | getargs(x, 0, 0, _("hgignore takes no arguments")) | ||
|
r24334 | ignore = mctx.ctx.repo().dirstate._ignore | ||
|
r14680 | return [f for f in mctx.subset if ignore(f)] | ||
|
r27460 | @predicate('portable()') | ||
|
r24408 | def portable(mctx, x): | ||
|
r27460 | """File that has a portable name. (This doesn't include filenames with case | ||
|
r24408 | collisions.) | ||
""" | ||||
# i18n: "portable" is a keyword | ||||
getargs(x, 0, 0, _("portable takes no arguments")) | ||||
checkwinfilename = util.checkwinfilename | ||||
return [f for f in mctx.subset if checkwinfilename(f) is None] | ||||
|
r27462 | @predicate('grep(regex)', callexisting=True) | ||
|
r14682 | def grep(mctx, x): | ||
|
r27460 | """File contains the given regular expression. | ||
|
r14682 | """ | ||
|
r17368 | try: | ||
# i18n: "grep" is a keyword | ||||
r = re.compile(getstring(x, _("grep requires a pattern"))) | ||||
|
r25660 | except re.error as e: | ||
|
r17368 | raise error.ParseError(_('invalid match pattern: %s') % e) | ||
|
r15963 | return [f for f in mctx.existing() if r.search(mctx.ctx[f].data())] | ||
|
r14682 | |||
|
r14683 | def _sizetomax(s): | ||
try: | ||||
r25925 | s = s.strip().lower() | |||
|
r19194 | for k, v in util._sizeunits: | ||
|
r14683 | if s.endswith(k): | ||
# max(4k) = 5k - 1, max(4.5k) = 4.6k - 1 | ||||
n = s[:-len(k)] | ||||
inc = 1.0 | ||||
if "." in n: | ||||
inc /= 10 ** len(n.split(".")[1]) | ||||
return int((float(n) + inc) * v) - 1 | ||||
# no extension, this is a precise value | ||||
return int(s) | ||||
except ValueError: | ||||
|
r14716 | raise error.ParseError(_("couldn't parse size: %s") % s) | ||
|
r14683 | |||
|
r27462 | @predicate('size(expression)', callexisting=True) | ||
|
r14683 | def size(mctx, x): | ||
|
r27460 | """File size matches the given expression. Examples: | ||
|
r14683 | |||
|
r29987 | - size('1k') - files from 1024 to 2047 bytes | ||
- size('< 20k') - files less than 20480 bytes | ||||
- size('>= .5MB') - files at least 524288 bytes | ||||
- size('4k - 1MB') - files from 4096 bytes to 1048576 bytes | ||||
|
r14683 | """ | ||
|
r14785 | # i18n: "size" is a keyword | ||
|
r14717 | expr = getstring(x, _("size requires an expression")).strip() | ||
|
r14683 | if '-' in expr: # do we have a range? | ||
a, b = expr.split('-', 1) | ||||
|
r19194 | a = util.sizetoint(a) | ||
b = util.sizetoint(b) | ||||
|
r14683 | m = lambda x: x >= a and x <= b | ||
elif expr.startswith("<="): | ||||
|
r19194 | a = util.sizetoint(expr[2:]) | ||
|
r14683 | m = lambda x: x <= a | ||
elif expr.startswith("<"): | ||||
|
r19194 | a = util.sizetoint(expr[1:]) | ||
|
r14683 | m = lambda x: x < a | ||
elif expr.startswith(">="): | ||||
|
r19194 | a = util.sizetoint(expr[2:]) | ||
|
r14683 | m = lambda x: x >= a | ||
elif expr.startswith(">"): | ||||
|
r19194 | a = util.sizetoint(expr[1:]) | ||
|
r14683 | m = lambda x: x > a | ||
elif expr[0].isdigit or expr[0] == '.': | ||||
|
r19194 | a = util.sizetoint(expr) | ||
|
r14683 | b = _sizetomax(expr) | ||
|
r14690 | m = lambda x: x >= a and x <= b | ||
|
r14683 | else: | ||
|
r14716 | raise error.ParseError(_("couldn't parse size: %s") % expr) | ||
|
r14683 | |||
|
r15963 | return [f for f in mctx.existing() if m(mctx.ctx[f].size())] | ||
|
r14683 | |||
|
r27462 | @predicate('encoding(name)', callexisting=True) | ||
|
r14684 | def encoding(mctx, x): | ||
|
r27460 | """File can be successfully decoded with the given character | ||
|
r14684 | encoding. May not be useful for encodings other than ASCII and | ||
UTF-8. | ||||
""" | ||||
|
r14785 | # i18n: "encoding" is a keyword | ||
|
r14684 | enc = getstring(x, _("encoding requires an encoding name")) | ||
s = [] | ||||
|
r15963 | for f in mctx.existing(): | ||
|
r14684 | d = mctx.ctx[f].data() | ||
try: | ||||
d.decode(enc) | ||||
except LookupError: | ||||
|
r26587 | raise error.Abort(_("unknown encoding '%s'") % enc) | ||
|
r14684 | except UnicodeDecodeError: | ||
continue | ||||
s.append(f) | ||||
return s | ||||
|
r27462 | @predicate('eol(style)', callexisting=True) | ||
|
r18842 | def eol(mctx, x): | ||
|
r27460 | """File contains newlines of the given style (dos, unix, mac). Binary | ||
|
r18842 | files are excluded, files with mixed line endings match multiple | ||
styles. | ||||
""" | ||||
|
r28056 | # i18n: "eol" is a keyword | ||
enc = getstring(x, _("eol requires a style name")) | ||||
|
r18842 | |||
s = [] | ||||
for f in mctx.existing(): | ||||
d = mctx.ctx[f].data() | ||||
if util.binary(d): | ||||
continue | ||||
if (enc == 'dos' or enc == 'win') and '\r\n' in d: | ||||
s.append(f) | ||||
elif enc == 'unix' and re.search('(?<!\r)\n', d): | ||||
s.append(f) | ||||
elif enc == 'mac' and re.search('\r(?!\n)', d): | ||||
s.append(f) | ||||
return s | ||||
|
r27460 | @predicate('copied()') | ||
|
r14685 | def copied(mctx, x): | ||
|
r27460 | """File that is recorded as being copied. | ||
|
r14685 | """ | ||
|
r14785 | # i18n: "copied" is a keyword | ||
|
r14718 | getargs(x, 0, 0, _("copied takes no arguments")) | ||
|
r14685 | s = [] | ||
for f in mctx.subset: | ||||
p = mctx.ctx[f].parents() | ||||
if p and p[0].path() != f: | ||||
s.append(f) | ||||
return s | ||||
|
r27460 | @predicate('subrepo([pattern])') | ||
|
r16443 | def subrepo(mctx, x): | ||
|
r27460 | """Subrepositories whose paths match the given pattern. | ||
|
r16443 | """ | ||
# i18n: "subrepo" is a keyword | ||||
getargs(x, 0, 1, _("subrepo takes at most one argument")) | ||||
ctx = mctx.ctx | ||||
|
r18364 | sstate = sorted(ctx.substate) | ||
|
r16443 | if x: | ||
|
r23113 | # i18n: "subrepo" is a keyword | ||
|
r16443 | pat = getstring(x, _("subrepo requires a pattern or no arguments")) | ||
|
r25938 | from . import match as matchmod # avoid circular import issues | ||
|
r16443 | fast = not matchmod.patkind(pat) | ||
if fast: | ||||
def m(s): | ||||
return (s == pat) | ||||
else: | ||||
|
r24334 | m = matchmod.match(ctx.repo().root, '', [pat], ctx=ctx) | ||
|
r16443 | return [sub for sub in sstate if m(sub)] | ||
else: | ||||
return [sub for sub in sstate] | ||||
|
r14551 | methods = { | ||
'string': stringset, | ||||
'symbol': stringset, | ||||
'and': andset, | ||||
'or': orset, | ||||
|
r17363 | 'minus': minusset, | ||
|
r14551 | 'list': listset, | ||
'group': getset, | ||||
|
r14676 | 'not': notset, | ||
'func': func, | ||||
|
r14551 | } | ||
class matchctx(object): | ||||
|
r14677 | def __init__(self, ctx, subset=None, status=None): | ||
|
r14551 | self.ctx = ctx | ||
self.subset = subset | ||||
|
r14677 | self._status = status | ||
|
r27464 | self._existingenabled = False | ||
|
r14677 | def status(self): | ||
return self._status | ||||
|
r14673 | def matcher(self, patterns): | ||
return self.ctx.match(patterns) | ||||
|
r14551 | def filter(self, files): | ||
return [f for f in files if f in self.subset] | ||||
|
r15963 | def existing(self): | ||
|
r27464 | assert self._existingenabled, 'unexpected existing() invocation' | ||
|
r17365 | if self._status is not None: | ||
removed = set(self._status[3]) | ||||
|
r17367 | unknown = set(self._status[4] + self._status[5]) | ||
|
r17365 | else: | ||
removed = set() | ||||
|
r17366 | unknown = set() | ||
|
r17365 | return (f for f in self.subset | ||
|
r17366 | if (f in self.ctx and f not in removed) or f in unknown) | ||
|
r14551 | def narrow(self, files): | ||
|
r14677 | return matchctx(self.ctx, self.filter(files), self._status) | ||
|
r14551 | |||
|
r31188 | class fullmatchctx(matchctx): | ||
"""A match context where any files in any revisions should be valid""" | ||||
def __init__(self, ctx, subset=None, status=None): | ||||
super(fullmatchctx, self).__init__(ctx, subset, status) | ||||
|
r14678 | def _intree(funcs, tree): | ||
if isinstance(tree, tuple): | ||||
if tree[0] == 'func' and tree[1][0] == 'symbol': | ||||
if tree[1][1] in funcs: | ||||
return True | ||||
for s in tree[1:]: | ||||
if _intree(funcs, s): | ||||
return True | ||||
return False | ||||
|
r31189 | def _buildsubset(ctx, status): | ||
if status: | ||||
subset = [] | ||||
for c in status: | ||||
subset.extend(c) | ||||
return subset | ||||
else: | ||||
return list(ctx.walk(ctx.match([]))) | ||||
|
r14673 | def getfileset(ctx, expr): | ||
|
r25252 | tree = parse(expr) | ||
|
r14678 | |||
# do we need status info? | ||||
|
r27461 | if (_intree(_statuscallers, tree) or | ||
|
r17365 | # Using matchctx.existing() on a workingctx requires us to check | ||
# for deleted files. | ||||
(ctx.rev() is None and _intree(_existingcallers, tree))): | ||||
|
r14678 | unknown = _intree(['unknown'], tree) | ||
ignored = _intree(['ignored'], tree) | ||||
|
r24334 | r = ctx.repo() | ||
|
r14678 | status = r.status(ctx.p1(), ctx, | ||
unknown=unknown, ignored=ignored, clean=True) | ||||
else: | ||||
status = None | ||||
|
r31189 | subset = _buildsubset(ctx, status) | ||
|
r31188 | return getset(fullmatchctx(ctx, subset, status), tree) | ||
|
r14681 | |||
|
r25255 | def prettyformat(tree): | ||
return parser.prettyformat(tree, ('string', 'symbol')) | ||||
|
r28447 | def loadpredicate(ui, extname, registrarobj): | ||
"""Load fileset predicates from specified registrarobj | ||||
""" | ||||
for name, func in registrarobj._table.iteritems(): | ||||
symbols[name] = func | ||||
if func._callstatus: | ||||
_statuscallers.add(name) | ||||
if func._callexisting: | ||||
_existingcallers.add(name) | ||||
|
r28448 | # load built-in predicates explicitly to setup _statuscallers/_existingcallers | ||
loadpredicate(None, None, predicate) | ||||
|
r14681 | # tell hggettext to extract docstrings from these functions: | ||
i18nfunctions = symbols.values() | ||||