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):