Patchwork [3,of,4,V2] templater: add option to parse template string just like raw string literal

login
register
mail settings
Submitter Yuya Nishihara
Date Feb. 18, 2018, 5:18 a.m.
Message ID <045d2ccca9c6979a31f8.1518931081@mimosa>
Download mbox | patch
Permalink /patch/28038/
State Accepted
Headers show

Comments

Yuya Nishihara - Feb. 18, 2018, 5:18 a.m.
# HG changeset patch
# User Yuya Nishihara <yuya@tcha.org>
# Date 1518922406 -32400
#      Sun Feb 18 11:53:26 2018 +0900
# Node ID 045d2ccca9c6979a31f88e4ac0c69728f4b56837
# Parent  35140c6c0e66f16061bf2757852655b476bacafa
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.

Patch

diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -172,8 +172,14 @@  def _parsetemplate(tmpl, start, stop, qu
             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)]
@@ -181,9 +187,11 @@  def scantemplate(tmpl):
     [('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':
@@ -192,27 +200,30 @@  def scantemplate(tmpl):
             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