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