mercurial/templater.py
changeset 25783 1f6878c87c25
parent 25782 babd2c93bd99
child 25784 33e613687dab
--- a/mercurial/templater.py	Mon Jun 15 23:03:30 2015 +0900
+++ b/mercurial/templater.py	Mon Jun 15 23:11:35 2015 +0900
@@ -24,6 +24,7 @@
     "symbol": (0, ("symbol",), None),
     "string": (0, ("template",), None),
     "rawstring": (0, ("rawstring",), None),
+    "template": (0, ("template",), None),
     "end": (0, None, None),
 }
 
@@ -35,6 +36,11 @@
             pass
         elif c in "(,)%|": # handle simple operators
             yield (c, None, pos)
+        elif c in '"\'': # handle quoted templates
+            s = pos + 1
+            data, pos = _parsetemplate(program, s, end, c)
+            yield ('template', data, s)
+            pos -= 1
         elif (c in '"\'' or c == 'r' and
               program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
             if c == 'r':
@@ -89,7 +95,7 @@
                 pos += 1
                 token = 'rawstring'
             else:
-                token = 'string'
+                token = 'template'
             quote = program[pos:pos + 2]
             s = pos = pos + 2
             while pos < end: # find closing escaped quote
@@ -102,6 +108,8 @@
                         data = program[s:pos].decode('string-escape')
                     except ValueError: # unbalanced escapes
                         raise error.ParseError(_("syntax error"), s)
+                    if token == 'template':
+                        data = _parsetemplate(data, 0, len(data))[0]
                     yield (token, data, s)
                     pos += 1
                     break
@@ -127,27 +135,47 @@
         pos += 1
     raise error.ParseError(_("unterminated template expansion"), start)
 
-def _parsetemplate(tmpl, start, stop):
+def _parsetemplate(tmpl, start, stop, quote=''):
+    r"""
+    >>> _parsetemplate('foo{bar}"baz', 0, 12)
+    ([('string', 'foo'), ('symbol', 'bar'), ('string', '"baz')], 12)
+    >>> _parsetemplate('foo{bar}"baz', 0, 12, quote='"')
+    ([('string', 'foo'), ('symbol', 'bar')], 9)
+    >>> _parsetemplate('foo"{bar}', 0, 9, quote='"')
+    ([('string', 'foo')], 4)
+    >>> _parsetemplate(r'foo\"bar"baz', 0, 12, quote='"')
+    ([('string', 'foo"'), ('string', 'bar')], 9)
+    >>> _parsetemplate(r'foo\\"bar', 0, 10, quote='"')
+    ([('string', 'foo\\\\')], 6)
+    """
     parsed = []
+    sepchars = '{' + quote
     pos = start
     p = parser.parser(elements)
     while pos < stop:
-        n = tmpl.find('{', pos, stop)
+        n = min((tmpl.find(c, pos, stop) for c in sepchars),
+                key=lambda n: (n < 0, n))
         if n < 0:
             parsed.append(('string', tmpl[pos:stop]))
             pos = stop
             break
+        c = tmpl[n]
         bs = (n - pos) - len(tmpl[pos:n].rstrip('\\'))
         if bs % 2 == 1:
             # escaped (e.g. '\{', '\\\{', but not '\\{')
-            parsed.append(('string', (tmpl[pos:n - 1] + "{")))
+            parsed.append(('string', (tmpl[pos:n - 1] + c)))
             pos = n + 1
             continue
         if n > pos:
             parsed.append(('string', tmpl[pos:n]))
+        if c == quote:
+            return parsed, n + 1
 
         parseres, pos = p.parse(tokenize(tmpl, n + 1, stop))
         parsed.append(parseres)
+
+    if quote:
+        raise error.ParseError(_("unterminated string"), start)
     return parsed, pos
 
 def compiletemplate(tmpl, context):
@@ -182,7 +210,7 @@
 
 def gettemplate(exp, context):
     if exp[0] == 'template':
-        return compiletemplate(exp[1], context)
+        return [compileexp(e, context, methods) for e in exp[1]]
     if exp[0] == 'symbol':
         # unlike runsymbol(), here 'symbol' is always taken as template name
         # even if it exists in mapping. this allows us to override mapping
@@ -215,7 +243,7 @@
     return v
 
 def buildtemplate(exp, context):
-    ctmpl = compiletemplate(exp[1], context)
+    ctmpl = [compileexp(e, context, methods) for e in exp[1]]
     if len(ctmpl) == 1:
         return ctmpl[0]  # fast path for string with no template fragment
     return (runtemplate, ctmpl)