# HG changeset patch # User Yuya Nishihara # Date 1521294422 -32400 # Node ID 0b64416224d9fa8ec58eed286c24f3beef1dd566 # Parent 3235afdfcf1c456725c1e5e15bf64873d7249003 templater: add class representing a nested mappings The mappinggenerator class is necessary to fix hgweb bugs without BC. The mappinglist is for nested formatter items. They are similar, so factored out the base class. The mappinglist could be implemented by using the mappinggenerator, but we'll probably need a direct access to the raw list, so they are implemented as separate classes. Note that tovalue() isn't conforming to the spec yet in that it may return a list of dicts containing unprintable resources. This problem will be fixed later. Tests will be added by subsequent patches. diff -r 3235afdfcf1c -r 0b64416224d9 mercurial/templater.py --- a/mercurial/templater.py Sat Mar 17 22:56:49 2018 +0900 +++ b/mercurial/templater.py Sat Mar 17 22:47:02 2018 +0900 @@ -44,6 +44,10 @@ mappable represents a scalar printable value, also supports % operator. + +mappinggenerator, mappinglist + represents mappings (i.e. a list of dicts), which may have default + output format. """ from __future__ import absolute_import, print_function diff -r 3235afdfcf1c -r 0b64416224d9 mercurial/templateutil.py --- a/mercurial/templateutil.py Sat Mar 17 22:56:49 2018 +0900 +++ b/mercurial/templateutil.py Sat Mar 17 22:47:02 2018 +0900 @@ -170,6 +170,63 @@ def tovalue(self, context, mapping): return _unthunk(context, mapping, self._value) +class _mappingsequence(wrapped): + """Wrapper for sequence of template mappings + + This represents an inner template structure (i.e. a list of dicts), + which can also be rendered by the specified named/literal template. + + Template mappings may be nested. + """ + + def __init__(self, name=None, tmpl=None, sep=''): + if name is not None and tmpl is not None: + raise error.ProgrammingError('name and tmpl are mutually exclusive') + self._name = name + self._tmpl = tmpl + self._defaultsep = sep + + def join(self, context, mapping, sep): + mapsiter = _iteroverlaymaps(context, mapping, self.itermaps(context)) + if self._name: + itemiter = (context.process(self._name, m) for m in mapsiter) + elif self._tmpl: + itemiter = (context.expand(self._tmpl, m) for m in mapsiter) + else: + raise error.ParseError(_('not displayable without template')) + return joinitems(itemiter, sep) + + def show(self, context, mapping): + return self.join(context, mapping, self._defaultsep) + + def tovalue(self, context, mapping): + return list(self.itermaps(context)) + +class mappinggenerator(_mappingsequence): + """Wrapper for generator of template mappings + + The function ``make(context, *args)`` should return a generator of + mapping dicts. + """ + + def __init__(self, make, args=(), name=None, tmpl=None, sep=''): + super(mappinggenerator, self).__init__(name, tmpl, sep) + self._make = make + self._args = args + + def itermaps(self, context): + return self._make(context, *self._args) + +class mappinglist(_mappingsequence): + """Wrapper for list of template mappings""" + + def __init__(self, mappings, name=None, tmpl=None, sep=''): + super(mappinglist, self).__init__(name, tmpl, sep) + self._mappings = mappings + + def itermaps(self, context): + return iter(self._mappings) + 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 @@ -510,6 +567,14 @@ 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 + of partial mappings to override the original""" + for i, nm in enumerate(newmappings): + lm = context.overlaymap(origmapping, nm) + lm['index'] = i + yield lm + def runmap(context, mapping, data): darg, targ = data d = evalrawexp(context, mapping, darg)