Mercurial > public > mercurial-scm > hg
comparison mercurial/templateutil.py @ 37272:7d3bc1d4e871
templater: pass (context, mapping) down to unwraphybrid()
See the subsequent patches for why.
I initially thought it would be wrong to pass a mapping to flatten() and
stringify() since these functions may be applied to a tree of generators,
where each node should be bound to the mapping when it was evaluated. But,
actually that isn't a problem. If an intermediate node has to override a
mapping dict, it can do on unwraphybrid() and yield "unwrapped" generator
of byte strings:
"{f(g(v))}" # literal template example.
^^^^ # g() want to override a mapping, so it returns a wrapped
# object 'G{V}' with partial mapping 'lm' attached.
^^^^^^^ # f() stringifies 'G{V}', starting from a mapping 'm'.
# when unwrapping 'G{}', it updates 'm' with 'lm', and
# passes it to 'V'.
This structure is important for the formatter (and the hgweb) to build a
static template keyword, which can't access a mapping dict until evaluation
phase.
author | Yuya Nishihara <yuya@tcha.org> |
---|---|
date | Sat, 17 Mar 2018 20:09:05 +0900 |
parents | dc4bb1422f2b |
children | 83e1bbd48991 |
comparison
equal
deleted
inserted
replaced
37271:0194dac77c93 | 37272:7d3bc1d4e871 |
---|---|
118 if fmt is None: | 118 if fmt is None: |
119 fmt = '%s' | 119 fmt = '%s' |
120 prefmt = pycompat.bytestr | 120 prefmt = pycompat.bytestr |
121 return hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % prefmt(x)) | 121 return hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % prefmt(x)) |
122 | 122 |
123 def unwraphybrid(thing): | 123 def unwraphybrid(context, mapping, thing): |
124 """Return an object which can be stringified possibly by using a legacy | 124 """Return an object which can be stringified possibly by using a legacy |
125 template""" | 125 template""" |
126 gen = getattr(thing, 'gen', None) | 126 gen = getattr(thing, 'gen', None) |
127 if gen is None: | 127 if gen is None: |
128 return thing | 128 return thing |
239 yield one(last, tag=lastname) | 239 yield one(last, tag=lastname) |
240 endname = 'end_' + plural | 240 endname = 'end_' + plural |
241 if context.preload(endname): | 241 if context.preload(endname): |
242 yield context.process(endname, mapping) | 242 yield context.process(endname, mapping) |
243 | 243 |
244 def flatten(thing): | 244 def flatten(context, mapping, thing): |
245 """Yield a single stream from a possibly nested set of iterators""" | 245 """Yield a single stream from a possibly nested set of iterators""" |
246 thing = unwraphybrid(thing) | 246 thing = unwraphybrid(context, mapping, thing) |
247 if isinstance(thing, bytes): | 247 if isinstance(thing, bytes): |
248 yield thing | 248 yield thing |
249 elif isinstance(thing, str): | 249 elif isinstance(thing, str): |
250 # We can only hit this on Python 3, and it's here to guard | 250 # We can only hit this on Python 3, and it's here to guard |
251 # against infinite recursion. | 251 # against infinite recursion. |
255 pass | 255 pass |
256 elif not util.safehasattr(thing, '__iter__'): | 256 elif not util.safehasattr(thing, '__iter__'): |
257 yield pycompat.bytestr(thing) | 257 yield pycompat.bytestr(thing) |
258 else: | 258 else: |
259 for i in thing: | 259 for i in thing: |
260 i = unwraphybrid(i) | 260 i = unwraphybrid(context, mapping, i) |
261 if isinstance(i, bytes): | 261 if isinstance(i, bytes): |
262 yield i | 262 yield i |
263 elif i is None: | 263 elif i is None: |
264 pass | 264 pass |
265 elif not util.safehasattr(i, '__iter__'): | 265 elif not util.safehasattr(i, '__iter__'): |
266 yield pycompat.bytestr(i) | 266 yield pycompat.bytestr(i) |
267 else: | 267 else: |
268 for j in flatten(i): | 268 for j in flatten(context, mapping, i): |
269 yield j | 269 yield j |
270 | 270 |
271 def stringify(thing): | 271 def stringify(context, mapping, thing): |
272 """Turn values into bytes by converting into text and concatenating them""" | 272 """Turn values into bytes by converting into text and concatenating them""" |
273 if isinstance(thing, bytes): | 273 if isinstance(thing, bytes): |
274 return thing # retain localstr to be round-tripped | 274 return thing # retain localstr to be round-tripped |
275 return b''.join(flatten(thing)) | 275 return b''.join(flatten(context, mapping, thing)) |
276 | 276 |
277 def findsymbolicname(arg): | 277 def findsymbolicname(arg): |
278 """Find symbolic name for the given compiled expression; returns None | 278 """Find symbolic name for the given compiled expression; returns None |
279 if nothing found reliably""" | 279 if nothing found reliably""" |
280 while True: | 280 while True: |
292 func, data = arg | 292 func, data = arg |
293 return func(context, mapping, data) | 293 return func(context, mapping, data) |
294 | 294 |
295 def evalfuncarg(context, mapping, arg): | 295 def evalfuncarg(context, mapping, arg): |
296 """Evaluate given argument as value type""" | 296 """Evaluate given argument as value type""" |
297 return _unwrapvalue(evalrawexp(context, mapping, arg)) | 297 return _unwrapvalue(context, mapping, evalrawexp(context, mapping, arg)) |
298 | 298 |
299 # TODO: unify this with unwrapvalue() once the bug of templatefunc.join() | 299 # TODO: unify this with unwrapvalue() once the bug of templatefunc.join() |
300 # is fixed. we can't do that right now because join() has to take a generator | 300 # is fixed. we can't do that right now because join() has to take a generator |
301 # of byte strings as it is, not a lazy byte string. | 301 # of byte strings as it is, not a lazy byte string. |
302 def _unwrapvalue(thing): | 302 def _unwrapvalue(context, mapping, thing): |
303 thing = unwrapvalue(thing) | 303 thing = unwrapvalue(thing) |
304 # evalrawexp() may return string, generator of strings or arbitrary object | 304 # evalrawexp() may return string, generator of strings or arbitrary object |
305 # such as date tuple, but filter does not want generator. | 305 # such as date tuple, but filter does not want generator. |
306 if isinstance(thing, types.GeneratorType): | 306 if isinstance(thing, types.GeneratorType): |
307 thing = stringify(thing) | 307 thing = stringify(context, mapping, thing) |
308 return thing | 308 return thing |
309 | 309 |
310 def evalboolean(context, mapping, arg): | 310 def evalboolean(context, mapping, arg): |
311 """Evaluate given argument as boolean, but also takes boolean literals""" | 311 """Evaluate given argument as boolean, but also takes boolean literals""" |
312 func, data = arg | 312 func, data = arg |
320 thing = unwrapvalue(thing) | 320 thing = unwrapvalue(thing) |
321 if isinstance(thing, bool): | 321 if isinstance(thing, bool): |
322 return thing | 322 return thing |
323 # other objects are evaluated as strings, which means 0 is True, but | 323 # other objects are evaluated as strings, which means 0 is True, but |
324 # empty dict/list should be False as they are expected to be '' | 324 # empty dict/list should be False as they are expected to be '' |
325 return bool(stringify(thing)) | 325 return bool(stringify(context, mapping, thing)) |
326 | 326 |
327 def evaldate(context, mapping, arg, err=None): | 327 def evaldate(context, mapping, arg, err=None): |
328 """Evaluate given argument as a date tuple or a date string; returns | 328 """Evaluate given argument as a date tuple or a date string; returns |
329 a (unixtime, offset) tuple""" | 329 a (unixtime, offset) tuple""" |
330 return unwrapdate(evalrawexp(context, mapping, arg), err) | 330 thing = evalrawexp(context, mapping, arg) |
331 | 331 return unwrapdate(context, mapping, thing, err) |
332 def unwrapdate(thing, err=None): | 332 |
333 thing = _unwrapvalue(thing) | 333 def unwrapdate(context, mapping, thing, err=None): |
334 thing = _unwrapvalue(context, mapping, thing) | |
334 try: | 335 try: |
335 return dateutil.parsedate(thing) | 336 return dateutil.parsedate(thing) |
336 except AttributeError: | 337 except AttributeError: |
337 raise error.ParseError(err or _('not a date tuple nor a string')) | 338 raise error.ParseError(err or _('not a date tuple nor a string')) |
338 except error.ParseError: | 339 except error.ParseError: |
339 if not err: | 340 if not err: |
340 raise | 341 raise |
341 raise error.ParseError(err) | 342 raise error.ParseError(err) |
342 | 343 |
343 def evalinteger(context, mapping, arg, err=None): | 344 def evalinteger(context, mapping, arg, err=None): |
344 return unwrapinteger(evalrawexp(context, mapping, arg), err) | 345 thing = evalrawexp(context, mapping, arg) |
345 | 346 return unwrapinteger(context, mapping, thing, err) |
346 def unwrapinteger(thing, err=None): | 347 |
347 thing = _unwrapvalue(thing) | 348 def unwrapinteger(context, mapping, thing, err=None): |
349 thing = _unwrapvalue(context, mapping, thing) | |
348 try: | 350 try: |
349 return int(thing) | 351 return int(thing) |
350 except (TypeError, ValueError): | 352 except (TypeError, ValueError): |
351 raise error.ParseError(err or _('not an integer')) | 353 raise error.ParseError(err or _('not an integer')) |
352 | 354 |
353 def evalstring(context, mapping, arg): | 355 def evalstring(context, mapping, arg): |
354 return stringify(evalrawexp(context, mapping, arg)) | 356 return stringify(context, mapping, evalrawexp(context, mapping, arg)) |
355 | 357 |
356 def evalstringliteral(context, mapping, arg): | 358 def evalstringliteral(context, mapping, arg): |
357 """Evaluate given argument as string template, but returns symbol name | 359 """Evaluate given argument as string template, but returns symbol name |
358 if it is unknown""" | 360 if it is unknown""" |
359 func, data = arg | 361 func, data = arg |
360 if func is runsymbol: | 362 if func is runsymbol: |
361 thing = func(context, mapping, data, default=data) | 363 thing = func(context, mapping, data, default=data) |
362 else: | 364 else: |
363 thing = func(context, mapping, data) | 365 thing = func(context, mapping, data) |
364 return stringify(thing) | 366 return stringify(context, mapping, thing) |
365 | 367 |
366 _unwrapfuncbytype = { | 368 _unwrapfuncbytype = { |
367 None: _unwrapvalue, | 369 None: _unwrapvalue, |
368 bytes: stringify, | 370 bytes: stringify, |
369 date: unwrapdate, | 371 date: unwrapdate, |
370 int: unwrapinteger, | 372 int: unwrapinteger, |
371 } | 373 } |
372 | 374 |
373 def unwrapastype(thing, typ): | 375 def unwrapastype(context, mapping, thing, typ): |
374 """Move the inner value object out of the wrapper and coerce its type""" | 376 """Move the inner value object out of the wrapper and coerce its type""" |
375 try: | 377 try: |
376 f = _unwrapfuncbytype[typ] | 378 f = _unwrapfuncbytype[typ] |
377 except KeyError: | 379 except KeyError: |
378 raise error.ProgrammingError('invalid type specified: %r' % typ) | 380 raise error.ProgrammingError('invalid type specified: %r' % typ) |
379 return f(thing) | 381 return f(context, mapping, thing) |
380 | 382 |
381 def runinteger(context, mapping, data): | 383 def runinteger(context, mapping, data): |
382 return int(data) | 384 return int(data) |
383 | 385 |
384 def runstring(context, mapping, data): | 386 def runstring(context, mapping, data): |
423 yield evalrawexp(context, mapping, arg) | 425 yield evalrawexp(context, mapping, arg) |
424 | 426 |
425 def runfilter(context, mapping, data): | 427 def runfilter(context, mapping, data): |
426 arg, filt = data | 428 arg, filt = data |
427 thing = evalrawexp(context, mapping, arg) | 429 thing = evalrawexp(context, mapping, arg) |
430 intype = getattr(filt, '_intype', None) | |
428 try: | 431 try: |
429 thing = unwrapastype(thing, getattr(filt, '_intype', None)) | 432 thing = unwrapastype(context, mapping, thing, intype) |
430 return filt(thing) | 433 return filt(thing) |
431 except error.ParseError as e: | 434 except error.ParseError as e: |
432 raise error.ParseError(bytes(e), hint=_formatfiltererror(arg, filt)) | 435 raise error.ParseError(bytes(e), hint=_formatfiltererror(arg, filt)) |
433 | 436 |
434 def _formatfiltererror(arg, filt): | 437 def _formatfiltererror(arg, filt): |