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