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.
--- 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