1# formatter.py - generic output formatting for mercurial 2# 3# Copyright 2012 Olivia Mackall <olivia@selenic.com> 4# 5# This software may be used and distributed according to the terms of the 6# GNU General Public License version 2 or any later version. 7 8"""Generic output formatting for Mercurial 9 10The formatter provides API to show data in various ways. The following 11functions should be used in place of ui.write(): 12 13- fm.write() for unconditional output 14- fm.condwrite() to show some extra data conditionally in plain output 15- fm.context() to provide changectx to template output 16- fm.data() to provide extra data to JSON or template output 17- fm.plain() to show raw text that isn't provided to JSON or template output 18 19To show structured data (e.g. date tuples, dicts, lists), apply fm.format*() 20beforehand so the data is converted to the appropriate data type. Use 21fm.isplain() if you need to convert or format data conditionally which isn't 22supported by the formatter API. 23 24To build nested structure (i.e. a list of dicts), use fm.nested(). 25 26See also https://www.mercurial-scm.org/wiki/GenericTemplatingPlan 27 28fm.condwrite() vs 'if cond:': 29 30In most cases, use fm.condwrite() so users can selectively show the data 31in template output. If it's costly to build data, use plain 'if cond:' with 32fm.write(). 33 34fm.nested() vs fm.formatdict() (or fm.formatlist()): 35 36fm.nested() should be used to form a tree structure (a list of dicts of 37lists of dicts...) which can be accessed through template keywords, e.g. 38"{foo % "{bar % {...}} {baz % {...}}"}". On the other hand, fm.formatdict() 39exports a dict-type object to template, which can be accessed by e.g. 40"{get(foo, key)}" function. 41 42Doctest helper: 43 44>>> def show(fn, verbose=False, **opts): 45... import sys 46... from . import ui as uimod 47... ui = uimod.ui() 48... ui.verbose = verbose 49... ui.pushbuffer() 50... try: 51... return fn(ui, ui.formatter(pycompat.sysbytes(fn.__name__), 52... pycompat.byteskwargs(opts))) 53... finally: 54... print(pycompat.sysstr(ui.popbuffer()), end='') 55 56Basic example: 57 58>>> def files(ui, fm): 59... files = [(b'foo', 123, (0, 0)), (b'bar', 456, (1, 0))] 60... for f in files: 61... fm.startitem() 62... fm.write(b'path', b'%s', f[0]) 63... fm.condwrite(ui.verbose, b'date', b' %s', 64... fm.formatdate(f[2], b'%Y-%m-%d %H:%M:%S')) 65... fm.data(size=f[1]) 66... fm.plain(b'\\n') 67... fm.end() 68>>> show(files) 69foo 70bar 71>>> show(files, verbose=True) 72foo 1970-01-01 00:00:00 73bar 1970-01-01 00:00:01 74>>> show(files, template=b'json') 75[ 76 { 77 "date": [0, 0], 78 "path": "foo", 79 "size": 123 80 }, 81 { 82 "date": [1, 0], 83 "path": "bar", 84 "size": 456 85 } 86] 87>>> show(files, template=b'path: {path}\\ndate: {date|rfc3339date}\\n') 88path: foo 89date: 1970-01-01T00:00:00+00:00 90path: bar 91date: 1970-01-01T00:00:01+00:00 92 93Nested example: 94 95>>> def subrepos(ui, fm): 96... fm.startitem() 97... fm.write(b'reponame', b'[%s]\\n', b'baz') 98... files(ui, fm.nested(b'files', tmpl=b'{reponame}')) 99... fm.end() 100>>> show(subrepos) 101[baz] 102foo 103bar 104>>> show(subrepos, template=b'{reponame}: {join(files % "{path}", ", ")}\\n') 105baz: foo, bar 106""" 107 108from __future__ import absolute_import, print_function 109 110import contextlib 111import itertools 112import os 113 114from .i18n import _ 115from .node import ( 116 hex, 117 short, 118) 119from .thirdparty import attr 120 121from . import ( 122 error, 123 pycompat, 124 templatefilters, 125 templatekw, 126 templater, 127 templateutil, 128 util, 129) 130from .utils import ( 131 cborutil, 132 dateutil, 133 stringutil, 134) 135 136pickle = util.pickle 137 138 139def isprintable(obj): 140 """Check if the given object can be directly passed in to formatter's 141 write() and data() functions 142 143 Returns False if the object is unsupported or must be pre-processed by 144 formatdate(), formatdict(), or formatlist(). 145 """ 146 return isinstance(obj, (type(None), bool, int, pycompat.long, float, bytes)) 147 148 149class _nullconverter(object): 150 '''convert non-primitive data types to be processed by formatter''' 151 152 # set to True if context object should be stored as item 153 storecontext = False 154 155 @staticmethod 156 def wrapnested(data, tmpl, sep): 157 '''wrap nested data by appropriate type''' 158 return data 159 160 @staticmethod 161 def formatdate(date, fmt): 162 '''convert date tuple to appropriate format''' 163 # timestamp can be float, but the canonical form should be int 164 ts, tz = date 165 return (int(ts), tz) 166 167 @staticmethod 168 def formatdict(data, key, value, fmt, sep): 169 '''convert dict or key-value pairs to appropriate dict format''' 170 # use plain dict instead of util.sortdict so that data can be 171 # serialized as a builtin dict in pickle output 172 return dict(data) 173 174 @staticmethod 175 def formatlist(data, name, fmt, sep): 176 '''convert iterable to appropriate list format''' 177 return list(data) 178 179 180class baseformatter(object): 181 182 # set to True if the formater output a strict format that does not support 183 # arbitrary output in the stream. 184 strict_format = False 185 186 def __init__(self, ui, topic, opts, converter): 187 self._ui = ui 188 self._topic = topic 189 self._opts = opts 190 self._converter = converter 191 self._item = None 192 # function to convert node to string suitable for this output 193 self.hexfunc = hex 194 195 def __enter__(self): 196 return self 197 198 def __exit__(self, exctype, excvalue, traceback): 199 if exctype is None: 200 self.end() 201 202 def _showitem(self): 203 '''show a formatted item once all data is collected''' 204 205 def startitem(self): 206 '''begin an item in the format list''' 207 if self._item is not None: 208 self._showitem() 209 self._item = {} 210 211 def formatdate(self, date, fmt=b'%a %b %d %H:%M:%S %Y %1%2'): 212 '''convert date tuple to appropriate format''' 213 return self._converter.formatdate(date, fmt) 214 215 def formatdict(self, data, key=b'key', value=b'value', fmt=None, sep=b' '): 216 '''convert dict or key-value pairs to appropriate dict format''' 217 return self._converter.formatdict(data, key, value, fmt, sep) 218 219 def formatlist(self, data, name, fmt=None, sep=b' '): 220 '''convert iterable to appropriate list format''' 221 # name is mandatory argument for now, but it could be optional if 222 # we have default template keyword, e.g. {item} 223 return self._converter.formatlist(data, name, fmt, sep) 224 225 def context(self, **ctxs): 226 '''insert context objects to be used to render template keywords''' 227 ctxs = pycompat.byteskwargs(ctxs) 228 assert all(k in {b'repo', b'ctx', b'fctx'} for k in ctxs) 229 if self._converter.storecontext: 230 # populate missing resources in fctx -> ctx -> repo order 231 if b'fctx' in ctxs and b'ctx' not in ctxs: 232 ctxs[b'ctx'] = ctxs[b'fctx'].changectx() 233 if b'ctx' in ctxs and b'repo' not in ctxs: 234 ctxs[b'repo'] = ctxs[b'ctx'].repo() 235 self._item.update(ctxs) 236 237 def datahint(self): 238 '''set of field names to be referenced''' 239 return set() 240 241 def data(self, **data): 242 '''insert data into item that's not shown in default output''' 243 data = pycompat.byteskwargs(data) 244 self._item.update(data) 245 246 def write(self, fields, deftext, *fielddata, **opts): 247 '''do default text output while assigning data to item''' 248 fieldkeys = fields.split() 249 assert len(fieldkeys) == len(fielddata), (fieldkeys, fielddata) 250 self._item.update(zip(fieldkeys, fielddata)) 251 252 def condwrite(self, cond, fields, deftext, *fielddata, **opts): 253 '''do conditional write (primarily for plain formatter)''' 254 fieldkeys = fields.split() 255 assert len(fieldkeys) == len(fielddata) 256 self._item.update(zip(fieldkeys, fielddata)) 257 258 def plain(self, text, **opts): 259 '''show raw text for non-templated mode''' 260 261 def isplain(self): 262 '''check for plain formatter usage''' 263 return False 264 265 def nested(self, field, tmpl=None, sep=b''): 266 '''sub formatter to store nested data in the specified field''' 267 data = [] 268 self._item[field] = self._converter.wrapnested(data, tmpl, sep) 269 return _nestedformatter(self._ui, self._converter, data) 270 271 def end(self): 272 '''end output for the formatter''' 273 if self._item is not None: 274 self._showitem() 275 276 277def nullformatter(ui, topic, opts): 278 '''formatter that prints nothing''' 279 return baseformatter(ui, topic, opts, converter=_nullconverter) 280 281 282class _nestedformatter(baseformatter): 283 '''build sub items and store them in the parent formatter''' 284 285 def __init__(self, ui, converter, data): 286 baseformatter.__init__( 287 self, ui, topic=b'', opts={}, converter=converter 288 ) 289 self._data = data 290 291 def _showitem(self): 292 self._data.append(self._item) 293 294 295def _iteritems(data): 296 '''iterate key-value pairs in stable order''' 297 if isinstance(data, dict): 298 return sorted(pycompat.iteritems(data)) 299 return data 300 301 302class _plainconverter(object): 303 '''convert non-primitive data types to text''' 304 305 storecontext = False 306 307 @staticmethod 308 def wrapnested(data, tmpl, sep): 309 raise error.ProgrammingError(b'plainformatter should never be nested') 310 311 @staticmethod 312 def formatdate(date, fmt): 313 '''stringify date tuple in the given format''' 314 return dateutil.datestr(date, fmt) 315 316 @staticmethod 317 def formatdict(data, key, value, fmt, sep): 318 '''stringify key-value pairs separated by sep''' 319 prefmt = pycompat.identity 320 if fmt is None: 321 fmt = b'%s=%s' 322 prefmt = pycompat.bytestr 323 return sep.join( 324 fmt % (prefmt(k), prefmt(v)) for k, v in _iteritems(data) 325 ) 326 327 @staticmethod 328 def formatlist(data, name, fmt, sep): 329 '''stringify iterable separated by sep''' 330 prefmt = pycompat.identity 331 if fmt is None: 332 fmt = b'%s' 333 prefmt = pycompat.bytestr 334 return sep.join(fmt % prefmt(e) for e in data) 335 336 337class plainformatter(baseformatter): 338 '''the default text output scheme''' 339 340 def __init__(self, ui, out, topic, opts): 341 baseformatter.__init__(self, ui, topic, opts, _plainconverter) 342 if ui.debugflag: 343 self.hexfunc = hex 344 else: 345 self.hexfunc = short 346 if ui is out: 347 self._write = ui.write 348 else: 349 self._write = lambda s, **opts: out.write(s) 350 351 def startitem(self): 352 pass 353 354 def data(self, **data): 355 pass 356 357 def write(self, fields, deftext, *fielddata, **opts): 358 self._write(deftext % fielddata, **opts) 359 360 def condwrite(self, cond, fields, deftext, *fielddata, **opts): 361 '''do conditional write''' 362 if cond: 363 self._write(deftext % fielddata, **opts) 364 365 def plain(self, text, **opts): 366 self._write(text, **opts) 367 368 def isplain(self): 369 return True 370 371 def nested(self, field, tmpl=None, sep=b''): 372 # nested data will be directly written to ui 373 return self 374 375 def end(self): 376 pass 377 378 379class debugformatter(baseformatter): 380 def __init__(self, ui, out, topic, opts): 381 baseformatter.__init__(self, ui, topic, opts, _nullconverter) 382 self._out = out 383 self._out.write(b"%s = [\n" % self._topic) 384 385 def _showitem(self): 386 self._out.write( 387 b' %s,\n' % stringutil.pprint(self._item, indent=4, level=1) 388 ) 389 390 def end(self): 391 baseformatter.end(self) 392 self._out.write(b"]\n") 393 394 395class pickleformatter(baseformatter): 396 def __init__(self, ui, out, topic, opts): 397 baseformatter.__init__(self, ui, topic, opts, _nullconverter) 398 self._out = out 399 self._data = [] 400 401 def _showitem(self): 402 self._data.append(self._item) 403 404 def end(self): 405 baseformatter.end(self) 406 self._out.write(pickle.dumps(self._data)) 407 408 409class cborformatter(baseformatter): 410 '''serialize items as an indefinite-length CBOR array''' 411 412 def __init__(self, ui, out, topic, opts): 413 baseformatter.__init__(self, ui, topic, opts, _nullconverter) 414 self._out = out 415 self._out.write(cborutil.BEGIN_INDEFINITE_ARRAY) 416 417 def _showitem(self): 418 self._out.write(b''.join(cborutil.streamencode(self._item))) 419 420 def end(self): 421 baseformatter.end(self) 422 self._out.write(cborutil.BREAK) 423 424 425class jsonformatter(baseformatter): 426 427 strict_format = True 428 429 def __init__(self, ui, out, topic, opts): 430 baseformatter.__init__(self, ui, topic, opts, _nullconverter) 431 self._out = out 432 self._out.write(b"[") 433 self._first = True 434 435 def _showitem(self): 436 if self._first: 437 self._first = False 438 else: 439 self._out.write(b",") 440 441 self._out.write(b"\n {\n") 442 first = True 443 for k, v in sorted(self._item.items()): 444 if first: 445 first = False 446 else: 447 self._out.write(b",\n") 448 u = templatefilters.json(v, paranoid=False) 449 self._out.write(b' "%s": %s' % (k, u)) 450 self._out.write(b"\n }") 451 452 def end(self): 453 baseformatter.end(self) 454 self._out.write(b"\n]\n") 455 456 457class _templateconverter(object): 458 '''convert non-primitive data types to be processed by templater''' 459 460 storecontext = True 461 462 @staticmethod 463 def wrapnested(data, tmpl, sep): 464 '''wrap nested data by templatable type''' 465 return templateutil.mappinglist(data, tmpl=tmpl, sep=sep) 466 467 @staticmethod 468 def formatdate(date, fmt): 469 '''return date tuple''' 470 return templateutil.date(date) 471 472 @staticmethod 473 def formatdict(data, key, value, fmt, sep): 474 '''build object that can be evaluated as either plain string or dict''' 475 data = util.sortdict(_iteritems(data)) 476 477 def f(): 478 yield _plainconverter.formatdict(data, key, value, fmt, sep) 479 480 return templateutil.hybriddict( 481 data, key=key, value=value, fmt=fmt, gen=f 482 ) 483 484 @staticmethod 485 def formatlist(data, name, fmt, sep): 486 '''build object that can be evaluated as either plain string or list''' 487 data = list(data) 488 489 def f(): 490 yield _plainconverter.formatlist(data, name, fmt, sep) 491 492 return templateutil.hybridlist(data, name=name, fmt=fmt, gen=f) 493 494 495class templateformatter(baseformatter): 496 def __init__(self, ui, out, topic, opts, spec, overridetemplates=None): 497 baseformatter.__init__(self, ui, topic, opts, _templateconverter) 498 self._out = out 499 self._tref = spec.ref 500 self._t = loadtemplater( 501 ui, 502 spec, 503 defaults=templatekw.keywords, 504 resources=templateresources(ui), 505 cache=templatekw.defaulttempl, 506 ) 507 if overridetemplates: 508 self._t.cache.update(overridetemplates) 509 self._parts = templatepartsmap( 510 spec, self._t, [b'docheader', b'docfooter', b'separator'] 511 ) 512 self._counter = itertools.count() 513 self._renderitem(b'docheader', {}) 514 515 def _showitem(self): 516 item = self._item.copy() 517 item[b'index'] = index = next(self._counter) 518 if index > 0: 519 self._renderitem(b'separator', {}) 520 self._renderitem(self._tref, item) 521 522 def _renderitem(self, part, item): 523 if part not in self._parts: 524 return 525 ref = self._parts[part] 526 # None can't be put in the mapping dict since it means <unset> 527 for k, v in item.items(): 528 if v is None: 529 item[k] = templateutil.wrappedvalue(v) 530 self._out.write(self._t.render(ref, item)) 531 532 @util.propertycache 533 def _symbolsused(self): 534 return self._t.symbolsused(self._tref) 535 536 def datahint(self): 537 '''set of field names to be referenced from the template''' 538 return self._symbolsused[0] 539 540 def end(self): 541 baseformatter.end(self) 542 self._renderitem(b'docfooter', {}) 543 544 545@attr.s(frozen=True) 546class templatespec(object): 547 ref = attr.ib() 548 tmpl = attr.ib() 549 mapfile = attr.ib() 550 refargs = attr.ib(default=None) 551 fp = attr.ib(default=None) 552 553 554def empty_templatespec(): 555 return templatespec(None, None, None) 556 557 558def reference_templatespec(ref, refargs=None): 559 return templatespec(ref, None, None, refargs) 560 561 562def literal_templatespec(tmpl): 563 if pycompat.ispy3: 564 assert not isinstance(tmpl, str), b'tmpl must not be a str' 565 return templatespec(b'', tmpl, None) 566 567 568def mapfile_templatespec(topic, mapfile, fp=None): 569 return templatespec(topic, None, mapfile, fp=fp) 570 571 572def lookuptemplate(ui, topic, tmpl): 573 """Find the template matching the given -T/--template spec 'tmpl' 574 575 'tmpl' can be any of the following: 576 577 - a literal template (e.g. '{rev}') 578 - a reference to built-in template (i.e. formatter) 579 - a map-file name or path (e.g. 'changelog') 580 - a reference to [templates] in config file 581 - a path to raw template file 582 583 A map file defines a stand-alone template environment. If a map file 584 selected, all templates defined in the file will be loaded, and the 585 template matching the given topic will be rendered. Aliases won't be 586 loaded from user config, but from the map file. 587 588 If no map file selected, all templates in [templates] section will be 589 available as well as aliases in [templatealias]. 590 """ 591 592 if not tmpl: 593 return empty_templatespec() 594 595 # looks like a literal template? 596 if b'{' in tmpl: 597 return literal_templatespec(tmpl) 598 599 # a reference to built-in (formatter) template 600 if tmpl in {b'cbor', b'json', b'pickle', b'debug'}: 601 return reference_templatespec(tmpl) 602 603 # a function-style reference to built-in template 604 func, fsep, ftail = tmpl.partition(b'(') 605 if func in {b'cbor', b'json'} and fsep and ftail.endswith(b')'): 606 templater.parseexpr(tmpl) # make sure syntax errors are confined 607 return reference_templatespec(func, refargs=ftail[:-1]) 608 609 # perhaps a stock style? 610 if not os.path.split(tmpl)[0]: 611 (mapname, fp) = templater.try_open_template( 612 b'map-cmdline.' + tmpl 613 ) or templater.try_open_template(tmpl) 614 if mapname: 615 return mapfile_templatespec(topic, mapname, fp) 616 617 # perhaps it's a reference to [templates] 618 if ui.config(b'templates', tmpl): 619 return reference_templatespec(tmpl) 620 621 if tmpl == b'list': 622 ui.write(_(b"available styles: %s\n") % templater.stylelist()) 623 raise error.Abort(_(b"specify a template")) 624 625 # perhaps it's a path to a map or a template 626 if (b'/' in tmpl or b'\\' in tmpl) and os.path.isfile(tmpl): 627 # is it a mapfile for a style? 628 if os.path.basename(tmpl).startswith(b"map-"): 629 return mapfile_templatespec(topic, os.path.realpath(tmpl)) 630 with util.posixfile(tmpl, b'rb') as f: 631 tmpl = f.read() 632 return literal_templatespec(tmpl) 633 634 # constant string? 635 return literal_templatespec(tmpl) 636 637 638def templatepartsmap(spec, t, partnames): 639 """Create a mapping of {part: ref}""" 640 partsmap = {spec.ref: spec.ref} # initial ref must exist in t 641 if spec.mapfile: 642 partsmap.update((p, p) for p in partnames if p in t) 643 elif spec.ref: 644 for part in partnames: 645 ref = b'%s:%s' % (spec.ref, part) # select config sub-section 646 if ref in t: 647 partsmap[part] = ref 648 return partsmap 649 650 651def loadtemplater(ui, spec, defaults=None, resources=None, cache=None): 652 """Create a templater from either a literal template or loading from 653 a map file""" 654 assert not (spec.tmpl and spec.mapfile) 655 if spec.mapfile: 656 return templater.templater.frommapfile( 657 spec.mapfile, 658 spec.fp, 659 defaults=defaults, 660 resources=resources, 661 cache=cache, 662 ) 663 return maketemplater( 664 ui, spec.tmpl, defaults=defaults, resources=resources, cache=cache 665 ) 666 667 668def maketemplater(ui, tmpl, defaults=None, resources=None, cache=None): 669 """Create a templater from a string template 'tmpl'""" 670 aliases = ui.configitems(b'templatealias') 671 t = templater.templater( 672 defaults=defaults, resources=resources, cache=cache, aliases=aliases 673 ) 674 t.cache.update( 675 (k, templater.unquotestring(v)) for k, v in ui.configitems(b'templates') 676 ) 677 if tmpl: 678 t.cache[b''] = tmpl 679 return t 680 681 682# marker to denote a resource to be loaded on demand based on mapping values 683# (e.g. (ctx, path) -> fctx) 684_placeholder = object() 685 686 687class templateresources(templater.resourcemapper): 688 """Resource mapper designed for the default templatekw and function""" 689 690 def __init__(self, ui, repo=None): 691 self._resmap = { 692 b'cache': {}, # for templatekw/funcs to store reusable data 693 b'repo': repo, 694 b'ui': ui, 695 } 696 697 def availablekeys(self, mapping): 698 return { 699 k for k in self.knownkeys() if self._getsome(mapping, k) is not None 700 } 701 702 def knownkeys(self): 703 return {b'cache', b'ctx', b'fctx', b'repo', b'revcache', b'ui'} 704 705 def lookup(self, mapping, key): 706 if key not in self.knownkeys(): 707 return None 708 v = self._getsome(mapping, key) 709 if v is _placeholder: 710 v = mapping[key] = self._loadermap[key](self, mapping) 711 return v 712 713 def populatemap(self, context, origmapping, newmapping): 714 mapping = {} 715 if self._hasnodespec(newmapping): 716 mapping[b'revcache'] = {} # per-ctx cache 717 if self._hasnodespec(origmapping) and self._hasnodespec(newmapping): 718 orignode = templateutil.runsymbol(context, origmapping, b'node') 719 mapping[b'originalnode'] = orignode 720 # put marker to override 'ctx'/'fctx' in mapping if any, and flag 721 # its existence to be reported by availablekeys() 722 if b'ctx' not in newmapping and self._hasliteral(newmapping, b'node'): 723 mapping[b'ctx'] = _placeholder 724 if b'fctx' not in newmapping and self._hasliteral(newmapping, b'path'): 725 mapping[b'fctx'] = _placeholder 726 return mapping 727 728 def _getsome(self, mapping, key): 729 v = mapping.get(key) 730 if v is not None: 731 return v 732 return self._resmap.get(key) 733 734 def _hasliteral(self, mapping, key): 735 """Test if a literal value is set or unset in the given mapping""" 736 return key in mapping and not callable(mapping[key]) 737 738 def _getliteral(self, mapping, key): 739 """Return value of the given name if it is a literal""" 740 v = mapping.get(key) 741 if callable(v): 742 return None 743 return v 744 745 def _hasnodespec(self, mapping): 746 """Test if context revision is set or unset in the given mapping""" 747 return b'node' in mapping or b'ctx' in mapping 748 749 def _loadctx(self, mapping): 750 repo = self._getsome(mapping, b'repo') 751 node = self._getliteral(mapping, b'node') 752 if repo is None or node is None: 753 return 754 try: 755 return repo[node] 756 except error.RepoLookupError: 757 return None # maybe hidden/non-existent node 758 759 def _loadfctx(self, mapping): 760 ctx = self._getsome(mapping, b'ctx') 761 path = self._getliteral(mapping, b'path') 762 if ctx is None or path is None: 763 return None 764 try: 765 return ctx[path] 766 except error.LookupError: 767 return None # maybe removed file? 768 769 _loadermap = { 770 b'ctx': _loadctx, 771 b'fctx': _loadfctx, 772 } 773 774 775def _internaltemplateformatter( 776 ui, 777 out, 778 topic, 779 opts, 780 spec, 781 tmpl, 782 docheader=b'', 783 docfooter=b'', 784 separator=b'', 785): 786 """Build template formatter that handles customizable built-in templates 787 such as -Tjson(...)""" 788 templates = {spec.ref: tmpl} 789 if docheader: 790 templates[b'%s:docheader' % spec.ref] = docheader 791 if docfooter: 792 templates[b'%s:docfooter' % spec.ref] = docfooter 793 if separator: 794 templates[b'%s:separator' % spec.ref] = separator 795 return templateformatter( 796 ui, out, topic, opts, spec, overridetemplates=templates 797 ) 798 799 800def formatter(ui, out, topic, opts): 801 spec = lookuptemplate(ui, topic, opts.get(b'template', b'')) 802 if spec.ref == b"cbor" and spec.refargs is not None: 803 return _internaltemplateformatter( 804 ui, 805 out, 806 topic, 807 opts, 808 spec, 809 tmpl=b'{dict(%s)|cbor}' % spec.refargs, 810 docheader=cborutil.BEGIN_INDEFINITE_ARRAY, 811 docfooter=cborutil.BREAK, 812 ) 813 elif spec.ref == b"cbor": 814 return cborformatter(ui, out, topic, opts) 815 elif spec.ref == b"json" and spec.refargs is not None: 816 return _internaltemplateformatter( 817 ui, 818 out, 819 topic, 820 opts, 821 spec, 822 tmpl=b'{dict(%s)|json}' % spec.refargs, 823 docheader=b'[\n ', 824 docfooter=b'\n]\n', 825 separator=b',\n ', 826 ) 827 elif spec.ref == b"json": 828 return jsonformatter(ui, out, topic, opts) 829 elif spec.ref == b"pickle": 830 assert spec.refargs is None, r'function-style not supported' 831 return pickleformatter(ui, out, topic, opts) 832 elif spec.ref == b"debug": 833 assert spec.refargs is None, r'function-style not supported' 834 return debugformatter(ui, out, topic, opts) 835 elif spec.ref or spec.tmpl or spec.mapfile: 836 assert spec.refargs is None, r'function-style not supported' 837 return templateformatter(ui, out, topic, opts, spec) 838 # developer config: ui.formatdebug 839 elif ui.configbool(b'ui', b'formatdebug'): 840 return debugformatter(ui, out, topic, opts) 841 # deprecated config: ui.formatjson 842 elif ui.configbool(b'ui', b'formatjson'): 843 return jsonformatter(ui, out, topic, opts) 844 return plainformatter(ui, out, topic, opts) 845 846 847@contextlib.contextmanager 848def openformatter(ui, filename, topic, opts): 849 """Create a formatter that writes outputs to the specified file 850 851 Must be invoked using the 'with' statement. 852 """ 853 with util.posixfile(filename, b'wb') as out: 854 with formatter(ui, out, topic, opts) as fm: 855 yield fm 856 857 858@contextlib.contextmanager 859def _neverending(fm): 860 yield fm 861 862 863def maybereopen(fm, filename): 864 """Create a formatter backed by file if filename specified, else return 865 the given formatter 866 867 Must be invoked using the 'with' statement. This will never call fm.end() 868 of the given formatter. 869 """ 870 if filename: 871 return openformatter(fm._ui, filename, fm._topic, fm._opts) 872 else: 873 return _neverending(fm) 874