--- a/mercurial/templateutil.py Sat Oct 05 10:29:34 2019 -0400
+++ b/mercurial/templateutil.py Sun Oct 06 09:45:02 2019 -0400
@@ -21,12 +21,15 @@
stringutil,
)
+
class ResourceUnavailable(error.Abort):
pass
+
class TemplateNotFound(error.Abort):
pass
+
class wrapped(object):
"""Object requiring extra conversion prior to displaying or processing
as value
@@ -103,6 +106,7 @@
A returned value must be serializable by templaterfilters.json().
"""
+
class mappable(object):
"""Object which can be converted to a single template mapping"""
@@ -113,6 +117,7 @@
def tomap(self, context):
"""Create a single template mapping representing this"""
+
class wrappedbytes(wrapped):
"""Wrapper for byte string"""
@@ -124,8 +129,9 @@
return item in self._value
def getmember(self, context, mapping, key):
- raise error.ParseError(_('%r is not a dictionary')
- % pycompat.bytestr(self._value))
+ raise error.ParseError(
+ _('%r is not a dictionary') % pycompat.bytestr(self._value)
+ )
def getmin(self, context, mapping):
return self._getby(context, mapping, min)
@@ -139,12 +145,14 @@
return func(pycompat.iterbytestr(self._value))
def filter(self, context, mapping, select):
- raise error.ParseError(_('%r is not filterable')
- % pycompat.bytestr(self._value))
+ raise error.ParseError(
+ _('%r is not filterable') % pycompat.bytestr(self._value)
+ )
def itermaps(self, context):
- raise error.ParseError(_('%r is not iterable of mappings')
- % pycompat.bytestr(self._value))
+ raise error.ParseError(
+ _('%r is not iterable of mappings') % pycompat.bytestr(self._value)
+ )
def join(self, context, mapping, sep):
return joinitems(pycompat.iterbytestr(self._value), sep)
@@ -158,6 +166,7 @@
def tovalue(self, context, mapping):
return self._value
+
class wrappedvalue(wrapped):
"""Generic wrapper for pure non-list/dict/bytes value"""
@@ -180,8 +189,9 @@
raise error.ParseError(_("%r is not iterable") % self._value)
def itermaps(self, context):
- raise error.ParseError(_('%r is not iterable of mappings')
- % self._value)
+ raise error.ParseError(
+ _('%r is not iterable of mappings') % self._value
+ )
def join(self, context, mapping, sep):
raise error.ParseError(_('%r is not iterable') % self._value)
@@ -202,6 +212,7 @@
def tovalue(self, context, mapping):
return self._value
+
class date(mappable, wrapped):
"""Wrapper for date tuple"""
@@ -241,6 +252,7 @@
def tovalue(self, context, mapping):
return (self._unixtime, self._tzoffset)
+
class hybrid(wrapped):
"""Wrapper for list or dict to support legacy template
@@ -293,8 +305,11 @@
def filter(self, context, mapping, select):
if util.safehasattr(self._values, 'get'):
- values = {k: v for k, v in self._values.iteritems()
- if select(self._wrapvalue(k, v))}
+ values = {
+ k: v
+ for k, v in self._values.iteritems()
+ if select(self._wrapvalue(k, v))
+ }
else:
values = [v for v in self._values if select(self._wrapvalue(v, v))]
return hybrid(None, values, self._makemap, self._joinfmt, self._keytype)
@@ -324,10 +339,12 @@
# TODO: make it non-recursive for trivial lists/dicts
xs = self._values
if util.safehasattr(xs, 'get'):
- return {k: unwrapvalue(context, mapping, v)
- for k, v in xs.iteritems()}
+ return {
+ k: unwrapvalue(context, mapping, v) for k, v in xs.iteritems()
+ }
return [unwrapvalue(context, mapping, x) for x in xs]
+
class hybriditem(mappable, wrapped):
"""Wrapper for non-list/dict object to support map operation
@@ -386,6 +403,7 @@
def tovalue(self, context, mapping):
return _unthunk(context, mapping, self._value)
+
class _mappingsequence(wrapped):
"""Wrapper for sequence of template mappings
@@ -437,10 +455,16 @@
for nm in self.itermaps(context):
# drop internal resources (recursively) which shouldn't be displayed
lm = context.overlaymap(mapping, nm)
- items.append({k: unwrapvalue(context, lm, v)
- for k, v in nm.iteritems() if k not in knownres})
+ items.append(
+ {
+ k: unwrapvalue(context, lm, v)
+ for k, v in nm.iteritems()
+ if k not in knownres
+ }
+ )
return items
+
class mappinggenerator(_mappingsequence):
"""Wrapper for generator of template mappings
@@ -459,6 +483,7 @@
def tobool(self, context, mapping):
return _nonempty(self.itermaps(context))
+
class mappinglist(_mappingsequence):
"""Wrapper for list of template mappings"""
@@ -472,6 +497,7 @@
def tobool(self, context, mapping):
return bool(self._mappings)
+
class mappingdict(mappable, _mappingsequence):
"""Wrapper for a single template mapping
@@ -495,6 +521,7 @@
def tovalue(self, context, mapping):
return super(mappingdict, self).tovalue(context, mapping)[0]
+
class mappingnone(wrappedvalue):
"""Wrapper for None, but supports map operation
@@ -508,6 +535,7 @@
def itermaps(self, context):
return iter([])
+
class mappedgenerator(wrapped):
"""Wrapper for generator of strings which acts as a list
@@ -568,14 +596,20 @@
def tovalue(self, context, mapping):
return [stringify(context, mapping, x) for x in self._gen(context)]
+
def hybriddict(data, key='key', value='value', fmt=None, gen=None):
"""Wrap data to support both dict-like and string-like operations"""
prefmt = pycompat.identity
if fmt is None:
fmt = '%s=%s'
prefmt = pycompat.bytestr
- return hybrid(gen, data, lambda k: {key: k, value: data[k]},
- lambda k: fmt % (prefmt(k), prefmt(data[k])))
+ return hybrid(
+ gen,
+ data,
+ lambda k: {key: k, value: data[k]},
+ lambda k: fmt % (prefmt(k), prefmt(data[k])),
+ )
+
def hybridlist(data, name, fmt=None, gen=None):
"""Wrap data to support both list-like and string-like operations"""
@@ -585,8 +619,18 @@
prefmt = pycompat.bytestr
return hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % prefmt(x))
-def compatdict(context, mapping, name, data, key='key', value='value',
- fmt=None, plural=None, separator=' '):
+
+def compatdict(
+ context,
+ mapping,
+ name,
+ data,
+ key='key',
+ value='value',
+ fmt=None,
+ plural=None,
+ separator=' ',
+):
"""Wrap data like hybriddict(), but also supports old-style list template
This exists for backward compatibility with the old-style template. Use
@@ -596,8 +640,17 @@
f = _showcompatlist(context, mapping, name, c, plural, separator)
return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
-def compatlist(context, mapping, name, data, element=None, fmt=None,
- plural=None, separator=' '):
+
+def compatlist(
+ context,
+ mapping,
+ name,
+ data,
+ element=None,
+ fmt=None,
+ plural=None,
+ separator=' ',
+):
"""Wrap data like hybridlist(), but also supports old-style list template
This exists for backward compatibility with the old-style template. Use
@@ -606,6 +659,7 @@
f = _showcompatlist(context, mapping, name, data, plural, separator)
return hybridlist(data, name=element or name, fmt=fmt, gen=f)
+
def compatfilecopiesdict(context, mapping, name, copies):
"""Wrap list of (dest, source) file names to support old-style list
template and field names
@@ -617,9 +671,13 @@
c = [{'name': k, 'source': v} for k, v in copies]
f = _showcompatlist(context, mapping, name, c, plural='file_copies')
copies = util.sortdict(copies)
- return hybrid(f, copies,
- lambda k: {'name': k, 'path': k, 'source': copies[k]},
- lambda k: '%s (%s)' % (k, copies[k]))
+ return hybrid(
+ f,
+ copies,
+ lambda k: {'name': k, 'path': k, 'source': copies[k]},
+ lambda k: '%s (%s)' % (k, copies[k]),
+ )
+
def compatfileslist(context, mapping, name, files):
"""Wrap list of file names to support old-style list template and field
@@ -629,8 +687,8 @@
keywords.
"""
f = _showcompatlist(context, mapping, name, files)
- return hybrid(f, files, lambda x: {'file': x, 'path': x},
- pycompat.identity)
+ return hybrid(f, files, lambda x: {'file': x, 'path': x}, pycompat.identity)
+
def _showcompatlist(context, mapping, name, values, plural=None, separator=' '):
"""Return a generator that renders old-style list template
@@ -673,6 +731,7 @@
startname = 'start_' + plural
if context.preload(startname):
yield context.process(startname, mapping)
+
def one(v, tag=name):
vmapping = {}
try:
@@ -689,6 +748,7 @@
vmapping[name] = v
vmapping = context.overlaymap(mapping, vmapping)
return context.process(tag, vmapping)
+
lastname = 'last_' + name
if context.preload(lastname):
last = values.pop()
@@ -702,6 +762,7 @@
if context.preload(endname):
yield context.process(endname, mapping)
+
def flatten(context, mapping, thing):
"""Yield a single stream from a possibly nested set of iterators"""
if isinstance(thing, wrapped):
@@ -711,8 +772,10 @@
elif isinstance(thing, str):
# We can only hit this on Python 3, and it's here to guard
# against infinite recursion.
- raise error.ProgrammingError('Mercurial IO including templates is done'
- ' with bytes, not strings, got %r' % thing)
+ raise error.ProgrammingError(
+ 'Mercurial IO including templates is done'
+ ' with bytes, not strings, got %r' % thing
+ )
elif thing is None:
pass
elif not util.safehasattr(thing, '__iter__'):
@@ -731,12 +794,14 @@
for j in flatten(context, mapping, i):
yield j
+
def stringify(context, mapping, thing):
"""Turn values into bytes by converting into text and concatenating them"""
if isinstance(thing, bytes):
return thing # retain localstr to be round-tripped
return b''.join(flatten(context, mapping, thing))
+
def findsymbolicname(arg):
"""Find symbolic name for the given compiled expression; returns None
if nothing found reliably"""
@@ -749,6 +814,7 @@
else:
return None
+
def _nonempty(xiter):
try:
next(xiter)
@@ -756,23 +822,27 @@
except StopIteration:
return False
+
def _unthunk(context, mapping, thing):
"""Evaluate a lazy byte string into value"""
if not isinstance(thing, types.GeneratorType):
return thing
return stringify(context, mapping, thing)
+
def evalrawexp(context, mapping, arg):
"""Evaluate given argument as a bare template object which may require
further processing (such as folding generator of strings)"""
func, data = arg
return func(context, mapping, data)
+
def evalwrapped(context, mapping, arg):
"""Evaluate given argument to wrapped object"""
thing = evalrawexp(context, mapping, arg)
return makewrapped(context, mapping, thing)
+
def makewrapped(context, mapping, thing):
"""Lift object to a wrapped type"""
if isinstance(thing, wrapped):
@@ -782,10 +852,12 @@
return wrappedbytes(thing)
return wrappedvalue(thing)
+
def evalfuncarg(context, mapping, arg):
"""Evaluate given argument as value type"""
return unwrapvalue(context, mapping, evalrawexp(context, mapping, arg))
+
def unwrapvalue(context, mapping, thing):
"""Move the inner value object out of the wrapper"""
if isinstance(thing, wrapped):
@@ -794,6 +866,7 @@
# such as date tuple, but filter does not want generator.
return _unthunk(context, mapping, thing)
+
def evalboolean(context, mapping, arg):
"""Evaluate given argument as boolean, but also takes boolean literals"""
func, data = arg
@@ -806,12 +879,14 @@
thing = func(context, mapping, data)
return makewrapped(context, mapping, thing).tobool(context, mapping)
+
def evaldate(context, mapping, arg, err=None):
"""Evaluate given argument as a date tuple or a date string; returns
a (unixtime, offset) tuple"""
thing = evalrawexp(context, mapping, arg)
return unwrapdate(context, mapping, thing, err)
+
def unwrapdate(context, mapping, thing, err=None):
if isinstance(thing, date):
return thing.tovalue(context, mapping)
@@ -826,10 +901,12 @@
raise
raise error.ParseError(err)
+
def evalinteger(context, mapping, arg, err=None):
thing = evalrawexp(context, mapping, arg)
return unwrapinteger(context, mapping, thing, err)
+
def unwrapinteger(context, mapping, thing, err=None):
thing = unwrapvalue(context, mapping, thing)
try:
@@ -837,9 +914,11 @@
except (TypeError, ValueError):
raise error.ParseError(err or _('not an integer'))
+
def evalstring(context, mapping, arg):
return stringify(context, mapping, evalrawexp(context, mapping, arg))
+
def evalstringliteral(context, mapping, arg):
"""Evaluate given argument as string template, but returns symbol name
if it is unknown"""
@@ -850,6 +929,7 @@
thing = func(context, mapping, data)
return stringify(context, mapping, thing)
+
_unwrapfuncbytype = {
None: unwrapvalue,
bytes: stringify,
@@ -857,6 +937,7 @@
int: unwrapinteger,
}
+
def unwrapastype(context, mapping, thing, typ):
"""Move the inner value object out of the wrapper and coerce its type"""
try:
@@ -865,17 +946,22 @@
raise error.ProgrammingError('invalid type specified: %r' % typ)
return f(context, mapping, thing)
+
def runinteger(context, mapping, data):
return int(data)
+
def runstring(context, mapping, data):
return data
+
def _recursivesymbolblocker(key):
def showrecursion(context, mapping):
raise error.Abort(_("recursive reference '%s' in template") % key)
+
return showrecursion
+
def runsymbol(context, mapping, key, default=''):
v = context.symbol(mapping, key)
if v is None:
@@ -896,10 +982,12 @@
return None
return v
+
def runtemplate(context, mapping, template):
for arg in template:
yield evalrawexp(context, mapping, arg)
+
def runfilter(context, mapping, data):
arg, filt = data
thing = evalrawexp(context, mapping, arg)
@@ -910,13 +998,17 @@
except error.ParseError as e:
raise error.ParseError(bytes(e), hint=_formatfiltererror(arg, filt))
+
def _formatfiltererror(arg, filt):
fn = pycompat.sysbytes(filt.__name__)
sym = findsymbolicname(arg)
if not sym:
return _("incompatible use of template filter '%s'") % fn
- return (_("template filter '%s' is not compatible with keyword '%s'")
- % (fn, sym))
+ return _("template filter '%s' is not compatible with keyword '%s'") % (
+ fn,
+ sym,
+ )
+
def _iteroverlaymaps(context, origmapping, newmappings):
"""Generate combined mappings from the original mapping and an iterable
@@ -926,6 +1018,7 @@
lm['index'] = i
yield lm
+
def _applymap(context, mapping, d, darg, targ):
try:
diter = d.itermaps(context)
@@ -938,11 +1031,13 @@
for lm in _iteroverlaymaps(context, mapping, diter):
yield evalrawexp(context, lm, targ)
+
def runmap(context, mapping, data):
darg, targ = data
d = evalwrapped(context, mapping, darg)
return mappedgenerator(_applymap, args=(mapping, d, darg, targ))
+
def runmember(context, mapping, data):
darg, memb = data
d = evalwrapped(context, mapping, darg)
@@ -958,22 +1053,28 @@
hint = _("keyword '%s' does not support member operation") % sym
raise error.ParseError(bytes(err), hint=hint)
+
def runnegate(context, mapping, data):
- data = evalinteger(context, mapping, data,
- _('negation needs an integer argument'))
+ data = evalinteger(
+ context, mapping, data, _('negation needs an integer argument')
+ )
return -data
+
def runarithmetic(context, mapping, data):
func, left, right = data
- left = evalinteger(context, mapping, left,
- _('arithmetic only defined on integers'))
- right = evalinteger(context, mapping, right,
- _('arithmetic only defined on integers'))
+ left = evalinteger(
+ context, mapping, left, _('arithmetic only defined on integers')
+ )
+ right = evalinteger(
+ context, mapping, right, _('arithmetic only defined on integers')
+ )
try:
return func(left, right)
except ZeroDivisionError:
raise error.Abort(_('division by zero is not defined'))
+
def joinitems(itemiter, sep):
"""Join items with the separator; Returns generator of bytes"""
first = True