# HG changeset patch # User Yuya Nishihara # Date 1519532675 -32400 # Node ID e8d37838f5df94798c035191db172456114029e4 # Parent 717a279c0c21c0934f9937439942c6bef79b721b templatekw: add 'requires' flag to switch to exception-safe interface The current templatekw interface, f(repo, ctx, templ, **args), is horrible because it's quite easy to encounter TypeError, ValueError, etc. It's also bad for Python 3 porting due to the **kwargs issue. This patch introduces a flag to switch to new f(context, mapping) API seen in templater functions. The requirement spec isn't verified (yet) because context.resource() can gracefully raise a ResourceUnavailable exception, but it's planned to be used as a filter in the help, such as "Revision Keywords" (if 'ctx' in requires), "File Keywords" (if 'fctx' in requires), etc. showauthor() is ported to the new API as an example. 20 more follows. diff -r 717a279c0c21 -r e8d37838f5df mercurial/formatter.py --- a/mercurial/formatter.py Sun Feb 25 12:50:30 2018 +0900 +++ b/mercurial/formatter.py Sun Feb 25 13:24:35 2018 +0900 @@ -383,9 +383,7 @@ return ref = self._parts[part] - # TODO: add support for filectx. probably each template keyword or - # function will have to declare dependent resources. e.g. - # @templatekeyword(..., requires=('ctx',)) + # TODO: add support for filectx props = {} # explicitly-defined fields precede templatekw props.update(item) diff -r 717a279c0c21 -r e8d37838f5df mercurial/registrar.py --- a/mercurial/registrar.py Sun Feb 25 12:50:30 2018 +0900 +++ b/mercurial/registrar.py Sun Feb 25 13:24:35 2018 +0900 @@ -283,6 +283,14 @@ templatekeyword = registrar.templatekeyword() + # new API (since Mercurial 4.6) + @templatekeyword('mykeyword', requires={'repo', 'ctx'}) + def mykeywordfunc(context, mapping): + '''Explanation of this template keyword .... + ''' + pass + + # old API @templatekeyword('mykeyword') def mykeywordfunc(repo, ctx, templ, cache, revcache, **args): '''Explanation of this template keyword .... @@ -291,6 +299,11 @@ The first string argument is used also in online help. + Optional argument 'requires' should be a collection of resource names + which the template keyword depends on. This also serves as a flag to + switch to the new API. If 'requires' is unspecified, all template + keywords and resources are expanded to the function arguments. + 'templatekeyword' instance in example above can be used to decorate multiple functions. @@ -301,6 +314,9 @@ Otherwise, explicit 'templatekw.loadkeyword()' is needed. """ + def _extrasetup(self, name, func, requires=None): + func._requires = requires + class templatefilter(_templateregistrarbase): """Decorator to register template filer diff -r 717a279c0c21 -r e8d37838f5df mercurial/templatekw.py --- a/mercurial/templatekw.py Sun Feb 25 12:50:30 2018 +0900 +++ b/mercurial/templatekw.py Sun Feb 25 13:24:35 2018 +0900 @@ -341,21 +341,14 @@ # filecopy is preserved for compatibility reasons defaulttempl['filecopy'] = defaulttempl['file_copy'] -# keywords are callables like: -# fn(repo, ctx, templ, cache, revcache, **args) -# with: -# repo - current repository instance -# ctx - the changectx being displayed -# templ - the templater instance -# cache - a cache dictionary for the whole templater run -# revcache - a cache dictionary for the current revision +# keywords are callables (see registrar.templatekeyword for details) keywords = {} - templatekeyword = registrar.templatekeyword(keywords) -@templatekeyword('author') -def showauthor(repo, ctx, templ, **args): +@templatekeyword('author', requires={'ctx'}) +def showauthor(context, mapping): """String. The unmodified author of the changeset.""" + ctx = context.resource(mapping, 'ctx') return ctx.user() @templatekeyword('bisect') diff -r 717a279c0c21 -r e8d37838f5df mercurial/templater.py --- a/mercurial/templater.py Sun Feb 25 12:50:30 2018 +0900 +++ b/mercurial/templater.py Sun Feb 25 13:24:35 2018 +0900 @@ -436,12 +436,18 @@ v = context.process(key, safemapping) except TemplateNotFound: v = default - if callable(v): - # TODO: templatekw functions will be updated to take (context, mapping) - # pair instead of **props + if callable(v) and getattr(v, '_requires', None) is None: + # old templatekw: expand all keywords and resources props = context._resources.copy() props.update(mapping) return v(**pycompat.strkwargs(props)) + if callable(v): + # new templatekw + try: + return v(context, mapping) + except ResourceUnavailable: + # unsupported keyword is mapped to empty just like unknown keyword + return None return v def buildtemplate(exp, context): diff -r 717a279c0c21 -r e8d37838f5df tests/test-command-template.t --- a/tests/test-command-template.t Sun Feb 25 12:50:30 2018 +0900 +++ b/tests/test-command-template.t Sun Feb 25 13:24:35 2018 +0900 @@ -214,6 +214,8 @@ abort: template resource not available: ctx [255] + $ hg config -T '{author}' + Quoting for ui.logtemplate $ hg tip --config "ui.logtemplate={rev}\n" diff -r 717a279c0c21 -r e8d37838f5df tests/test-template-engine.t --- a/tests/test-template-engine.t Sun Feb 25 12:50:30 2018 +0900 +++ b/tests/test-template-engine.t Sun Feb 25 13:24:35 2018 +0900 @@ -9,6 +9,15 @@ > self._defaults = defaults > self._resources = resources > + > def symbol(self, mapping, key): + > return mapping[key] + > + > def resource(self, mapping, key): + > v = self._resources[key] + > if v is None: + > v = mapping[key] + > return v + > > def process(self, t, map): > tmpl = self.loader(t) > props = self._defaults.copy() @@ -16,10 +25,12 @@ > for k, v in props.items(): > if k in ('templ', 'ctx', 'repo', 'revcache', 'cache', 'troubles'): > continue - > if hasattr(v, '__call__'): + > if callable(v) and getattr(v, '_requires', None) is None: > props = self._resources.copy() > props.update(map) > v = v(**props) + > elif callable(v): + > v = v(self, props) > v = templater.stringify(v) > tmpl = tmpl.replace('{{%s}}' % k, v) > yield tmpl