diff mercurial/templater.py @ 28545:1d461ee26e1b

templater: lift parsed and compiled templates to generic data types Before this patch, parsed and compiled templates were kept as lists. That was inconvenient for applying transformation such as alias expansion. This patch changes the types of the outermost objects as follows: stage old new -------- -------------- ------------------------------ parsed [(op, ..)] ('template', [(op, ..)]) compiled [(func, data)] (runtemplate, [(func, data)]) New templater.parse() function has the same signature as revset.parse() and fileset.parse().
author Yuya Nishihara <yuya@tcha.org>
date Sat, 13 Feb 2016 23:54:24 +0900
parents dbba18ba26d4
children 1987ed32efca
line wrap: on
line diff
--- a/mercurial/templater.py	Tue Mar 15 15:50:57 2016 -0700
+++ b/mercurial/templater.py	Sat Feb 13 23:54:24 2016 +0900
@@ -177,9 +177,15 @@
         raise error.ParseError(_("unterminated string"), start)
     return parsed, pos
 
-def compiletemplate(tmpl, context):
+def parse(tmpl):
+    """Parse template string into tree"""
     parsed, pos = _parsetemplate(tmpl, 0, len(tmpl))
-    return [compileexp(e, context, methods) for e in parsed]
+    assert pos == len(tmpl), 'unquoted template should be consumed'
+    return ('template', parsed)
+
+def compiletemplate(tmpl, context):
+    """Parse and compile template string to (func, data) pair"""
+    return compileexp(parse(tmpl), context, methods)
 
 def compileexp(exp, context, curmethods):
     t = exp[0]
@@ -202,8 +208,10 @@
     return [x]
 
 def gettemplate(exp, context):
+    """Compile given template tree or load named template from map file;
+    returns (func, data) pair"""
     if exp[0] == 'template':
-        return [compileexp(e, context, methods) for e in exp[1]]
+        return compileexp(exp, context, methods)
     if exp[0] == 'symbol':
         # unlike runsymbol(), here 'symbol' is always taken as template name
         # even if it exists in mapping. this allows us to override mapping
@@ -308,11 +316,11 @@
 
 def buildmap(exp, context):
     func, data = compileexp(exp[1], context, methods)
-    ctmpl = gettemplate(exp[2], context)
-    return (runmap, (func, data, ctmpl))
+    tfunc, tdata = gettemplate(exp[2], context)
+    return (runmap, (func, data, tfunc, tdata))
 
 def runmap(context, mapping, data):
-    func, data, ctmpl = data
+    func, data, tfunc, tdata = data
     d = func(context, mapping, data)
     if util.safehasattr(d, 'itermaps'):
         diter = d.itermaps()
@@ -330,7 +338,7 @@
         if isinstance(i, dict):
             lm.update(i)
             lm['originalnode'] = mapping.get('node')
-            yield runtemplate(context, lm, ctmpl)
+            yield tfunc(context, lm, tdata)
         else:
             # v is not an iterable of dicts, this happen when 'key'
             # has been fully expanded already and format is useless.
@@ -857,13 +865,13 @@
         if defaults is None:
             defaults = {}
         self._defaults = defaults
-        self._cache = {}
+        self._cache = {}  # key: (func, data)
 
     def _load(self, t):
         '''load, parse, and cache a template'''
         if t not in self._cache:
             # put poison to cut recursion while compiling 't'
-            self._cache[t] = [(_runrecursivesymbol, t)]
+            self._cache[t] = (_runrecursivesymbol, t)
             try:
                 self._cache[t] = compiletemplate(self._loader(t), self)
             except: # re-raises
@@ -875,7 +883,8 @@
         '''Perform expansion. t is name of map element to expand.
         mapping contains added elements for use during expansion. Is a
         generator.'''
-        return _flatten(runtemplate(self, mapping, self._load(t)))
+        func, data = self._load(t)
+        return _flatten(func(self, mapping, data))
 
 engines = {'default': engine}