##// END OF EJS Templates
debugobsolete: remove spurious ctx from variable name...
debugobsolete: remove spurious ctx from variable name 'contexts' are objects like the ones described in context.py, not anything that describes a situation.

File last commit:

r16443:9e02e032 default
r17123:8e030168 default
Show More
fileset.py
465 lines | 13.4 KiB | text/x-python | PythonLexer
Matt Mackall
filesets: introduce basic fileset expression parser
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.
Matt Mackall
fileset: add grep predicate
r14682 import parser, error, util, merge, re
Matt Mackall
filesets: introduce basic fileset expression parser
r14511 from i18n import _
elements = {
"(": (20, ("group", 1, ")"), ("func", 1, ")")),
"-": (5, ("negate", 19), ("minus", 5)),
"not": (10, ("not", 10)),
"!": (10, ("not", 10)),
"and": (5, None, ("and", 5)),
"&": (5, None, ("and", 5)),
"or": (4, None, ("or", 4)),
"|": (4, None, ("or", 4)),
"+": (4, None, ("or", 4)),
",": (2, None, ("list", 2)),
")": (0, None, None),
"symbol": (0, ("symbol",), None),
"string": (0, ("string",), None),
"end": (0, None, None),
}
keywords = set(['and', 'or', 'not'])
Matt Mackall
fileset: basic pattern and boolean support...
r14551 globchars = ".*{}[]?/\\"
Matt Mackall
filesets: introduce basic fileset expression parser
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:
decode = lambda x: x.decode('string-escape')
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)
Matt Mackall
fileset: basic pattern and boolean support...
r14551 elif c.isalnum() or c in globchars or ord(c) > 127:
Matt Mackall
fileset: fix long line
r14513 # gather up a symbol/keyword
Matt Mackall
filesets: introduce basic fileset expression parser
r14511 s = pos
pos += 1
while pos < l: # find end of symbol
d = program[pos]
Matt Mackall
fileset: basic pattern and boolean support...
r14551 if not (d.isalnum() or d in globchars or ord(d) > 127):
Matt Mackall
filesets: introduce basic fileset expression parser
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)
parse = parser.parser(tokenize, elements).parse
Matt Mackall
fileset: basic pattern and boolean support...
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]
def listset(mctx, a, b):
raise error.ParseError(_("can't use a list in this context"))
Matt Mackall
fileset: add support for file status predicates...
r14677 def modified(mctx, x):
Matt Mackall
fileset: add some function help text
r14681 """``modified()``
File that is modified according to status.
"""
Wagner Bruna
fileset: add i18n hints for keywords
r14785 # i18n: "modified" is a keyword
Matt Mackall
fileset: add support for file status predicates...
r14677 getargs(x, 0, 0, _("modified takes no arguments"))
s = mctx.status()[0]
return [f for f in mctx.subset if f in s]
def added(mctx, x):
Matt Mackall
fileset: add some function help text
r14681 """``added()``
File that is added according to status.
"""
Wagner Bruna
fileset: add i18n hints for keywords
r14785 # i18n: "added" is a keyword
Matt Mackall
fileset: add support for file status predicates...
r14677 getargs(x, 0, 0, _("added takes no arguments"))
s = mctx.status()[1]
return [f for f in mctx.subset if f in s]
def removed(mctx, x):
Matt Mackall
fileset: add some function help text
r14681 """``removed()``
File that is removed according to status.
"""
Wagner Bruna
fileset: add i18n hints for keywords
r14785 # i18n: "removed" is a keyword
Matt Mackall
fileset: add support for file status predicates...
r14677 getargs(x, 0, 0, _("removed takes no arguments"))
s = mctx.status()[2]
return [f for f in mctx.subset if f in s]
def deleted(mctx, x):
Matt Mackall
fileset: add some function help text
r14681 """``deleted()``
File that is deleted according to status.
"""
Wagner Bruna
fileset: add i18n hints for keywords
r14785 # i18n: "deleted" is a keyword
Matt Mackall
fileset: add support for file status predicates...
r14677 getargs(x, 0, 0, _("deleted takes no arguments"))
s = mctx.status()[3]
return [f for f in mctx.subset if f in s]
def unknown(mctx, x):
Matt Mackall
fileset: add some function help text
r14681 """``unknown()``
File that is unknown according to status. These files will only be
considered if this predicate is used.
"""
Wagner Bruna
fileset: add i18n hints for keywords
r14785 # i18n: "unknown" is a keyword
Matt Mackall
fileset: add support for file status predicates...
r14677 getargs(x, 0, 0, _("unknown takes no arguments"))
s = mctx.status()[4]
return [f for f in mctx.subset if f in s]
def ignored(mctx, x):
Matt Mackall
fileset: add some function help text
r14681 """``ignored()``
File that is ignored according to status. These files will only be
considered if this predicate is used.
"""
Wagner Bruna
fileset: add i18n hints for keywords
r14785 # i18n: "ignored" is a keyword
Matt Mackall
fileset: add support for file status predicates...
r14677 getargs(x, 0, 0, _("ignored takes no arguments"))
s = mctx.status()[5]
return [f for f in mctx.subset if f in s]
def clean(mctx, x):
Matt Mackall
fileset: add some function help text
r14681 """``clean()``
File that is clean according to status.
"""
Wagner Bruna
fileset: add i18n hints for keywords
r14785 # i18n: "clean" is a keyword
Matt Mackall
fileset: add support for file status predicates...
r14677 getargs(x, 0, 0, _("clean takes no arguments"))
s = mctx.status()[6]
return [f for f in mctx.subset if f in s]
Matt Mackall
fileset: add some basic predicates
r14676 def func(mctx, a, b):
if a[0] == 'symbol' and a[1] in symbols:
return symbols[a[1]](mctx, b)
raise error.ParseError(_("not a function: %s") % a[1])
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
def binary(mctx, x):
Matt Mackall
fileset: add some function help text
r14681 """``binary()``
Idan Kamara
fileset: fix typo in binary() doc
r14830 File that appears to be binary (contains NUL bytes).
Matt Mackall
fileset: add some function help text
r14681 """
Wagner Bruna
fileset: add i18n hints for keywords
r14785 # i18n: "binary" is a keyword
Matt Mackall
fileset: add some basic predicates
r14676 getargs(x, 0, 0, _("binary takes no arguments"))
Matt Mackall
fileset: don't attempt to check data predicates against removed files
r15963 return [f for f in mctx.existing() if util.binary(mctx.ctx[f].data())]
Matt Mackall
fileset: add some basic predicates
r14676
def exec_(mctx, x):
Matt Mackall
fileset: add some function help text
r14681 """``exec()``
File that is marked as executable.
"""
Wagner Bruna
fileset: add i18n hints for keywords
r14785 # i18n: "exec" is a keyword
Matt Mackall
fileset: add some basic predicates
r14676 getargs(x, 0, 0, _("exec takes no arguments"))
Matt Mackall
fileset: don't attempt to check data predicates against removed files
r15963 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'x']
Matt Mackall
fileset: add some basic predicates
r14676
def symlink(mctx, x):
Matt Mackall
fileset: add some function help text
r14681 """``symlink()``
File that is marked as a symlink.
"""
Wagner Bruna
fileset: add i18n hints for keywords
r14785 # i18n: "symlink" is a keyword
Matt Mackall
fileset: add some basic predicates
r14676 getargs(x, 0, 0, _("symlink takes no arguments"))
Matt Mackall
fileset: don't attempt to check data predicates against removed files
r15963 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'l']
Matt Mackall
fileset: add some basic predicates
r14676
Matt Mackall
fileset: add resolved and unresolved predicates
r14679 def resolved(mctx, x):
Matt Mackall
fileset: add some function help text
r14681 """``resolved()``
File that is marked resolved according to the resolve state.
"""
Wagner Bruna
fileset: add i18n hints for keywords
r14785 # i18n: "resolved" is a keyword
Matt Mackall
fileset: add resolved and unresolved predicates
r14679 getargs(x, 0, 0, _("resolved takes no arguments"))
if mctx.ctx.rev() is not None:
return []
ms = merge.mergestate(mctx.ctx._repo)
return [f for f in mctx.subset if f in ms and ms[f] == 'r']
def unresolved(mctx, x):
Matt Mackall
fileset: add some function help text
r14681 """``unresolved()``
File that is marked unresolved according to the resolve state.
"""
Wagner Bruna
fileset: add i18n hints for keywords
r14785 # i18n: "unresolved" is a keyword
Matt Mackall
fileset: add resolved and unresolved predicates
r14679 getargs(x, 0, 0, _("unresolved takes no arguments"))
if mctx.ctx.rev() is not None:
return []
ms = merge.mergestate(mctx.ctx._repo)
return [f for f in mctx.subset if f in ms and ms[f] == 'u']
Matt Mackall
fileset: add hgignore
r14680 def hgignore(mctx, x):
Ollie Rutherfurd
fileset: use correct function name for hgignore in docstring...
r14700 """``hgignore()``
Matt Mackall
fileset: add some function help text
r14681 File that matches the active .hgignore pattern.
"""
Matt Mackall
fileset: add hgignore
r14680 getargs(x, 0, 0, _("hgignore takes no arguments"))
ignore = mctx.ctx._repo.dirstate._ignore
return [f for f in mctx.subset if ignore(f)]
Matt Mackall
fileset: add grep predicate
r14682 def grep(mctx, x):
"""``grep(regex)``
File contains the given regular expression.
"""
pat = getstring(x, _("grep requires a pattern"))
r = re.compile(pat)
Matt Mackall
fileset: don't attempt to check data predicates against removed files
r15963 return [f for f in mctx.existing() if r.search(mctx.ctx[f].data())]
Matt Mackall
fileset: add grep predicate
r14682
Matt Mackall
fileset: add size() predicate
r14683 _units = dict(k=2**10, K=2**10, kB=2**10, KB=2**10,
Matt Mackall
fileset: drop backwards SI size units...
r14689 M=2**20, MB=2**20, G=2**30, GB=2**30)
Matt Mackall
fileset: add size() predicate
r14683
def _sizetoint(s):
try:
s = s.strip()
for k, v in _units.items():
if s.endswith(k):
return int(float(s[:-len(k)]) * v)
return int(s)
except ValueError:
Mads Kiilerich
fileset: use ParseError pos field correctly...
r14716 raise error.ParseError(_("couldn't parse size: %s") % s)
Matt Mackall
fileset: add size() predicate
r14683
def _sizetomax(s):
try:
s = s.strip()
for k, v in _units.items():
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:
Mads Kiilerich
fileset: use ParseError pos field correctly...
r14716 raise error.ParseError(_("couldn't parse size: %s") % s)
Matt Mackall
fileset: add size() predicate
r14683
def size(mctx, x):
"""``size(expression)``
File size matches the given expression. Examples:
- 1k (files from 1024 to 2047 bytes)
- < 20k (files less than 20480 bytes)
Matt Mackall
fileset: drop backwards SI size units...
r14689 - >= .5MB (files at least 524288 bytes)
Matt Mackall
fileset: add size() predicate
r14683 - 4k - 1MB (files from 4096 bytes to 1048576 bytes)
"""
Wagner Bruna
fileset: add i18n hints for keywords
r14785 # i18n: "size" is a keyword
Mads Kiilerich
revset and fileset: fix typos in parser error messages
r14717 expr = getstring(x, _("size requires an expression")).strip()
Matt Mackall
fileset: add size() predicate
r14683 if '-' in expr: # do we have a range?
a, b = expr.split('-', 1)
a = _sizetoint(a)
b = _sizetoint(b)
m = lambda x: x >= a and x <= b
elif expr.startswith("<="):
a = _sizetoint(expr[2:])
m = lambda x: x <= a
elif expr.startswith("<"):
a = _sizetoint(expr[1:])
m = lambda x: x < a
elif expr.startswith(">="):
a = _sizetoint(expr[2:])
m = lambda x: x >= a
elif expr.startswith(">"):
a = _sizetoint(expr[1:])
m = lambda x: x > a
elif expr[0].isdigit or expr[0] == '.':
a = _sizetoint(expr)
b = _sizetomax(expr)
Thomas Arendsen Hein
fileset: add missing whitespace around operator
r14690 m = lambda x: x >= a and x <= b
Matt Mackall
fileset: add size() predicate
r14683 else:
Mads Kiilerich
fileset: use ParseError pos field correctly...
r14716 raise error.ParseError(_("couldn't parse size: %s") % expr)
Matt Mackall
fileset: add size() predicate
r14683
Matt Mackall
fileset: don't attempt to check data predicates against removed files
r15963 return [f for f in mctx.existing() if m(mctx.ctx[f].size())]
Matt Mackall
fileset: add size() predicate
r14683
Matt Mackall
fileset: add encoding() predicate
r14684 def encoding(mctx, x):
"""``encoding(name)``
File can be successfully decoded with the given character
encoding. May not be useful for encodings other than ASCII and
UTF-8.
"""
Wagner Bruna
fileset: add i18n hints for keywords
r14785 # i18n: "encoding" is a keyword
Matt Mackall
fileset: add encoding() predicate
r14684 enc = getstring(x, _("encoding requires an encoding name"))
s = []
Matt Mackall
fileset: don't attempt to check data predicates against removed files
r15963 for f in mctx.existing():
Matt Mackall
fileset: add encoding() predicate
r14684 d = mctx.ctx[f].data()
try:
d.decode(enc)
except LookupError:
raise util.Abort(_("unknown encoding '%s'") % enc)
except UnicodeDecodeError:
continue
s.append(f)
return s
Matt Mackall
fileset: add copied predicate
r14685 def copied(mctx, x):
"""``copied()``
File that is recorded as being copied.
"""
Wagner Bruna
fileset: add i18n hints for keywords
r14785 # i18n: "copied" is a keyword
Mads Kiilerich
fileset: copied takes no arguments
r14718 getargs(x, 0, 0, _("copied takes no arguments"))
Matt Mackall
fileset: add copied predicate
r14685 s = []
for f in mctx.subset:
p = mctx.ctx[f].parents()
if p and p[0].path() != f:
s.append(f)
return s
Angel Ezquerra
fileset: add "subrepo" fileset symbol...
r16443 def subrepo(mctx, x):
"""``subrepo([pattern])``
Subrepositories whose paths match the given pattern.
"""
# i18n: "subrepo" is a keyword
getargs(x, 0, 1, _("subrepo takes at most one argument"))
ctx = mctx.ctx
sstate = ctx.substate
if x:
pat = getstring(x, _("subrepo requires a pattern or no arguments"))
import match as matchmod # avoid circular import issues
fast = not matchmod.patkind(pat)
if fast:
def m(s):
return (s == pat)
else:
m = matchmod.match(ctx._repo.root, '', [pat], ctx=ctx)
return [sub for sub in sstate if m(sub)]
else:
return [sub for sub in sstate]
Matt Mackall
fileset: add some basic predicates
r14676 symbols = {
Matt Mackall
fileset: add support for file status predicates...
r14677 'added': added,
Matt Mackall
fileset: add some basic predicates
r14676 'binary': binary,
Matt Mackall
fileset: add support for file status predicates...
r14677 'clean': clean,
Matt Mackall
fileset: add copied predicate
r14685 'copied': copied,
Matt Mackall
fileset: add support for file status predicates...
r14677 'deleted': deleted,
Matt Mackall
fileset: add encoding() predicate
r14684 'encoding': encoding,
Matt Mackall
fileset: add some basic predicates
r14676 'exec': exec_,
Matt Mackall
fileset: add grep predicate
r14682 'grep': grep,
Matt Mackall
fileset: add support for file status predicates...
r14677 'ignored': ignored,
Matt Mackall
fileset: add hgignore
r14680 'hgignore': hgignore,
Matt Mackall
fileset: add support for file status predicates...
r14677 'modified': modified,
'removed': removed,
Matt Mackall
fileset: add resolved and unresolved predicates
r14679 'resolved': resolved,
Matt Mackall
fileset: add size() predicate
r14683 'size': size,
Matt Mackall
fileset: add some basic predicates
r14676 'symlink': symlink,
Matt Mackall
fileset: add support for file status predicates...
r14677 'unknown': unknown,
Matt Mackall
fileset: add resolved and unresolved predicates
r14679 'unresolved': unresolved,
Angel Ezquerra
fileset: add "subrepo" fileset symbol...
r16443 'subrepo': subrepo,
Matt Mackall
fileset: add some basic predicates
r14676 }
Matt Mackall
fileset: basic pattern and boolean support...
r14551 methods = {
'string': stringset,
'symbol': stringset,
'and': andset,
'or': orset,
'list': listset,
'group': getset,
Matt Mackall
fileset: add some basic predicates
r14676 'not': notset,
'func': func,
Matt Mackall
fileset: basic pattern and boolean support...
r14551 }
class matchctx(object):
Matt Mackall
fileset: add support for file status predicates...
r14677 def __init__(self, ctx, subset=None, status=None):
Matt Mackall
fileset: basic pattern and boolean support...
r14551 self.ctx = ctx
self.subset = subset
Matt Mackall
fileset: add support for file status predicates...
r14677 self._status = status
def status(self):
return self._status
Matt Mackall
fileset: drop matchfn...
r14673 def matcher(self, patterns):
return self.ctx.match(patterns)
Matt Mackall
fileset: basic pattern and boolean support...
r14551 def filter(self, files):
return [f for f in files if f in self.subset]
Matt Mackall
fileset: don't attempt to check data predicates against removed files
r15963 def existing(self):
return (f for f in self.subset if f in self.ctx)
Matt Mackall
fileset: basic pattern and boolean support...
r14551 def narrow(self, files):
Matt Mackall
fileset: add support for file status predicates...
r14677 return matchctx(self.ctx, self.filter(files), self._status)
Matt Mackall
fileset: basic pattern and boolean support...
r14551
Matt Mackall
fileset: prescan parse tree to optimize status usage...
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
Matt Mackall
fileset: drop matchfn...
r14673 def getfileset(ctx, expr):
Matt Mackall
fileset: basic pattern and boolean support...
r14551 tree, pos = parse(expr)
if (pos != len(expr)):
Mads Kiilerich
parsers: fix localization markup of parser errors
r14701 raise error.ParseError(_("invalid token"), pos)
Matt Mackall
fileset: prescan parse tree to optimize status usage...
r14678
# do we need status info?
if _intree(['modified', 'added', 'removed', 'deleted',
'unknown', 'ignored', 'clean'], tree):
unknown = _intree(['unknown'], tree)
ignored = _intree(['ignored'], tree)
r = ctx._repo
status = r.status(ctx.p1(), ctx,
unknown=unknown, ignored=ignored, clean=True)
subset = []
for c in status:
subset.extend(c)
else:
status = None
subset = ctx.walk(ctx.match([]))
return getset(matchctx(ctx, subset, status), tree)
Matt Mackall
fileset: add some function help text
r14681
# tell hggettext to extract docstrings from these functions:
i18nfunctions = symbols.values()