##// END OF EJS Templates
formatter: more details on assertion failure...
Boris Feld -
r40175:46f9b1d2 default
parent child Browse files
Show More
@@ -1,656 +1,656 b''
1 1 # formatter.py - generic output formatting for mercurial
2 2 #
3 3 # Copyright 2012 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 """Generic output formatting for Mercurial
9 9
10 10 The formatter provides API to show data in various ways. The following
11 11 functions should be used in place of ui.write():
12 12
13 13 - fm.write() for unconditional output
14 14 - fm.condwrite() to show some extra data conditionally in plain output
15 15 - fm.context() to provide changectx to template output
16 16 - fm.data() to provide extra data to JSON or template output
17 17 - fm.plain() to show raw text that isn't provided to JSON or template output
18 18
19 19 To show structured data (e.g. date tuples, dicts, lists), apply fm.format*()
20 20 beforehand so the data is converted to the appropriate data type. Use
21 21 fm.isplain() if you need to convert or format data conditionally which isn't
22 22 supported by the formatter API.
23 23
24 24 To build nested structure (i.e. a list of dicts), use fm.nested().
25 25
26 26 See also https://www.mercurial-scm.org/wiki/GenericTemplatingPlan
27 27
28 28 fm.condwrite() vs 'if cond:':
29 29
30 30 In most cases, use fm.condwrite() so users can selectively show the data
31 31 in template output. If it's costly to build data, use plain 'if cond:' with
32 32 fm.write().
33 33
34 34 fm.nested() vs fm.formatdict() (or fm.formatlist()):
35 35
36 36 fm.nested() should be used to form a tree structure (a list of dicts of
37 37 lists of dicts...) which can be accessed through template keywords, e.g.
38 38 "{foo % "{bar % {...}} {baz % {...}}"}". On the other hand, fm.formatdict()
39 39 exports a dict-type object to template, which can be accessed by e.g.
40 40 "{get(foo, key)}" function.
41 41
42 42 Doctest helper:
43 43
44 44 >>> def show(fn, verbose=False, **opts):
45 45 ... import sys
46 46 ... from . import ui as uimod
47 47 ... ui = uimod.ui()
48 48 ... ui.verbose = verbose
49 49 ... ui.pushbuffer()
50 50 ... try:
51 51 ... return fn(ui, ui.formatter(pycompat.sysbytes(fn.__name__),
52 52 ... pycompat.byteskwargs(opts)))
53 53 ... finally:
54 54 ... print(pycompat.sysstr(ui.popbuffer()), end='')
55 55
56 56 Basic example:
57 57
58 58 >>> def files(ui, fm):
59 59 ... files = [(b'foo', 123, (0, 0)), (b'bar', 456, (1, 0))]
60 60 ... for f in files:
61 61 ... fm.startitem()
62 62 ... fm.write(b'path', b'%s', f[0])
63 63 ... fm.condwrite(ui.verbose, b'date', b' %s',
64 64 ... fm.formatdate(f[2], b'%Y-%m-%d %H:%M:%S'))
65 65 ... fm.data(size=f[1])
66 66 ... fm.plain(b'\\n')
67 67 ... fm.end()
68 68 >>> show(files)
69 69 foo
70 70 bar
71 71 >>> show(files, verbose=True)
72 72 foo 1970-01-01 00:00:00
73 73 bar 1970-01-01 00:00:01
74 74 >>> show(files, template=b'json')
75 75 [
76 76 {
77 77 "date": [0, 0],
78 78 "path": "foo",
79 79 "size": 123
80 80 },
81 81 {
82 82 "date": [1, 0],
83 83 "path": "bar",
84 84 "size": 456
85 85 }
86 86 ]
87 87 >>> show(files, template=b'path: {path}\\ndate: {date|rfc3339date}\\n')
88 88 path: foo
89 89 date: 1970-01-01T00:00:00+00:00
90 90 path: bar
91 91 date: 1970-01-01T00:00:01+00:00
92 92
93 93 Nested example:
94 94
95 95 >>> def subrepos(ui, fm):
96 96 ... fm.startitem()
97 97 ... fm.write(b'reponame', b'[%s]\\n', b'baz')
98 98 ... files(ui, fm.nested(b'files', tmpl=b'{reponame}'))
99 99 ... fm.end()
100 100 >>> show(subrepos)
101 101 [baz]
102 102 foo
103 103 bar
104 104 >>> show(subrepos, template=b'{reponame}: {join(files % "{path}", ", ")}\\n')
105 105 baz: foo, bar
106 106 """
107 107
108 108 from __future__ import absolute_import, print_function
109 109
110 110 import contextlib
111 111 import itertools
112 112 import os
113 113
114 114 from .i18n import _
115 115 from .node import (
116 116 hex,
117 117 short,
118 118 )
119 119 from .thirdparty import (
120 120 attr,
121 121 )
122 122
123 123 from . import (
124 124 error,
125 125 pycompat,
126 126 templatefilters,
127 127 templatekw,
128 128 templater,
129 129 templateutil,
130 130 util,
131 131 )
132 132 from .utils import dateutil
133 133
134 134 pickle = util.pickle
135 135
136 136 class _nullconverter(object):
137 137 '''convert non-primitive data types to be processed by formatter'''
138 138
139 139 # set to True if context object should be stored as item
140 140 storecontext = False
141 141
142 142 @staticmethod
143 143 def wrapnested(data, tmpl, sep):
144 144 '''wrap nested data by appropriate type'''
145 145 return data
146 146 @staticmethod
147 147 def formatdate(date, fmt):
148 148 '''convert date tuple to appropriate format'''
149 149 # timestamp can be float, but the canonical form should be int
150 150 ts, tz = date
151 151 return (int(ts), tz)
152 152 @staticmethod
153 153 def formatdict(data, key, value, fmt, sep):
154 154 '''convert dict or key-value pairs to appropriate dict format'''
155 155 # use plain dict instead of util.sortdict so that data can be
156 156 # serialized as a builtin dict in pickle output
157 157 return dict(data)
158 158 @staticmethod
159 159 def formatlist(data, name, fmt, sep):
160 160 '''convert iterable to appropriate list format'''
161 161 return list(data)
162 162
163 163 class baseformatter(object):
164 164 def __init__(self, ui, topic, opts, converter):
165 165 self._ui = ui
166 166 self._topic = topic
167 167 self._opts = opts
168 168 self._converter = converter
169 169 self._item = None
170 170 # function to convert node to string suitable for this output
171 171 self.hexfunc = hex
172 172 def __enter__(self):
173 173 return self
174 174 def __exit__(self, exctype, excvalue, traceback):
175 175 if exctype is None:
176 176 self.end()
177 177 def _showitem(self):
178 178 '''show a formatted item once all data is collected'''
179 179 def startitem(self):
180 180 '''begin an item in the format list'''
181 181 if self._item is not None:
182 182 self._showitem()
183 183 self._item = {}
184 184 def formatdate(self, date, fmt='%a %b %d %H:%M:%S %Y %1%2'):
185 185 '''convert date tuple to appropriate format'''
186 186 return self._converter.formatdate(date, fmt)
187 187 def formatdict(self, data, key='key', value='value', fmt=None, sep=' '):
188 188 '''convert dict or key-value pairs to appropriate dict format'''
189 189 return self._converter.formatdict(data, key, value, fmt, sep)
190 190 def formatlist(self, data, name, fmt=None, sep=' '):
191 191 '''convert iterable to appropriate list format'''
192 192 # name is mandatory argument for now, but it could be optional if
193 193 # we have default template keyword, e.g. {item}
194 194 return self._converter.formatlist(data, name, fmt, sep)
195 195 def context(self, **ctxs):
196 196 '''insert context objects to be used to render template keywords'''
197 197 ctxs = pycompat.byteskwargs(ctxs)
198 198 assert all(k in {'repo', 'ctx', 'fctx'} for k in ctxs)
199 199 if self._converter.storecontext:
200 200 # populate missing resources in fctx -> ctx -> repo order
201 201 if 'fctx' in ctxs and 'ctx' not in ctxs:
202 202 ctxs['ctx'] = ctxs['fctx'].changectx()
203 203 if 'ctx' in ctxs and 'repo' not in ctxs:
204 204 ctxs['repo'] = ctxs['ctx'].repo()
205 205 self._item.update(ctxs)
206 206 def datahint(self):
207 207 '''set of field names to be referenced'''
208 208 return set()
209 209 def data(self, **data):
210 210 '''insert data into item that's not shown in default output'''
211 211 data = pycompat.byteskwargs(data)
212 212 self._item.update(data)
213 213 def write(self, fields, deftext, *fielddata, **opts):
214 214 '''do default text output while assigning data to item'''
215 215 fieldkeys = fields.split()
216 assert len(fieldkeys) == len(fielddata)
216 assert len(fieldkeys) == len(fielddata), (fieldkeys, fielddata)
217 217 self._item.update(zip(fieldkeys, fielddata))
218 218 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
219 219 '''do conditional write (primarily for plain formatter)'''
220 220 fieldkeys = fields.split()
221 221 assert len(fieldkeys) == len(fielddata)
222 222 self._item.update(zip(fieldkeys, fielddata))
223 223 def plain(self, text, **opts):
224 224 '''show raw text for non-templated mode'''
225 225 def isplain(self):
226 226 '''check for plain formatter usage'''
227 227 return False
228 228 def nested(self, field, tmpl=None, sep=''):
229 229 '''sub formatter to store nested data in the specified field'''
230 230 data = []
231 231 self._item[field] = self._converter.wrapnested(data, tmpl, sep)
232 232 return _nestedformatter(self._ui, self._converter, data)
233 233 def end(self):
234 234 '''end output for the formatter'''
235 235 if self._item is not None:
236 236 self._showitem()
237 237
238 238 def nullformatter(ui, topic, opts):
239 239 '''formatter that prints nothing'''
240 240 return baseformatter(ui, topic, opts, converter=_nullconverter)
241 241
242 242 class _nestedformatter(baseformatter):
243 243 '''build sub items and store them in the parent formatter'''
244 244 def __init__(self, ui, converter, data):
245 245 baseformatter.__init__(self, ui, topic='', opts={}, converter=converter)
246 246 self._data = data
247 247 def _showitem(self):
248 248 self._data.append(self._item)
249 249
250 250 def _iteritems(data):
251 251 '''iterate key-value pairs in stable order'''
252 252 if isinstance(data, dict):
253 253 return sorted(data.iteritems())
254 254 return data
255 255
256 256 class _plainconverter(object):
257 257 '''convert non-primitive data types to text'''
258 258
259 259 storecontext = False
260 260
261 261 @staticmethod
262 262 def wrapnested(data, tmpl, sep):
263 263 raise error.ProgrammingError('plainformatter should never be nested')
264 264 @staticmethod
265 265 def formatdate(date, fmt):
266 266 '''stringify date tuple in the given format'''
267 267 return dateutil.datestr(date, fmt)
268 268 @staticmethod
269 269 def formatdict(data, key, value, fmt, sep):
270 270 '''stringify key-value pairs separated by sep'''
271 271 prefmt = pycompat.identity
272 272 if fmt is None:
273 273 fmt = '%s=%s'
274 274 prefmt = pycompat.bytestr
275 275 return sep.join(fmt % (prefmt(k), prefmt(v))
276 276 for k, v in _iteritems(data))
277 277 @staticmethod
278 278 def formatlist(data, name, fmt, sep):
279 279 '''stringify iterable separated by sep'''
280 280 prefmt = pycompat.identity
281 281 if fmt is None:
282 282 fmt = '%s'
283 283 prefmt = pycompat.bytestr
284 284 return sep.join(fmt % prefmt(e) for e in data)
285 285
286 286 class plainformatter(baseformatter):
287 287 '''the default text output scheme'''
288 288 def __init__(self, ui, out, topic, opts):
289 289 baseformatter.__init__(self, ui, topic, opts, _plainconverter)
290 290 if ui.debugflag:
291 291 self.hexfunc = hex
292 292 else:
293 293 self.hexfunc = short
294 294 if ui is out:
295 295 self._write = ui.write
296 296 else:
297 297 self._write = lambda s, **opts: out.write(s)
298 298 def startitem(self):
299 299 pass
300 300 def data(self, **data):
301 301 pass
302 302 def write(self, fields, deftext, *fielddata, **opts):
303 303 self._write(deftext % fielddata, **opts)
304 304 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
305 305 '''do conditional write'''
306 306 if cond:
307 307 self._write(deftext % fielddata, **opts)
308 308 def plain(self, text, **opts):
309 309 self._write(text, **opts)
310 310 def isplain(self):
311 311 return True
312 312 def nested(self, field, tmpl=None, sep=''):
313 313 # nested data will be directly written to ui
314 314 return self
315 315 def end(self):
316 316 pass
317 317
318 318 class debugformatter(baseformatter):
319 319 def __init__(self, ui, out, topic, opts):
320 320 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
321 321 self._out = out
322 322 self._out.write("%s = [\n" % self._topic)
323 323 def _showitem(self):
324 324 self._out.write(' %s,\n' % pycompat.byterepr(self._item))
325 325 def end(self):
326 326 baseformatter.end(self)
327 327 self._out.write("]\n")
328 328
329 329 class pickleformatter(baseformatter):
330 330 def __init__(self, ui, out, topic, opts):
331 331 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
332 332 self._out = out
333 333 self._data = []
334 334 def _showitem(self):
335 335 self._data.append(self._item)
336 336 def end(self):
337 337 baseformatter.end(self)
338 338 self._out.write(pickle.dumps(self._data))
339 339
340 340 class jsonformatter(baseformatter):
341 341 def __init__(self, ui, out, topic, opts):
342 342 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
343 343 self._out = out
344 344 self._out.write("[")
345 345 self._first = True
346 346 def _showitem(self):
347 347 if self._first:
348 348 self._first = False
349 349 else:
350 350 self._out.write(",")
351 351
352 352 self._out.write("\n {\n")
353 353 first = True
354 354 for k, v in sorted(self._item.items()):
355 355 if first:
356 356 first = False
357 357 else:
358 358 self._out.write(",\n")
359 359 u = templatefilters.json(v, paranoid=False)
360 360 self._out.write(' "%s": %s' % (k, u))
361 361 self._out.write("\n }")
362 362 def end(self):
363 363 baseformatter.end(self)
364 364 self._out.write("\n]\n")
365 365
366 366 class _templateconverter(object):
367 367 '''convert non-primitive data types to be processed by templater'''
368 368
369 369 storecontext = True
370 370
371 371 @staticmethod
372 372 def wrapnested(data, tmpl, sep):
373 373 '''wrap nested data by templatable type'''
374 374 return templateutil.mappinglist(data, tmpl=tmpl, sep=sep)
375 375 @staticmethod
376 376 def formatdate(date, fmt):
377 377 '''return date tuple'''
378 378 return templateutil.date(date)
379 379 @staticmethod
380 380 def formatdict(data, key, value, fmt, sep):
381 381 '''build object that can be evaluated as either plain string or dict'''
382 382 data = util.sortdict(_iteritems(data))
383 383 def f():
384 384 yield _plainconverter.formatdict(data, key, value, fmt, sep)
385 385 return templateutil.hybriddict(data, key=key, value=value, fmt=fmt,
386 386 gen=f)
387 387 @staticmethod
388 388 def formatlist(data, name, fmt, sep):
389 389 '''build object that can be evaluated as either plain string or list'''
390 390 data = list(data)
391 391 def f():
392 392 yield _plainconverter.formatlist(data, name, fmt, sep)
393 393 return templateutil.hybridlist(data, name=name, fmt=fmt, gen=f)
394 394
395 395 class templateformatter(baseformatter):
396 396 def __init__(self, ui, out, topic, opts):
397 397 baseformatter.__init__(self, ui, topic, opts, _templateconverter)
398 398 self._out = out
399 399 spec = lookuptemplate(ui, topic, opts.get('template', ''))
400 400 self._tref = spec.ref
401 401 self._t = loadtemplater(ui, spec, defaults=templatekw.keywords,
402 402 resources=templateresources(ui),
403 403 cache=templatekw.defaulttempl)
404 404 self._parts = templatepartsmap(spec, self._t,
405 405 ['docheader', 'docfooter', 'separator'])
406 406 self._counter = itertools.count()
407 407 self._renderitem('docheader', {})
408 408
409 409 def _showitem(self):
410 410 item = self._item.copy()
411 411 item['index'] = index = next(self._counter)
412 412 if index > 0:
413 413 self._renderitem('separator', {})
414 414 self._renderitem(self._tref, item)
415 415
416 416 def _renderitem(self, part, item):
417 417 if part not in self._parts:
418 418 return
419 419 ref = self._parts[part]
420 420 self._out.write(self._t.render(ref, item))
421 421
422 422 @util.propertycache
423 423 def _symbolsused(self):
424 424 return self._t.symbolsused(self._tref)
425 425
426 426 def datahint(self):
427 427 '''set of field names to be referenced from the template'''
428 428 return self._symbolsused[0]
429 429
430 430 def end(self):
431 431 baseformatter.end(self)
432 432 self._renderitem('docfooter', {})
433 433
434 434 @attr.s(frozen=True)
435 435 class templatespec(object):
436 436 ref = attr.ib()
437 437 tmpl = attr.ib()
438 438 mapfile = attr.ib()
439 439
440 440 def lookuptemplate(ui, topic, tmpl):
441 441 """Find the template matching the given -T/--template spec 'tmpl'
442 442
443 443 'tmpl' can be any of the following:
444 444
445 445 - a literal template (e.g. '{rev}')
446 446 - a map-file name or path (e.g. 'changelog')
447 447 - a reference to [templates] in config file
448 448 - a path to raw template file
449 449
450 450 A map file defines a stand-alone template environment. If a map file
451 451 selected, all templates defined in the file will be loaded, and the
452 452 template matching the given topic will be rendered. Aliases won't be
453 453 loaded from user config, but from the map file.
454 454
455 455 If no map file selected, all templates in [templates] section will be
456 456 available as well as aliases in [templatealias].
457 457 """
458 458
459 459 # looks like a literal template?
460 460 if '{' in tmpl:
461 461 return templatespec('', tmpl, None)
462 462
463 463 # perhaps a stock style?
464 464 if not os.path.split(tmpl)[0]:
465 465 mapname = (templater.templatepath('map-cmdline.' + tmpl)
466 466 or templater.templatepath(tmpl))
467 467 if mapname and os.path.isfile(mapname):
468 468 return templatespec(topic, None, mapname)
469 469
470 470 # perhaps it's a reference to [templates]
471 471 if ui.config('templates', tmpl):
472 472 return templatespec(tmpl, None, None)
473 473
474 474 if tmpl == 'list':
475 475 ui.write(_("available styles: %s\n") % templater.stylelist())
476 476 raise error.Abort(_("specify a template"))
477 477
478 478 # perhaps it's a path to a map or a template
479 479 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
480 480 # is it a mapfile for a style?
481 481 if os.path.basename(tmpl).startswith("map-"):
482 482 return templatespec(topic, None, os.path.realpath(tmpl))
483 483 with util.posixfile(tmpl, 'rb') as f:
484 484 tmpl = f.read()
485 485 return templatespec('', tmpl, None)
486 486
487 487 # constant string?
488 488 return templatespec('', tmpl, None)
489 489
490 490 def templatepartsmap(spec, t, partnames):
491 491 """Create a mapping of {part: ref}"""
492 492 partsmap = {spec.ref: spec.ref} # initial ref must exist in t
493 493 if spec.mapfile:
494 494 partsmap.update((p, p) for p in partnames if p in t)
495 495 elif spec.ref:
496 496 for part in partnames:
497 497 ref = '%s:%s' % (spec.ref, part) # select config sub-section
498 498 if ref in t:
499 499 partsmap[part] = ref
500 500 return partsmap
501 501
502 502 def loadtemplater(ui, spec, defaults=None, resources=None, cache=None):
503 503 """Create a templater from either a literal template or loading from
504 504 a map file"""
505 505 assert not (spec.tmpl and spec.mapfile)
506 506 if spec.mapfile:
507 507 frommapfile = templater.templater.frommapfile
508 508 return frommapfile(spec.mapfile, defaults=defaults, resources=resources,
509 509 cache=cache)
510 510 return maketemplater(ui, spec.tmpl, defaults=defaults, resources=resources,
511 511 cache=cache)
512 512
513 513 def maketemplater(ui, tmpl, defaults=None, resources=None, cache=None):
514 514 """Create a templater from a string template 'tmpl'"""
515 515 aliases = ui.configitems('templatealias')
516 516 t = templater.templater(defaults=defaults, resources=resources,
517 517 cache=cache, aliases=aliases)
518 518 t.cache.update((k, templater.unquotestring(v))
519 519 for k, v in ui.configitems('templates'))
520 520 if tmpl:
521 521 t.cache[''] = tmpl
522 522 return t
523 523
524 524 # marker to denote a resource to be loaded on demand based on mapping values
525 525 # (e.g. (ctx, path) -> fctx)
526 526 _placeholder = object()
527 527
528 528 class templateresources(templater.resourcemapper):
529 529 """Resource mapper designed for the default templatekw and function"""
530 530
531 531 def __init__(self, ui, repo=None):
532 532 self._resmap = {
533 533 'cache': {}, # for templatekw/funcs to store reusable data
534 534 'repo': repo,
535 535 'ui': ui,
536 536 }
537 537
538 538 def availablekeys(self, mapping):
539 539 return {k for k in self.knownkeys()
540 540 if self._getsome(mapping, k) is not None}
541 541
542 542 def knownkeys(self):
543 543 return {'cache', 'ctx', 'fctx', 'repo', 'revcache', 'ui'}
544 544
545 545 def lookup(self, mapping, key):
546 546 if key not in self.knownkeys():
547 547 return None
548 548 v = self._getsome(mapping, key)
549 549 if v is _placeholder:
550 550 v = mapping[key] = self._loadermap[key](self, mapping)
551 551 return v
552 552
553 553 def populatemap(self, context, origmapping, newmapping):
554 554 mapping = {}
555 555 if self._hasnodespec(newmapping):
556 556 mapping['revcache'] = {} # per-ctx cache
557 557 if self._hasnodespec(origmapping) and self._hasnodespec(newmapping):
558 558 orignode = templateutil.runsymbol(context, origmapping, 'node')
559 559 mapping['originalnode'] = orignode
560 560 # put marker to override 'ctx'/'fctx' in mapping if any, and flag
561 561 # its existence to be reported by availablekeys()
562 562 if 'ctx' not in newmapping and self._hasliteral(newmapping, 'node'):
563 563 mapping['ctx'] = _placeholder
564 564 if 'fctx' not in newmapping and self._hasliteral(newmapping, 'path'):
565 565 mapping['fctx'] = _placeholder
566 566 return mapping
567 567
568 568 def _getsome(self, mapping, key):
569 569 v = mapping.get(key)
570 570 if v is not None:
571 571 return v
572 572 return self._resmap.get(key)
573 573
574 574 def _hasliteral(self, mapping, key):
575 575 """Test if a literal value is set or unset in the given mapping"""
576 576 return key in mapping and not callable(mapping[key])
577 577
578 578 def _getliteral(self, mapping, key):
579 579 """Return value of the given name if it is a literal"""
580 580 v = mapping.get(key)
581 581 if callable(v):
582 582 return None
583 583 return v
584 584
585 585 def _hasnodespec(self, mapping):
586 586 """Test if context revision is set or unset in the given mapping"""
587 587 return 'node' in mapping or 'ctx' in mapping
588 588
589 589 def _loadctx(self, mapping):
590 590 repo = self._getsome(mapping, 'repo')
591 591 node = self._getliteral(mapping, 'node')
592 592 if repo is None or node is None:
593 593 return
594 594 try:
595 595 return repo[node]
596 596 except error.RepoLookupError:
597 597 return None # maybe hidden/non-existent node
598 598
599 599 def _loadfctx(self, mapping):
600 600 ctx = self._getsome(mapping, 'ctx')
601 601 path = self._getliteral(mapping, 'path')
602 602 if ctx is None or path is None:
603 603 return None
604 604 try:
605 605 return ctx[path]
606 606 except error.LookupError:
607 607 return None # maybe removed file?
608 608
609 609 _loadermap = {
610 610 'ctx': _loadctx,
611 611 'fctx': _loadfctx,
612 612 }
613 613
614 614 def formatter(ui, out, topic, opts):
615 615 template = opts.get("template", "")
616 616 if template == "json":
617 617 return jsonformatter(ui, out, topic, opts)
618 618 elif template == "pickle":
619 619 return pickleformatter(ui, out, topic, opts)
620 620 elif template == "debug":
621 621 return debugformatter(ui, out, topic, opts)
622 622 elif template != "":
623 623 return templateformatter(ui, out, topic, opts)
624 624 # developer config: ui.formatdebug
625 625 elif ui.configbool('ui', 'formatdebug'):
626 626 return debugformatter(ui, out, topic, opts)
627 627 # deprecated config: ui.formatjson
628 628 elif ui.configbool('ui', 'formatjson'):
629 629 return jsonformatter(ui, out, topic, opts)
630 630 return plainformatter(ui, out, topic, opts)
631 631
632 632 @contextlib.contextmanager
633 633 def openformatter(ui, filename, topic, opts):
634 634 """Create a formatter that writes outputs to the specified file
635 635
636 636 Must be invoked using the 'with' statement.
637 637 """
638 638 with util.posixfile(filename, 'wb') as out:
639 639 with formatter(ui, out, topic, opts) as fm:
640 640 yield fm
641 641
642 642 @contextlib.contextmanager
643 643 def _neverending(fm):
644 644 yield fm
645 645
646 646 def maybereopen(fm, filename):
647 647 """Create a formatter backed by file if filename specified, else return
648 648 the given formatter
649 649
650 650 Must be invoked using the 'with' statement. This will never call fm.end()
651 651 of the given formatter.
652 652 """
653 653 if filename:
654 654 return openformatter(fm._ui, filename, fm._topic, fm._opts)
655 655 else:
656 656 return _neverending(fm)
General Comments 0
You need to be logged in to leave comments. Login now