minifileset.py
90 lines
| 3.4 KiB
| text/x-python
|
PythonLexer
/ mercurial / minifileset.py
|
r35634 | # minifileset.py - a simple language to select files | ||
# | ||||
# Copyright 2017 Facebook, Inc. | ||||
# | ||||
# This software may be used and distributed according to the terms of the | ||||
# GNU General Public License version 2 or any later version. | ||||
from __future__ import absolute_import | ||||
from .i18n import _ | ||||
from . import ( | ||||
error, | ||||
fileset, | ||||
|
r38841 | filesetlang, | ||
|
r37893 | pycompat, | ||
|
r35634 | ) | ||
|
r38709 | def _sizep(x): | ||
# i18n: "size" is a keyword | ||||
|
r38841 | expr = filesetlang.getstring(x, _("size requires an expression")) | ||
|
r38709 | return fileset.sizematcher(expr) | ||
|
r35634 | def _compile(tree): | ||
if not tree: | ||||
raise error.ParseError(_("missing argument")) | ||||
op = tree[0] | ||||
|
r35759 | if op in {'symbol', 'string', 'kindpat'}: | ||
|
r38841 | name = filesetlang.getpattern(tree, {'path'}, _('invalid file pattern')) | ||
|
r35634 | if name.startswith('**'): # file extension test, ex. "**.tar.gz" | ||
ext = name[2:] | ||||
|
r37893 | for c in pycompat.bytestr(ext): | ||
|
r35634 | if c in '*{}[]?/\\': | ||
raise error.ParseError(_('reserved character: %s') % c) | ||||
return lambda n, s: n.endswith(ext) | ||||
|
r35758 | elif name.startswith('path:'): # directory or full path test | ||
|
r35634 | p = name[5:] # prefix | ||
pl = len(p) | ||||
|
r37893 | f = lambda n, s: n.startswith(p) and (len(n) == pl | ||
or n[pl:pl + 1] == '/') | ||||
|
r35634 | return f | ||
|
r35818 | raise error.ParseError(_("unsupported file pattern: %s") % name, | ||
|
r35634 | hint=_('paths must be prefixed with "path:"')) | ||
|
r38901 | elif op in {'or', 'patterns'}: | ||
|
r38840 | funcs = [_compile(x) for x in tree[1:]] | ||
return lambda n, s: any(f(n, s) for f in funcs) | ||||
|
r35634 | elif op == 'and': | ||
func1 = _compile(tree[1]) | ||||
func2 = _compile(tree[2]) | ||||
return lambda n, s: func1(n, s) and func2(n, s) | ||||
elif op == 'not': | ||||
return lambda n, s: not _compile(tree[1])(n, s) | ||||
elif op == 'func': | ||||
symbols = { | ||||
'all': lambda n, s: True, | ||||
'none': lambda n, s: False, | ||||
|
r38709 | 'size': lambda n, s: _sizep(tree[2])(s), | ||
|
r35634 | } | ||
|
r38841 | name = filesetlang.getsymbol(tree[1]) | ||
|
r35709 | if name in symbols: | ||
|
r35634 | return symbols[name] | ||
raise error.UnknownIdentifier(name, symbols.keys()) | ||||
elif op == 'minus': # equivalent to 'x and not y' | ||||
func1 = _compile(tree[1]) | ||||
func2 = _compile(tree[2]) | ||||
return lambda n, s: func1(n, s) and not func2(n, s) | ||||
elif op == 'list': | ||||
raise error.ParseError(_("can't use a list in this context"), | ||||
|
r38846 | hint=_('see \'hg help "filesets.x or y"\'')) | ||
|
r35634 | raise error.ProgrammingError('illegal tree: %r' % (tree,)) | ||
def compile(text): | ||||
"""generate a function (path, size) -> bool from filter specification. | ||||
"text" could contain the operators defined by the fileset language for | ||||
common logic operations, and parenthesis for grouping. The supported path | ||||
tests are '**.extname' for file extension test, and '"path:dir/subdir"' | ||||
for prefix test. The ``size()`` predicate is borrowed from filesets to test | ||||
file size. The predicates ``all()`` and ``none()`` are also supported. | ||||
|
r35759 | '(**.php & size(">10MB")) | **.zip | (path:bin & !path:bin/README)' for | ||
|
r35634 | example, will catch all php files whose size is greater than 10 MB, all | ||
files whose name ends with ".zip", and all files under "bin" in the repo | ||||
root except for "bin/README". | ||||
""" | ||||
|
r38841 | tree = filesetlang.parse(text) | ||
|
r38862 | tree = filesetlang.analyze(tree) | ||
|
r38865 | tree = filesetlang.optimize(tree) | ||
|
r35634 | return _compile(tree) | ||