Mercurial > hg-stable
changeset 35472:32c278eb876f
templater: keep default resources per template engine (API)
This allows us to register a repo object as a resource in hgweb template,
without loosing '{repo}' symbol:
symbol('repo') -> mapping['repo'] (n/a) -> defaults['repo']
resource('repo') -> mapping['repo'] (n/a) -> resources['repo']
I'm thinking of redesigning the templatekw API to take (context, mapping)
in place of **(context._resources + mapping), but that will be a big change
and not implemented yet.
props['templ'] is ported to the resources dict as an example.
.. api::
mapping does not contain all template resources. use context.resource()
in template functions.
author | Yuya Nishihara <yuya@tcha.org> |
---|---|
date | Thu, 21 Dec 2017 21:29:06 +0900 |
parents | d6cfa722b044 |
children | f1c54d003327 |
files | mercurial/cmdutil.py mercurial/formatter.py mercurial/templater.py tests/test-template-engine.t |
diffstat | 4 files changed, 51 insertions(+), 22 deletions(-) [+] |
line wrap: on
line diff
--- a/mercurial/cmdutil.py Thu Dec 21 21:03:25 2017 +0900 +++ b/mercurial/cmdutil.py Thu Dec 21 21:29:06 2017 +0900 @@ -1886,7 +1886,6 @@ '''show a single changeset or file revision''' props = props.copy() props.update(templatekw.keywords) - props['templ'] = self.t props['ctx'] = ctx props['repo'] = self.repo props['ui'] = self.repo.ui @@ -2663,7 +2662,6 @@ if isinstance(displayer, changeset_templater): cache = displayer.cache # reuse cache of slow templates props = templatekw.keywords.copy() - props['templ'] = templ props['cache'] = cache def formatnode(repo, ctx): props['ctx'] = ctx
--- a/mercurial/formatter.py Thu Dec 21 21:03:25 2017 +0900 +++ b/mercurial/formatter.py Thu Dec 21 21:29:06 2017 +0900 @@ -392,7 +392,6 @@ props.update(item) if 'ctx' in item: # but template resources must be always available - props['templ'] = self._t props['repo'] = props['ctx'].repo() props['revcache'] = {} props = pycompat.strkwargs(props) @@ -468,18 +467,19 @@ partsmap[part] = ref return partsmap -def loadtemplater(ui, spec, cache=None): +def loadtemplater(ui, spec, resources=None, cache=None): """Create a templater from either a literal template or loading from a map file""" assert not (spec.tmpl and spec.mapfile) if spec.mapfile: - return templater.templater.frommapfile(spec.mapfile, cache=cache) - return maketemplater(ui, spec.tmpl, cache=cache) + frommapfile = templater.templater.frommapfile + return frommapfile(spec.mapfile, resources=resources, cache=cache) + return maketemplater(ui, spec.tmpl, resources=resources, cache=cache) -def maketemplater(ui, tmpl, cache=None): +def maketemplater(ui, tmpl, resources=None, cache=None): """Create a templater from a string template 'tmpl'""" aliases = ui.configitems('templatealias') - t = templater.templater(cache=cache, aliases=aliases) + t = templater.templater(resources=resources, cache=cache, aliases=aliases) t.cache.update((k, templater.unquotestring(v)) for k, v in ui.configitems('templates')) if tmpl:
--- a/mercurial/templater.py Thu Dec 21 21:03:25 2017 +0900 +++ b/mercurial/templater.py Thu Dec 21 21:29:06 2017 +0900 @@ -393,7 +393,11 @@ except TemplateNotFound: v = default if callable(v): - return v(**pycompat.strkwargs(mapping)) + # TODO: templatekw functions will be updated to take (context, mapping) + # pair instead of **props + props = context._resources.copy() + props.update(mapping) + return v(**props) return v def buildtemplate(exp, context): @@ -657,7 +661,10 @@ ctx = context.resource(mapping, 'ctx') m = ctx.match([raw]) files = list(ctx.matches(m)) - return templatekw.showlist("file", files, mapping) + # TODO: pass (context, mapping) pair to keyword function + props = context._resources.copy() + props.update(mapping) + return templatekw.showlist("file", files, props) @templatefunc('fill(text[, width[, initialident[, hangindent]]])') def fill(context, mapping, args): @@ -878,7 +885,10 @@ if len(args) == 1: pattern = evalstring(context, mapping, args[0]) - return templatekw.showlatesttags(pattern, **pycompat.strkwargs(mapping)) + # TODO: pass (context, mapping) pair to keyword function + props = context._resources.copy() + props.update(mapping) + return templatekw.showlatesttags(pattern, **pycompat.strkwargs(props)) @templatefunc('localdate(date[, tz])') def localdate(context, mapping, args): @@ -1062,8 +1072,11 @@ revs = list(revs) revsetcache[raw] = revs + # TODO: pass (context, mapping) pair to keyword function + props = context._resources.copy() + props.update(mapping) return templatekw.showrevslist("revision", revs, - **pycompat.strkwargs(mapping)) + **pycompat.strkwargs(props)) @templatefunc('rstdoc(text, style)') def rstdoc(context, mapping, args): @@ -1290,14 +1303,18 @@ filter uses function to transform value. syntax is {key|filter1|filter2|...}.''' - def __init__(self, loader, filters=None, defaults=None, aliases=()): + def __init__(self, loader, filters=None, defaults=None, resources=None, + aliases=()): self._loader = loader if filters is None: filters = {} self._filters = filters if defaults is None: defaults = {} + if resources is None: + resources = {} self._defaults = defaults + self._resources = resources self._aliasmap = _aliasrules.buildmap(aliases) self._cache = {} # key: (func, data) @@ -1311,7 +1328,12 @@ def resource(self, mapping, key): """Return internal data (e.g. cache) used for keyword/function evaluation""" - return mapping[key] + v = mapping.get(key) + if v is None: + v = self._resources.get(key) + if v is None: + raise KeyError + return v def _load(self, t): '''load, parse, and cache a template''' @@ -1406,17 +1428,21 @@ class templater(object): - def __init__(self, filters=None, defaults=None, cache=None, aliases=(), - minchunk=1024, maxchunk=65536): + def __init__(self, filters=None, defaults=None, resources=None, + cache=None, aliases=(), minchunk=1024, maxchunk=65536): '''set up template engine. filters is dict of functions. each transforms a value into another. defaults is dict of default map definitions. + resources is dict of internal data (e.g. cache), which are inaccessible + from user template. aliases is list of alias (name, replacement) pairs. ''' if filters is None: filters = {} if defaults is None: defaults = {} + if resources is None: + resources = {} if cache is None: cache = {} self.cache = cache.copy() @@ -1424,15 +1450,17 @@ self.filters = templatefilters.filters.copy() self.filters.update(filters) self.defaults = defaults + self._resources = {'templ': self} + self._resources.update(resources) self._aliases = aliases self.minchunk, self.maxchunk = minchunk, maxchunk self.ecache = {} @classmethod - def frommapfile(cls, mapfile, filters=None, defaults=None, cache=None, - minchunk=1024, maxchunk=65536): + def frommapfile(cls, mapfile, filters=None, defaults=None, resources=None, + cache=None, minchunk=1024, maxchunk=65536): """Create templater from the specified map file""" - t = cls(filters, defaults, cache, [], minchunk, maxchunk) + t = cls(filters, defaults, resources, cache, [], minchunk, maxchunk) cache, tmap, aliases = _readmapfile(mapfile) t.cache.update(cache) t.map = tmap @@ -1469,7 +1497,7 @@ except KeyError: raise error.Abort(_('invalid template engine: %s') % ttype) self.ecache[ttype] = ecls(self.load, self.filters, self.defaults, - self._aliases) + self._resources, self._aliases) proc = self.ecache[ttype] stream = proc.process(t, mapping)
--- a/tests/test-template-engine.t Thu Dec 21 21:03:25 2017 +0900 +++ b/tests/test-template-engine.t Thu Dec 21 21:29:06 2017 +0900 @@ -4,8 +4,9 @@ > from mercurial import templater > > class mytemplater(object): - > def __init__(self, loader, filters, defaults, aliases): + > def __init__(self, loader, filters, defaults, resources, aliases): > self.loader = loader + > self._resources = resources > > def process(self, t, map): > tmpl = self.loader(t) @@ -13,7 +14,9 @@ > if k in ('templ', 'ctx', 'repo', 'revcache', 'cache', 'troubles'): > continue > if hasattr(v, '__call__'): - > v = v(**map) + > props = self._resources.copy() + > props.update(map) + > v = v(**props) > v = templater.stringify(v) > tmpl = tmpl.replace('{{%s}}' % k, v) > yield tmpl