##// END OF EJS Templates
interfaces: make the `peer` mixin not a Protocol to fix Python 3.10 failures...
interfaces: make the `peer` mixin not a Protocol to fix Python 3.10 failures I can't find any documentation on this, but it appears that Protocol class attributes don't get inherited in subclasses that explicitly subclass a Protocol until Python 3.11, which caused a ton of failures in CI on macOS and Windows (which both test using Python 3.9). The problem started with 1df97507c6b8, and typically manifested as most tests failing to access `ui` on various `peer` classes. Here's a short proof of concept: from __future__ import annotations from typing import ( Protocol, ) class peer(Protocol): limitedarguments: bool = False def __init__(self, arg1, arg2, remotehidden: bool = False) -> None: self.arg1 = arg1 self.arg2 = arg2 class subclass(peer): def __init__(self, arg1, arg2): super(subclass, self).__init__(arg1, arg2, False) sub = subclass(1, 2) print("sub.arg1 is %r" % sub.arg1) When run with Python 3.8.10, 3.9.13, and 3.10.11, the result is: $ py -3.8 prot-test.py Traceback (most recent call last): File "prot-test.py", line 20, in <module> print("sub.arg1 is %r" % sub.arg1) AttributeError: 'subclass' object has no attribute 'arg1' On Python 3.11.9, 3.12.7, and 3.13.0, the result is: $ py -3.11 ../prot-test.py sub.arg1 is 1 Explicitly adding annotations to `peer` like `limitedarguments` didn't help.

File last commit:

r52756:f4733654 default
r53403:199b0e62 default
Show More
revsetlang.py
954 lines | 29.0 KiB | text/x-python | PythonLexer
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 # revsetlang.py - parser, tokenizer and utility for revision set language
#
Raphaël Gomès
contributor: change mentions of mpm to olivia...
r47575 # Copyright 2010 Olivia Mackall <olivia@selenic.com>
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 #
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
Matt Harbison
typing: add `from __future__ import annotations` to most files...
r52756 from __future__ import annotations
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024
import string
from .i18n import _
Joerg Sonnenberger
node: import symbols explicitly...
r46729 from .node import hex
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 from . import (
error,
parser,
pycompat,
Boris Feld
revset: detect integer list on parsing...
r41257 smartset,
Augie Fackler
revsetlang: perform quoting using ui.escapestr instead of repr()...
r31606 util,
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 )
Augie Fackler
formatting: blacken the codebase...
r43346 from .utils import stringutil
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024
elements = {
# token-type: binding-strength, primary, prefix, infix, suffix
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b"(": (21, None, (b"group", 1, b")"), (b"func", 1, b")"), None),
b"[": (21, None, None, (b"subscript", 1, b"]"), None),
b"#": (21, None, None, (b"relation", 21), None),
b"##": (20, None, None, (b"_concat", 20), None),
b"~": (18, None, None, (b"ancestor", 18), None),
b"^": (18, None, None, (b"parent", 18), b"parentpost"),
b"-": (5, None, (b"negate", 19), (b"minus", 5), None),
b"::": (
Augie Fackler
formatting: blacken the codebase...
r43346 17,
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b"dagrangeall",
(b"dagrangepre", 17),
(b"dagrange", 17),
b"dagrangepost",
Augie Fackler
formatting: blacken the codebase...
r43346 ),
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b"..": (
Augie Fackler
formatting: blacken the codebase...
r43346 17,
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b"dagrangeall",
(b"dagrangepre", 17),
(b"dagrange", 17),
b"dagrangepost",
Augie Fackler
formatting: blacken the codebase...
r43346 ),
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b":": (15, b"rangeall", (b"rangepre", 15), (b"range", 15), b"rangepost"),
b"not": (10, None, (b"not", 10), None, None),
b"!": (10, None, (b"not", 10), None, None),
b"and": (5, None, None, (b"and", 5), None),
b"&": (5, None, None, (b"and", 5), None),
b"%": (5, None, None, (b"only", 5), b"onlypost"),
b"or": (4, None, None, (b"or", 4), None),
b"|": (4, None, None, (b"or", 4), None),
b"+": (4, None, None, (b"or", 4), None),
b"=": (3, None, None, (b"keyvalue", 3), None),
b",": (2, None, None, (b"list", 2), None),
b")": (0, None, None, None, None),
b"]": (0, None, None, None, None),
b"symbol": (0, b"symbol", None, None, None),
b"string": (0, b"string", None, None, None),
b"end": (0, None, None, None, None),
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 }
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 keywords = {b'and', b'or', b'not'}
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024
Jun Wu
revset: move weight information to predicate...
r34274 symbols = {}
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 _quoteletters = {b'"', b"'"}
_simpleopletters = set(pycompat.iterbytestr(b"()[]#:=,-|&+!~^%"))
Yuya Nishihara
py3: make set of revset operators and quotes in bytes
r31384
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 # default set of valid characters for the initial letter of symbols
Augie Fackler
formatting: blacken the codebase...
r43346 _syminitletters = set(
pycompat.iterbytestr(
pycompat.sysbytes(string.ascii_letters)
+ pycompat.sysbytes(string.digits)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 + b'._@'
Augie Fackler
formatting: blacken the codebase...
r43346 )
Manuel Jacob
py3: replace `pycompat.xrange` by `range`
r50179 ) | set(map(pycompat.bytechr, range(128, 256)))
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024
# default set of valid characters for non-initial letters of symbols
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 _symletters = _syminitletters | set(pycompat.iterbytestr(b'-/'))
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 def tokenize(program, lookup=None, syminitletters=None, symletters=None):
Augie Fackler
formating: upgrade to black 20.8b1...
r46554 """
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 Parse a revset statement into a stream of tokens
``syminitletters`` is the set of valid characters for the initial
letter of symbols.
By default, character ``c`` is recognized as valid for initial
letter of symbols, if ``c.isalnum() or c in '._@' or ord(c) > 127``.
``symletters`` is the set of valid characters for non-initial
letters of symbols.
By default, character ``c`` is recognized as valid for non-initial
letters of symbols, if ``c.isalnum() or c in '-._/@' or ord(c) > 127``.
Check that @ is a valid unquoted token character (issue3686):
Yuya Nishihara
doctest: bulk-replace string literals with b'' for Python 3...
r34133 >>> list(tokenize(b"@::"))
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 [('symbol', '@', 0), ('::', None, 1), ('end', None, 3)]
Augie Fackler
formating: upgrade to black 20.8b1...
r46554 """
Yuya Nishihara
revsetlang: do not pass in non-bytes to parse()...
r37793 if not isinstance(program, bytes):
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.ProgrammingError(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'revset statement must be bytes, got %r' % program
Augie Fackler
formatting: blacken the codebase...
r43346 )
Yuya Nishihara
py3: use bytestr wrapper in revsetlang.tokenize()...
r31441 program = pycompat.bytestr(program)
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 if syminitletters is None:
syminitletters = _syminitletters
if symletters is None:
symletters = _symletters
if program and lookup:
# attempt to parse old-style ranges first to deal with
# things like old-tag which contain query metacharacters
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 parts = program.split(b':', 1)
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 if all(lookup(sym) for sym in parts if sym):
if parts[0]:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 yield (b'symbol', parts[0], 0)
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 if len(parts) > 1:
s = len(parts[0])
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 yield (b':', None, s)
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 if parts[1]:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 yield (b'symbol', parts[1], s + 1)
yield (b'end', None, len(program))
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 return
pos, l = 0, len(program)
while pos < l:
Yuya Nishihara
py3: use bytestr wrapper in revsetlang.tokenize()...
r31441 c = program[pos]
Augie Fackler
formatting: blacken the codebase...
r43346 if c.isspace(): # skip inter-token whitespace
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 pass
Augie Fackler
formatting: blacken the codebase...
r43346 elif (
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 c == b':' and program[pos : pos + 2] == b'::'
Augie Fackler
formatting: blacken the codebase...
r43346 ): # look ahead carefully
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 yield (b'::', None, pos)
Augie Fackler
formatting: blacken the codebase...
r43346 pos += 1 # skip ahead
elif (
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 c == b'.' and program[pos : pos + 2] == b'..'
Augie Fackler
formatting: blacken the codebase...
r43346 ): # look ahead carefully
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 yield (b'..', None, pos)
Augie Fackler
formatting: blacken the codebase...
r43346 pos += 1 # skip ahead
elif (
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 c == b'#' and program[pos : pos + 2] == b'##'
Augie Fackler
formatting: blacken the codebase...
r43346 ): # look ahead carefully
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 yield (b'##', None, pos)
Augie Fackler
formatting: blacken the codebase...
r43346 pos += 1 # skip ahead
elif c in _simpleopletters: # handle simple operators
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 yield (c, None, pos)
Augie Fackler
formatting: blacken the codebase...
r43346 elif (
c in _quoteletters
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 or c == b'r'
and program[pos : pos + 2] in (b"r'", b'r"')
Augie Fackler
formatting: blacken the codebase...
r43346 ): # handle quoted strings
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if c == b'r':
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 pos += 1
Yuya Nishihara
py3: use bytestr wrapper in revsetlang.tokenize()...
r31441 c = program[pos]
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 decode = lambda x: x
else:
decode = parser.unescapestr
pos += 1
s = pos
Augie Fackler
formatting: blacken the codebase...
r43346 while pos < l: # find closing quote
Yuya Nishihara
py3: use bytestr wrapper in revsetlang.tokenize()...
r31441 d = program[pos]
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if d == b'\\': # skip over escaped characters
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 pos += 2
continue
if d == c:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 yield (b'string', decode(program[s:pos]), s)
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 break
pos += 1
else:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.ParseError(_(b"unterminated string"), s)
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 # gather up a symbol/keyword
elif c in syminitletters:
s = pos
pos += 1
Augie Fackler
formatting: blacken the codebase...
r43346 while pos < l: # find end of symbol
Yuya Nishihara
py3: use bytestr wrapper in revsetlang.tokenize()...
r31441 d = program[pos]
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 if d not in symletters:
break
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if (
d == b'.' and program[pos - 1] == b'.'
): # special case for ..
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 pos -= 1
break
pos += 1
sym = program[s:pos]
Augie Fackler
formatting: blacken the codebase...
r43346 if sym in keywords: # operator keywords
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 yield (sym, None, s)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif b'-' in sym:
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 # some jerk gave us foo-bar-baz, try to check if it's a symbol
if lookup and lookup(sym):
# looks like a real symbol
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 yield (b'symbol', sym, s)
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 else:
# looks like an expression
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 parts = sym.split(b'-')
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 for p in parts[:-1]:
Augie Fackler
formatting: blacken the codebase...
r43346 if p: # possible consecutive -
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 yield (b'symbol', p, s)
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 s += len(p)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 yield (b'-', None, s)
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 s += 1
Augie Fackler
formatting: blacken the codebase...
r43346 if parts[-1]: # possible trailing -
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 yield (b'symbol', parts[-1], s)
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 else:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 yield (b'symbol', sym, s)
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 pos -= 1
else:
Augie Fackler
formatting: blacken the codebase...
r43346 raise error.ParseError(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 _(b"syntax error in revset '%s'") % program, pos
Augie Fackler
formatting: blacken the codebase...
r43346 )
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 pos += 1
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 yield (b'end', None, pos)
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 # helpers
_notset = object()
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 def getsymbol(x):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if x and x[0] == b'symbol':
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 return x[1]
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.ParseError(_(b'not a symbol'))
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 def getstring(x, err):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if x and (x[0] == b'string' or x[0] == b'symbol'):
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 return x[1]
raise error.ParseError(err)
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 def getinteger(x, err, default=_notset):
if not x and default is not _notset:
return default
try:
return int(getstring(x, err))
except ValueError:
raise error.ParseError(err)
Augie Fackler
formatting: blacken the codebase...
r43346
Denis Laxalde
revsetlang: add a getboolean helper function...
r31997 def getboolean(x, err):
Yuya Nishihara
stringutil: bulk-replace call sites to point to new module...
r37102 value = stringutil.parsebool(getsymbol(x))
Denis Laxalde
revsetlang: add a getboolean helper function...
r31997 if value is not None:
return value
raise error.ParseError(err)
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 def getlist(x):
if not x:
return []
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if x[0] == b'list':
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 return list(x[1:])
return [x]
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 def getrange(x, err):
if not x:
raise error.ParseError(err)
op = x[0]
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if op == b'range':
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 return x[1], x[2]
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif op == b'rangepre':
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 return None, x[1]
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif op == b'rangepost':
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 return x[1], None
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif op == b'rangeall':
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 return None, None
raise error.ParseError(err)
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
revset: extract a helper to parse integer range...
r41702 def getintrange(x, err1, err2, deffirst=_notset, deflast=_notset):
"""Get [first, last] integer range (both inclusive) from a parsed tree
If any of the sides omitted, and if no default provided, ParseError will
be raised.
"""
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if x and (x[0] == b'string' or x[0] == b'symbol'):
Yuya Nishihara
revset: allow to parse single integer as a range...
r41703 n = getinteger(x, err1)
return n, n
Yuya Nishihara
revset: extract a helper to parse integer range...
r41702 a, b = getrange(x, err1)
return getinteger(a, err2, deffirst), getinteger(b, err2, deflast)
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 def getargs(x, min, max, err):
l = getlist(x)
if len(l) < min or (max >= 0 and len(l) > max):
raise error.ParseError(err)
return l
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 def getargsdict(x, funcname, keys):
Augie Fackler
formatting: blacken the codebase...
r43346 return parser.buildargsdict(
getlist(x),
funcname,
parser.splitargspec(keys),
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 keyvaluenode=b'keyvalue',
keynode=b'symbol',
Augie Fackler
formatting: blacken the codebase...
r43346 )
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024
Yuya Nishihara
revsetlang: build optimized tree by helper function...
r34046 # cache of {spec: raw parsed tree} built internally
_treecache = {}
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
revsetlang: build optimized tree by helper function...
r34046 def _cachedtree(spec):
# thread safe because parse() is reentrant and dict.__setitem__() is atomic
tree = _treecache.get(spec)
if tree is None:
_treecache[spec] = tree = parse(spec)
return tree
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
revsetlang: build optimized tree by helper function...
r34046 def _build(tmplspec, *repls):
"""Create raw parsed tree from a template revset statement
Yuya Nishihara
doctest: bulk-replace string literals with b'' for Python 3...
r34133 >>> _build(b'f(_) and _', (b'string', b'1'), (b'symbol', b'2'))
Yuya Nishihara
revsetlang: build optimized tree by helper function...
r34046 ('and', ('func', ('symbol', 'f'), ('string', '1')), ('symbol', '2'))
"""
template = _cachedtree(tmplspec)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return parser.buildtree(template, (b'symbol', b'_'), *repls)
Yuya Nishihara
revsetlang: build optimized tree by helper function...
r34046
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
revsetlang: match tree by helper function on optimize...
r34048 def _match(patspec, tree):
"""Test if a tree matches the given pattern statement; return the matches
Yuya Nishihara
doctest: bulk-replace string literals with b'' for Python 3...
r34133 >>> _match(b'f(_)', parse(b'f()'))
>>> _match(b'f(_)', parse(b'f(1)'))
Yuya Nishihara
revsetlang: match tree by helper function on optimize...
r34048 [('func', ('symbol', 'f'), ('symbol', '1')), ('symbol', '1')]
Yuya Nishihara
doctest: bulk-replace string literals with b'' for Python 3...
r34133 >>> _match(b'f(_)', parse(b'f(1, 2)'))
Yuya Nishihara
revsetlang: match tree by helper function on optimize...
r34048 """
pattern = _cachedtree(patspec)
Augie Fackler
formatting: blacken the codebase...
r43346 return parser.matchtree(
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 pattern, tree, (b'symbol', b'_'), {b'keyvalue', b'list'}
Augie Fackler
formatting: blacken the codebase...
r43346 )
Yuya Nishihara
revsetlang: match tree by helper function on optimize...
r34048
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 def _matchonly(revs, bases):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return _match(b'ancestors(_) and not ancestors(_)', (b'and', revs, bases))
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 def _fixops(x):
"""Rewrite raw parsed tree to resolve ambiguous syntax which cannot be
handled well by our simple top-down parser"""
if not isinstance(x, tuple):
return x
op = x[0]
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if op == b'parent':
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 # x^:y means (x^) : y, not x ^ (:y)
# x^: means (x^) :, not x ^ (:)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 post = (b'parentpost', x[1])
if x[2][0] == b'dagrangepre':
return _fixops((b'dagrange', post, x[2][1]))
elif x[2][0] == b'dagrangeall':
return _fixops((b'dagrangepost', post))
elif x[2][0] == b'rangepre':
return _fixops((b'range', post, x[2][1]))
elif x[2][0] == b'rangeall':
return _fixops((b'rangepost', post))
elif op == b'or':
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 # make number of arguments deterministic:
# x + y + z -> (or x y z) -> (or (list x y z))
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return (op, _fixops((b'list',) + x[1:]))
elif op == b'subscript' and x[1][0] == b'relation':
Yuya Nishihara
revset: add experimental relation and subscript operators...
r33416 # x#y[z] ternary
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return _fixops((b'relsubscript', x[1][1], x[1][2], x[2]))
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024
return (op,) + tuple(_fixops(y) for y in x[1:])
Augie Fackler
formatting: blacken the codebase...
r43346
Jun Wu
revset: remove order information from tree (API)...
r34013 def _analyze(x):
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 if x is None:
return x
op = x[0]
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if op == b'minus':
return _analyze(_build(b'_ and not _', *x[1:]))
elif op == b'only':
return _analyze(_build(b'only(_, _)', *x[1:]))
elif op == b'onlypost':
return _analyze(_build(b'only(_)', x[1]))
elif op == b'dagrangeall':
raise error.ParseError(_(b"can't use '::' in this context"))
elif op == b'dagrangepre':
return _analyze(_build(b'ancestors(_)', x[1]))
elif op == b'dagrangepost':
return _analyze(_build(b'descendants(_)', x[1]))
elif op == b'negate':
s = getstring(x[1], _(b"can't negate that"))
return _analyze((b'string', b'-' + s))
revset: stop serializing node when using "%ln"...
r52469 elif op in (b'string', b'symbol', b'smartset', b'nodeset'):
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 return x
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif op == b'rangeall':
Jun Wu
revset: remove order information from tree (API)...
r34013 return (op, None)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif op in {b'or', b'not', b'rangepre', b'rangepost', b'parentpost'}:
Jun Wu
revset: remove order information from tree (API)...
r34013 return (op, _analyze(x[1]))
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif op == b'group':
Jun Wu
revset: remove order information from tree (API)...
r34013 return _analyze(x[1])
Augie Fackler
formatting: blacken the codebase...
r43346 elif op in {
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'and',
b'dagrange',
b'range',
b'parent',
b'ancestor',
b'relation',
b'subscript',
Augie Fackler
formatting: blacken the codebase...
r43346 }:
Jun Wu
revset: remove order information from tree (API)...
r34013 ta = _analyze(x[1])
tb = _analyze(x[2])
return (op, ta, tb)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif op == b'relsubscript':
Jun Wu
revset: remove order information from tree (API)...
r34013 ta = _analyze(x[1])
tb = _analyze(x[2])
tc = _analyze(x[3])
return (op, ta, tb, tc)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif op == b'list':
Jun Wu
revset: remove order information from tree (API)...
r34013 return (op,) + tuple(_analyze(y) for y in x[1:])
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif op == b'keyvalue':
Jun Wu
revset: remove order information from tree (API)...
r34013 return (op, x[1], _analyze(x[2]))
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif op == b'func':
Jun Wu
revset: remove order information from tree (API)...
r34013 return (op, x[1], _analyze(x[2]))
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise ValueError(b'invalid operator %r' % op)
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024
Augie Fackler
formatting: blacken the codebase...
r43346
Jun Wu
revset: remove order information from tree (API)...
r34013 def analyze(x):
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 """Transform raw parsed tree to evaluatable tree which can be fed to
optimize() or getset()
All pseudo operations should be mapped to real operations or functions
defined in methods or symbols table respectively.
"""
Jun Wu
revset: remove order information from tree (API)...
r34013 return _analyze(x)
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024
Augie Fackler
formatting: blacken the codebase...
r43346
Jun Wu
revset: remove "small" argument from "_optimize"...
r34273 def _optimize(x):
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 if x is None:
return 0, x
op = x[0]
revset: stop serializing node when using "%ln"...
r52469 if op in (b'string', b'symbol', b'smartset', b'nodeset'):
# single revisions are small, and set of already computed revision are assumed to be cheap.
return 0.5, x
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif op == b'and':
Jun Wu
revset: remove "small" argument from "_optimize"...
r34273 wa, ta = _optimize(x[1])
wb, tb = _optimize(x[2])
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 w = min(wa, wb)
Jun Wu
revset: optimize "draft() & ::x" pattern...
r34067 # (draft/secret/_notpublic() & ::x) have a fast path
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 m = _match(b'_() & ancestors(_)', (b'and', ta, tb))
if m and getsymbol(m[1]) in {b'draft', b'secret', b'_notpublic'}:
return w, _build(b'_phaseandancestors(_, _)', m[1], m[2])
Jun Wu
revset: optimize "draft() & ::x" pattern...
r34067
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 # (::x and not ::y)/(not ::y and ::x) have a fast path
Yuya Nishihara
revsetlang: build optimized tree by helper function...
r34046 m = _matchonly(ta, tb) or _matchonly(tb, ta)
if m:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return w, _build(b'only(_, _)', *m[1:])
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 m = _match(b'not _', tb)
Yuya Nishihara
revsetlang: match tree by helper function on optimize...
r34048 if m:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return wa, (b'difference', ta, m[1])
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 if wa > wb:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 op = b'andsmally'
Jun Wu
revset: remove order information from tree (API)...
r34013 return w, (op, ta, tb)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif op == b'or':
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 # fast path for machine-generated expression, that is likely to have
# lots of trivial revisions: 'a + b + c()' to '_list(a b) + c()'
ws, ts, ss = [], [], []
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 def flushss():
if not ss:
return
if len(ss) == 1:
w, t = ss[0]
else:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 s = b'\0'.join(t[1] for w, t in ss)
y = _build(b'_list(_)', (b'string', s))
Jun Wu
revset: remove "small" argument from "_optimize"...
r34273 w, t = _optimize(y)
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 ws.append(w)
ts.append(t)
del ss[:]
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 for y in getlist(x[1]):
Jun Wu
revset: remove "small" argument from "_optimize"...
r34273 w, t = _optimize(y)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if t is not None and (t[0] == b'string' or t[0] == b'symbol'):
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 ss.append((w, t))
continue
flushss()
ws.append(w)
ts.append(t)
flushss()
if len(ts) == 1:
Augie Fackler
formatting: blacken the codebase...
r43346 return ws[0], ts[0] # 'or' operation is fully optimized out
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return max(ws), (op, (b'list',) + tuple(ts))
elif op == b'not':
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 # Optimize not public() to _notpublic() because we have a fast version
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if _match(b'public()', x[1]):
o = _optimize(_build(b'_notpublic()'))
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 return o[0], o[1]
else:
Jun Wu
revset: remove "small" argument from "_optimize"...
r34273 o = _optimize(x[1])
Jun Wu
revset: remove order information from tree (API)...
r34013 return o[0], (op, o[1])
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif op == b'rangeall':
Jun Wu
revset: remove "small" argument from "_optimize"...
r34273 return 1, x
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif op in (b'rangepre', b'rangepost', b'parentpost'):
Jun Wu
revset: remove "small" argument from "_optimize"...
r34273 o = _optimize(x[1])
Jun Wu
revset: remove order information from tree (API)...
r34013 return o[0], (op, o[1])
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif op in (b'dagrange', b'range'):
Jun Wu
revset: remove "small" argument from "_optimize"...
r34273 wa, ta = _optimize(x[1])
wb, tb = _optimize(x[2])
Jun Wu
revset: remove order information from tree (API)...
r34013 return wa + wb, (op, ta, tb)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif op in (b'parent', b'ancestor', b'relation', b'subscript'):
Jun Wu
revset: remove "small" argument from "_optimize"...
r34273 w, t = _optimize(x[1])
Jun Wu
revset: remove order information from tree (API)...
r34013 return w, (op, t, x[2])
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif op == b'relsubscript':
Jun Wu
revset: remove "small" argument from "_optimize"...
r34273 w, t = _optimize(x[1])
Jun Wu
revset: remove order information from tree (API)...
r34013 return w, (op, t, x[2], x[3])
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif op == b'list':
Jun Wu
revset: remove "small" argument from "_optimize"...
r34273 ws, ts = zip(*(_optimize(y) for y in x[1:]))
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 return sum(ws), (op,) + ts
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif op == b'keyvalue':
Jun Wu
revset: remove "small" argument from "_optimize"...
r34273 w, t = _optimize(x[2])
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 return w, (op, x[1], t)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif op == b'func':
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 f = getsymbol(x[1])
Jun Wu
revset: remove "small" argument from "_optimize"...
r34273 wa, ta = _optimize(x[2])
Jun Wu
revset: move weight information to predicate...
r34274 w = getattr(symbols.get(f), '_weight', 1)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 m = _match(b'commonancestors(_)', ta)
Sean Farley
revset: add optimization for heads(commonancestors())...
r38644
# Optimize heads(commonancestors(_)) because we have a fast version
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if f == b'heads' and m:
return w + wa, _build(b'_commonancestorheads(_)', m[1])
Sean Farley
revset: add optimization for heads(commonancestors())...
r38644
Jun Wu
revset: remove order information from tree (API)...
r34013 return w + wa, (op, x[1], ta)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise ValueError(b'invalid operator %r' % op)
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 def optimize(tree):
"""Optimize evaluatable tree
All pseudo operations should be transformed beforehand.
"""
Jun Wu
revset: remove "small" argument from "_optimize"...
r34273 _weight, newtree = _optimize(tree)
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 return newtree
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 # the set of valid characters for the initial letter of symbols in
# alias declarations and definitions
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 _aliassyminitletters = _syminitletters | {b'$'}
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 def _parsewith(spec, lookup=None, syminitletters=None):
"""Generate a parse tree of given spec with given tokenizing options
Yuya Nishihara
doctest: bulk-replace string literals with b'' for Python 3...
r34133 >>> _parsewith(b'foo($1)', syminitletters=_aliassyminitletters)
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 ('func', ('symbol', 'foo'), ('symbol', '$1'))
Martin von Zweigbergk
tests: make doctests not depend on str(ParseError()) format...
r46498 >>> from . import error
>>> from . import pycompat
>>> try:
... _parsewith(b'$1')
... except error.ParseError as e:
... pycompat.sysstr(e.message)
... e.location
"syntax error in revset '$1'"
0
>>> try:
... _parsewith(b'foo bar')
... except error.ParseError as e:
... pycompat.sysstr(e.message)
... e.location
'invalid token'
4
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 """
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if lookup and spec.startswith(b'revset(') and spec.endswith(b')'):
Boris Feld
revset: skip legacy lookup for revspec wrapped in 'revset(...)'...
r37778 lookup = None
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 p = parser.parser(elements)
Augie Fackler
formatting: blacken the codebase...
r43346 tree, pos = p.parse(
tokenize(spec, lookup=lookup, syminitletters=syminitletters)
)
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 if pos != len(spec):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.ParseError(_(b'invalid token'), pos)
return _fixops(parser.simplifyinfixops(tree, (b'list', b'or')))
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 class _aliasrules(parser.basealiasrules):
"""Parsing and expansion rule set of revset aliases"""
Augie Fackler
formatting: blacken the codebase...
r43346
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 _section = _(b'revset alias')
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024
@staticmethod
def _parse(spec):
"""Parse alias declaration/definition ``spec``
This allows symbol names to use also ``$`` as an initial letter
(for backward compatibility), and callers of this function should
examine whether ``$`` is used also for unexpected symbols or not.
"""
return _parsewith(spec, syminitletters=_aliassyminitletters)
@staticmethod
def _trygetfunc(tree):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if tree[0] == b'func' and tree[1][0] == b'symbol':
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 return tree[1][1], getlist(tree[2])
Augie Fackler
formatting: blacken the codebase...
r43346
Jun Wu
revset: make repo.anyrevs accept customized alias override (API)...
r33336 def expandaliases(tree, aliases, warn=None):
"""Expand aliases in a tree, aliases is a list of (name, value) tuples"""
aliases = _aliasrules.buildmap(aliases)
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 tree = _aliasrules.expand(aliases, tree)
# warn about problematic (but not referred) aliases
Jun Wu
revset: make repo.anyrevs accept customized alias override (API)...
r33336 if warn is not None:
Gregory Szorc
global: bulk replace simple pycompat.iteritems(x) with x.items()...
r49768 for name, alias in sorted(aliases.items()):
Jun Wu
revset: make repo.anyrevs accept customized alias override (API)...
r33336 if alias.error and not alias.warned:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 warn(_(b'warning: %s\n') % (alias.error))
Jun Wu
revset: make repo.anyrevs accept customized alias override (API)...
r33336 alias.warned = True
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 return tree
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 def foldconcat(tree):
Augie Fackler
formating: upgrade to black 20.8b1...
r46554 """Fold elements to be concatenated by `##`"""
Augie Fackler
formatting: blacken the codebase...
r43346 if not isinstance(tree, tuple) or tree[0] in (
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'string',
b'symbol',
b'smartset',
Augie Fackler
formatting: blacken the codebase...
r43346 ):
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 return tree
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if tree[0] == b'_concat':
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 pending = [tree]
l = []
while pending:
e = pending.pop()
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if e[0] == b'_concat':
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 pending.extend(reversed(e[1:]))
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif e[0] in (b'string', b'symbol'):
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 l.append(e[1])
else:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 msg = _(b"\"##\" can't concatenate \"%s\" element") % (e[0])
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 raise error.ParseError(msg)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return (b'string', b''.join(l))
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 else:
return tuple(foldconcat(t) for t in tree)
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 def parse(spec, lookup=None):
Ryan McElroy
revsetlang: add a hint for more useful parse errors...
r36703 try:
return _parsewith(spec, lookup=lookup)
except error.ParseError as inst:
Martin von Zweigbergk
errors: name arguments to ParseError constructor...
r46361 if inst.location is not None:
loc = inst.location
Ryan McElroy
revsetlang: add a hint for more useful parse errors...
r36703 # Remove newlines -- spaces are equivalent whitespace.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 spec = spec.replace(b'\n', b' ')
Ryan McElroy
revsetlang: add a hint for more useful parse errors...
r36703 # We want the caret to point to the place in the template that
# failed to parse, but in a hint we get a open paren at the
# start. Therefore, we print "loc + 1" spaces (instead of "loc")
# to line up the caret with the location of the error.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 inst.hint = spec + b'\n' + b' ' * (loc + 1) + b'^ ' + _(b'here')
Ryan McElroy
revsetlang: add a hint for more useful parse errors...
r36703 raise
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024
Augie Fackler
formatting: blacken the codebase...
r43346
Augie Fackler
revsetlang: move quoting function to not be a closure...
r31604 def _quote(s):
Augie Fackler
revsetlang: add docstring with some tests to _quote
r31605 r"""Quote a value in order to make it safe for the revset engine.
Yuya Nishihara
doctest: bulk-replace string literals with b'' for Python 3...
r34133 >>> _quote(b'asdf')
Augie Fackler
revsetlang: add docstring with some tests to _quote
r31605 "'asdf'"
Yuya Nishihara
doctest: bulk-replace string literals with b'' for Python 3...
r34133 >>> _quote(b"asdf'\"")
Augie Fackler
revsetlang: add docstring with some tests to _quote
r31605 '\'asdf\\\'"\''
Yuya Nishihara
doctest: bulk-replace string literals with b'' for Python 3...
r34133 >>> _quote(b'asdf\'')
Augie Fackler
revsetlang: perform quoting using ui.escapestr instead of repr()...
r31606 "'asdf\\''"
Augie Fackler
revsetlang: add docstring with some tests to _quote
r31605 >>> _quote(1)
"'1'"
"""
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return b"'%s'" % stringutil.escapestr(pycompat.bytestr(s))
Augie Fackler
revsetlang: move quoting function to not be a closure...
r31604
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
revsetlang: unnest inner functions from formatspec()
r35614 def _formatargtype(c, arg):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if c == b'd':
return b'_rev(%d)' % int(arg)
elif c == b's':
Yuya Nishihara
revsetlang: unnest inner functions from formatspec()
r35614 return _quote(arg)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif c == b'r':
Yuya Nishihara
revsetlang: do not pass in non-bytes to parse()...
r37793 if not isinstance(arg, bytes):
raise TypeError
Augie Fackler
formatting: blacken the codebase...
r43346 parse(arg) # make sure syntax errors are confined
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return b'(%s)' % arg
elif c == b'n':
Joerg Sonnenberger
node: import symbols explicitly...
r46729 return _quote(hex(arg))
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif c == b'b':
Yuya Nishihara
revsetlang: unnest inner functions from formatspec()
r35614 try:
return _quote(arg.branch())
except AttributeError:
raise TypeError
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.ParseError(_(b'unexpected revspec format character %s') % c)
Yuya Nishihara
revsetlang: unnest inner functions from formatspec()
r35614
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
revsetlang: unnest inner functions from formatspec()
r35614 def _formatlistexp(s, t):
l = len(s)
if l == 0:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return b"_list('')"
Yuya Nishihara
revsetlang: unnest inner functions from formatspec()
r35614 elif l == 1:
return _formatargtype(t, s[0])
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif t == b'd':
Boris Feld
revert: extract "%ld" formatting in a _formatintlist function...
r41256 return _formatintlist(s)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif t == b's':
return b"_list(%s)" % _quote(b"\0".join(s))
elif t == b'n':
Joerg Sonnenberger
node: import symbols explicitly...
r46729 return b"_hexlist('%s')" % b"\0".join(hex(a) for a in s)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif t == b'b':
Yuya Nishihara
revsetlang: unnest inner functions from formatspec()
r35614 try:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return b"_list('%s')" % b"\0".join(a.branch() for a in s)
Yuya Nishihara
revsetlang: unnest inner functions from formatspec()
r35614 except AttributeError:
raise TypeError
m = l // 2
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return b'(%s or %s)' % (_formatlistexp(s[:m], t), _formatlistexp(s[m:], t))
Yuya Nishihara
revsetlang: unnest inner functions from formatspec()
r35614
Augie Fackler
formatting: blacken the codebase...
r43346
Boris Feld
revert: extract "%ld" formatting in a _formatintlist function...
r41256 def _formatintlist(data):
try:
l = len(data)
if l == 0:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return b"_list('')"
Boris Feld
revert: extract "%ld" formatting in a _formatintlist function...
r41256 elif l == 1:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return _formatargtype(b'd', data[0])
return b"_intlist('%s')" % b"\0".join(b'%d' % int(a) for a in data)
Boris Feld
revert: extract "%ld" formatting in a _formatintlist function...
r41256 except (TypeError, ValueError):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.ParseError(_(b'invalid argument for revspec'))
Boris Feld
revert: extract "%ld" formatting in a _formatintlist function...
r41256
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
revsetlang: add %p specifier to format list of function arguments...
r35615 def _formatparamexp(args, t):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return b', '.join(_formatargtype(t, a) for a in args)
Yuya Nishihara
revsetlang: add %p specifier to format list of function arguments...
r35615
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
revsetlang: add %p specifier to format list of function arguments...
r35615 _formatlistfuncs = {
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 b'l': _formatlistexp,
b'p': _formatparamexp,
Yuya Nishihara
revsetlang: add %p specifier to format list of function arguments...
r35615 }
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 def formatspec(expr, *args):
Augie Fackler
formating: upgrade to black 20.8b1...
r46554 """
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 This is a convenience function for using revsets internally, and
escapes arguments appropriately. Aliases are intentionally ignored
so that intended expression behavior isn't accidentally subverted.
Supported arguments:
%r = revset expression, parenthesized
Boris Feld
revset: enforce "%d" to be interpreted as literal revision number (API) (BC)...
r41254 %d = rev(int(arg)), no quoting
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 %s = string(arg), escaped and single-quoted
%b = arg.branch(), escaped and single-quoted
%n = hex(arg), single-quoted
%% = a literal '%'
Yuya Nishihara
revsetlang: add %p specifier to format list of function arguments...
r35615 Prefixing the type with 'l' specifies a parenthesized list of that type,
and 'p' specifies a list of function parameters of that type.
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024
Yuya Nishihara
doctest: bulk-replace string literals with b'' for Python 3...
r34133 >>> formatspec(b'%r:: and %lr', b'10 or 11', (b"this()", b"that()"))
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 '(10 or 11):: and ((this()) or (that()))'
Yuya Nishihara
doctest: bulk-replace string literals with b'' for Python 3...
r34133 >>> formatspec(b'%d:: and not %d::', 10, 20)
Boris Feld
revset: introduce an internal `_rev` predicate for '%d' usage...
r41333 '_rev(10):: and not _rev(20)::'
Yuya Nishihara
doctest: bulk-replace string literals with b'' for Python 3...
r34133 >>> formatspec(b'%ld or %ld', [], [1])
Boris Feld
revset: introduce an internal `_rev` predicate for '%d' usage...
r41333 "_list('') or _rev(1)"
Yuya Nishihara
doctest: bulk-replace string literals with b'' for Python 3...
r34133 >>> formatspec(b'keyword(%s)', b'foo\\xe9')
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 "keyword('foo\\\\xe9')"
Yuya Nishihara
doctest: bulk-replace string literals with b'' for Python 3...
r34133 >>> b = lambda: b'default'
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 >>> b.branch = b
Yuya Nishihara
doctest: bulk-replace string literals with b'' for Python 3...
r34133 >>> formatspec(b'branch(%b)', b)
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 "branch('default')"
Yuya Nishihara
doctest: bulk-replace string literals with b'' for Python 3...
r34133 >>> formatspec(b'root(%ls)', [b'a', b'b', b'c', b'd'])
Yuya Nishihara
revsetlang: fix quoting of %ls string...
r35613 "root(_list('a\\\\x00b\\\\x00c\\\\x00d'))"
Yuya Nishihara
revsetlang: add %p specifier to format list of function arguments...
r35615 >>> formatspec(b'sort(%r, %ps)', b':', [b'desc', b'user'])
"sort((:), 'desc', 'user')"
Augie Fackler
revsetlang: fix a doctest example on Python 3...
r35840 >>> formatspec(b'%ls', [b'a', b"'"])
Yuya Nishihara
revsetlang: fix quoting of %ls string...
r35613 "_list('a\\\\x00\\\\'')"
Augie Fackler
formating: upgrade to black 20.8b1...
r46554 """
Boris Feld
revset: extract parsing logic out of formatspec...
r41255 parsed = _parseargs(expr, args)
ret = []
for t, arg in parsed:
if t is None:
ret.append(arg)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif t == b'baseset':
Boris Feld
revset: detect integer list on parsing...
r41257 if isinstance(arg, set):
arg = sorted(arg)
ret.append(_formatintlist(list(arg)))
revset: stop serializing node when using "%ln"...
r52469 elif t == b'nodeset':
ret.append(_formatlistexp(list(arg), b"n"))
Boris Feld
revset: extract parsing logic out of formatspec...
r41255 else:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.ProgrammingError(b"unknown revspec item type: %r" % t)
Boris Feld
revset: extract parsing logic out of formatspec...
r41255 return b''.join(ret)
Augie Fackler
formatting: blacken the codebase...
r43346
Boris Feld
revset: introduce an API that avoids `formatspec` input serialization...
r41258 def spectree(expr, *args):
"""similar to formatspec but return a parsed and optimized tree"""
parsed = _parseargs(expr, args)
ret = []
inputs = []
for t, arg in parsed:
if t is None:
ret.append(arg)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif t == b'baseset':
newtree = (b'smartset', smartset.baseset(arg))
Boris Feld
revset: introduce an API that avoids `formatspec` input serialization...
r41258 inputs.append(newtree)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ret.append(b"$")
revset: stop serializing node when using "%ln"...
r52469 elif t == b'nodeset':
newtree = (b'nodeset', arg)
inputs.append(newtree)
ret.append(b"$")
Boris Feld
revset: introduce an API that avoids `formatspec` input serialization...
r41258 else:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.ProgrammingError(b"unknown revspec item type: %r" % t)
Boris Feld
revset: introduce an API that avoids `formatspec` input serialization...
r41258 expr = b''.join(ret)
tree = _parsewith(expr, syminitletters=_aliassyminitletters)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 tree = parser.buildtree(tree, (b'symbol', b'$'), *inputs)
Boris Feld
revset: introduce an API that avoids `formatspec` input serialization...
r41258 tree = foldconcat(tree)
tree = analyze(tree)
tree = optimize(tree)
return tree
Augie Fackler
formatting: blacken the codebase...
r43346
Boris Feld
revset: extract parsing logic out of formatspec...
r41255 def _parseargs(expr, args):
"""parse the expression and replace all inexpensive args
return a list of tuple [(arg-type, arg-value)]
Arg-type can be:
Boris Feld
revset: detect integer list on parsing...
r41257 * None: a string ready to be concatenated into a final spec
* 'baseset': an iterable of revisions
Boris Feld
revset: extract parsing logic out of formatspec...
r41255 """
Yuya Nishihara
py3: use bytestr wrapper in revsetlang.formatspec()...
r31440 expr = pycompat.bytestr(expr)
Yuya Nishihara
revsetlang: use iterator to track current argument in formatspec()
r35574 argiter = iter(args)
Yuya Nishihara
revsetlang: avoid string concatenation in formatspec()
r35571 ret = []
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 pos = 0
while pos < len(expr):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 q = expr.find(b'%', pos)
Yuya Nishihara
revsetlang: use str.find() to scan expr in formatspec()...
r35572 if q < 0:
Boris Feld
revset: extract parsing logic out of formatspec...
r41255 ret.append((None, expr[pos:]))
Yuya Nishihara
revsetlang: use str.find() to scan expr in formatspec()...
r35572 break
Boris Feld
revset: extract parsing logic out of formatspec...
r41255 ret.append((None, expr[pos:q]))
Yuya Nishihara
revsetlang: use str.find() to scan expr in formatspec()...
r35572 pos = q + 1
Yuya Nishihara
revsetlang: check incomplete revspec format character
r35611 try:
d = expr[pos]
except IndexError:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.ParseError(_(b'incomplete revspec format character'))
if d == b'%':
Boris Feld
revset: extract parsing logic out of formatspec...
r41255 ret.append((None, d))
Yuya Nishihara
revsetlang: check number of arguments passed to formatspec()
r35610 pos += 1
continue
try:
arg = next(argiter)
except StopIteration:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.ParseError(_(b'missing argument for revspec'))
Yuya Nishihara
revsetlang: add %p specifier to format list of function arguments...
r35615 f = _formatlistfuncs.get(d)
if f:
Boris Feld
revset: extract parsing logic out of formatspec...
r41255 # a list of some type, might be expensive, do not replace
Yuya Nishihara
revsetlang: unnest "if True" in formatrevspec()
r35573 pos += 1
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 islist = d == b'l'
Yuya Nishihara
revsetlang: check incomplete revspec format character
r35611 try:
d = expr[pos]
except IndexError:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.ParseError(
_(b'incomplete revspec format character')
)
if islist and d == b'd' and arg:
Boris Feld
revset: transparently forward _intlist argument in all case...
r41275 # we don't create a baseset yet, because it come with an
# extra cost. If we are going to serialize it we better
# skip it.
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ret.append((b'baseset', arg))
Boris Feld
revset: transparently forward _intlist argument in all case...
r41275 pos += 1
continue
revset: stop serializing node when using "%ln"...
r52469 elif islist and d == b'n' and arg:
# we cannot turn the node into revision yet, but not
# serializing them will same a lot of time for large set.
ret.append((b'nodeset', arg))
pos += 1
continue
Yuya Nishihara
revsetlang: catch invalid value passed to formatspec()...
r35612 try:
Boris Feld
revset: extract parsing logic out of formatspec...
r41255 ret.append((None, f(list(arg), d)))
Yuya Nishihara
revsetlang: catch invalid value passed to formatspec()...
r35612 except (TypeError, ValueError):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.ParseError(_(b'invalid argument for revspec'))
Yuya Nishihara
revsetlang: unnest "if True" in formatrevspec()
r35573 else:
Boris Feld
revset: extract parsing logic out of formatspec...
r41255 # a single entry, not expensive, replace
Yuya Nishihara
revsetlang: catch invalid value passed to formatspec()...
r35612 try:
Boris Feld
revset: extract parsing logic out of formatspec...
r41255 ret.append((None, _formatargtype(d, arg)))
Yuya Nishihara
revsetlang: catch invalid value passed to formatspec()...
r35612 except (TypeError, ValueError):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.ParseError(_(b'invalid argument for revspec'))
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 pos += 1
Yuya Nishihara
revsetlang: check number of arguments passed to formatspec()
r35610 try:
next(argiter)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 raise error.ParseError(_(b'too many revspec arguments specified'))
Yuya Nishihara
revsetlang: check number of arguments passed to formatspec()
r35610 except StopIteration:
pass
Boris Feld
revset: extract parsing logic out of formatspec...
r41255 return ret
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 def prettyformat(tree):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 return parser.prettyformat(tree, (b'string', b'symbol'))
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 def depth(tree):
if isinstance(tree, tuple):
return max(map(depth, tree)) + 1
else:
return 0
Augie Fackler
formatting: blacken the codebase...
r43346
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 def funcsused(tree):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if not isinstance(tree, tuple) or tree[0] in (b'string', b'symbol'):
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 return set()
else:
funcs = set()
for s in tree[1:]:
funcs |= funcsused(s)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if tree[0] == b'func':
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 funcs.add(tree[1][1])
return funcs
Pulkit Goyal
revsetlang: add utility function to return hash like symbols from the tree...
r35510
Augie Fackler
formatting: blacken the codebase...
r43346
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 _hashre = util.re.compile(b'[0-9a-fA-F]{1,40}$')
Pulkit Goyal
revsetlang: add utility function to return hash like symbols from the tree...
r35510
Augie Fackler
formatting: blacken the codebase...
r43346
Pulkit Goyal
revsetlang: add utility function to return hash like symbols from the tree...
r35510 def _ishashlikesymbol(symbol):
"""returns true if the symbol looks like a hash"""
return _hashre.match(symbol)
Augie Fackler
formatting: blacken the codebase...
r43346
Pulkit Goyal
revsetlang: add utility function to return hash like symbols from the tree...
r35510 def gethashlikesymbols(tree):
"""returns the list of symbols of the tree that look like hashes
Yuya Nishihara
tests: make doctest py3-compatible again...
r35900 >>> gethashlikesymbols(parse(b'3::abe3ff'))
Pulkit Goyal
revsetlang: add utility function to return hash like symbols from the tree...
r35510 ['3', 'abe3ff']
Yuya Nishihara
tests: make doctest py3-compatible again...
r35900 >>> gethashlikesymbols(parse(b'precursors(.)'))
Pulkit Goyal
revsetlang: add utility function to return hash like symbols from the tree...
r35510 []
Yuya Nishihara
tests: make doctest py3-compatible again...
r35900 >>> gethashlikesymbols(parse(b'precursors(34)'))
Pulkit Goyal
revsetlang: add utility function to return hash like symbols from the tree...
r35510 ['34']
Yuya Nishihara
tests: make doctest py3-compatible again...
r35900 >>> gethashlikesymbols(parse(b'abe3ffZ'))
Pulkit Goyal
revsetlang: add utility function to return hash like symbols from the tree...
r35510 []
"""
if not tree:
return []
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if tree[0] == b"symbol":
Pulkit Goyal
revsetlang: add utility function to return hash like symbols from the tree...
r35510 if _ishashlikesymbol(tree[1]):
return [tree[1]]
elif len(tree) >= 3:
results = []
for subtree in tree[1:]:
results += gethashlikesymbols(subtree)
return results
return []