templater: create string unescape helper (issue4798)
authorMatt Mackall <mpm@selenic.com>
Wed, 09 Sep 2015 14:43:45 -0700
changeset 26215 72aad184f061
parent 26214 46605888faf3
child 26216 e86d12404d69
templater: create string unescape helper (issue4798) This gives us a unified place to do error-handling of string-escaping syntax errors
mercurial/templater.py
tests/test-command-template.t
--- a/mercurial/templater.py	Sat Sep 05 16:50:35 2015 +0900
+++ b/mercurial/templater.py	Wed Sep 09 14:43:45 2015 -0700
@@ -39,6 +39,13 @@
     "end": (0, None, None, None, None),
 }
 
+def _unescape(s):
+    try:
+        return s.decode("string_escape")
+    except ValueError as e:
+        # mangle Python's exception into our format
+        raise error.ParseError(str(e).lower())
+
 def tokenize(program, start, end):
     pos = start
     while pos < end:
@@ -105,11 +112,8 @@
                     pos += 4 # skip over double escaped characters
                     continue
                 if program.startswith(quote, pos, end):
-                    try:
-                        # interpret as if it were a part of an outer string
-                        data = program[s:pos].decode('string-escape')
-                    except ValueError: # unbalanced escapes
-                        raise error.ParseError(_("syntax error"), s)
+                    # interpret as if it were a part of an outer string
+                    data = _unescape(program[s:pos])
                     if token == 'template':
                         data = _parsetemplate(data, 0, len(data))[0]
                     yield (token, data, s)
@@ -158,19 +162,18 @@
         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].decode('string-escape')))
+            parsed.append(('string', _unescape(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].decode('string-escape') + c))
+            parsed.append(('string', _unescape(tmpl[pos:n - 1]) + c))
             pos = n + 1
             continue
         if n > pos:
-            parsed.append(('string', tmpl[pos:n].decode('string-escape')))
+            parsed.append(('string', _unescape(tmpl[pos:n])))
         if c == quote:
             return parsed, n + 1
 
--- a/tests/test-command-template.t	Sat Sep 05 16:50:35 2015 +0900
+++ b/tests/test-command-template.t	Wed Sep 09 14:43:45 2015 -0700
@@ -2936,10 +2936,10 @@
   hg: parse error at 21: unterminated string
   [255]
   $ hg log -r 2 -T '{if(rev, \"\\"")}\n'
-  hg: parse error at 11: syntax error
+  hg: parse error: trailing \ in string
   [255]
   $ hg log -r 2 -T '{if(rev, r\"\\"")}\n'
-  hg: parse error at 12: syntax error
+  hg: parse error: trailing \ in string
   [255]
 
   $ cd ..
@@ -3417,3 +3417,12 @@
   $ hg log -T "{indent(date, '   ')}\n" -r 2:3 -R a
      1200000.00
      1300000.00
+
+Test broken string escapes:
+
+  $ hg log -T "bogus\\" -R a
+  hg: parse error: trailing \ in string
+  [255]
+  $ hg log -T "\\xy" -R a
+  hg: parse error: invalid \x escape
+  [255]