changeset 37073:44757e6dad93

templater: introduce resourcemapper class A couple more functions will be added later to work around nested mapping bugs such as the issue 5612.
author Yuya Nishihara <yuya@tcha.org>
date Thu, 15 Mar 2018 20:43:39 +0900
parents d64ae4fef471
children 2891079fb0c0
files mercurial/formatter.py mercurial/templater.py mercurial/templateutil.py
diffstat 3 files changed, 59 insertions(+), 31 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/formatter.py	Thu Mar 15 20:27:38 2018 +0900
+++ b/mercurial/formatter.py	Thu Mar 15 20:43:39 2018 +0900
@@ -494,22 +494,32 @@
         t.cache[''] = tmpl
     return t
 
-def templateresources(ui, repo=None):
-    """Create a dict of template resources designed for the default templatekw
-    and function"""
-    resmap = {
-        'cache': {},  # for templatekw/funcs to store reusable data
-        'repo': repo,
-        'ui': ui,
-    }
+class templateresources(templater.resourcemapper):
+    """Resource mapper designed for the default templatekw and function"""
+
+    def __init__(self, ui, repo=None):
+        self._resmap = {
+            'cache': {},  # for templatekw/funcs to store reusable data
+            'repo': repo,
+            'ui': ui,
+        }
 
-    def getsome(context, mapping, key):
+    def knownkeys(self):
+        return self._knownkeys
+
+    def lookup(self, context, mapping, key):
+        get = self._gettermap.get(key)
+        if not get:
+            return None
+        return get(self, context, mapping, key)
+
+    def _getsome(self, context, mapping, key):
         v = mapping.get(key)
         if v is not None:
             return v
-        return resmap.get(key)
+        return self._resmap.get(key)
 
-    def getctx(context, mapping, key):
+    def _getctx(self, context, mapping, key):
         ctx = mapping.get('ctx')
         if ctx is not None:
             return ctx
@@ -517,20 +527,21 @@
         if fctx is not None:
             return fctx.changectx()
 
-    def getrepo(context, mapping, key):
-        ctx = getctx(context, mapping, 'ctx')
+    def _getrepo(self, context, mapping, key):
+        ctx = self._getctx(context, mapping, 'ctx')
         if ctx is not None:
             return ctx.repo()
-        return getsome(context, mapping, key)
+        return self._getsome(context, mapping, key)
 
-    return {
-        'cache': getsome,
-        'ctx': getctx,
-        'fctx': getsome,
-        'repo': getrepo,
-        'revcache': getsome,  # per-ctx cache; set later
-        'ui': getsome,
+    _gettermap = {
+        'cache': _getsome,
+        'ctx': _getctx,
+        'fctx': _getsome,
+        'repo': _getrepo,
+        'revcache': _getsome,  # per-ctx cache; set later
+        'ui': _getsome,
     }
+    _knownkeys = set(_gettermap.keys())
 
 def formatter(ui, out, topic, opts):
     template = opts.get("template", "")
--- a/mercurial/templater.py	Thu Mar 15 20:27:38 2018 +0900
+++ b/mercurial/templater.py	Thu Mar 15 20:43:39 2018 +0900
@@ -48,6 +48,7 @@
 
 from __future__ import absolute_import, print_function
 
+import abc
 import os
 
 from .i18n import _
@@ -556,6 +557,26 @@
         return s
     return s[1:-1]
 
+class resourcemapper(object):
+    """Mapper of internal template resources"""
+
+    __metaclass__ = abc.ABCMeta
+
+    @abc.abstractmethod
+    def knownkeys(self):
+        """Return a set of supported resource keys"""
+
+    @abc.abstractmethod
+    def lookup(self, context, mapping, key):
+        """Return a resource for the key if available; otherwise None"""
+
+class nullresourcemapper(resourcemapper):
+    def knownkeys(self):
+        return set()
+
+    def lookup(self, context, mapping, key):
+        return None
+
 class engine(object):
     '''template expansion engine.
 
@@ -586,7 +607,7 @@
         if defaults is None:
             defaults = {}
         if resources is None:
-            resources = {}
+            resources = nullresourcemapper()
         self._defaults = defaults
         self._resources = resources
         self._aliasmap = _aliasrules.buildmap(aliases)
@@ -595,7 +616,7 @@
     def symbol(self, mapping, key):
         """Resolve symbol to value or function; None if nothing found"""
         v = None
-        if key not in self._resources:
+        if key not in self._resources.knownkeys():
             v = mapping.get(key)
         if v is None:
             v = self._defaults.get(key)
@@ -604,9 +625,7 @@
     def resource(self, mapping, key):
         """Return internal data (e.g. cache) used for keyword/function
         evaluation"""
-        v = None
-        if key in self._resources:
-            v = self._resources[key](self, mapping, key)
+        v = self._resources.lookup(self, mapping, key)
         if v is None:
             raise templateutil.ResourceUnavailable(
                 _('template resource not available: %s') % key)
@@ -717,7 +736,7 @@
         - ``filters``: a dict of functions to transform a value into another.
         - ``defaults``: a dict of symbol values/functions; may be overridden
           by a ``mapping`` dict.
-        - ``resources``: a dict of functions returning internal data
+        - ``resources``: a resourcemapper object to look up internal data
           (e.g. cache), inaccessible from user template.
         - ``cache``: a dict of preloaded template fragments.
         - ``aliases``: a list of alias (name, replacement) pairs.
@@ -729,8 +748,6 @@
             filters = {}
         if defaults is None:
             defaults = {}
-        if resources is None:
-            resources = {}
         if cache is None:
             cache = {}
         self.cache = cache.copy()
--- a/mercurial/templateutil.py	Thu Mar 15 20:27:38 2018 +0900
+++ b/mercurial/templateutil.py	Thu Mar 15 20:43:39 2018 +0900
@@ -349,8 +349,8 @@
     if callable(v) and getattr(v, '_requires', None) is None:
         # old templatekw: expand all keywords and resources
         # (TODO: deprecate this after porting web template keywords to new API)
-        props = {k: f(context, mapping, k)
-                 for k, f in context._resources.items()}
+        props = {k: context._resources.lookup(context, mapping, k)
+                 for k in context._resources.knownkeys()}
         # pass context to _showcompatlist() through templatekw._showlist()
         props['templ'] = context
         props.update(mapping)