templater: add option to parse template string just like raw string literal
authorYuya Nishihara <yuya@tcha.org>
Sun, 18 Feb 2018 11:53:26 +0900
changeset 36509 638c012a87ef
parent 36508 d7a23d6184a2
child 36511 aa3294027936
templater: add option to parse template string just like raw string literal This seems a bit odd because the template syntax has no raw string literal containing template fragments, but is necessary to port filename format string to templater. See the next patch.
mercurial/templater.py
--- a/mercurial/templater.py	Sun Feb 18 10:58:15 2018 +0900
+++ b/mercurial/templater.py	Sun Feb 18 11:53:26 2018 +0900
@@ -178,8 +178,14 @@
             raise error.ProgrammingError('unexpected type: %s' % typ)
     raise error.ProgrammingError('unterminated scanning of template')
 
-def scantemplate(tmpl):
-    """Scan (type, start, end) positions of outermost elements in template
+def scantemplate(tmpl, raw=False):
+    r"""Scan (type, start, end) positions of outermost elements in template
+
+    If raw=True, a backslash is not taken as an escape character just like
+    r'' string in Python. Note that this is different from r'' literal in
+    template in that no template fragment can appear in r'', e.g. r'{foo}'
+    is a literal '{foo}', but ('{foo}', raw=True) is a template expression
+    'foo'.
 
     >>> list(scantemplate(b'foo{bar}"baz'))
     [('string', 0, 3), ('template', 3, 8), ('string', 8, 12)]
@@ -187,9 +193,11 @@
     [('string', 0, 5), ('template', 5, 14), ('string', 14, 19)]
     >>> list(scantemplate(b'foo\\{escaped}'))
     [('string', 0, 5), ('string', 5, 13)]
+    >>> list(scantemplate(b'foo\\{escaped}', raw=True))
+    [('string', 0, 4), ('template', 4, 13)]
     """
     last = None
-    for typ, val, pos in _scantemplate(tmpl, 0, len(tmpl)):
+    for typ, val, pos in _scantemplate(tmpl, 0, len(tmpl), raw=raw):
         if last:
             yield last + (pos,)
         if typ == 'end':
@@ -198,27 +206,30 @@
             last = (typ, pos)
     raise error.ProgrammingError('unterminated scanning of template')
 
-def _scantemplate(tmpl, start, stop, quote=''):
+def _scantemplate(tmpl, start, stop, quote='', raw=False):
     """Parse template string into chunks of strings and template expressions"""
     sepchars = '{' + quote
+    unescape = [parser.unescapestr, pycompat.identity][raw]
     pos = start
     p = parser.parser(elements)
     while pos < stop:
         n = min((tmpl.find(c, pos, stop) for c in sepchars),
                 key=lambda n: (n < 0, n))
         if n < 0:
-            yield ('string', parser.unescapestr(tmpl[pos:stop]), pos)
+            yield ('string', unescape(tmpl[pos:stop]), pos)
             pos = stop
             break
         c = tmpl[n:n + 1]
-        bs = (n - pos) - len(tmpl[pos:n].rstrip('\\'))
+        bs = 0  # count leading backslashes
+        if not raw:
+            bs = (n - pos) - len(tmpl[pos:n].rstrip('\\'))
         if bs % 2 == 1:
             # escaped (e.g. '\{', '\\\{', but not '\\{')
-            yield ('string', parser.unescapestr(tmpl[pos:n - 1]) + c, pos)
+            yield ('string', unescape(tmpl[pos:n - 1]) + c, pos)
             pos = n + 1
             continue
         if n > pos:
-            yield ('string', parser.unescapestr(tmpl[pos:n]), pos)
+            yield ('string', unescape(tmpl[pos:n]), pos)
         if c == quote:
             yield ('end', None, n + 1)
             return