##// END OF EJS Templates
templater: mark .keytype as a private attribute
Yuya Nishihara -
r38287:49ef1539 default
parent child Browse files
Show More
@@ -1,823 +1,823 b''
1 1 # templateutil.py - utility for template evaluation
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import abc
11 11 import types
12 12
13 13 from .i18n import _
14 14 from . import (
15 15 error,
16 16 pycompat,
17 17 util,
18 18 )
19 19 from .utils import (
20 20 dateutil,
21 21 stringutil,
22 22 )
23 23
24 24 class ResourceUnavailable(error.Abort):
25 25 pass
26 26
27 27 class TemplateNotFound(error.Abort):
28 28 pass
29 29
30 30 class wrapped(object):
31 31 """Object requiring extra conversion prior to displaying or processing
32 32 as value
33 33
34 34 Use unwrapvalue(), unwrapastype(), or unwraphybrid() to obtain the inner
35 35 object.
36 36 """
37 37
38 38 __metaclass__ = abc.ABCMeta
39 39
40 40 @abc.abstractmethod
41 41 def contains(self, context, mapping, item):
42 42 """Test if the specified item is in self
43 43
44 44 The item argument may be a wrapped object.
45 45 """
46 46
47 47 @abc.abstractmethod
48 48 def getmember(self, context, mapping, key):
49 49 """Return a member item for the specified key
50 50
51 51 The key argument may be a wrapped object.
52 52 A returned object may be either a wrapped object or a pure value
53 53 depending on the self type.
54 54 """
55 55
56 56 @abc.abstractmethod
57 57 def getmin(self, context, mapping):
58 58 """Return the smallest item, which may be either a wrapped or a pure
59 59 value depending on the self type"""
60 60
61 61 @abc.abstractmethod
62 62 def getmax(self, context, mapping):
63 63 """Return the largest item, which may be either a wrapped or a pure
64 64 value depending on the self type"""
65 65
66 66 @abc.abstractmethod
67 67 def itermaps(self, context):
68 68 """Yield each template mapping"""
69 69
70 70 @abc.abstractmethod
71 71 def join(self, context, mapping, sep):
72 72 """Join items with the separator; Returns a bytes or (possibly nested)
73 73 generator of bytes
74 74
75 75 A pre-configured template may be rendered per item if this container
76 76 holds unprintable items.
77 77 """
78 78
79 79 @abc.abstractmethod
80 80 def show(self, context, mapping):
81 81 """Return a bytes or (possibly nested) generator of bytes representing
82 82 the underlying object
83 83
84 84 A pre-configured template may be rendered if the underlying object is
85 85 not printable.
86 86 """
87 87
88 88 @abc.abstractmethod
89 89 def tovalue(self, context, mapping):
90 90 """Move the inner value object out or create a value representation
91 91
92 92 A returned value must be serializable by templaterfilters.json().
93 93 """
94 94
95 95 class wrappedbytes(wrapped):
96 96 """Wrapper for byte string"""
97 97
98 98 def __init__(self, value):
99 99 self._value = value
100 100
101 101 def contains(self, context, mapping, item):
102 102 item = stringify(context, mapping, item)
103 103 return item in self._value
104 104
105 105 def getmember(self, context, mapping, key):
106 106 raise error.ParseError(_('%r is not a dictionary')
107 107 % pycompat.bytestr(self._value))
108 108
109 109 def getmin(self, context, mapping):
110 110 return self._getby(context, mapping, min)
111 111
112 112 def getmax(self, context, mapping):
113 113 return self._getby(context, mapping, max)
114 114
115 115 def _getby(self, context, mapping, func):
116 116 if not self._value:
117 117 raise error.ParseError(_('empty string'))
118 118 return func(pycompat.iterbytestr(self._value))
119 119
120 120 def itermaps(self, context):
121 121 raise error.ParseError(_('%r is not iterable of mappings')
122 122 % pycompat.bytestr(self._value))
123 123
124 124 def join(self, context, mapping, sep):
125 125 return joinitems(pycompat.iterbytestr(self._value), sep)
126 126
127 127 def show(self, context, mapping):
128 128 return self._value
129 129
130 130 def tovalue(self, context, mapping):
131 131 return self._value
132 132
133 133 class wrappedvalue(wrapped):
134 134 """Generic wrapper for pure non-list/dict/bytes value"""
135 135
136 136 def __init__(self, value):
137 137 self._value = value
138 138
139 139 def contains(self, context, mapping, item):
140 140 raise error.ParseError(_("%r is not iterable") % self._value)
141 141
142 142 def getmember(self, context, mapping, key):
143 143 raise error.ParseError(_('%r is not a dictionary') % self._value)
144 144
145 145 def getmin(self, context, mapping):
146 146 raise error.ParseError(_("%r is not iterable") % self._value)
147 147
148 148 def getmax(self, context, mapping):
149 149 raise error.ParseError(_("%r is not iterable") % self._value)
150 150
151 151 def itermaps(self, context):
152 152 raise error.ParseError(_('%r is not iterable of mappings')
153 153 % self._value)
154 154
155 155 def join(self, context, mapping, sep):
156 156 raise error.ParseError(_('%r is not iterable') % self._value)
157 157
158 158 def show(self, context, mapping):
159 159 return pycompat.bytestr(self._value)
160 160
161 161 def tovalue(self, context, mapping):
162 162 return self._value
163 163
164 164 # stub for representing a date type; may be a real date type that can
165 165 # provide a readable string value
166 166 class date(object):
167 167 pass
168 168
169 169 class hybrid(wrapped):
170 170 """Wrapper for list or dict to support legacy template
171 171
172 172 This class allows us to handle both:
173 173 - "{files}" (legacy command-line-specific list hack) and
174 174 - "{files % '{file}\n'}" (hgweb-style with inlining and function support)
175 175 and to access raw values:
176 176 - "{ifcontains(file, files, ...)}", "{ifcontains(key, extras, ...)}"
177 177 - "{get(extras, key)}"
178 178 - "{files|json}"
179 179 """
180 180
181 181 def __init__(self, gen, values, makemap, joinfmt, keytype=None):
182 182 self._gen = gen # generator or function returning generator
183 183 self._values = values
184 184 self._makemap = makemap
185 185 self._joinfmt = joinfmt
186 self.keytype = keytype # hint for 'x in y' where type(x) is unresolved
186 self._keytype = keytype # hint for 'x in y' where type(x) is unresolved
187 187
188 188 def contains(self, context, mapping, item):
189 item = unwrapastype(context, mapping, item, self.keytype)
189 item = unwrapastype(context, mapping, item, self._keytype)
190 190 return item in self._values
191 191
192 192 def getmember(self, context, mapping, key):
193 193 # TODO: maybe split hybrid list/dict types?
194 194 if not util.safehasattr(self._values, 'get'):
195 195 raise error.ParseError(_('not a dictionary'))
196 key = unwrapastype(context, mapping, key, self.keytype)
196 key = unwrapastype(context, mapping, key, self._keytype)
197 197 return self._wrapvalue(key, self._values.get(key))
198 198
199 199 def getmin(self, context, mapping):
200 200 return self._getby(context, mapping, min)
201 201
202 202 def getmax(self, context, mapping):
203 203 return self._getby(context, mapping, max)
204 204
205 205 def _getby(self, context, mapping, func):
206 206 if not self._values:
207 207 raise error.ParseError(_('empty sequence'))
208 208 val = func(self._values)
209 209 return self._wrapvalue(val, val)
210 210
211 211 def _wrapvalue(self, key, val):
212 212 if val is None:
213 213 return
214 214 if util.safehasattr(val, '_makemap'):
215 215 # a nested hybrid list/dict, which has its own way of map operation
216 216 return val
217 217 return mappable(None, key, val, self._makemap)
218 218
219 219 def itermaps(self, context):
220 220 makemap = self._makemap
221 221 for x in self._values:
222 222 yield makemap(x)
223 223
224 224 def join(self, context, mapping, sep):
225 225 # TODO: switch gen to (context, mapping) API?
226 226 return joinitems((self._joinfmt(x) for x in self._values), sep)
227 227
228 228 def show(self, context, mapping):
229 229 # TODO: switch gen to (context, mapping) API?
230 230 gen = self._gen
231 231 if gen is None:
232 232 return self.join(context, mapping, ' ')
233 233 if callable(gen):
234 234 return gen()
235 235 return gen
236 236
237 237 def tovalue(self, context, mapping):
238 238 # TODO: return self._values and get rid of proxy methods
239 239 return self
240 240
241 241 def __contains__(self, x):
242 242 return x in self._values
243 243 def __getitem__(self, key):
244 244 return self._values[key]
245 245 def __len__(self):
246 246 return len(self._values)
247 247 def __iter__(self):
248 248 return iter(self._values)
249 249 def __getattr__(self, name):
250 250 if name not in (r'get', r'items', r'iteritems', r'iterkeys',
251 251 r'itervalues', r'keys', r'values'):
252 252 raise AttributeError(name)
253 253 return getattr(self._values, name)
254 254
255 255 class mappable(wrapped):
256 256 """Wrapper for non-list/dict object to support map operation
257 257
258 258 This class allows us to handle both:
259 259 - "{manifest}"
260 260 - "{manifest % '{rev}:{node}'}"
261 261 - "{manifest.rev}"
262 262
263 263 Unlike a hybrid, this does not simulate the behavior of the underling
264 264 value.
265 265 """
266 266
267 267 def __init__(self, gen, key, value, makemap):
268 268 self._gen = gen # generator or function returning generator
269 269 self._key = key
270 270 self._value = value # may be generator of strings
271 271 self._makemap = makemap
272 272
273 273 def tomap(self):
274 274 return self._makemap(self._key)
275 275
276 276 def contains(self, context, mapping, item):
277 277 w = makewrapped(context, mapping, self._value)
278 278 return w.contains(context, mapping, item)
279 279
280 280 def getmember(self, context, mapping, key):
281 281 w = makewrapped(context, mapping, self._value)
282 282 return w.getmember(context, mapping, key)
283 283
284 284 def getmin(self, context, mapping):
285 285 w = makewrapped(context, mapping, self._value)
286 286 return w.getmin(context, mapping)
287 287
288 288 def getmax(self, context, mapping):
289 289 w = makewrapped(context, mapping, self._value)
290 290 return w.getmax(context, mapping)
291 291
292 292 def itermaps(self, context):
293 293 yield self.tomap()
294 294
295 295 def join(self, context, mapping, sep):
296 296 w = makewrapped(context, mapping, self._value)
297 297 return w.join(context, mapping, sep)
298 298
299 299 def show(self, context, mapping):
300 300 # TODO: switch gen to (context, mapping) API?
301 301 gen = self._gen
302 302 if gen is None:
303 303 return pycompat.bytestr(self._value)
304 304 if callable(gen):
305 305 return gen()
306 306 return gen
307 307
308 308 def tovalue(self, context, mapping):
309 309 return _unthunk(context, mapping, self._value)
310 310
311 311 class _mappingsequence(wrapped):
312 312 """Wrapper for sequence of template mappings
313 313
314 314 This represents an inner template structure (i.e. a list of dicts),
315 315 which can also be rendered by the specified named/literal template.
316 316
317 317 Template mappings may be nested.
318 318 """
319 319
320 320 def __init__(self, name=None, tmpl=None, sep=''):
321 321 if name is not None and tmpl is not None:
322 322 raise error.ProgrammingError('name and tmpl are mutually exclusive')
323 323 self._name = name
324 324 self._tmpl = tmpl
325 325 self._defaultsep = sep
326 326
327 327 def contains(self, context, mapping, item):
328 328 raise error.ParseError(_('not comparable'))
329 329
330 330 def getmember(self, context, mapping, key):
331 331 raise error.ParseError(_('not a dictionary'))
332 332
333 333 def getmin(self, context, mapping):
334 334 raise error.ParseError(_('not comparable'))
335 335
336 336 def getmax(self, context, mapping):
337 337 raise error.ParseError(_('not comparable'))
338 338
339 339 def join(self, context, mapping, sep):
340 340 mapsiter = _iteroverlaymaps(context, mapping, self.itermaps(context))
341 341 if self._name:
342 342 itemiter = (context.process(self._name, m) for m in mapsiter)
343 343 elif self._tmpl:
344 344 itemiter = (context.expand(self._tmpl, m) for m in mapsiter)
345 345 else:
346 346 raise error.ParseError(_('not displayable without template'))
347 347 return joinitems(itemiter, sep)
348 348
349 349 def show(self, context, mapping):
350 350 return self.join(context, mapping, self._defaultsep)
351 351
352 352 def tovalue(self, context, mapping):
353 353 knownres = context.knownresourcekeys()
354 354 items = []
355 355 for nm in self.itermaps(context):
356 356 # drop internal resources (recursively) which shouldn't be displayed
357 357 lm = context.overlaymap(mapping, nm)
358 358 items.append({k: unwrapvalue(context, lm, v)
359 359 for k, v in nm.iteritems() if k not in knownres})
360 360 return items
361 361
362 362 class mappinggenerator(_mappingsequence):
363 363 """Wrapper for generator of template mappings
364 364
365 365 The function ``make(context, *args)`` should return a generator of
366 366 mapping dicts.
367 367 """
368 368
369 369 def __init__(self, make, args=(), name=None, tmpl=None, sep=''):
370 370 super(mappinggenerator, self).__init__(name, tmpl, sep)
371 371 self._make = make
372 372 self._args = args
373 373
374 374 def itermaps(self, context):
375 375 return self._make(context, *self._args)
376 376
377 377 class mappinglist(_mappingsequence):
378 378 """Wrapper for list of template mappings"""
379 379
380 380 def __init__(self, mappings, name=None, tmpl=None, sep=''):
381 381 super(mappinglist, self).__init__(name, tmpl, sep)
382 382 self._mappings = mappings
383 383
384 384 def itermaps(self, context):
385 385 return iter(self._mappings)
386 386
387 387 class mappedgenerator(wrapped):
388 388 """Wrapper for generator of strings which acts as a list
389 389
390 390 The function ``make(context, *args)`` should return a generator of
391 391 byte strings, or a generator of (possibly nested) generators of byte
392 392 strings (i.e. a generator for a list of byte strings.)
393 393 """
394 394
395 395 def __init__(self, make, args=()):
396 396 self._make = make
397 397 self._args = args
398 398
399 399 def contains(self, context, mapping, item):
400 400 item = stringify(context, mapping, item)
401 401 return item in self.tovalue(context, mapping)
402 402
403 403 def _gen(self, context):
404 404 return self._make(context, *self._args)
405 405
406 406 def getmember(self, context, mapping, key):
407 407 raise error.ParseError(_('not a dictionary'))
408 408
409 409 def getmin(self, context, mapping):
410 410 return self._getby(context, mapping, min)
411 411
412 412 def getmax(self, context, mapping):
413 413 return self._getby(context, mapping, max)
414 414
415 415 def _getby(self, context, mapping, func):
416 416 xs = self.tovalue(context, mapping)
417 417 if not xs:
418 418 raise error.ParseError(_('empty sequence'))
419 419 return func(xs)
420 420
421 421 def itermaps(self, context):
422 422 raise error.ParseError(_('list of strings is not mappable'))
423 423
424 424 def join(self, context, mapping, sep):
425 425 return joinitems(self._gen(context), sep)
426 426
427 427 def show(self, context, mapping):
428 428 return self.join(context, mapping, '')
429 429
430 430 def tovalue(self, context, mapping):
431 431 return [stringify(context, mapping, x) for x in self._gen(context)]
432 432
433 433 def hybriddict(data, key='key', value='value', fmt=None, gen=None):
434 434 """Wrap data to support both dict-like and string-like operations"""
435 435 prefmt = pycompat.identity
436 436 if fmt is None:
437 437 fmt = '%s=%s'
438 438 prefmt = pycompat.bytestr
439 439 return hybrid(gen, data, lambda k: {key: k, value: data[k]},
440 440 lambda k: fmt % (prefmt(k), prefmt(data[k])))
441 441
442 442 def hybridlist(data, name, fmt=None, gen=None):
443 443 """Wrap data to support both list-like and string-like operations"""
444 444 prefmt = pycompat.identity
445 445 if fmt is None:
446 446 fmt = '%s'
447 447 prefmt = pycompat.bytestr
448 448 return hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % prefmt(x))
449 449
450 450 def unwraphybrid(context, mapping, thing):
451 451 """Return an object which can be stringified possibly by using a legacy
452 452 template"""
453 453 if not isinstance(thing, wrapped):
454 454 return thing
455 455 return thing.show(context, mapping)
456 456
457 457 def compatdict(context, mapping, name, data, key='key', value='value',
458 458 fmt=None, plural=None, separator=' '):
459 459 """Wrap data like hybriddict(), but also supports old-style list template
460 460
461 461 This exists for backward compatibility with the old-style template. Use
462 462 hybriddict() for new template keywords.
463 463 """
464 464 c = [{key: k, value: v} for k, v in data.iteritems()]
465 465 f = _showcompatlist(context, mapping, name, c, plural, separator)
466 466 return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
467 467
468 468 def compatlist(context, mapping, name, data, element=None, fmt=None,
469 469 plural=None, separator=' '):
470 470 """Wrap data like hybridlist(), but also supports old-style list template
471 471
472 472 This exists for backward compatibility with the old-style template. Use
473 473 hybridlist() for new template keywords.
474 474 """
475 475 f = _showcompatlist(context, mapping, name, data, plural, separator)
476 476 return hybridlist(data, name=element or name, fmt=fmt, gen=f)
477 477
478 478 def _showcompatlist(context, mapping, name, values, plural=None, separator=' '):
479 479 """Return a generator that renders old-style list template
480 480
481 481 name is name of key in template map.
482 482 values is list of strings or dicts.
483 483 plural is plural of name, if not simply name + 's'.
484 484 separator is used to join values as a string
485 485
486 486 expansion works like this, given name 'foo'.
487 487
488 488 if values is empty, expand 'no_foos'.
489 489
490 490 if 'foo' not in template map, return values as a string,
491 491 joined by 'separator'.
492 492
493 493 expand 'start_foos'.
494 494
495 495 for each value, expand 'foo'. if 'last_foo' in template
496 496 map, expand it instead of 'foo' for last key.
497 497
498 498 expand 'end_foos'.
499 499 """
500 500 if not plural:
501 501 plural = name + 's'
502 502 if not values:
503 503 noname = 'no_' + plural
504 504 if context.preload(noname):
505 505 yield context.process(noname, mapping)
506 506 return
507 507 if not context.preload(name):
508 508 if isinstance(values[0], bytes):
509 509 yield separator.join(values)
510 510 else:
511 511 for v in values:
512 512 r = dict(v)
513 513 r.update(mapping)
514 514 yield r
515 515 return
516 516 startname = 'start_' + plural
517 517 if context.preload(startname):
518 518 yield context.process(startname, mapping)
519 519 def one(v, tag=name):
520 520 vmapping = {}
521 521 try:
522 522 vmapping.update(v)
523 523 # Python 2 raises ValueError if the type of v is wrong. Python
524 524 # 3 raises TypeError.
525 525 except (AttributeError, TypeError, ValueError):
526 526 try:
527 527 # Python 2 raises ValueError trying to destructure an e.g.
528 528 # bytes. Python 3 raises TypeError.
529 529 for a, b in v:
530 530 vmapping[a] = b
531 531 except (TypeError, ValueError):
532 532 vmapping[name] = v
533 533 vmapping = context.overlaymap(mapping, vmapping)
534 534 return context.process(tag, vmapping)
535 535 lastname = 'last_' + name
536 536 if context.preload(lastname):
537 537 last = values.pop()
538 538 else:
539 539 last = None
540 540 for v in values:
541 541 yield one(v)
542 542 if last is not None:
543 543 yield one(last, tag=lastname)
544 544 endname = 'end_' + plural
545 545 if context.preload(endname):
546 546 yield context.process(endname, mapping)
547 547
548 548 def flatten(context, mapping, thing):
549 549 """Yield a single stream from a possibly nested set of iterators"""
550 550 thing = unwraphybrid(context, mapping, thing)
551 551 if isinstance(thing, bytes):
552 552 yield thing
553 553 elif isinstance(thing, str):
554 554 # We can only hit this on Python 3, and it's here to guard
555 555 # against infinite recursion.
556 556 raise error.ProgrammingError('Mercurial IO including templates is done'
557 557 ' with bytes, not strings, got %r' % thing)
558 558 elif thing is None:
559 559 pass
560 560 elif not util.safehasattr(thing, '__iter__'):
561 561 yield pycompat.bytestr(thing)
562 562 else:
563 563 for i in thing:
564 564 i = unwraphybrid(context, mapping, i)
565 565 if isinstance(i, bytes):
566 566 yield i
567 567 elif i is None:
568 568 pass
569 569 elif not util.safehasattr(i, '__iter__'):
570 570 yield pycompat.bytestr(i)
571 571 else:
572 572 for j in flatten(context, mapping, i):
573 573 yield j
574 574
575 575 def stringify(context, mapping, thing):
576 576 """Turn values into bytes by converting into text and concatenating them"""
577 577 if isinstance(thing, bytes):
578 578 return thing # retain localstr to be round-tripped
579 579 return b''.join(flatten(context, mapping, thing))
580 580
581 581 def findsymbolicname(arg):
582 582 """Find symbolic name for the given compiled expression; returns None
583 583 if nothing found reliably"""
584 584 while True:
585 585 func, data = arg
586 586 if func is runsymbol:
587 587 return data
588 588 elif func is runfilter:
589 589 arg = data[0]
590 590 else:
591 591 return None
592 592
593 593 def _unthunk(context, mapping, thing):
594 594 """Evaluate a lazy byte string into value"""
595 595 if not isinstance(thing, types.GeneratorType):
596 596 return thing
597 597 return stringify(context, mapping, thing)
598 598
599 599 def evalrawexp(context, mapping, arg):
600 600 """Evaluate given argument as a bare template object which may require
601 601 further processing (such as folding generator of strings)"""
602 602 func, data = arg
603 603 return func(context, mapping, data)
604 604
605 605 def evalwrapped(context, mapping, arg):
606 606 """Evaluate given argument to wrapped object"""
607 607 thing = evalrawexp(context, mapping, arg)
608 608 return makewrapped(context, mapping, thing)
609 609
610 610 def makewrapped(context, mapping, thing):
611 611 """Lift object to a wrapped type"""
612 612 if isinstance(thing, wrapped):
613 613 return thing
614 614 thing = _unthunk(context, mapping, thing)
615 615 if isinstance(thing, bytes):
616 616 return wrappedbytes(thing)
617 617 return wrappedvalue(thing)
618 618
619 619 def evalfuncarg(context, mapping, arg):
620 620 """Evaluate given argument as value type"""
621 621 return unwrapvalue(context, mapping, evalrawexp(context, mapping, arg))
622 622
623 623 def unwrapvalue(context, mapping, thing):
624 624 """Move the inner value object out of the wrapper"""
625 625 if isinstance(thing, wrapped):
626 626 return thing.tovalue(context, mapping)
627 627 # evalrawexp() may return string, generator of strings or arbitrary object
628 628 # such as date tuple, but filter does not want generator.
629 629 return _unthunk(context, mapping, thing)
630 630
631 631 def evalboolean(context, mapping, arg):
632 632 """Evaluate given argument as boolean, but also takes boolean literals"""
633 633 func, data = arg
634 634 if func is runsymbol:
635 635 thing = func(context, mapping, data, default=None)
636 636 if thing is None:
637 637 # not a template keyword, takes as a boolean literal
638 638 thing = stringutil.parsebool(data)
639 639 else:
640 640 thing = func(context, mapping, data)
641 641 if isinstance(thing, wrapped):
642 642 thing = thing.tovalue(context, mapping)
643 643 if isinstance(thing, bool):
644 644 return thing
645 645 # other objects are evaluated as strings, which means 0 is True, but
646 646 # empty dict/list should be False as they are expected to be ''
647 647 return bool(stringify(context, mapping, thing))
648 648
649 649 def evaldate(context, mapping, arg, err=None):
650 650 """Evaluate given argument as a date tuple or a date string; returns
651 651 a (unixtime, offset) tuple"""
652 652 thing = evalrawexp(context, mapping, arg)
653 653 return unwrapdate(context, mapping, thing, err)
654 654
655 655 def unwrapdate(context, mapping, thing, err=None):
656 656 thing = unwrapvalue(context, mapping, thing)
657 657 try:
658 658 return dateutil.parsedate(thing)
659 659 except AttributeError:
660 660 raise error.ParseError(err or _('not a date tuple nor a string'))
661 661 except error.ParseError:
662 662 if not err:
663 663 raise
664 664 raise error.ParseError(err)
665 665
666 666 def evalinteger(context, mapping, arg, err=None):
667 667 thing = evalrawexp(context, mapping, arg)
668 668 return unwrapinteger(context, mapping, thing, err)
669 669
670 670 def unwrapinteger(context, mapping, thing, err=None):
671 671 thing = unwrapvalue(context, mapping, thing)
672 672 try:
673 673 return int(thing)
674 674 except (TypeError, ValueError):
675 675 raise error.ParseError(err or _('not an integer'))
676 676
677 677 def evalstring(context, mapping, arg):
678 678 return stringify(context, mapping, evalrawexp(context, mapping, arg))
679 679
680 680 def evalstringliteral(context, mapping, arg):
681 681 """Evaluate given argument as string template, but returns symbol name
682 682 if it is unknown"""
683 683 func, data = arg
684 684 if func is runsymbol:
685 685 thing = func(context, mapping, data, default=data)
686 686 else:
687 687 thing = func(context, mapping, data)
688 688 return stringify(context, mapping, thing)
689 689
690 690 _unwrapfuncbytype = {
691 691 None: unwrapvalue,
692 692 bytes: stringify,
693 693 date: unwrapdate,
694 694 int: unwrapinteger,
695 695 }
696 696
697 697 def unwrapastype(context, mapping, thing, typ):
698 698 """Move the inner value object out of the wrapper and coerce its type"""
699 699 try:
700 700 f = _unwrapfuncbytype[typ]
701 701 except KeyError:
702 702 raise error.ProgrammingError('invalid type specified: %r' % typ)
703 703 return f(context, mapping, thing)
704 704
705 705 def runinteger(context, mapping, data):
706 706 return int(data)
707 707
708 708 def runstring(context, mapping, data):
709 709 return data
710 710
711 711 def _recursivesymbolblocker(key):
712 712 def showrecursion(**args):
713 713 raise error.Abort(_("recursive reference '%s' in template") % key)
714 714 return showrecursion
715 715
716 716 def runsymbol(context, mapping, key, default=''):
717 717 v = context.symbol(mapping, key)
718 718 if v is None:
719 719 # put poison to cut recursion. we can't move this to parsing phase
720 720 # because "x = {x}" is allowed if "x" is a keyword. (issue4758)
721 721 safemapping = mapping.copy()
722 722 safemapping[key] = _recursivesymbolblocker(key)
723 723 try:
724 724 v = context.process(key, safemapping)
725 725 except TemplateNotFound:
726 726 v = default
727 727 if callable(v) and getattr(v, '_requires', None) is None:
728 728 # old templatekw: expand all keywords and resources
729 729 # (TODO: deprecate this after porting web template keywords to new API)
730 730 props = {k: context._resources.lookup(context, mapping, k)
731 731 for k in context._resources.knownkeys()}
732 732 # pass context to _showcompatlist() through templatekw._showlist()
733 733 props['templ'] = context
734 734 props.update(mapping)
735 735 return v(**pycompat.strkwargs(props))
736 736 if callable(v):
737 737 # new templatekw
738 738 try:
739 739 return v(context, mapping)
740 740 except ResourceUnavailable:
741 741 # unsupported keyword is mapped to empty just like unknown keyword
742 742 return None
743 743 return v
744 744
745 745 def runtemplate(context, mapping, template):
746 746 for arg in template:
747 747 yield evalrawexp(context, mapping, arg)
748 748
749 749 def runfilter(context, mapping, data):
750 750 arg, filt = data
751 751 thing = evalrawexp(context, mapping, arg)
752 752 intype = getattr(filt, '_intype', None)
753 753 try:
754 754 thing = unwrapastype(context, mapping, thing, intype)
755 755 return filt(thing)
756 756 except error.ParseError as e:
757 757 raise error.ParseError(bytes(e), hint=_formatfiltererror(arg, filt))
758 758
759 759 def _formatfiltererror(arg, filt):
760 760 fn = pycompat.sysbytes(filt.__name__)
761 761 sym = findsymbolicname(arg)
762 762 if not sym:
763 763 return _("incompatible use of template filter '%s'") % fn
764 764 return (_("template filter '%s' is not compatible with keyword '%s'")
765 765 % (fn, sym))
766 766
767 767 def _iteroverlaymaps(context, origmapping, newmappings):
768 768 """Generate combined mappings from the original mapping and an iterable
769 769 of partial mappings to override the original"""
770 770 for i, nm in enumerate(newmappings):
771 771 lm = context.overlaymap(origmapping, nm)
772 772 lm['index'] = i
773 773 yield lm
774 774
775 775 def _applymap(context, mapping, d, targ):
776 776 for lm in _iteroverlaymaps(context, mapping, d.itermaps(context)):
777 777 yield evalrawexp(context, lm, targ)
778 778
779 779 def runmap(context, mapping, data):
780 780 darg, targ = data
781 781 d = evalwrapped(context, mapping, darg)
782 782 return mappedgenerator(_applymap, args=(mapping, d, targ))
783 783
784 784 def runmember(context, mapping, data):
785 785 darg, memb = data
786 786 d = evalwrapped(context, mapping, darg)
787 787 if util.safehasattr(d, 'tomap'):
788 788 lm = context.overlaymap(mapping, d.tomap())
789 789 return runsymbol(context, lm, memb)
790 790 try:
791 791 return d.getmember(context, mapping, memb)
792 792 except error.ParseError as err:
793 793 sym = findsymbolicname(darg)
794 794 if not sym:
795 795 raise
796 796 hint = _("keyword '%s' does not support member operation") % sym
797 797 raise error.ParseError(bytes(err), hint=hint)
798 798
799 799 def runnegate(context, mapping, data):
800 800 data = evalinteger(context, mapping, data,
801 801 _('negation needs an integer argument'))
802 802 return -data
803 803
804 804 def runarithmetic(context, mapping, data):
805 805 func, left, right = data
806 806 left = evalinteger(context, mapping, left,
807 807 _('arithmetic only defined on integers'))
808 808 right = evalinteger(context, mapping, right,
809 809 _('arithmetic only defined on integers'))
810 810 try:
811 811 return func(left, right)
812 812 except ZeroDivisionError:
813 813 raise error.Abort(_('division by zero is not defined'))
814 814
815 815 def joinitems(itemiter, sep):
816 816 """Join items with the separator; Returns generator of bytes"""
817 817 first = True
818 818 for x in itemiter:
819 819 if first:
820 820 first = False
821 821 elif sep:
822 822 yield sep
823 823 yield x
General Comments 0
You need to be logged in to leave comments. Login now