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.
--- a/mercurial/formatter.py Sun Apr 03 13:23:40 2016 +0900
+++ b/mercurial/formatter.py Sun Mar 27 20:59:36 2016 +0900
@@ -197,7 +197,8 @@
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
--- a/mercurial/help/config.txt Sun Apr 03 13:23:40 2016 +0900
+++ b/mercurial/help/config.txt Sun Mar 27 20:59:36 2016 +0900
@@ -1488,6 +1488,11 @@
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``
-----------
--- a/mercurial/help/templates.txt Sun Apr 03 13:23:40 2016 +0900
+++ b/mercurial/help/templates.txt Sun Mar 27 20:59:36 2016 +0900
@@ -51,6 +51,26 @@
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::
--- a/mercurial/templater.py Sun Apr 03 13:23:40 2016 +0900
+++ b/mercurial/templater.py Sun Mar 27 20:59:36 2016 +0900
@@ -936,7 +936,7 @@
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 @@
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 @@
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 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 @@
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 @@
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 @@
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)
--- a/tests/test-command-template.t Sun Apr 03 13:23:40 2016 +0900
+++ b/tests/test-command-template.t Sun Mar 27 20:59:36 2016 +0900
@@ -3693,8 +3693,7 @@
('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 @@
('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 @@
('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 @@
('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 ..
--- a/tests/test-template-engine.t Sun Apr 03 13:23:40 2016 +0900
+++ b/tests/test-template-engine.t Sun Mar 27 20:59:36 2016 +0900
@@ -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):