Mercurial > hg
changeset 36669:80d7fb6c2dec
templater: add hint to template parse errors to help locate issues
Previously, we would print the error name and location, but this isn't as
helpful as we can be. Let's add a hint that shows the location where we
encountered the parse error.
Differential Revision: https://phab.mercurial-scm.org/D2608
author | Ryan McElroy <rmcelroy@fb.com> |
---|---|
date | Sat, 03 Mar 2018 14:23:40 -0800 |
parents | e77cee5de1c7 |
children | 44048f1bcee5 |
files | mercurial/templater.py tests/test-command-template.t tests/test-export.t tests/test-log.t |
diffstat | 4 files changed, 77 insertions(+), 27 deletions(-) [+] |
line wrap: on
line diff
--- a/mercurial/templater.py Fri Mar 02 07:17:06 2018 +0530 +++ b/mercurial/templater.py Sat Mar 03 14:23:40 2018 -0800 @@ -213,35 +213,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):
--- a/tests/test-command-template.t Fri Mar 02 07:17:06 2018 +0530 +++ b/tests/test-command-template.t Sat Mar 03 14:23:40 2018 -0800 @@ -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
--- a/tests/test-export.t Fri Mar 02 07:17:06 2018 +0530 +++ b/tests/test-export.t Sat Mar 03 14:23:40 2018 -0800 @@ -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
--- a/tests/test-log.t Fri Mar 02 07:17:06 2018 +0530 +++ b/tests/test-log.t Sat Mar 03 14:23:40 2018 -0800 @@ -2289,6 +2289,23 @@ $ hg --config extensions.names=../names.py log -r 0 --template '{bars}\n' foo +Templater parse errors: + +simple error + $ hg log -r . -T '{shortest(node}' + hg: parse error at 15: unexpected token: end + ({shortest(node} + ^ here) + [255] + +multi-line template with error + $ hg log -r . -T 'line 1 + > line2 + > {shortest(node} + > line4\nline5' + hg: parse error at 28: unexpected token: end + [255] + $ cd .. hg log -f dir across branches