Patchwork [2,of,8] templater: add class representing a nested mappings

login
register
mail settings
Submitter Yuya Nishihara
Date April 5, 2018, 2:37 p.m.
Message ID <41f4b8e798c8c46261aa.1522939040@mimosa>
Download mbox | patch
Permalink /patch/30377/
State Accepted
Headers show

Comments

Yuya Nishihara - April 5, 2018, 2:37 p.m.
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1521294422 -32400
#      Sat Mar 17 22:47:02 2018 +0900
# Node ID 41f4b8e798c8c46261aa4b6060f5aae4954c1b24
# Parent  befd68a9bd8bb3ee4e450d20e6e7d279464f3f36
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.

Patch

diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -44,6 +44,10 @@  hybrid
 
 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 --git a/mercurial/templateutil.py b/mercurial/templateutil.py
--- a/mercurial/templateutil.py
+++ b/mercurial/templateutil.py
@@ -170,6 +170,63 @@  class mappable(wrapped):
     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 @@  def _formatfiltererror(arg, filt):
     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)