Patchwork [2,of,5] templater: take any string literals as template, but not for rawstring (BC)

mail settings
Submitter Yuya Nishihara
Date June 16, 2015, 3:10 p.m.
Message ID <fde14bbc4236a3380932.1434467437@mimosa>
Download mbox | patch
Permalink /patch/9664/
State Accepted
Headers show


Yuya Nishihara - June 16, 2015, 3:10 p.m.
# HG changeset patch
# User Yuya Nishihara <>
# Date 1434192594 -32400
#      Sat Jun 13 19:49:54 2015 +0900
# Node ID fde14bbc4236a33809325c6ec30bb64d8fe2c3cb
# Parent  fce3854318c2128aa8188678d26b2320ecba8972
templater: take any string literals as template, but not for rawstring (BC)

This patch series is intended to unify the interpretation of string literals.
It is breaking change that boldly assumes

 a. string literal "..." never contains template-like fragment or it is
    intended to be a template
 b. we tend to use raw string literal r"..." for regexp pattern in which "{"
    should have different meaning

Currently, we don't have a comprehensible rule how string literals are
evaluated in template functions. For example, fill() takes "initialindent"
and "hangindent" as templates, but not for "text", whereas "text" is a
template in pad() function.

  date(date, fmt)
  diff(includepattern, excludepattern)
  fill(text, width, initialident: T, hangindent: T)
  get(dict, key)
  if(expr, then: T, else: T)
  ifcontains(search, thing, then: T, else: T)
  ifeq(expr1, expr2, then: T, else: T)
  indent(text, indentchars, firstline)
  join(list, sep)
  label(label: T, expr: T)
  pad(text: T, width, fillchar, right)
  revset(query, formatargs...])
  rstdoc(text, style)
  shortest(node, minlength)
  startswith(pattern, text)
  strip(text, chars)
  sub(pattern, replacement, expression: T)
  word(number, text, separator)
  expr % template: T

  T: interpret "string" or r"rawstring" as template

This patch series adjusts the rule as follows:

 a. string literal, '' or "", starts template processing (BC)
 b. raw string literal, r'' or r"", disables both \-escape and template
    processing (BC, done by subsequent patches)
 c. fragment not surrounded by {} is non-templated string

   ------------------  *: template
   ---                 c: string
        ---            a: template
                ---    b: rawstring

Because this can eliminate the compilation of template arguments from the
evaluation phase, "hg log -Tdefault" gets faster.

  % cd mozilla-central
  % LANG=C HGRCPATH=/dev/null hg log -Tdefault -r0:10000 --time > /dev/null
  before: real 4.870 secs (user 4.860+0.000 sys 0.010+0.000)
  after:  real 3.480 secs (user 3.440+0.000 sys 0.030+0.000)

Also, this will allow us to parse nested templates at once for better error


diff --git a/mercurial/help/templates.txt b/mercurial/help/templates.txt
--- a/mercurial/help/templates.txt
+++ b/mercurial/help/templates.txt
@@ -47,6 +47,10 @@  Also, for any expression that returns a 
 - expr % "{template}"
+As seen in the above example, "{template}" is interpreted as a template.
+To prevent it from being interpreted, you can use an escape character "\{"
+or a raw string prefix, "r'...'".
 Some sample command line templates:
 - Format lists, e.g. files::
diff --git a/mercurial/ b/mercurial/
--- a/mercurial/
+++ b/mercurial/
@@ -22,7 +22,7 @@  elements = {
     ")": (0, None, None),
     "integer": (0, ("integer",), None),
     "symbol": (0, ("symbol",), None),
-    "string": (0, ("string",), None),
+    "string": (0, ("template",), None),
     "rawstring": (0, ("rawstring",), None),
     "end": (0, None, None),
@@ -144,7 +144,9 @@  def getfilter(exp, context):
     return context._filters[f]
 def gettemplate(exp, context):
-    if exp[0] == 'string' or exp[0] == 'rawstring':
+    if exp[0] == 'template':
+        return compiletemplate(exp[1], context)
+    if exp[0] == 'rawstring':
         return compiletemplate(exp[1], context, strtoken=exp[0])
     if exp[0] == 'symbol':
         return context._load(exp[1])
@@ -174,6 +176,12 @@  def runsymbol(context, mapping, key):
         v = list(v)
     return v
+def buildtemplate(exp, context):
+    ctmpl = compiletemplate(exp[1], context)
+    if len(ctmpl) == 1:
+        return ctmpl[0]  # fast path for string with no template fragment
+    return (runtemplate, ctmpl)
 def runtemplate(context, mapping, template):
     for func, data in template:
         yield func(context, mapping, data)
@@ -362,7 +370,7 @@  def get(context, mapping, args):
 def _evalifliteral(arg, context, mapping):
     # get back to token tag to reinterpret string as template
-    strtoken = {runstring: 'string', runrawstring: 'rawstring'}.get(arg[0])
+    strtoken = {runrawstring: 'rawstring'}.get(arg[0])
     if strtoken:
         yield runtemplate(context, mapping,
                           compiletemplate(arg[1], context, strtoken))
@@ -606,6 +614,7 @@  exprmethods = {
     "string": lambda e, c: (runstring, e[1]),
     "rawstring": lambda e, c: (runrawstring, e[1]),
     "symbol": lambda e, c: (runsymbol, e[1]),
+    "template": buildtemplate,
     "group": lambda e, c: compileexp(e[1], c, exprmethods),
 #    ".": buildmember,
     "|": buildfilter,
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
@@ -2803,6 +2803,15 @@  unless explicit symbol is expected:
   hg: parse error: expected a symbol, got 'integer'
+Test string literal:
+  $ hg log -Ra -r0 -T '{"string with no template fragment"}\n'
+  string with no template fragment
+  $ hg log -Ra -r0 -T '{"template: {rev}"}\n'
+  template: 0
+  $ hg log -Ra -r0 -T '{r"rawstring: {rev}"}\n'
+  rawstring: {rev}
 Test string escaping:
   $ hg log -R latesttag -r 0 --template '>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'