Patchwork [5,of,7] templater: abstract away from joinfmt

login
register
mail settings
Submitter Yuya Nishihara
Date April 4, 2018, 2:48 p.m.
Message ID <4bfb025606df5d48f905.1522853336@mimosa>
Download mbox | patch
Permalink /patch/30263/
State Accepted
Headers show

Comments

Yuya Nishihara - April 4, 2018, 2:48 p.m.
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1521291991 -32400
#      Sat Mar 17 22:06:31 2018 +0900
# Node ID 4bfb025606df5d48f9052f47a45cdfe6a2d61439
# Parent  2c73a43fd4dfbc6cbf1971cb192d48e74506856d
templater: abstract away from joinfmt

Future patches will add a wrapper for a list of template mappings, which
will implement a custom join() something like {join(mappings % template)}.

The original join() function is broken down as follows:

  if hasattr(joinset, 'joinfmt'):
      # hybrid.join() where values must be a list or a dict
      joinitems((joinfmt(x) for x in values), sep)
  elif isinstance(joinset, templateutil.wrapped):
      # mappable.join()
      show()
  else:
      # a plain list, a generator, or a byte string; joinfmt was identity()
      joinset = templateutil.unwrapvalue(context, joinset)
      joinitems(pycompat.maybebytestr(joinset), joiner)

Patch

diff --git a/mercurial/templatefuncs.py b/mercurial/templatefuncs.py
--- a/mercurial/templatefuncs.py
+++ b/mercurial/templatefuncs.py
@@ -316,16 +316,16 @@  def join(context, mapping, args):
         # i18n: "join" is a keyword
         raise error.ParseError(_("join expects one or two arguments"))
 
-    # TODO: perhaps this should be evalfuncarg(), but it can't because hgweb
-    # abuses generator as a keyword that returns a list of dicts.
     joinset = evalrawexp(context, mapping, args[0])
-    joinset = templateutil.unwrapvalue(context, mapping, joinset)
-    joinfmt = getattr(joinset, 'joinfmt', pycompat.identity)
     joiner = " "
     if len(args) > 1:
         joiner = evalstring(context, mapping, args[1])
-    itemiter = (joinfmt(x) for x in pycompat.maybebytestr(joinset))
-    return templateutil.joinitems(itemiter, joiner)
+    if isinstance(joinset, templateutil.wrapped):
+        return joinset.join(context, mapping, joiner)
+    # TODO: perhaps a generator should be stringify()-ed here, but we can't
+    # because hgweb abuses it as a keyword that returns a list of dicts.
+    joinset = templateutil.unwrapvalue(context, mapping, joinset)
+    return templateutil.joinitems(pycompat.maybebytestr(joinset), joiner)
 
 @templatefunc('label(label, expr)')
 def label(context, mapping, args):
diff --git a/mercurial/templateutil.py b/mercurial/templateutil.py
--- a/mercurial/templateutil.py
+++ b/mercurial/templateutil.py
@@ -42,6 +42,15 @@  class wrapped(object):
         """Yield each template mapping"""
 
     @abc.abstractmethod
+    def join(self, context, mapping, sep):
+        """Join items with the separator; Returns a bytes or (possibly nested)
+        generator of bytes
+
+        A pre-configured template may be rendered per item if this container
+        holds unprintable items.
+        """
+
+    @abc.abstractmethod
     def show(self, context, mapping):
         """Return a bytes or (possibly nested) generator of bytes representing
         the underlying object
@@ -86,11 +95,15 @@  class hybrid(wrapped):
         for x in self._values:
             yield makemap(x)
 
+    def join(self, context, mapping, sep):
+        # TODO: switch gen to (context, mapping) API?
+        return joinitems((self.joinfmt(x) for x in self._values), sep)
+
     def show(self, context, mapping):
         # TODO: switch gen to (context, mapping) API?
         gen = self._gen
         if gen is None:
-            return joinitems((self.joinfmt(x) for x in self._values), ' ')
+            return self.join(context, mapping, ' ')
         if callable(gen):
             return gen()
         return gen
@@ -137,6 +150,14 @@  class mappable(wrapped):
     def itermaps(self, context):
         yield self.tomap()
 
+    def join(self, context, mapping, sep):
+        # TODO: just copies the old behavior where a value was a generator
+        # yielding one item, but reconsider about it. join() over a string
+        # has no consistent result because a string may be a bytes, or a
+        # generator yielding an item, or a generator yielding multiple items.
+        # Preserving all of the current behaviors wouldn't make any sense.
+        return self.show(context, mapping)
+
     def show(self, context, mapping):
         # TODO: switch gen to (context, mapping) API?
         gen = self._gen