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