Patchwork [2,of,4] templater: keep default resources per template engine (API)

login
register
mail settings
Submitter Yuya Nishihara
Date Dec. 21, 2017, 3:08 p.m.
Message ID <420618063c2cf0ab50e0.1513868925@mimosa>
Download mbox | patch
Permalink /patch/26380/
State Accepted
Headers show

Comments

Yuya Nishihara - Dec. 21, 2017, 3:08 p.m.
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1513859346 -32400
#      Thu Dec 21 21:29:06 2017 +0900
# Node ID 420618063c2cf0ab50e06e075b2feb49eef77767
# Parent  692f9bbcae0853b547ab0c2f2d9d0b93ede85ab0
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.

Patch

diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -1886,7 +1886,6 @@  class changeset_templater(changeset_prin
         '''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 @@  def _graphnodeformatter(ui, displayer):
     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
diff --git a/mercurial/formatter.py b/mercurial/formatter.py
--- a/mercurial/formatter.py
+++ b/mercurial/formatter.py
@@ -392,7 +392,6 @@  class templateformatter(baseformatter):
         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 @@  def templatepartsmap(spec, t, partnames)
                 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:
diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -393,7 +393,11 @@  def runsymbol(context, mapping, key, def
         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 @@  def files(context, mapping, args):
     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 @@  def latesttag(context, mapping, args):
     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 @@  def revset(context, mapping, args):
             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 @@  class engine(object):
     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 @@  class engine(object):
     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 TemplateNotFound(error.Abort):
 
 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 @@  class templater(object):
         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 @@  class templater(object):
             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)
diff --git a/tests/test-template-engine.t b/tests/test-template-engine.t
--- a/tests/test-template-engine.t
+++ b/tests/test-template-engine.t
@@ -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