Show More
@@ -24,6 +24,7 b' elements = {' | |||||
24 | "symbol": (0, ("symbol",), None), |
|
24 | "symbol": (0, ("symbol",), None), | |
25 | "string": (0, ("template",), None), |
|
25 | "string": (0, ("template",), None), | |
26 | "rawstring": (0, ("rawstring",), None), |
|
26 | "rawstring": (0, ("rawstring",), None), | |
|
27 | "template": (0, ("template",), None), | |||
27 | "end": (0, None, None), |
|
28 | "end": (0, None, None), | |
28 | } |
|
29 | } | |
29 |
|
30 | |||
@@ -35,6 +36,11 b' def tokenize(program, start, end):' | |||||
35 | pass |
|
36 | pass | |
36 | elif c in "(,)%|": # handle simple operators |
|
37 | elif c in "(,)%|": # handle simple operators | |
37 | yield (c, None, pos) |
|
38 | yield (c, None, pos) | |
|
39 | elif c in '"\'': # handle quoted templates | |||
|
40 | s = pos + 1 | |||
|
41 | data, pos = _parsetemplate(program, s, end, c) | |||
|
42 | yield ('template', data, s) | |||
|
43 | pos -= 1 | |||
38 | elif (c in '"\'' or c == 'r' and |
|
44 | elif (c in '"\'' or c == 'r' and | |
39 | program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings |
|
45 | program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings | |
40 | if c == 'r': |
|
46 | if c == 'r': | |
@@ -89,7 +95,7 b' def tokenize(program, start, end):' | |||||
89 | pos += 1 |
|
95 | pos += 1 | |
90 | token = 'rawstring' |
|
96 | token = 'rawstring' | |
91 | else: |
|
97 | else: | |
92 |
token = ' |
|
98 | token = 'template' | |
93 | quote = program[pos:pos + 2] |
|
99 | quote = program[pos:pos + 2] | |
94 | s = pos = pos + 2 |
|
100 | s = pos = pos + 2 | |
95 | while pos < end: # find closing escaped quote |
|
101 | while pos < end: # find closing escaped quote | |
@@ -102,6 +108,8 b' def tokenize(program, start, end):' | |||||
102 | data = program[s:pos].decode('string-escape') |
|
108 | data = program[s:pos].decode('string-escape') | |
103 | except ValueError: # unbalanced escapes |
|
109 | except ValueError: # unbalanced escapes | |
104 | raise error.ParseError(_("syntax error"), s) |
|
110 | raise error.ParseError(_("syntax error"), s) | |
|
111 | if token == 'template': | |||
|
112 | data = _parsetemplate(data, 0, len(data))[0] | |||
105 | yield (token, data, s) |
|
113 | yield (token, data, s) | |
106 | pos += 1 |
|
114 | pos += 1 | |
107 | break |
|
115 | break | |
@@ -127,27 +135,47 b' def tokenize(program, start, end):' | |||||
127 | pos += 1 |
|
135 | pos += 1 | |
128 | raise error.ParseError(_("unterminated template expansion"), start) |
|
136 | raise error.ParseError(_("unterminated template expansion"), start) | |
129 |
|
137 | |||
130 | def _parsetemplate(tmpl, start, stop): |
|
138 | def _parsetemplate(tmpl, start, stop, quote=''): | |
|
139 | r""" | |||
|
140 | >>> _parsetemplate('foo{bar}"baz', 0, 12) | |||
|
141 | ([('string', 'foo'), ('symbol', 'bar'), ('string', '"baz')], 12) | |||
|
142 | >>> _parsetemplate('foo{bar}"baz', 0, 12, quote='"') | |||
|
143 | ([('string', 'foo'), ('symbol', 'bar')], 9) | |||
|
144 | >>> _parsetemplate('foo"{bar}', 0, 9, quote='"') | |||
|
145 | ([('string', 'foo')], 4) | |||
|
146 | >>> _parsetemplate(r'foo\"bar"baz', 0, 12, quote='"') | |||
|
147 | ([('string', 'foo"'), ('string', 'bar')], 9) | |||
|
148 | >>> _parsetemplate(r'foo\\"bar', 0, 10, quote='"') | |||
|
149 | ([('string', 'foo\\\\')], 6) | |||
|
150 | """ | |||
131 | parsed = [] |
|
151 | parsed = [] | |
|
152 | sepchars = '{' + quote | |||
132 | pos = start |
|
153 | pos = start | |
133 | p = parser.parser(elements) |
|
154 | p = parser.parser(elements) | |
134 | while pos < stop: |
|
155 | while pos < stop: | |
135 |
n = tmpl.find( |
|
156 | n = min((tmpl.find(c, pos, stop) for c in sepchars), | |
|
157 | key=lambda n: (n < 0, n)) | |||
136 | if n < 0: |
|
158 | if n < 0: | |
137 | parsed.append(('string', tmpl[pos:stop])) |
|
159 | parsed.append(('string', tmpl[pos:stop])) | |
138 | pos = stop |
|
160 | pos = stop | |
139 | break |
|
161 | break | |
|
162 | c = tmpl[n] | |||
140 | bs = (n - pos) - len(tmpl[pos:n].rstrip('\\')) |
|
163 | bs = (n - pos) - len(tmpl[pos:n].rstrip('\\')) | |
141 | if bs % 2 == 1: |
|
164 | if bs % 2 == 1: | |
142 | # escaped (e.g. '\{', '\\\{', but not '\\{') |
|
165 | # escaped (e.g. '\{', '\\\{', but not '\\{') | |
143 |
parsed.append(('string', (tmpl[pos:n - 1] + |
|
166 | parsed.append(('string', (tmpl[pos:n - 1] + c))) | |
144 | pos = n + 1 |
|
167 | pos = n + 1 | |
145 | continue |
|
168 | continue | |
146 | if n > pos: |
|
169 | if n > pos: | |
147 | parsed.append(('string', tmpl[pos:n])) |
|
170 | parsed.append(('string', tmpl[pos:n])) | |
|
171 | if c == quote: | |||
|
172 | return parsed, n + 1 | |||
148 |
|
173 | |||
149 | parseres, pos = p.parse(tokenize(tmpl, n + 1, stop)) |
|
174 | parseres, pos = p.parse(tokenize(tmpl, n + 1, stop)) | |
150 | parsed.append(parseres) |
|
175 | parsed.append(parseres) | |
|
176 | ||||
|
177 | if quote: | |||
|
178 | raise error.ParseError(_("unterminated string"), start) | |||
151 | return parsed, pos |
|
179 | return parsed, pos | |
152 |
|
180 | |||
153 | def compiletemplate(tmpl, context): |
|
181 | def compiletemplate(tmpl, context): | |
@@ -182,7 +210,7 b' def getfilter(exp, context):' | |||||
182 |
|
210 | |||
183 | def gettemplate(exp, context): |
|
211 | def gettemplate(exp, context): | |
184 | if exp[0] == 'template': |
|
212 | if exp[0] == 'template': | |
185 |
return compile |
|
213 | return [compileexp(e, context, methods) for e in exp[1]] | |
186 | if exp[0] == 'symbol': |
|
214 | if exp[0] == 'symbol': | |
187 | # unlike runsymbol(), here 'symbol' is always taken as template name |
|
215 | # unlike runsymbol(), here 'symbol' is always taken as template name | |
188 | # even if it exists in mapping. this allows us to override mapping |
|
216 | # even if it exists in mapping. this allows us to override mapping | |
@@ -215,7 +243,7 b' def runsymbol(context, mapping, key):' | |||||
215 | return v |
|
243 | return v | |
216 |
|
244 | |||
217 | def buildtemplate(exp, context): |
|
245 | def buildtemplate(exp, context): | |
218 |
ctmpl = compile |
|
246 | ctmpl = [compileexp(e, context, methods) for e in exp[1]] | |
219 | if len(ctmpl) == 1: |
|
247 | if len(ctmpl) == 1: | |
220 | return ctmpl[0] # fast path for string with no template fragment |
|
248 | return ctmpl[0] # fast path for string with no template fragment | |
221 | return (runtemplate, ctmpl) |
|
249 | return (runtemplate, ctmpl) |
@@ -2541,6 +2541,16 b' Behind the scenes, this will throw Value' | |||||
2541 | abort: template filter 'datefilter' is not compatible with keyword 'author' |
|
2541 | abort: template filter 'datefilter' is not compatible with keyword 'author' | |
2542 | [255] |
|
2542 | [255] | |
2543 |
|
2543 | |||
|
2544 | Error in nested template: | |||
|
2545 | ||||
|
2546 | $ hg log -T '{"date' | |||
|
2547 | hg: parse error at 2: unterminated string | |||
|
2548 | [255] | |||
|
2549 | ||||
|
2550 | $ hg log -T '{"foo{date|=}"}' | |||
|
2551 | hg: parse error at 11: syntax error | |||
|
2552 | [255] | |||
|
2553 | ||||
2544 | Thrown an error if a template function doesn't exist |
|
2554 | Thrown an error if a template function doesn't exist | |
2545 |
|
2555 | |||
2546 | $ hg tip --template '{foo()}\n' |
|
2556 | $ hg tip --template '{foo()}\n' | |
@@ -2952,7 +2962,7 b' escaped single quotes and errors:' | |||||
2952 | $ hg log -r 2 -T "{if(rev, '{if(rev, r\'foo\')}')}"'\n' |
|
2962 | $ hg log -r 2 -T "{if(rev, '{if(rev, r\'foo\')}')}"'\n' | |
2953 | foo |
|
2963 | foo | |
2954 | $ hg log -r 2 -T '{if(rev, "{if(rev, \")}")}\n' |
|
2964 | $ hg log -r 2 -T '{if(rev, "{if(rev, \")}")}\n' | |
2955 |
hg: parse error at |
|
2965 | hg: parse error at 21: unterminated string | |
2956 | [255] |
|
2966 | [255] | |
2957 | $ hg log -r 2 -T '{if(rev, \"\\"")}\n' |
|
2967 | $ hg log -r 2 -T '{if(rev, \"\\"")}\n' | |
2958 | hg: parse error at 11: syntax error |
|
2968 | hg: parse error at 11: syntax error | |
@@ -3069,6 +3079,14 b' Test string escaping in nested expressio' | |||||
3069 | 3:\x6eo user, \x6eo domai\x6e |
|
3079 | 3:\x6eo user, \x6eo domai\x6e | |
3070 | 4:\x5c\x786eew bra\x5c\x786ech |
|
3080 | 4:\x5c\x786eew bra\x5c\x786ech | |
3071 |
|
3081 | |||
|
3082 | Test quotes in nested expression are evaluated just like a $(command) | |||
|
3083 | substitution in POSIX shells: | |||
|
3084 | ||||
|
3085 | $ hg log -R a -r 8 -T '{"{"{rev}:{node|short}"}"}\n' | |||
|
3086 | 8:95c24699272e | |||
|
3087 | $ hg log -R a -r 8 -T '{"{"\{{rev}} \"{node|short}\""}"}\n' | |||
|
3088 | {8} "95c24699272e" | |||
|
3089 | ||||
3072 | Test recursive evaluation: |
|
3090 | Test recursive evaluation: | |
3073 |
|
3091 | |||
3074 | $ hg init r |
|
3092 | $ hg init r |
@@ -26,6 +26,7 b" testmod('mercurial.revset')" | |||||
26 | testmod('mercurial.store') |
|
26 | testmod('mercurial.store') | |
27 | testmod('mercurial.subrepo') |
|
27 | testmod('mercurial.subrepo') | |
28 | testmod('mercurial.templatefilters') |
|
28 | testmod('mercurial.templatefilters') | |
|
29 | testmod('mercurial.templater') | |||
29 | testmod('mercurial.ui') |
|
30 | testmod('mercurial.ui') | |
30 | testmod('mercurial.url') |
|
31 | testmod('mercurial.url') | |
31 | testmod('mercurial.util') |
|
32 | testmod('mercurial.util') |
General Comments 0
You need to be logged in to leave comments.
Login now