templater: add dot operator to easily access a sub item
authorYuya Nishihara <yuya@tcha.org>
Sat, 09 Sep 2017 19:32:56 +0900
changeset 34535 78590585c0db
parent 34534 b3073e175c17
child 34536 4c1cfe54c08d
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.
mercurial/help/templates.txt
mercurial/templatekw.py
mercurial/templater.py
tests/test-command-template.t
--- a/mercurial/help/templates.txt	Sat Sep 09 19:13:25 2017 +0900
+++ b/mercurial/help/templates.txt	Sat Sep 09 19:32:56 2017 +0900
@@ -72,6 +72,11 @@
 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
 =======
 
--- a/mercurial/templatekw.py	Sat Sep 09 19:13:25 2017 +0900
+++ b/mercurial/templatekw.py	Sat Sep 09 19:32:56 2017 +0900
@@ -73,6 +73,7 @@
     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.
--- a/mercurial/templater.py	Sat Sep 09 19:13:25 2017 +0900
+++ b/mercurial/templater.py	Sat Sep 09 19:32:56 2017 +0900
@@ -35,6 +35,7 @@
 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 @@
         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 @@
             # 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 @@
     "symbol": lambda e, c: (runsymbol, e[1]),
     "template": buildtemplate,
     "group": lambda e, c: compileexp(e[1], c, exprmethods),
-#    ".": buildmember,
+    ".": buildmember,
     "|": buildfilter,
     "%": buildmap,
     "func": buildfunc,
--- a/tests/test-command-template.t	Sat Sep 09 19:13:25 2017 +0900
+++ b/tests/test-command-template.t	Sat Sep 09 19:32:56 2017 +0900
@@ -3147,6 +3147,51 @@
   $ 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'