Patchwork [8,of,9] templater: load and expand aliases by template engine (API) (issue4842)

login
register
mail settings
Submitter Yuya Nishihara
Date April 15, 2016, 1:15 p.m.
Message ID <2c175aa76db0df4ecfd8.1460726147@mimosa>
Download mbox | patch
Permalink /patch/14647/
State Accepted
Headers show

Comments

Yuya Nishihara - April 15, 2016, 1:15 p.m.
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1459079976 -32400
#      Sun Mar 27 20:59:36 2016 +0900
# Node ID 2c175aa76db0df4ecfd89861215d6dbd226329eb
# Parent  8205edad95550991c2e31246b4b2c1093bf4c6d6
templater: load and expand aliases by template engine (API) (issue4842)

Now template aliases are fully supported in log and formatter templates.

As I said before, aliases are not expanded in map files. This avoids possible
corruption of our stock styles and web templates. This behavior is undocumented
since no map file nor [templates] section are documented at all. Later on,
we might want to add [aliases] section to map files if it appears to be useful.

Patch

diff --git a/mercurial/formatter.py b/mercurial/formatter.py
--- a/mercurial/formatter.py
+++ b/mercurial/formatter.py
@@ -197,7 +197,8 @@  def gettemplater(ui, topic, spec):
 
 def maketemplater(ui, topic, tmpl, filters=None, cache=None):
     """Create a templater from a string template 'tmpl'"""
-    t = templater.templater(filters=filters, cache=cache)
+    aliases = ui.configitems('templatealias')
+    t = templater.templater(filters=filters, cache=cache, aliases=aliases)
     if tmpl:
         t.cache[topic] = tmpl
     return t
diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt
--- a/mercurial/help/config.txt
+++ b/mercurial/help/config.txt
@@ -1488,6 +1488,11 @@  Relative subrepository paths are first m
 rewrite rules are then applied on the full (absolute) path. The rules
 are applied in definition order.
 
+``templatealias``
+-----------------
+
+Alias definitions for templates. See :hg:`help templates` for details.
+
 ``trusted``
 -----------
 
diff --git a/mercurial/help/templates.txt b/mercurial/help/templates.txt
--- a/mercurial/help/templates.txt
+++ b/mercurial/help/templates.txt
@@ -51,6 +51,26 @@  As seen in the above example, ``{templat
 To prevent it from being interpreted, you can use an escape character ``\{``
 or a raw string prefix, ``r'...'``.
 
+New keywords and functions can be defined in the ``templatealias`` section of
+a Mercurial configuration file::
+
+  <alias> = <definition>
+
+Arguments of the form `a1`, `a2`, etc. are substituted from the alias into
+the definition.
+
+For example,
+
+::
+
+  [templatealias]
+  r = rev
+  rn = "{r}:{node|short}"
+  leftpad(s, w) = pad(s, w, ' ', True)
+
+defines two symbol aliases, ``r`` and ``rn``, and a function alias
+``leftpad()``.
+
 Some sample command line templates:
 
 - Format lists, e.g. files::
diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -936,7 +936,7 @@  class engine(object):
     filter uses function to transform value. syntax is
     {key|filter1|filter2|...}.'''
 
-    def __init__(self, loader, filters=None, defaults=None):
+    def __init__(self, loader, filters=None, defaults=None, aliases=()):
         self._loader = loader
         if filters is None:
             filters = {}
@@ -944,6 +944,7 @@  class engine(object):
         if defaults is None:
             defaults = {}
         self._defaults = defaults
+        self._aliasmap = _aliasrules.buildmap(aliases)
         self._cache = {}  # key: (func, data)
 
     def _load(self, t):
@@ -953,6 +954,8 @@  class engine(object):
             self._cache[t] = (_runrecursivesymbol, t)
             try:
                 x = parse(self._loader(t))
+                if self._aliasmap:
+                    x = _aliasrules.expand(self._aliasmap, x)
                 self._cache[t] = compileexp(x, self, methods)
             except: # re-raises
                 del self._cache[t]
@@ -1014,11 +1017,13 @@  class TemplateNotFound(error.Abort):
 
 class templater(object):
 
-    def __init__(self, filters=None, defaults=None, cache=None,
+    def __init__(self, filters=None, defaults=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.'''
+        defaults is dict of default map definitions.
+        aliases is list of alias (name, replacement) pairs.
+        '''
         if filters is None:
             filters = {}
         if defaults is None:
@@ -1030,6 +1035,7 @@  class templater(object):
         self.filters = templatefilters.filters.copy()
         self.filters.update(filters)
         self.defaults = defaults
+        self._aliases = aliases
         self.minchunk, self.maxchunk = minchunk, maxchunk
         self.ecache = {}
 
@@ -1037,7 +1043,7 @@  class templater(object):
     def frommapfile(cls, mapfile, filters=None, defaults=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, cache, [], minchunk, maxchunk)
         cache, tmap = _readmapfile(mapfile)
         t.cache.update(cache)
         t.map = tmap
@@ -1066,7 +1072,8 @@  class templater(object):
                 ecls = engines[ttype]
             except KeyError:
                 raise error.Abort(_('invalid template engine: %s') % ttype)
-            self.ecache[ttype] = ecls(self.load, self.filters, self.defaults)
+            self.ecache[ttype] = ecls(self.load, self.filters, self.defaults,
+                                      self._aliases)
         proc = self.ecache[ttype]
 
         stream = proc.process(t, 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
@@ -3693,8 +3693,7 @@  Templater supports aliases of symbol and
           ('string', 'UTC')))
       ('symbol', 'isodate'))
     ('string', '\n'))
-  hg: parse error: unknown function 'utcdate'
-  [255]
+  0:1e4e1b8f71e0 1970-01-12 13:46 +0000
 
   $ hg debugtemplate -vr0 '{status("A", file_adds)}'
   (template
@@ -3712,8 +3711,7 @@  Templater supports aliases of symbol and
         ('string', ' ')
         ('symbol', 'file')
         ('string', '\n'))))
-  hg: parse error: unknown function 'status'
-  [255]
+  A a
 
 A unary function alias can be called as a filter:
 
@@ -3735,8 +3733,28 @@  A unary function alias can be called as 
           ('string', 'UTC')))
       ('symbol', 'isodate'))
     ('string', '\n'))
-  hg: parse error: unknown function 'utcdate'
-  [255]
+  1970-01-12 13:46 +0000
+
+Aliases should be applied only to command arguments and templates in hgrc.
+Otherwise, our stock styles and web templates could be corrupted:
+
+  $ hg log -r0 -T '{rn} {utcdate(date)|isodate}\n'
+  0:1e4e1b8f71e0 1970-01-12 13:46 +0000
+
+  $ hg log -r0 --config ui.logtemplate='"{rn} {utcdate(date)|isodate}\n"'
+  0:1e4e1b8f71e0 1970-01-12 13:46 +0000
+
+  $ cat <<EOF > tmpl
+  > changeset = 'nothing expanded:{rn}\n'
+  > EOF
+  $ hg log -r0 --style ./tmpl
+  nothing expanded:
+
+Aliases in formatter:
+
+  $ hg branches -T '{pad(branch, 7)} {rn}\n'
+  default 6:d41e714fe50d
+  foo     4:bbe44766e73d
 
 Unparsable alias:
 
@@ -3745,6 +3763,9 @@  Unparsable alias:
     ('symbol', 'bad'))
   abort: failed to parse the definition of template alias "bad": at 2: not a prefix: end
   [255]
+  $ hg log --config templatealias.bad='x(' -T '{bad}'
+  abort: failed to parse the definition of template alias "bad": at 2: not a prefix: end
+  [255]
 
   $ cd ..
 
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,7 +4,7 @@ 
   > from mercurial import templater
   > 
   > class mytemplater(object):
-  >     def __init__(self, loader, filters, defaults):
+  >     def __init__(self, loader, filters, defaults, aliases):
   >         self.loader = loader
   > 
   >     def process(self, t, map):