Patchwork [2,of,5] formatter: add function to convert list to appropriate format (issue5217)

mail settings
Submitter Yuya Nishihara
Date Aug. 3, 2016, 1:04 p.m.
Message ID <1b5145c36bacafb90f2e.1470229465@mimosa>
Download mbox | patch
Permalink /patch/16052/
State Accepted
Headers show


Yuya Nishihara - Aug. 3, 2016, 1:04 p.m.
# HG changeset patch
# User Yuya Nishihara <>
# Date 1468152186 -32400
#      Sun Jul 10 21:03:06 2016 +0900
# Node ID 1b5145c36bacafb90f2efb686bc5993f0bdd1d9b
# Parent  fc0ddb2c7e8fe36be80e5634702956eb7aebf0f8
formatter: add function to convert list to appropriate format (issue5217)

Before, it wasn't possible for formatter to handle array structure other
than date tuple. We've discussed that at the last sprint, which ended we
would probably want to allow only templatable data structure, i.e. a list
of dicts:

  data(tags=[{'tag': a}, {'tag': b}, ...])

Unfortunately, it turned out not working well with template functions:

  "{ifcontains(a, tags, ...)}"
    "a in tags", where tags should be a plain list/set of tags

So the formatter must at least know if the type [{}] was constructed from
a plain list or was actually a list of dicts.

This patch introduces new explicit interface to convert an array structure
to an appropriate data type for the current formatter, which can be used
as follows:

  fm.write('tags', _('tags: %s\n'), fm.formatlist(tags, name='tag'))

No separate call should be necessary.


diff --git a/mercurial/ b/mercurial/
--- a/mercurial/
+++ b/mercurial/
@@ -18,6 +18,7 @@  from .node import (
 from . import (
+    templatekw,
@@ -45,6 +46,10 @@  class baseformatter(object):
         if self._item is not None:
         self._item = {}
+    @staticmethod
+    def formatlist(data, name, fmt='%s', sep=' '):
+        '''convert iterable to appropriate list format'''
+        return list(data)
     def data(self, **data):
         '''insert data into item that's not shown in default output'''
@@ -78,6 +83,10 @@  class plainformatter(baseformatter):
         return False
     def startitem(self):
+    @staticmethod
+    def formatlist(data, name, fmt='%s', sep=' '):
+        '''stringify iterable separated by sep'''
+        return sep.join(fmt % e for e in data)
     def data(self, **data):
     def write(self, fields, deftext, *fielddata, **opts):
@@ -112,7 +121,7 @@  class pickleformatter(baseformatter):
 def _jsonifyobj(v):
-    if isinstance(v, tuple):
+    if isinstance(v, (list, tuple)):
         return '[' + ', '.join(_jsonifyobj(e) for e in v) + ']'
     elif v is None:
         return 'null'
@@ -157,6 +166,16 @@  class templateformatter(baseformatter):
     def _showitem(self):
         g = self._t(self._topic, ui=self._ui, **self._item)
+    @staticmethod
+    def formatlist(data, name, fmt='%s', sep=' '):
+        '''build object that can be evaluated as either plain string or list'''
+        # name is mandatory argument for now, but it could be optional if
+        # we have default template keyword, e.g. {item}
+        data = list(data)
+        def f():
+            yield plainformatter.formatlist(data, name, fmt, sep)
+        return templatekw._hybrid(f(), data, lambda x: {name: x},
+                                  lambda d: fmt % d[name])
 def lookuptemplate(ui, topic, tmpl):
     # looks like a literal template?