minifileset.py
101 lines
| 3.5 KiB
| text/x-python
|
PythonLexer
/ mercurial / minifileset.py
Matt Harbison
|
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 .i18n import _ | ||||
from . import ( | ||||
error, | ||||
fileset, | ||||
Yuya Nishihara
|
r38841 | filesetlang, | ||
Augie Fackler
|
r37893 | pycompat, | ||
Matt Harbison
|
r35634 | ) | ||
Augie Fackler
|
r43345 | |||
Yuya Nishihara
|
r38709 | def _sizep(x): | ||
# i18n: "size" is a keyword | ||||
Augie Fackler
|
r43347 | expr = filesetlang.getstring(x, _(b"size requires an expression")) | ||
Yuya Nishihara
|
r38709 | return fileset.sizematcher(expr) | ||
Augie Fackler
|
r43345 | |||
Matt Harbison
|
r35634 | def _compile(tree): | ||
if not tree: | ||||
Augie Fackler
|
r43347 | raise error.ParseError(_(b"missing argument")) | ||
Matt Harbison
|
r35634 | op = tree[0] | ||
Augie Fackler
|
r43347 | if op == b'withstatus': | ||
Yuya Nishihara
|
r38915 | return _compile(tree[1]) | ||
Augie Fackler
|
r43347 | elif op in {b'symbol', b'string', b'kindpat'}: | ||
name = filesetlang.getpattern( | ||||
tree, {b'path'}, _(b'invalid file pattern') | ||||
) | ||||
if name.startswith(b'**'): # file extension test, ex. "**.tar.gz" | ||||
Matt Harbison
|
r35634 | ext = name[2:] | ||
Augie Fackler
|
r37893 | for c in pycompat.bytestr(ext): | ||
Augie Fackler
|
r43347 | if c in b'*{}[]?/\\': | ||
raise error.ParseError(_(b'reserved character: %s') % c) | ||||
Matt Harbison
|
r35634 | return lambda n, s: n.endswith(ext) | ||
Augie Fackler
|
r43347 | elif name.startswith(b'path:'): # directory or full path test | ||
Augie Fackler
|
r43345 | p = name[5:] # prefix | ||
Matt Harbison
|
r35634 | pl = len(p) | ||
Augie Fackler
|
r43345 | f = lambda n, s: n.startswith(p) and ( | ||
Augie Fackler
|
r43347 | len(n) == pl or n[pl : pl + 1] == b'/' | ||
Augie Fackler
|
r43345 | ) | ||
Matt Harbison
|
r35634 | return f | ||
Augie Fackler
|
r43345 | raise error.ParseError( | ||
Augie Fackler
|
r43347 | _(b"unsupported file pattern: %s") % name, | ||
hint=_(b'paths must be prefixed with "path:"'), | ||||
Augie Fackler
|
r43345 | ) | ||
Augie Fackler
|
r43347 | elif op in {b'or', b'patterns'}: | ||
Yuya Nishihara
|
r38840 | funcs = [_compile(x) for x in tree[1:]] | ||
return lambda n, s: any(f(n, s) for f in funcs) | ||||
Augie Fackler
|
r43347 | elif op == b'and': | ||
Matt Harbison
|
r35634 | func1 = _compile(tree[1]) | ||
func2 = _compile(tree[2]) | ||||
return lambda n, s: func1(n, s) and func2(n, s) | ||||
Augie Fackler
|
r43347 | elif op == b'not': | ||
Matt Harbison
|
r35634 | return lambda n, s: not _compile(tree[1])(n, s) | ||
Augie Fackler
|
r43347 | elif op == b'func': | ||
Matt Harbison
|
r35634 | symbols = { | ||
Augie Fackler
|
r43347 | b'all': lambda n, s: True, | ||
b'none': lambda n, s: False, | ||||
b'size': lambda n, s: _sizep(tree[2])(s), | ||||
Matt Harbison
|
r35634 | } | ||
Yuya Nishihara
|
r38841 | name = filesetlang.getsymbol(tree[1]) | ||
Yuya Nishihara
|
r35709 | if name in symbols: | ||
Matt Harbison
|
r35634 | return symbols[name] | ||
raise error.UnknownIdentifier(name, symbols.keys()) | ||||
Augie Fackler
|
r43347 | elif op == b'minus': # equivalent to 'x and not y' | ||
Matt Harbison
|
r35634 | func1 = _compile(tree[1]) | ||
func2 = _compile(tree[2]) | ||||
return lambda n, s: func1(n, s) and not func2(n, s) | ||||
Augie Fackler
|
r43347 | elif op == b'list': | ||
Augie Fackler
|
r43345 | raise error.ParseError( | ||
Augie Fackler
|
r43347 | _(b"can't use a list in this context"), | ||
hint=_(b'see \'hg help "filesets.x or y"\''), | ||||
Augie Fackler
|
r43345 | ) | ||
Augie Fackler
|
r43347 | raise error.ProgrammingError(b'illegal tree: %r' % (tree,)) | ||
Matt Harbison
|
r35634 | |||
Augie Fackler
|
r43345 | |||
Matt Harbison
|
r35634 | 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. | ||||
Yuya Nishihara
|
r35759 | '(**.php & size(">10MB")) | **.zip | (path:bin & !path:bin/README)' for | ||
Matt Harbison
|
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". | ||||
""" | ||||
Yuya Nishihara
|
r38841 | tree = filesetlang.parse(text) | ||
Yuya Nishihara
|
r38862 | tree = filesetlang.analyze(tree) | ||
Yuya Nishihara
|
r38865 | tree = filesetlang.optimize(tree) | ||
Matt Harbison
|
r35634 | return _compile(tree) | ||