# HG changeset patch # User Yuya Nishihara # Date 1521291991 -32400 # Node ID ebf139cbd4a182e45245212d3830277ee91e4620 # Parent 9cd88dd3bf64a1b3a6b7b72a8fe93e0e4e9118e0 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) diff -r 9cd88dd3bf64 -r ebf139cbd4a1 mercurial/templatefuncs.py --- 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): diff -r 9cd88dd3bf64 -r ebf139cbd4a1 mercurial/templateutil.py --- 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