templater: add class representing a nested mappings
authorYuya Nishihara <yuya@tcha.org>
Sat, 17 Mar 2018 22:47:02 +0900
changeset 37399 0b64416224d9
parent 37398 3235afdfcf1c
child 37400 47aea60d114d
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.
mercurial/templater.py
mercurial/templateutil.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
--- 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)