diff mercurial/templater.py @ 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 a33be093ec62
line wrap: on
line diff
--- 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)