Patchwork [5,of,8] templater: add function to look up symbols used in template

login
register
mail settings
Submitter Yuya Nishihara
Date June 14, 2018, 3:40 p.m.
Message ID <92efa37a0696007f5445.1528990830@mimosa>
Download mbox | patch
Permalink /patch/32135/
State Accepted
Headers show

Comments

Yuya Nishihara - June 14, 2018, 3:40 p.m.
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1525316036 -32400
#      Thu May 03 11:53:56 2018 +0900
# Node ID 92efa37a0696007f5445c6cd69a395fb2e1cdd5f
# Parent  04ae3f0792bc165d30347aa6a456ab3421d4c394
templater: add function to look up symbols used in template

Formatter can use this information to enable slow paths such as loading
ctx object only when necessary.

Patch

diff --git a/mercurial/debugcommands.py b/mercurial/debugcommands.py
--- a/mercurial/debugcommands.py
+++ b/mercurial/debugcommands.py
@@ -2482,9 +2482,17 @@  def debugtemplate(ui, repo, tmpl, **opts
     if revs is None:
         tres = formatter.templateresources(ui, repo)
         t = formatter.maketemplater(ui, tmpl, resources=tres)
+        if ui.verbose:
+            kwds, funcs = t.symbolsuseddefault()
+            ui.write(("* keywords: %s\n") % ', '.join(sorted(kwds)))
+            ui.write(("* functions: %s\n") % ', '.join(sorted(funcs)))
         ui.write(t.renderdefault(props))
     else:
         displayer = logcmdutil.maketemplater(ui, repo, tmpl)
+        if ui.verbose:
+            kwds, funcs = displayer.t.symbolsuseddefault()
+            ui.write(("* keywords: %s\n") % ', '.join(sorted(kwds)))
+            ui.write(("* functions: %s\n") % ', '.join(sorted(funcs)))
         for r in revs:
             displayer.show(repo[r], **pycompat.strkwargs(props))
         displayer.close()
diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -842,6 +842,51 @@  class templater(object):
             x = _aliasrules.expand(self._aliasmap, x)
         return x
 
+    def _findsymbolsused(self, tree, syms):
+        if not tree:
+            return
+        op = tree[0]
+        if op == 'symbol':
+            s = tree[1]
+            if s in syms[0]:
+                return # avoid recursion: s -> cache[s] -> s
+            syms[0].add(s)
+            if s in self.cache or s in self._map:
+                # s may be a reference for named template
+                self._findsymbolsused(self.load(s), syms)
+            return
+        if op in {'integer', 'string'}:
+            return
+        # '{arg|func}' == '{func(arg)}'
+        if op == '|':
+            syms[1].add(getsymbol(tree[2]))
+            self._findsymbolsused(tree[1], syms)
+            return
+        if op == 'func':
+            syms[1].add(getsymbol(tree[1]))
+            self._findsymbolsused(tree[2], syms)
+            return
+        for x in tree[1:]:
+            self._findsymbolsused(x, syms)
+
+    def symbolsuseddefault(self):
+        """Look up (keywords, filters/functions) referenced from the default
+        unnamed template
+
+        This may load additional templates from the map file.
+        """
+        return self.symbolsused('')
+
+    def symbolsused(self, t):
+        """Look up (keywords, filters/functions) referenced from the name
+        template 't'
+
+        This may load additional templates from the map file.
+        """
+        syms = (set(), set())
+        self._findsymbolsused(self.load(t), syms)
+        return syms
+
     def renderdefault(self, mapping):
         """Render the default unnamed template and return result as string"""
         return self.render('', mapping)
diff --git a/tests/test-command-template.t b/tests/test-command-template.t
--- a/tests/test-command-template.t
+++ b/tests/test-command-template.t
@@ -50,6 +50,8 @@  Test division:
         (integer '5')
         (integer '2')))
     (string '\n'))
+  * keywords: 
+  * functions: mod
   2 1
   $ hg debugtemplate -r0 -v '{5 / -2} {mod(5, -2)}\n'
   (template
@@ -65,6 +67,8 @@  Test division:
         (negate
           (integer '2'))))
     (string '\n'))
+  * keywords: 
+  * functions: mod
   -3 -1
   $ hg debugtemplate -r0 -v '{-5 / 2} {mod(-5, 2)}\n'
   (template
@@ -80,6 +84,8 @@  Test division:
           (integer '5'))
         (integer '2')))
     (string '\n'))
+  * keywords: 
+  * functions: mod
   -3 1
   $ hg debugtemplate -r0 -v '{-5 / -2} {mod(-5, -2)}\n'
   (template
@@ -97,6 +103,8 @@  Test division:
         (negate
           (integer '2'))))
     (string '\n'))
+  * keywords: 
+  * functions: mod
   2 -1
 
 Filters bind closer than arithmetic:
@@ -111,6 +119,8 @@  Filters bind closer than arithmetic:
         (symbol 'count'))
       (integer '1'))
     (string '\n'))
+  * keywords: 
+  * functions: count, revset
   0
 
 But negate binds closer still:
@@ -123,6 +133,8 @@  But negate binds closer still:
         (integer '3')
         (symbol 'stringify')))
     (string '\n'))
+  * keywords: 
+  * functions: stringify
   hg: parse error: arithmetic only defined on integers
   [255]
   $ hg debugtemplate -r0 -v '{-3|stringify}\n'
@@ -132,6 +144,8 @@  But negate binds closer still:
         (integer '3'))
       (symbol 'stringify'))
     (string '\n'))
+  * keywords: 
+  * functions: stringify
   -3
 
 Filters bind as close as map operator:
@@ -145,6 +159,8 @@  Filters bind as close as map operator:
       (template
         (symbol 'line')
         (string '\n'))))
+  * keywords: desc, line
+  * functions: splitlines
   line 1
   line 2
 
@@ -157,6 +173,8 @@  Keyword arguments:
       (|
         (symbol 'bar')
         (symbol 'baz'))))
+  * keywords: bar, foo
+  * functions: baz
   hg: parse error: can't use a key-value pair in this context
   [255]
 
@@ -2800,6 +2818,8 @@  Error on syntax:
   (template
     (group
       None))
+  * keywords: 
+  * functions: 
   hg: parse error: missing argument
   [255]
 
@@ -3369,6 +3389,8 @@  Test dot operator precedence:
         (symbol 'node'))
       (symbol 'short'))
     (string '\n'))
+  * keywords: manifest, node, rev
+  * functions: formatnode, short
   89f4071fec70
 
  (the following examples are invalid, but seem natural in parsing POV)
@@ -3390,6 +3412,8 @@  Test dot operator precedence:
         (symbol 'bar')
         None))
     (string '\n'))
+  * keywords: foo
+  * functions: bar
   [255]
 
 Test evaluation of dot operator:
@@ -3480,12 +3504,16 @@  Test integer literal:
     (group
       (integer '0'))
     (string '\n'))
+  * keywords: 
+  * functions: 
   0
   $ hg debugtemplate -v '{(123)}\n'
   (template
     (group
       (integer '123'))
     (string '\n'))
+  * keywords: 
+  * functions: 
   123
   $ hg debugtemplate -v '{(-4)}\n'
   (template
@@ -3493,6 +3521,8 @@  Test integer literal:
       (negate
         (integer '4')))
     (string '\n'))
+  * keywords: 
+  * functions: 
   -4
   $ hg debugtemplate '{(-)}\n'
   hg: parse error at 3: not a prefix: )
@@ -3509,6 +3539,8 @@  top-level integer literal is interpreted
   (template
     (integer '1')
     (string '\n'))
+  * keywords: 
+  * functions: 
   one
   $ hg debugtemplate -D 1=one -v '{if("t", "{1}")}\n'
   (template
@@ -3519,6 +3551,8 @@  top-level integer literal is interpreted
         (template
           (integer '1'))))
     (string '\n'))
+  * keywords: 
+  * functions: if
   one
   $ hg debugtemplate -D 1=one -v '{1|stringify}\n'
   (template
@@ -3526,6 +3560,8 @@  top-level integer literal is interpreted
       (integer '1')
       (symbol 'stringify'))
     (string '\n'))
+  * keywords: 
+  * functions: stringify
   one
 
 unless explicit symbol is expected:
@@ -3543,6 +3579,8 @@  Test string literal:
   (template
     (string 'string with no template fragment')
     (string '\n'))
+  * keywords: 
+  * functions: 
   string with no template fragment
   $ hg debugtemplate -Ra -r0 -v '{"template: {rev}"}\n'
   (template
@@ -3550,11 +3588,15 @@  Test string literal:
       (string 'template: ')
       (symbol 'rev'))
     (string '\n'))
+  * keywords: rev
+  * functions: 
   template: 0
   $ hg debugtemplate -Ra -r0 -v '{r"rawstring: {rev}"}\n'
   (template
     (string 'rawstring: {rev}')
     (string '\n'))
+  * keywords: 
+  * functions: 
   rawstring: {rev}
   $ hg debugtemplate -Ra -r0 -v '{files % r"rawstring: {file}"}\n'
   (template
@@ -3562,6 +3604,8 @@  Test string literal:
       (symbol 'files')
       (string 'rawstring: {file}'))
     (string '\n'))
+  * keywords: files
+  * functions: 
   rawstring: {file}
 
 Test string escaping:
@@ -4681,6 +4725,8 @@  Templater supports aliases of symbol and
           (string 'UTC')))
       (symbol 'isodate'))
     (string '\n'))
+  * keywords: date, node, rev
+  * functions: isodate, localdate, short
   0:1e4e1b8f71e0 1970-01-12 13:46 +0000
 
   $ hg debugtemplate -vr0 '{status("A", file_adds)}'
@@ -4699,6 +4745,8 @@  Templater supports aliases of symbol and
         (string ' ')
         (symbol 'file')
         (string '\n'))))
+  * keywords: file, file_adds
+  * functions: 
   A a
 
 A unary function alias can be called as a filter:
@@ -4721,6 +4769,8 @@  A unary function alias can be called as 
           (string 'UTC')))
       (symbol 'isodate'))
     (string '\n'))
+  * keywords: date
+  * functions: isodate, localdate
   1970-01-12 13:46 +0000
 
 Aliases should be applied only to command arguments and templates in hgrc.