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)
--- a/mercurial/templatefuncs.py Tue Mar 20 23:16:28 2018 +0900
+++ b/mercurial/templatefuncs.py Sat Mar 17 22:06:31 2018 +0900
@@ -316,16 +316,16 @@
# 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):
--- a/mercurial/templateutil.py Tue Mar 20 23:16:28 2018 +0900
+++ b/mercurial/templateutil.py Sat Mar 17 22:06:31 2018 +0900
@@ -42,6 +42,15 @@
"""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 @@
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 @@
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