D2608: templater: add hint to template parse errors to help locate issues
ryanmce (Ryan McElroy)
phabricator at mercurial-scm.org
Sat Mar 3 22:11:31 UTC 2018
ryanmce updated this revision to Diff 6484.
REPOSITORY
rHG Mercurial
CHANGES SINCE LAST UPDATE
https://phab.mercurial-scm.org/D2608?vs=6483&id=6484
REVISION DETAIL
https://phab.mercurial-scm.org/D2608
AFFECTED FILES
mercurial/templater.py
tests/test-command-template.t
tests/test-export.t
tests/test-parse-errors.t
CHANGE DETAILS
diff --git a/tests/test-parse-errors.t b/tests/test-parse-errors.t
new file mode 100644
--- /dev/null
+++ b/tests/test-parse-errors.t
@@ -0,0 +1,21 @@
+ $ hg init repo
+ $ cd repo
+ $ echo a > a
+ $ hg ci -qAm aa
+
+simple error
+
+ $ hg log -r . -T '{shortest(node}'
+ hg: parse error at 15: unexpected token: end
+ ({shortest(node}
+ ^ here)
+ [255]
+
+multi-line
+
+ $ hg log -r . -T 'line 1
+ > line2
+ > {shortest(node}
+ > line4'
+ hg: parse error at 28: unexpected token: end
+ [255]
diff --git a/tests/test-export.t b/tests/test-export.t
--- a/tests/test-export.t
+++ b/tests/test-export.t
@@ -218,6 +218,8 @@
[255]
$ hg export -o '%m{' tip
hg: parse error at 3: unterminated template expansion
+ (%m{
+ ^ here)
[255]
$ hg export -o '%\' tip
abort: invalid format spec '%\' in output filename
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
@@ -2766,19 +2766,29 @@
$ hg log -T '{date'
hg: parse error at 1: unterminated template expansion
+ ({date
+ ^ here)
[255]
$ hg log -T '{date(}'
hg: parse error at 7: not a prefix: end
+ ({date(}
+ ^ here)
[255]
$ hg log -T '{date)}'
hg: parse error at 5: invalid token
+ ({date)}
+ ^ here)
[255]
$ hg log -T '{date date}'
hg: parse error at 6: invalid token
+ ({date date}
+ ^ here)
[255]
$ hg log -T '{}'
hg: parse error at 2: not a prefix: end
+ ({}
+ ^ here)
[255]
$ hg debugtemplate -v '{()}'
(template
@@ -2827,10 +2837,14 @@
$ hg log -T '{"date'
hg: parse error at 2: unterminated string
+ ({"date
+ ^ here)
[255]
$ hg log -T '{"foo{date|?}"}'
hg: parse error at 11: syntax error
+ ({"foo{date|?}"}
+ ^ here)
[255]
Thrown an error if a template function doesn't exist
@@ -3362,6 +3376,8 @@
-4
$ hg debugtemplate '{(-)}\n'
hg: parse error at 3: not a prefix: )
+ ({(-)}\n
+ ^ here)
[255]
$ hg debugtemplate '{(-a)}\n'
hg: parse error: negation needs an integer argument
@@ -3527,6 +3543,8 @@
foo
$ hg log -r 2 -T '{if(rev, "{if(rev, \")}")}\n'
hg: parse error at 21: unterminated string
+ ({if(rev, "{if(rev, \")}")}\n
+ ^ here)
[255]
$ hg log -r 2 -T '{if(rev, \"\\"")}\n'
hg: parse error: trailing \ in string
diff --git a/mercurial/templater.py b/mercurial/templater.py
--- a/mercurial/templater.py
+++ b/mercurial/templater.py
@@ -212,35 +212,48 @@
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', unescape(tmpl[pos:stop]), pos)
- pos = stop
- break
- c = tmpl[n:n + 1]
- 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', unescape(tmpl[pos:n - 1]) + c, pos)
- pos = n + 1
- continue
- if n > pos:
- yield ('string', unescape(tmpl[pos:n]), pos)
- if c == quote:
- yield ('end', None, n + 1)
- return
+ try:
+ 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', unescape(tmpl[pos:stop]), pos)
+ pos = stop
+ break
+ c = tmpl[n:n + 1]
+ 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', unescape(tmpl[pos:n - 1]) + c, pos)
+ pos = n + 1
+ continue
+ if n > pos:
+ yield ('string', unescape(tmpl[pos:n]), pos)
+ if c == quote:
+ yield ('end', None, n + 1)
+ return
- parseres, pos = p.parse(tokenize(tmpl, n + 1, stop, '}'))
- if not tmpl.endswith('}', n + 1, pos):
- raise error.ParseError(_("invalid token"), pos)
- yield ('template', parseres, n)
+ parseres, pos = p.parse(tokenize(tmpl, n + 1, stop, '}'))
+ if not tmpl.endswith('}', n + 1, pos):
+ raise error.ParseError(_("invalid token"), pos)
+ yield ('template', parseres, n)
- if quote:
- raise error.ParseError(_("unterminated string"), start)
+ if quote:
+ raise error.ParseError(_("unterminated string"), start)
+ except error.ParseError as inst:
+ if len(inst.args) > 1: # has location
+ loc = inst.args[1]
+ # TODO: Opportunity for improvement! If there is a newline in the
+ # template, this hint does not point to the right place, so skip.
+ if '\n' not in tmpl:
+ # We want the caret to point to the place in the template that
+ # failed to parse, but in a hint we get a open paren at the
+ # start. Therefore, we print "loc" spaces (instead of "loc - 1")
+ # to line up the caret with the location of the error.
+ inst.hint = tmpl + '\n' + ' ' * (loc) + '^ ' + _('here')
+ raise
yield ('end', None, pos)
def _unnesttemplatelist(tree):
To: ryanmce, #hg-reviewers, durin42
Cc: durin42, mercurial-devel
More information about the Mercurial-devel
mailing list