# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 2016-07-10 12:03:06
# Node ID c3a9cd78b1517a5d85d2c37a744590c2ea81e435
# Parent  12f04946053c1d5ee0c4fda50b969d06de3297fe

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 fm.data() call should be necessary.

diff --git a/mercurial/formatter.py b/mercurial/formatter.py
--- a/mercurial/formatter.py
+++ b/mercurial/formatter.py
@@ -18,6 +18,7 @@ from .node import (
 from . import (
     encoding,
     error,
+    templatekw,
     templater,
     util,
 )
@@ -45,6 +46,10 @@ class baseformatter(object):
         if self._item is not None:
             self._showitem()
         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'''
         self._item.update(data)
@@ -78,6 +83,10 @@ class plainformatter(baseformatter):
         return False
     def startitem(self):
         pass
+    @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):
         pass
     def write(self, fields, deftext, *fielddata, **opts):
@@ -112,7 +121,7 @@ class pickleformatter(baseformatter):
         self._ui.write(pickle.dumps(self._data))
 
 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)
         self._ui.write(templater.stringify(g))
+    @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?