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'