Patchwork [2,of,8] templater: add dot operator to easily access a sub item

login
register
mail settings
Submitter Yuya Nishihara
Date Oct. 6, 2017, 4:05 p.m.
Message ID <b191c62651e04bf4ed9a.1507305952@mimosa>
Download mbox | patch
Permalink /patch/24603/
State Accepted
Headers show

Comments

Yuya Nishihara - Oct. 6, 2017, 4:05 p.m.
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1504953176 -32400
#      Sat Sep 09 19:32:56 2017 +0900
# Node ID b191c62651e04bf4ed9aceda32fd08c000570d82
# Parent  ebbbf772650c276998b87b307c9c9ddc775e9236
templater: add dot operator to easily access a sub item

This and the next patch will allow us to access a deeply-nested item
by foo.bar.baz syntax.

Patch

diff --git a/mercurial/help/templates.txt b/mercurial/help/templates.txt
--- a/mercurial/help/templates.txt
+++ b/mercurial/help/templates.txt
@@ -72,6 +72,11 @@  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'...'``.
 
+The dot operator can be used as a shorthand for accessing a sub item:
+
+- ``expr.member`` is roughly equivalent to ``expr % "{member}"`` if ``expr``
+  returns a non-list/dict. The returned value is not stringified.
+
 Aliases
 =======
 
diff --git a/mercurial/templatekw.py b/mercurial/templatekw.py
--- a/mercurial/templatekw.py
+++ b/mercurial/templatekw.py
@@ -73,6 +73,7 @@  class _mappable(object):
     This class allows us to handle both:
     - "{manifest}"
     - "{manifest % '{rev}:{node}'}"
+    - "{manifest.rev}"
 
     Unlike a _hybrid, this does not simulate the behavior of the underling
     value. Use unwrapvalue() or unwraphybrid() to obtain the inner object.
diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -35,6 +35,7 @@  from . import (
 elements = {
     # token-type: binding-strength, primary, prefix, infix, suffix
     "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None),
+    ".": (18, None, None, (".", 18), None),
     "%": (15, None, None, ("%", 15), None),
     "|": (15, None, None, ("|", 15), None),
     "*": (5, None, None, ("*", 5), None),
@@ -60,7 +61,7 @@  def tokenize(program, start, end, term=N
         c = program[pos]
         if c.isspace(): # skip inter-token whitespace
             pass
-        elif c in "(=,)%|+-*/": # handle simple operators
+        elif c in "(=,).%|+-*/": # handle simple operators
             yield (c, None, pos)
         elif c in '"\'': # handle quoted templates
             s = pos + 1
@@ -450,6 +451,26 @@  def runmap(context, mapping, data):
             # If so, return the expanded value.
             yield v
 
+def buildmember(exp, context):
+    darg = compileexp(exp[1], context, methods)
+    memb = getsymbol(exp[2])
+    return (runmember, (darg, memb))
+
+def runmember(context, mapping, data):
+    darg, memb = data
+    d = evalrawexp(context, mapping, darg)
+    if util.safehasattr(d, 'tomap'):
+        lm = mapping.copy()
+        lm.update(d.tomap())
+        return runsymbol(context, lm, memb)
+    # TODO: d.get(memb) if dict-like?
+
+    sym = findsymbolicname(darg)
+    if sym:
+        raise error.ParseError(_("keyword '%s' has no member") % sym)
+    else:
+        raise error.ParseError(_("%r has no member") % d)
+
 def buildnegate(exp, context):
     arg = compileexp(exp[1], context, exprmethods)
     return (runnegate, arg)
@@ -1152,7 +1173,7 @@  exprmethods = {
     "symbol": lambda e, c: (runsymbol, e[1]),
     "template": buildtemplate,
     "group": lambda e, c: compileexp(e[1], c, exprmethods),
-#    ".": buildmember,
+    ".": buildmember,
     "|": buildfilter,
     "%": buildmap,
     "func": buildfunc,
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
@@ -3147,6 +3147,51 @@  Test manifest/get() can be join()-ed as 
   $ hg log -R latesttag -r tip -T '{join(get(extras, "branch"), "")}\n'
   default
 
+Test dot operator precedence:
+
+  $ hg debugtemplate -R latesttag -r0 -v '{manifest.node|short}\n'
+  (template
+    (|
+      (.
+        (symbol 'manifest')
+        (symbol 'node'))
+      (symbol 'short'))
+    (string '\n'))
+  89f4071fec70
+
+ (the following examples are invalid, but seem natural in parsing POV)
+
+  $ hg debugtemplate -R latesttag -r0 -v '{foo|bar.baz}\n' 2> /dev/null
+  (template
+    (|
+      (symbol 'foo')
+      (.
+        (symbol 'bar')
+        (symbol 'baz')))
+    (string '\n'))
+  [255]
+  $ hg debugtemplate -R latesttag -r0 -v '{foo.bar()}\n' 2> /dev/null
+  (template
+    (.
+      (symbol 'foo')
+      (func
+        (symbol 'bar')
+        None))
+    (string '\n'))
+  [255]
+
+Test evaluation of dot operator:
+
+  $ hg log -R latesttag -l1 -T '{min(revset("0:9")).node}\n'
+  ce3cec86e6c26bd9bdfc590a6b92abc9680f1796
+
+  $ hg log -R latesttag -l1 -T '{author.invalid}\n'
+  hg: parse error: keyword 'author' has no member
+  [255]
+  $ hg log -R latesttag -l1 -T '{min("abc").invalid}\n'
+  hg: parse error: 'a' has no member
+  [255]
+
 Test the sub function of templating for expansion:
 
   $ hg log -R latesttag -r 10 --template '{sub("[0-9]", "x", "{rev}")}\n'