83 |
83 |
84 # template parsing |
84 # template parsing |
85 |
85 |
86 elements = { |
86 elements = { |
87 # token-type: binding-strength, primary, prefix, infix, suffix |
87 # token-type: binding-strength, primary, prefix, infix, suffix |
88 "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None), |
88 b"(": (20, None, (b"group", 1, b")"), (b"func", 1, b")"), None), |
89 ".": (18, None, None, (".", 18), None), |
89 b".": (18, None, None, (b".", 18), None), |
90 "%": (15, None, None, ("%", 15), None), |
90 b"%": (15, None, None, (b"%", 15), None), |
91 "|": (15, None, None, ("|", 15), None), |
91 b"|": (15, None, None, (b"|", 15), None), |
92 "*": (5, None, None, ("*", 5), None), |
92 b"*": (5, None, None, (b"*", 5), None), |
93 "/": (5, None, None, ("/", 5), None), |
93 b"/": (5, None, None, (b"/", 5), None), |
94 "+": (4, None, None, ("+", 4), None), |
94 b"+": (4, None, None, (b"+", 4), None), |
95 "-": (4, None, ("negate", 19), ("-", 4), None), |
95 b"-": (4, None, (b"negate", 19), (b"-", 4), None), |
96 "=": (3, None, None, ("keyvalue", 3), None), |
96 b"=": (3, None, None, (b"keyvalue", 3), None), |
97 ",": (2, None, None, ("list", 2), None), |
97 b",": (2, None, None, (b"list", 2), None), |
98 ")": (0, None, None, None, None), |
98 b")": (0, None, None, None, None), |
99 "integer": (0, "integer", None, None, None), |
99 b"integer": (0, b"integer", None, None, None), |
100 "symbol": (0, "symbol", None, None, None), |
100 b"symbol": (0, b"symbol", None, None, None), |
101 "string": (0, "string", None, None, None), |
101 b"string": (0, b"string", None, None, None), |
102 "template": (0, "template", None, None, None), |
102 b"template": (0, b"template", None, None, None), |
103 "end": (0, None, None, None, None), |
103 b"end": (0, None, None, None, None), |
104 } |
104 } |
105 |
105 |
106 |
106 |
107 def tokenize(program, start, end, term=None): |
107 def tokenize(program, start, end, term=None): |
108 """Parse a template expression into a stream of tokens, which must end |
108 """Parse a template expression into a stream of tokens, which must end |
111 program = pycompat.bytestr(program) |
111 program = pycompat.bytestr(program) |
112 while pos < end: |
112 while pos < end: |
113 c = program[pos] |
113 c = program[pos] |
114 if c.isspace(): # skip inter-token whitespace |
114 if c.isspace(): # skip inter-token whitespace |
115 pass |
115 pass |
116 elif c in "(=,).%|+-*/": # handle simple operators |
116 elif c in b"(=,).%|+-*/": # handle simple operators |
117 yield (c, None, pos) |
117 yield (c, None, pos) |
118 elif c in '"\'': # handle quoted templates |
118 elif c in b'"\'': # handle quoted templates |
119 s = pos + 1 |
119 s = pos + 1 |
120 data, pos = _parsetemplate(program, s, end, c) |
120 data, pos = _parsetemplate(program, s, end, c) |
121 yield ('template', data, s) |
121 yield (b'template', data, s) |
122 pos -= 1 |
122 pos -= 1 |
123 elif c == 'r' and program[pos : pos + 2] in ("r'", 'r"'): |
123 elif c == b'r' and program[pos : pos + 2] in (b"r'", b'r"'): |
124 # handle quoted strings |
124 # handle quoted strings |
125 c = program[pos + 1] |
125 c = program[pos + 1] |
126 s = pos = pos + 2 |
126 s = pos = pos + 2 |
127 while pos < end: # find closing quote |
127 while pos < end: # find closing quote |
128 d = program[pos] |
128 d = program[pos] |
129 if d == '\\': # skip over escaped characters |
129 if d == b'\\': # skip over escaped characters |
130 pos += 2 |
130 pos += 2 |
131 continue |
131 continue |
132 if d == c: |
132 if d == c: |
133 yield ('string', program[s:pos], s) |
133 yield (b'string', program[s:pos], s) |
134 break |
134 break |
135 pos += 1 |
135 pos += 1 |
136 else: |
136 else: |
137 raise error.ParseError(_("unterminated string"), s) |
137 raise error.ParseError(_(b"unterminated string"), s) |
138 elif c.isdigit(): |
138 elif c.isdigit(): |
139 s = pos |
139 s = pos |
140 while pos < end: |
140 while pos < end: |
141 d = program[pos] |
141 d = program[pos] |
142 if not d.isdigit(): |
142 if not d.isdigit(): |
143 break |
143 break |
144 pos += 1 |
144 pos += 1 |
145 yield ('integer', program[s:pos], s) |
145 yield (b'integer', program[s:pos], s) |
146 pos -= 1 |
146 pos -= 1 |
147 elif ( |
147 elif ( |
148 c == '\\' |
148 c == b'\\' |
149 and program[pos : pos + 2] in (br"\'", br'\"') |
149 and program[pos : pos + 2] in (br"\'", br'\"') |
150 or c == 'r' |
150 or c == b'r' |
151 and program[pos : pos + 3] in (br"r\'", br'r\"') |
151 and program[pos : pos + 3] in (br"r\'", br'r\"') |
152 ): |
152 ): |
153 # handle escaped quoted strings for compatibility with 2.9.2-3.4, |
153 # handle escaped quoted strings for compatibility with 2.9.2-3.4, |
154 # where some of nested templates were preprocessed as strings and |
154 # where some of nested templates were preprocessed as strings and |
155 # then compiled. therefore, \"...\" was allowed. (issue4733) |
155 # then compiled. therefore, \"...\" was allowed. (issue4733) |
158 # outer template string -> stringify() -> compiletemplate() |
158 # outer template string -> stringify() -> compiletemplate() |
159 # ------------------------ ------------ ------------------ |
159 # ------------------------ ------------ ------------------ |
160 # {f("\\\\ {g(\"\\\"\")}"} \\ {g("\"")} [r'\\', {g("\"")}] |
160 # {f("\\\\ {g(\"\\\"\")}"} \\ {g("\"")} [r'\\', {g("\"")}] |
161 # ~~~~~~~~ |
161 # ~~~~~~~~ |
162 # escaped quoted string |
162 # escaped quoted string |
163 if c == 'r': |
163 if c == b'r': |
164 pos += 1 |
164 pos += 1 |
165 token = 'string' |
165 token = b'string' |
166 else: |
166 else: |
167 token = 'template' |
167 token = b'template' |
168 quote = program[pos : pos + 2] |
168 quote = program[pos : pos + 2] |
169 s = pos = pos + 2 |
169 s = pos = pos + 2 |
170 while pos < end: # find closing escaped quote |
170 while pos < end: # find closing escaped quote |
171 if program.startswith('\\\\\\', pos, end): |
171 if program.startswith(b'\\\\\\', pos, end): |
172 pos += 4 # skip over double escaped characters |
172 pos += 4 # skip over double escaped characters |
173 continue |
173 continue |
174 if program.startswith(quote, pos, end): |
174 if program.startswith(quote, pos, end): |
175 # interpret as if it were a part of an outer string |
175 # interpret as if it were a part of an outer string |
176 data = parser.unescapestr(program[s:pos]) |
176 data = parser.unescapestr(program[s:pos]) |
177 if token == 'template': |
177 if token == b'template': |
178 data = _parsetemplate(data, 0, len(data))[0] |
178 data = _parsetemplate(data, 0, len(data))[0] |
179 yield (token, data, s) |
179 yield (token, data, s) |
180 pos += 1 |
180 pos += 1 |
181 break |
181 break |
182 pos += 1 |
182 pos += 1 |
183 else: |
183 else: |
184 raise error.ParseError(_("unterminated string"), s) |
184 raise error.ParseError(_(b"unterminated string"), s) |
185 elif c.isalnum() or c in '_': |
185 elif c.isalnum() or c in b'_': |
186 s = pos |
186 s = pos |
187 pos += 1 |
187 pos += 1 |
188 while pos < end: # find end of symbol |
188 while pos < end: # find end of symbol |
189 d = program[pos] |
189 d = program[pos] |
190 if not (d.isalnum() or d == "_"): |
190 if not (d.isalnum() or d == b"_"): |
191 break |
191 break |
192 pos += 1 |
192 pos += 1 |
193 sym = program[s:pos] |
193 sym = program[s:pos] |
194 yield ('symbol', sym, s) |
194 yield (b'symbol', sym, s) |
195 pos -= 1 |
195 pos -= 1 |
196 elif c == term: |
196 elif c == term: |
197 yield ('end', None, pos) |
197 yield (b'end', None, pos) |
198 return |
198 return |
199 else: |
199 else: |
200 raise error.ParseError(_("syntax error"), pos) |
200 raise error.ParseError(_(b"syntax error"), pos) |
201 pos += 1 |
201 pos += 1 |
202 if term: |
202 if term: |
203 raise error.ParseError(_("unterminated template expansion"), start) |
203 raise error.ParseError(_(b"unterminated template expansion"), start) |
204 yield ('end', None, pos) |
204 yield (b'end', None, pos) |
205 |
205 |
206 |
206 |
207 def _parsetemplate(tmpl, start, stop, quote=''): |
207 def _parsetemplate(tmpl, start, stop, quote=b''): |
208 r""" |
208 r""" |
209 >>> _parsetemplate(b'foo{bar}"baz', 0, 12) |
209 >>> _parsetemplate(b'foo{bar}"baz', 0, 12) |
210 ([('string', 'foo'), ('symbol', 'bar'), ('string', '"baz')], 12) |
210 ([('string', 'foo'), ('symbol', 'bar'), ('string', '"baz')], 12) |
211 >>> _parsetemplate(b'foo{bar}"baz', 0, 12, quote=b'"') |
211 >>> _parsetemplate(b'foo{bar}"baz', 0, 12, quote=b'"') |
212 ([('string', 'foo'), ('symbol', 'bar')], 9) |
212 ([('string', 'foo'), ('symbol', 'bar')], 9) |
217 >>> _parsetemplate(br'foo\\"bar', 0, 10, quote=b'"') |
217 >>> _parsetemplate(br'foo\\"bar', 0, 10, quote=b'"') |
218 ([('string', 'foo\\')], 6) |
218 ([('string', 'foo\\')], 6) |
219 """ |
219 """ |
220 parsed = [] |
220 parsed = [] |
221 for typ, val, pos in _scantemplate(tmpl, start, stop, quote): |
221 for typ, val, pos in _scantemplate(tmpl, start, stop, quote): |
222 if typ == 'string': |
222 if typ == b'string': |
223 parsed.append((typ, val)) |
223 parsed.append((typ, val)) |
224 elif typ == 'template': |
224 elif typ == b'template': |
225 parsed.append(val) |
225 parsed.append(val) |
226 elif typ == 'end': |
226 elif typ == b'end': |
227 return parsed, pos |
227 return parsed, pos |
228 else: |
228 else: |
229 raise error.ProgrammingError('unexpected type: %s' % typ) |
229 raise error.ProgrammingError(b'unexpected type: %s' % typ) |
230 raise error.ProgrammingError('unterminated scanning of template') |
230 raise error.ProgrammingError(b'unterminated scanning of template') |
231 |
231 |
232 |
232 |
233 def scantemplate(tmpl, raw=False): |
233 def scantemplate(tmpl, raw=False): |
234 r"""Scan (type, start, end) positions of outermost elements in template |
234 r"""Scan (type, start, end) positions of outermost elements in template |
235 |
235 |
250 """ |
250 """ |
251 last = None |
251 last = None |
252 for typ, val, pos in _scantemplate(tmpl, 0, len(tmpl), raw=raw): |
252 for typ, val, pos in _scantemplate(tmpl, 0, len(tmpl), raw=raw): |
253 if last: |
253 if last: |
254 yield last + (pos,) |
254 yield last + (pos,) |
255 if typ == 'end': |
255 if typ == b'end': |
256 return |
256 return |
257 else: |
257 else: |
258 last = (typ, pos) |
258 last = (typ, pos) |
259 raise error.ProgrammingError('unterminated scanning of template') |
259 raise error.ProgrammingError(b'unterminated scanning of template') |
260 |
260 |
261 |
261 |
262 def _scantemplate(tmpl, start, stop, quote='', raw=False): |
262 def _scantemplate(tmpl, start, stop, quote=b'', raw=False): |
263 """Parse template string into chunks of strings and template expressions""" |
263 """Parse template string into chunks of strings and template expressions""" |
264 sepchars = '{' + quote |
264 sepchars = b'{' + quote |
265 unescape = [parser.unescapestr, pycompat.identity][raw] |
265 unescape = [parser.unescapestr, pycompat.identity][raw] |
266 pos = start |
266 pos = start |
267 p = parser.parser(elements) |
267 p = parser.parser(elements) |
268 try: |
268 try: |
269 while pos < stop: |
269 while pos < stop: |
270 n = min( |
270 n = min( |
271 (tmpl.find(c, pos, stop) for c in pycompat.bytestr(sepchars)), |
271 (tmpl.find(c, pos, stop) for c in pycompat.bytestr(sepchars)), |
272 key=lambda n: (n < 0, n), |
272 key=lambda n: (n < 0, n), |
273 ) |
273 ) |
274 if n < 0: |
274 if n < 0: |
275 yield ('string', unescape(tmpl[pos:stop]), pos) |
275 yield (b'string', unescape(tmpl[pos:stop]), pos) |
276 pos = stop |
276 pos = stop |
277 break |
277 break |
278 c = tmpl[n : n + 1] |
278 c = tmpl[n : n + 1] |
279 bs = 0 # count leading backslashes |
279 bs = 0 # count leading backslashes |
280 if not raw: |
280 if not raw: |
281 bs = (n - pos) - len(tmpl[pos:n].rstrip('\\')) |
281 bs = (n - pos) - len(tmpl[pos:n].rstrip(b'\\')) |
282 if bs % 2 == 1: |
282 if bs % 2 == 1: |
283 # escaped (e.g. '\{', '\\\{', but not '\\{') |
283 # escaped (e.g. '\{', '\\\{', but not '\\{') |
284 yield ('string', unescape(tmpl[pos : n - 1]) + c, pos) |
284 yield (b'string', unescape(tmpl[pos : n - 1]) + c, pos) |
285 pos = n + 1 |
285 pos = n + 1 |
286 continue |
286 continue |
287 if n > pos: |
287 if n > pos: |
288 yield ('string', unescape(tmpl[pos:n]), pos) |
288 yield (b'string', unescape(tmpl[pos:n]), pos) |
289 if c == quote: |
289 if c == quote: |
290 yield ('end', None, n + 1) |
290 yield (b'end', None, n + 1) |
291 return |
291 return |
292 |
292 |
293 parseres, pos = p.parse(tokenize(tmpl, n + 1, stop, '}')) |
293 parseres, pos = p.parse(tokenize(tmpl, n + 1, stop, b'}')) |
294 if not tmpl.startswith('}', pos): |
294 if not tmpl.startswith(b'}', pos): |
295 raise error.ParseError(_("invalid token"), pos) |
295 raise error.ParseError(_(b"invalid token"), pos) |
296 yield ('template', parseres, n) |
296 yield (b'template', parseres, n) |
297 pos += 1 |
297 pos += 1 |
298 |
298 |
299 if quote: |
299 if quote: |
300 raise error.ParseError(_("unterminated string"), start) |
300 raise error.ParseError(_(b"unterminated string"), start) |
301 except error.ParseError as inst: |
301 except error.ParseError as inst: |
302 if len(inst.args) > 1: # has location |
302 if len(inst.args) > 1: # has location |
303 loc = inst.args[1] |
303 loc = inst.args[1] |
304 # Offset the caret location by the number of newlines before the |
304 # Offset the caret location by the number of newlines before the |
305 # location of the error, since we will replace one-char newlines |
305 # location of the error, since we will replace one-char newlines |
306 # with the two-char literal r'\n'. |
306 # with the two-char literal r'\n'. |
307 offset = tmpl[:loc].count('\n') |
307 offset = tmpl[:loc].count(b'\n') |
308 tmpl = tmpl.replace('\n', br'\n') |
308 tmpl = tmpl.replace(b'\n', br'\n') |
309 # We want the caret to point to the place in the template that |
309 # We want the caret to point to the place in the template that |
310 # failed to parse, but in a hint we get a open paren at the |
310 # failed to parse, but in a hint we get a open paren at the |
311 # start. Therefore, we print "loc + 1" spaces (instead of "loc") |
311 # start. Therefore, we print "loc + 1" spaces (instead of "loc") |
312 # to line up the caret with the location of the error. |
312 # to line up the caret with the location of the error. |
313 inst.hint = ( |
313 inst.hint = ( |
314 tmpl + '\n' + ' ' * (loc + 1 + offset) + '^ ' + _('here') |
314 tmpl + b'\n' + b' ' * (loc + 1 + offset) + b'^ ' + _(b'here') |
315 ) |
315 ) |
316 raise |
316 raise |
317 yield ('end', None, pos) |
317 yield (b'end', None, pos) |
318 |
318 |
319 |
319 |
320 def _unnesttemplatelist(tree): |
320 def _unnesttemplatelist(tree): |
321 """Expand list of templates to node tuple |
321 """Expand list of templates to node tuple |
322 |
322 |
337 (string 'foo') |
337 (string 'foo') |
338 """ |
338 """ |
339 if not isinstance(tree, tuple): |
339 if not isinstance(tree, tuple): |
340 return tree |
340 return tree |
341 op = tree[0] |
341 op = tree[0] |
342 if op != 'template': |
342 if op != b'template': |
343 return (op,) + tuple(_unnesttemplatelist(x) for x in tree[1:]) |
343 return (op,) + tuple(_unnesttemplatelist(x) for x in tree[1:]) |
344 |
344 |
345 assert len(tree) == 2 |
345 assert len(tree) == 2 |
346 xs = tuple(_unnesttemplatelist(x) for x in tree[1]) |
346 xs = tuple(_unnesttemplatelist(x) for x in tree[1]) |
347 if not xs: |
347 if not xs: |
348 return ('string', '') # empty template "" |
348 return (b'string', b'') # empty template "" |
349 elif len(xs) == 1 and xs[0][0] == 'string': |
349 elif len(xs) == 1 and xs[0][0] == b'string': |
350 return xs[0] # fast path for string with no template fragment "x" |
350 return xs[0] # fast path for string with no template fragment "x" |
351 else: |
351 else: |
352 return (op,) + xs |
352 return (op,) + xs |
353 |
353 |
354 |
354 |
355 def parse(tmpl): |
355 def parse(tmpl): |
356 """Parse template string into tree""" |
356 """Parse template string into tree""" |
357 parsed, pos = _parsetemplate(tmpl, 0, len(tmpl)) |
357 parsed, pos = _parsetemplate(tmpl, 0, len(tmpl)) |
358 assert pos == len(tmpl), 'unquoted template should be consumed' |
358 assert pos == len(tmpl), b'unquoted template should be consumed' |
359 return _unnesttemplatelist(('template', parsed)) |
359 return _unnesttemplatelist((b'template', parsed)) |
360 |
360 |
361 |
361 |
362 def _parseexpr(expr): |
362 def _parseexpr(expr): |
363 """Parse a template expression into tree |
363 """Parse a template expression into tree |
364 |
364 |
376 ParseError: ('invalid token', 7) |
376 ParseError: ('invalid token', 7) |
377 """ |
377 """ |
378 p = parser.parser(elements) |
378 p = parser.parser(elements) |
379 tree, pos = p.parse(tokenize(expr, 0, len(expr))) |
379 tree, pos = p.parse(tokenize(expr, 0, len(expr))) |
380 if pos != len(expr): |
380 if pos != len(expr): |
381 raise error.ParseError(_('invalid token'), pos) |
381 raise error.ParseError(_(b'invalid token'), pos) |
382 return _unnesttemplatelist(tree) |
382 return _unnesttemplatelist(tree) |
383 |
383 |
384 |
384 |
385 def prettyformat(tree): |
385 def prettyformat(tree): |
386 return parser.prettyformat(tree, ('integer', 'string', 'symbol')) |
386 return parser.prettyformat(tree, (b'integer', b'string', b'symbol')) |
387 |
387 |
388 |
388 |
389 def compileexp(exp, context, curmethods): |
389 def compileexp(exp, context, curmethods): |
390 """Compile parsed template tree to (func, data) pair""" |
390 """Compile parsed template tree to (func, data) pair""" |
391 if not exp: |
391 if not exp: |
392 raise error.ParseError(_("missing argument")) |
392 raise error.ParseError(_(b"missing argument")) |
393 t = exp[0] |
393 t = exp[0] |
394 return curmethods[t](exp, context) |
394 return curmethods[t](exp, context) |
395 |
395 |
396 |
396 |
397 # template evaluation |
397 # template evaluation |
398 |
398 |
399 |
399 |
400 def getsymbol(exp): |
400 def getsymbol(exp): |
401 if exp[0] == 'symbol': |
401 if exp[0] == b'symbol': |
402 return exp[1] |
402 return exp[1] |
403 raise error.ParseError(_("expected a symbol, got '%s'") % exp[0]) |
403 raise error.ParseError(_(b"expected a symbol, got '%s'") % exp[0]) |
404 |
404 |
405 |
405 |
406 def getlist(x): |
406 def getlist(x): |
407 if not x: |
407 if not x: |
408 return [] |
408 return [] |
409 if x[0] == 'list': |
409 if x[0] == b'list': |
410 return getlist(x[1]) + [x[2]] |
410 return getlist(x[1]) + [x[2]] |
411 return [x] |
411 return [x] |
412 |
412 |
413 |
413 |
414 def gettemplate(exp, context): |
414 def gettemplate(exp, context): |
415 """Compile given template tree or load named template from map file; |
415 """Compile given template tree or load named template from map file; |
416 returns (func, data) pair""" |
416 returns (func, data) pair""" |
417 if exp[0] in ('template', 'string'): |
417 if exp[0] in (b'template', b'string'): |
418 return compileexp(exp, context, methods) |
418 return compileexp(exp, context, methods) |
419 if exp[0] == 'symbol': |
419 if exp[0] == b'symbol': |
420 # unlike runsymbol(), here 'symbol' is always taken as template name |
420 # unlike runsymbol(), here 'symbol' is always taken as template name |
421 # even if it exists in mapping. this allows us to override mapping |
421 # even if it exists in mapping. this allows us to override mapping |
422 # by web templates, e.g. 'changelogtag' is redefined in map file. |
422 # by web templates, e.g. 'changelogtag' is redefined in map file. |
423 return context._load(exp[1]) |
423 return context._load(exp[1]) |
424 raise error.ParseError(_("expected template specifier")) |
424 raise error.ParseError(_(b"expected template specifier")) |
425 |
425 |
426 |
426 |
427 def _runrecursivesymbol(context, mapping, key): |
427 def _runrecursivesymbol(context, mapping, key): |
428 raise error.Abort(_("recursive reference '%s' in template") % key) |
428 raise error.Abort(_(b"recursive reference '%s' in template") % key) |
429 |
429 |
430 |
430 |
431 def buildtemplate(exp, context): |
431 def buildtemplate(exp, context): |
432 ctmpl = [compileexp(e, context, methods) for e in exp[1:]] |
432 ctmpl = [compileexp(e, context, methods) for e in exp[1:]] |
433 return (templateutil.runtemplate, ctmpl) |
433 return (templateutil.runtemplate, ctmpl) |
476 args = _buildfuncargs(exp[2], context, exprmethods, n, f._argspec) |
476 args = _buildfuncargs(exp[2], context, exprmethods, n, f._argspec) |
477 return (f, args) |
477 return (f, args) |
478 if n in context._filters: |
478 if n in context._filters: |
479 args = _buildfuncargs(exp[2], context, exprmethods, n, argspec=None) |
479 args = _buildfuncargs(exp[2], context, exprmethods, n, argspec=None) |
480 if len(args) != 1: |
480 if len(args) != 1: |
481 raise error.ParseError(_("filter %s expects one argument") % n) |
481 raise error.ParseError(_(b"filter %s expects one argument") % n) |
482 f = context._filters[n] |
482 f = context._filters[n] |
483 return (templateutil.runfilter, (args[0], f)) |
483 return (templateutil.runfilter, (args[0], f)) |
484 raise error.ParseError(_("unknown function '%s'") % n) |
484 raise error.ParseError(_(b"unknown function '%s'") % n) |
485 |
485 |
486 |
486 |
487 def _buildfuncargs(exp, context, curmethods, funcname, argspec): |
487 def _buildfuncargs(exp, context, curmethods, funcname, argspec): |
488 """Compile parsed tree of function arguments into list or dict of |
488 """Compile parsed tree of function arguments into list or dict of |
489 (func, data) pairs |
489 (func, data) pairs |
529 compargs.update(compiledict(treeargs)) |
529 compargs.update(compiledict(treeargs)) |
530 return compargs |
530 return compargs |
531 |
531 |
532 |
532 |
533 def buildkeyvaluepair(exp, content): |
533 def buildkeyvaluepair(exp, content): |
534 raise error.ParseError(_("can't use a key-value pair in this context")) |
534 raise error.ParseError(_(b"can't use a key-value pair in this context")) |
535 |
535 |
536 |
536 |
537 def buildlist(exp, context): |
537 def buildlist(exp, context): |
538 raise error.ParseError( |
538 raise error.ParseError( |
539 _("can't use a list in this context"), |
539 _(b"can't use a list in this context"), |
540 hint=_('check place of comma and parens'), |
540 hint=_(b'check place of comma and parens'), |
541 ) |
541 ) |
542 |
542 |
543 |
543 |
544 # methods to interpret function arguments or inner expressions (e.g. {_(x)}) |
544 # methods to interpret function arguments or inner expressions (e.g. {_(x)}) |
545 exprmethods = { |
545 exprmethods = { |
546 "integer": lambda e, c: (templateutil.runinteger, e[1]), |
546 b"integer": lambda e, c: (templateutil.runinteger, e[1]), |
547 "string": lambda e, c: (templateutil.runstring, e[1]), |
547 b"string": lambda e, c: (templateutil.runstring, e[1]), |
548 "symbol": lambda e, c: (templateutil.runsymbol, e[1]), |
548 b"symbol": lambda e, c: (templateutil.runsymbol, e[1]), |
549 "template": buildtemplate, |
549 b"template": buildtemplate, |
550 "group": lambda e, c: compileexp(e[1], c, exprmethods), |
550 b"group": lambda e, c: compileexp(e[1], c, exprmethods), |
551 ".": buildmember, |
551 b".": buildmember, |
552 "|": buildfilter, |
552 b"|": buildfilter, |
553 "%": buildmap, |
553 b"%": buildmap, |
554 "func": buildfunc, |
554 b"func": buildfunc, |
555 "keyvalue": buildkeyvaluepair, |
555 b"keyvalue": buildkeyvaluepair, |
556 "list": buildlist, |
556 b"list": buildlist, |
557 "+": lambda e, c: buildarithmetic(e, c, lambda a, b: a + b), |
557 b"+": lambda e, c: buildarithmetic(e, c, lambda a, b: a + b), |
558 "-": lambda e, c: buildarithmetic(e, c, lambda a, b: a - b), |
558 b"-": lambda e, c: buildarithmetic(e, c, lambda a, b: a - b), |
559 "negate": buildnegate, |
559 b"negate": buildnegate, |
560 "*": lambda e, c: buildarithmetic(e, c, lambda a, b: a * b), |
560 b"*": lambda e, c: buildarithmetic(e, c, lambda a, b: a * b), |
561 "/": lambda e, c: buildarithmetic(e, c, lambda a, b: a // b), |
561 b"/": lambda e, c: buildarithmetic(e, c, lambda a, b: a // b), |
562 } |
562 } |
563 |
563 |
564 # methods to interpret top-level template (e.g. {x}, {x|_}, {x % "y"}) |
564 # methods to interpret top-level template (e.g. {x}, {x|_}, {x % "y"}) |
565 methods = exprmethods.copy() |
565 methods = exprmethods.copy() |
566 methods["integer"] = exprmethods["symbol"] # '{1}' as variable |
566 methods[b"integer"] = exprmethods[b"symbol"] # '{1}' as variable |
567 |
567 |
568 |
568 |
569 class _aliasrules(parser.basealiasrules): |
569 class _aliasrules(parser.basealiasrules): |
570 """Parsing and expansion rule set of template aliases""" |
570 """Parsing and expansion rule set of template aliases""" |
571 |
571 |
572 _section = _('template alias') |
572 _section = _(b'template alias') |
573 _parse = staticmethod(_parseexpr) |
573 _parse = staticmethod(_parseexpr) |
574 |
574 |
575 @staticmethod |
575 @staticmethod |
576 def _trygetfunc(tree): |
576 def _trygetfunc(tree): |
577 """Return (name, args) if tree is func(...) or ...|filter; otherwise |
577 """Return (name, args) if tree is func(...) or ...|filter; otherwise |
578 None""" |
578 None""" |
579 if tree[0] == 'func' and tree[1][0] == 'symbol': |
579 if tree[0] == b'func' and tree[1][0] == b'symbol': |
580 return tree[1][1], getlist(tree[2]) |
580 return tree[1][1], getlist(tree[2]) |
581 if tree[0] == '|' and tree[2][0] == 'symbol': |
581 if tree[0] == b'|' and tree[2][0] == b'symbol': |
582 return tree[2][1], [tree[1]] |
582 return tree[2][1], [tree[1]] |
583 |
583 |
584 |
584 |
585 def expandaliases(tree, aliases): |
585 def expandaliases(tree, aliases): |
586 """Return new tree of aliases are expanded""" |
586 """Return new tree of aliases are expanded""" |
781 |
781 |
782 |
782 |
783 def stylelist(): |
783 def stylelist(): |
784 paths = templatepaths() |
784 paths = templatepaths() |
785 if not paths: |
785 if not paths: |
786 return _('no templates found, try `hg debuginstall` for more info') |
786 return _(b'no templates found, try `hg debuginstall` for more info') |
787 dirlist = os.listdir(paths[0]) |
787 dirlist = os.listdir(paths[0]) |
788 stylelist = [] |
788 stylelist = [] |
789 for file in dirlist: |
789 for file in dirlist: |
790 split = file.split(".") |
790 split = file.split(b".") |
791 if split[-1] in ('orig', 'rej'): |
791 if split[-1] in (b'orig', b'rej'): |
792 continue |
792 continue |
793 if split[0] == "map-cmdline": |
793 if split[0] == b"map-cmdline": |
794 stylelist.append(split[1]) |
794 stylelist.append(split[1]) |
795 return ", ".join(sorted(stylelist)) |
795 return b", ".join(sorted(stylelist)) |
796 |
796 |
797 |
797 |
798 def _readmapfile(mapfile): |
798 def _readmapfile(mapfile): |
799 """Load template elements from the given map file""" |
799 """Load template elements from the given map file""" |
800 if not os.path.exists(mapfile): |
800 if not os.path.exists(mapfile): |
801 raise error.Abort( |
801 raise error.Abort( |
802 _("style '%s' not found") % mapfile, |
802 _(b"style '%s' not found") % mapfile, |
803 hint=_("available styles: %s") % stylelist(), |
803 hint=_(b"available styles: %s") % stylelist(), |
804 ) |
804 ) |
805 |
805 |
806 base = os.path.dirname(mapfile) |
806 base = os.path.dirname(mapfile) |
807 conf = config.config(includepaths=templatepaths()) |
807 conf = config.config(includepaths=templatepaths()) |
808 conf.read(mapfile, remap={'': 'templates'}) |
808 conf.read(mapfile, remap={b'': b'templates'}) |
809 |
809 |
810 cache = {} |
810 cache = {} |
811 tmap = {} |
811 tmap = {} |
812 aliases = [] |
812 aliases = [] |
813 |
813 |
814 val = conf.get('templates', '__base__') |
814 val = conf.get(b'templates', b'__base__') |
815 if val and val[0] not in "'\"": |
815 if val and val[0] not in b"'\"": |
816 # treat as a pointer to a base class for this style |
816 # treat as a pointer to a base class for this style |
817 path = util.normpath(os.path.join(base, val)) |
817 path = util.normpath(os.path.join(base, val)) |
818 |
818 |
819 # fallback check in template paths |
819 # fallback check in template paths |
820 if not os.path.exists(path): |
820 if not os.path.exists(path): |
821 for p in templatepaths(): |
821 for p in templatepaths(): |
822 p2 = util.normpath(os.path.join(p, val)) |
822 p2 = util.normpath(os.path.join(p, val)) |
823 if os.path.isfile(p2): |
823 if os.path.isfile(p2): |
824 path = p2 |
824 path = p2 |
825 break |
825 break |
826 p3 = util.normpath(os.path.join(p2, "map")) |
826 p3 = util.normpath(os.path.join(p2, b"map")) |
827 if os.path.isfile(p3): |
827 if os.path.isfile(p3): |
828 path = p3 |
828 path = p3 |
829 break |
829 break |
830 |
830 |
831 cache, tmap, aliases = _readmapfile(path) |
831 cache, tmap, aliases = _readmapfile(path) |
832 |
832 |
833 for key, val in conf['templates'].items(): |
833 for key, val in conf[b'templates'].items(): |
834 if not val: |
834 if not val: |
835 raise error.ParseError( |
835 raise error.ParseError( |
836 _('missing value'), conf.source('templates', key) |
836 _(b'missing value'), conf.source(b'templates', key) |
837 ) |
837 ) |
838 if val[0] in "'\"": |
838 if val[0] in b"'\"": |
839 if val[0] != val[-1]: |
839 if val[0] != val[-1]: |
840 raise error.ParseError( |
840 raise error.ParseError( |
841 _('unmatched quotes'), conf.source('templates', key) |
841 _(b'unmatched quotes'), conf.source(b'templates', key) |
842 ) |
842 ) |
843 cache[key] = unquotestring(val) |
843 cache[key] = unquotestring(val) |
844 elif key != '__base__': |
844 elif key != b'__base__': |
845 tmap[key] = os.path.join(base, val) |
845 tmap[key] = os.path.join(base, val) |
846 aliases.extend(conf['templatealias'].items()) |
846 aliases.extend(conf[b'templatealias'].items()) |
847 return cache, tmap, aliases |
847 return cache, tmap, aliases |
848 |
848 |
849 |
849 |
850 class loader(object): |
850 class loader(object): |
851 """Load template fragments optionally from a map file""" |
851 """Load template fragments optionally from a map file""" |
885 |
885 |
886 def _findsymbolsused(self, tree, syms): |
886 def _findsymbolsused(self, tree, syms): |
887 if not tree: |
887 if not tree: |
888 return |
888 return |
889 op = tree[0] |
889 op = tree[0] |
890 if op == 'symbol': |
890 if op == b'symbol': |
891 s = tree[1] |
891 s = tree[1] |
892 if s in syms[0]: |
892 if s in syms[0]: |
893 return # avoid recursion: s -> cache[s] -> s |
893 return # avoid recursion: s -> cache[s] -> s |
894 syms[0].add(s) |
894 syms[0].add(s) |
895 if s in self.cache or s in self._map: |
895 if s in self.cache or s in self._map: |
896 # s may be a reference for named template |
896 # s may be a reference for named template |
897 self._findsymbolsused(self.load(s), syms) |
897 self._findsymbolsused(self.load(s), syms) |
898 return |
898 return |
899 if op in {'integer', 'string'}: |
899 if op in {b'integer', b'string'}: |
900 return |
900 return |
901 # '{arg|func}' == '{func(arg)}' |
901 # '{arg|func}' == '{func(arg)}' |
902 if op == '|': |
902 if op == b'|': |
903 syms[1].add(getsymbol(tree[2])) |
903 syms[1].add(getsymbol(tree[2])) |
904 self._findsymbolsused(tree[1], syms) |
904 self._findsymbolsused(tree[1], syms) |
905 return |
905 return |
906 if op == 'func': |
906 if op == b'func': |
907 syms[1].add(getsymbol(tree[1])) |
907 syms[1].add(getsymbol(tree[1])) |
908 self._findsymbolsused(tree[2], syms) |
908 self._findsymbolsused(tree[2], syms) |
909 return |
909 return |
910 for x in tree[1:]: |
910 for x in tree[1:]: |
911 self._findsymbolsused(x, syms) |
911 self._findsymbolsused(x, syms) |