comparison mercurial/formatter.py @ 43077:687b865b95ad

formatting: byteify all mercurial/ and hgext/ string literals Done with python3.7 contrib/byteify-strings.py -i $(hg files 'set:mercurial/**.py - mercurial/thirdparty/** + hgext/**.py - hgext/fsmonitor/pywatchman/** - mercurial/__init__.py') black -l 80 -t py33 -S $(hg files 'set:**.py - mercurial/thirdparty/** - "contrib/python-zstandard/**" - hgext/fsmonitor/pywatchman/**') # skip-blame mass-reformatting only Differential Revision: https://phab.mercurial-scm.org/D6972
author Augie Fackler <augie@google.com>
date Sun, 06 Oct 2019 09:48:39 -0400
parents 2372284d9457
children f1c5358f0d65
comparison
equal deleted inserted replaced
43076:2372284d9457 43077:687b865b95ad
191 '''begin an item in the format list''' 191 '''begin an item in the format list'''
192 if self._item is not None: 192 if self._item is not None:
193 self._showitem() 193 self._showitem()
194 self._item = {} 194 self._item = {}
195 195
196 def formatdate(self, date, fmt='%a %b %d %H:%M:%S %Y %1%2'): 196 def formatdate(self, date, fmt=b'%a %b %d %H:%M:%S %Y %1%2'):
197 '''convert date tuple to appropriate format''' 197 '''convert date tuple to appropriate format'''
198 return self._converter.formatdate(date, fmt) 198 return self._converter.formatdate(date, fmt)
199 199
200 def formatdict(self, data, key='key', value='value', fmt=None, sep=' '): 200 def formatdict(self, data, key=b'key', value=b'value', fmt=None, sep=b' '):
201 '''convert dict or key-value pairs to appropriate dict format''' 201 '''convert dict or key-value pairs to appropriate dict format'''
202 return self._converter.formatdict(data, key, value, fmt, sep) 202 return self._converter.formatdict(data, key, value, fmt, sep)
203 203
204 def formatlist(self, data, name, fmt=None, sep=' '): 204 def formatlist(self, data, name, fmt=None, sep=b' '):
205 '''convert iterable to appropriate list format''' 205 '''convert iterable to appropriate list format'''
206 # name is mandatory argument for now, but it could be optional if 206 # name is mandatory argument for now, but it could be optional if
207 # we have default template keyword, e.g. {item} 207 # we have default template keyword, e.g. {item}
208 return self._converter.formatlist(data, name, fmt, sep) 208 return self._converter.formatlist(data, name, fmt, sep)
209 209
210 def context(self, **ctxs): 210 def context(self, **ctxs):
211 '''insert context objects to be used to render template keywords''' 211 '''insert context objects to be used to render template keywords'''
212 ctxs = pycompat.byteskwargs(ctxs) 212 ctxs = pycompat.byteskwargs(ctxs)
213 assert all(k in {'repo', 'ctx', 'fctx'} for k in ctxs) 213 assert all(k in {b'repo', b'ctx', b'fctx'} for k in ctxs)
214 if self._converter.storecontext: 214 if self._converter.storecontext:
215 # populate missing resources in fctx -> ctx -> repo order 215 # populate missing resources in fctx -> ctx -> repo order
216 if 'fctx' in ctxs and 'ctx' not in ctxs: 216 if b'fctx' in ctxs and b'ctx' not in ctxs:
217 ctxs['ctx'] = ctxs['fctx'].changectx() 217 ctxs[b'ctx'] = ctxs[b'fctx'].changectx()
218 if 'ctx' in ctxs and 'repo' not in ctxs: 218 if b'ctx' in ctxs and b'repo' not in ctxs:
219 ctxs['repo'] = ctxs['ctx'].repo() 219 ctxs[b'repo'] = ctxs[b'ctx'].repo()
220 self._item.update(ctxs) 220 self._item.update(ctxs)
221 221
222 def datahint(self): 222 def datahint(self):
223 '''set of field names to be referenced''' 223 '''set of field names to be referenced'''
224 return set() 224 return set()
245 245
246 def isplain(self): 246 def isplain(self):
247 '''check for plain formatter usage''' 247 '''check for plain formatter usage'''
248 return False 248 return False
249 249
250 def nested(self, field, tmpl=None, sep=''): 250 def nested(self, field, tmpl=None, sep=b''):
251 '''sub formatter to store nested data in the specified field''' 251 '''sub formatter to store nested data in the specified field'''
252 data = [] 252 data = []
253 self._item[field] = self._converter.wrapnested(data, tmpl, sep) 253 self._item[field] = self._converter.wrapnested(data, tmpl, sep)
254 return _nestedformatter(self._ui, self._converter, data) 254 return _nestedformatter(self._ui, self._converter, data)
255 255
266 266
267 class _nestedformatter(baseformatter): 267 class _nestedformatter(baseformatter):
268 '''build sub items and store them in the parent formatter''' 268 '''build sub items and store them in the parent formatter'''
269 269
270 def __init__(self, ui, converter, data): 270 def __init__(self, ui, converter, data):
271 baseformatter.__init__(self, ui, topic='', opts={}, converter=converter) 271 baseformatter.__init__(
272 self, ui, topic=b'', opts={}, converter=converter
273 )
272 self._data = data 274 self._data = data
273 275
274 def _showitem(self): 276 def _showitem(self):
275 self._data.append(self._item) 277 self._data.append(self._item)
276 278
287 289
288 storecontext = False 290 storecontext = False
289 291
290 @staticmethod 292 @staticmethod
291 def wrapnested(data, tmpl, sep): 293 def wrapnested(data, tmpl, sep):
292 raise error.ProgrammingError('plainformatter should never be nested') 294 raise error.ProgrammingError(b'plainformatter should never be nested')
293 295
294 @staticmethod 296 @staticmethod
295 def formatdate(date, fmt): 297 def formatdate(date, fmt):
296 '''stringify date tuple in the given format''' 298 '''stringify date tuple in the given format'''
297 return dateutil.datestr(date, fmt) 299 return dateutil.datestr(date, fmt)
299 @staticmethod 301 @staticmethod
300 def formatdict(data, key, value, fmt, sep): 302 def formatdict(data, key, value, fmt, sep):
301 '''stringify key-value pairs separated by sep''' 303 '''stringify key-value pairs separated by sep'''
302 prefmt = pycompat.identity 304 prefmt = pycompat.identity
303 if fmt is None: 305 if fmt is None:
304 fmt = '%s=%s' 306 fmt = b'%s=%s'
305 prefmt = pycompat.bytestr 307 prefmt = pycompat.bytestr
306 return sep.join( 308 return sep.join(
307 fmt % (prefmt(k), prefmt(v)) for k, v in _iteritems(data) 309 fmt % (prefmt(k), prefmt(v)) for k, v in _iteritems(data)
308 ) 310 )
309 311
310 @staticmethod 312 @staticmethod
311 def formatlist(data, name, fmt, sep): 313 def formatlist(data, name, fmt, sep):
312 '''stringify iterable separated by sep''' 314 '''stringify iterable separated by sep'''
313 prefmt = pycompat.identity 315 prefmt = pycompat.identity
314 if fmt is None: 316 if fmt is None:
315 fmt = '%s' 317 fmt = b'%s'
316 prefmt = pycompat.bytestr 318 prefmt = pycompat.bytestr
317 return sep.join(fmt % prefmt(e) for e in data) 319 return sep.join(fmt % prefmt(e) for e in data)
318 320
319 321
320 class plainformatter(baseformatter): 322 class plainformatter(baseformatter):
349 self._write(text, **opts) 351 self._write(text, **opts)
350 352
351 def isplain(self): 353 def isplain(self):
352 return True 354 return True
353 355
354 def nested(self, field, tmpl=None, sep=''): 356 def nested(self, field, tmpl=None, sep=b''):
355 # nested data will be directly written to ui 357 # nested data will be directly written to ui
356 return self 358 return self
357 359
358 def end(self): 360 def end(self):
359 pass 361 pass
361 363
362 class debugformatter(baseformatter): 364 class debugformatter(baseformatter):
363 def __init__(self, ui, out, topic, opts): 365 def __init__(self, ui, out, topic, opts):
364 baseformatter.__init__(self, ui, topic, opts, _nullconverter) 366 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
365 self._out = out 367 self._out = out
366 self._out.write("%s = [\n" % self._topic) 368 self._out.write(b"%s = [\n" % self._topic)
367 369
368 def _showitem(self): 370 def _showitem(self):
369 self._out.write( 371 self._out.write(
370 ' %s,\n' % stringutil.pprint(self._item, indent=4, level=1) 372 b' %s,\n' % stringutil.pprint(self._item, indent=4, level=1)
371 ) 373 )
372 374
373 def end(self): 375 def end(self):
374 baseformatter.end(self) 376 baseformatter.end(self)
375 self._out.write("]\n") 377 self._out.write(b"]\n")
376 378
377 379
378 class pickleformatter(baseformatter): 380 class pickleformatter(baseformatter):
379 def __init__(self, ui, out, topic, opts): 381 def __init__(self, ui, out, topic, opts):
380 baseformatter.__init__(self, ui, topic, opts, _nullconverter) 382 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
407 409
408 class jsonformatter(baseformatter): 410 class jsonformatter(baseformatter):
409 def __init__(self, ui, out, topic, opts): 411 def __init__(self, ui, out, topic, opts):
410 baseformatter.__init__(self, ui, topic, opts, _nullconverter) 412 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
411 self._out = out 413 self._out = out
412 self._out.write("[") 414 self._out.write(b"[")
413 self._first = True 415 self._first = True
414 416
415 def _showitem(self): 417 def _showitem(self):
416 if self._first: 418 if self._first:
417 self._first = False 419 self._first = False
418 else: 420 else:
419 self._out.write(",") 421 self._out.write(b",")
420 422
421 self._out.write("\n {\n") 423 self._out.write(b"\n {\n")
422 first = True 424 first = True
423 for k, v in sorted(self._item.items()): 425 for k, v in sorted(self._item.items()):
424 if first: 426 if first:
425 first = False 427 first = False
426 else: 428 else:
427 self._out.write(",\n") 429 self._out.write(b",\n")
428 u = templatefilters.json(v, paranoid=False) 430 u = templatefilters.json(v, paranoid=False)
429 self._out.write(' "%s": %s' % (k, u)) 431 self._out.write(b' "%s": %s' % (k, u))
430 self._out.write("\n }") 432 self._out.write(b"\n }")
431 433
432 def end(self): 434 def end(self):
433 baseformatter.end(self) 435 baseformatter.end(self)
434 self._out.write("\n]\n") 436 self._out.write(b"\n]\n")
435 437
436 438
437 class _templateconverter(object): 439 class _templateconverter(object):
438 '''convert non-primitive data types to be processed by templater''' 440 '''convert non-primitive data types to be processed by templater'''
439 441
474 476
475 class templateformatter(baseformatter): 477 class templateformatter(baseformatter):
476 def __init__(self, ui, out, topic, opts): 478 def __init__(self, ui, out, topic, opts):
477 baseformatter.__init__(self, ui, topic, opts, _templateconverter) 479 baseformatter.__init__(self, ui, topic, opts, _templateconverter)
478 self._out = out 480 self._out = out
479 spec = lookuptemplate(ui, topic, opts.get('template', '')) 481 spec = lookuptemplate(ui, topic, opts.get(b'template', b''))
480 self._tref = spec.ref 482 self._tref = spec.ref
481 self._t = loadtemplater( 483 self._t = loadtemplater(
482 ui, 484 ui,
483 spec, 485 spec,
484 defaults=templatekw.keywords, 486 defaults=templatekw.keywords,
485 resources=templateresources(ui), 487 resources=templateresources(ui),
486 cache=templatekw.defaulttempl, 488 cache=templatekw.defaulttempl,
487 ) 489 )
488 self._parts = templatepartsmap( 490 self._parts = templatepartsmap(
489 spec, self._t, ['docheader', 'docfooter', 'separator'] 491 spec, self._t, [b'docheader', b'docfooter', b'separator']
490 ) 492 )
491 self._counter = itertools.count() 493 self._counter = itertools.count()
492 self._renderitem('docheader', {}) 494 self._renderitem(b'docheader', {})
493 495
494 def _showitem(self): 496 def _showitem(self):
495 item = self._item.copy() 497 item = self._item.copy()
496 item['index'] = index = next(self._counter) 498 item[b'index'] = index = next(self._counter)
497 if index > 0: 499 if index > 0:
498 self._renderitem('separator', {}) 500 self._renderitem(b'separator', {})
499 self._renderitem(self._tref, item) 501 self._renderitem(self._tref, item)
500 502
501 def _renderitem(self, part, item): 503 def _renderitem(self, part, item):
502 if part not in self._parts: 504 if part not in self._parts:
503 return 505 return
512 '''set of field names to be referenced from the template''' 514 '''set of field names to be referenced from the template'''
513 return self._symbolsused[0] 515 return self._symbolsused[0]
514 516
515 def end(self): 517 def end(self):
516 baseformatter.end(self) 518 baseformatter.end(self)
517 self._renderitem('docfooter', {}) 519 self._renderitem(b'docfooter', {})
518 520
519 521
520 @attr.s(frozen=True) 522 @attr.s(frozen=True)
521 class templatespec(object): 523 class templatespec(object):
522 ref = attr.ib() 524 ref = attr.ib()
542 If no map file selected, all templates in [templates] section will be 544 If no map file selected, all templates in [templates] section will be
543 available as well as aliases in [templatealias]. 545 available as well as aliases in [templatealias].
544 """ 546 """
545 547
546 # looks like a literal template? 548 # looks like a literal template?
547 if '{' in tmpl: 549 if b'{' in tmpl:
548 return templatespec('', tmpl, None) 550 return templatespec(b'', tmpl, None)
549 551
550 # perhaps a stock style? 552 # perhaps a stock style?
551 if not os.path.split(tmpl)[0]: 553 if not os.path.split(tmpl)[0]:
552 mapname = templater.templatepath( 554 mapname = templater.templatepath(
553 'map-cmdline.' + tmpl 555 b'map-cmdline.' + tmpl
554 ) or templater.templatepath(tmpl) 556 ) or templater.templatepath(tmpl)
555 if mapname and os.path.isfile(mapname): 557 if mapname and os.path.isfile(mapname):
556 return templatespec(topic, None, mapname) 558 return templatespec(topic, None, mapname)
557 559
558 # perhaps it's a reference to [templates] 560 # perhaps it's a reference to [templates]
559 if ui.config('templates', tmpl): 561 if ui.config(b'templates', tmpl):
560 return templatespec(tmpl, None, None) 562 return templatespec(tmpl, None, None)
561 563
562 if tmpl == 'list': 564 if tmpl == b'list':
563 ui.write(_("available styles: %s\n") % templater.stylelist()) 565 ui.write(_(b"available styles: %s\n") % templater.stylelist())
564 raise error.Abort(_("specify a template")) 566 raise error.Abort(_(b"specify a template"))
565 567
566 # perhaps it's a path to a map or a template 568 # perhaps it's a path to a map or a template
567 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl): 569 if (b'/' in tmpl or b'\\' in tmpl) and os.path.isfile(tmpl):
568 # is it a mapfile for a style? 570 # is it a mapfile for a style?
569 if os.path.basename(tmpl).startswith("map-"): 571 if os.path.basename(tmpl).startswith(b"map-"):
570 return templatespec(topic, None, os.path.realpath(tmpl)) 572 return templatespec(topic, None, os.path.realpath(tmpl))
571 with util.posixfile(tmpl, 'rb') as f: 573 with util.posixfile(tmpl, b'rb') as f:
572 tmpl = f.read() 574 tmpl = f.read()
573 return templatespec('', tmpl, None) 575 return templatespec(b'', tmpl, None)
574 576
575 # constant string? 577 # constant string?
576 return templatespec('', tmpl, None) 578 return templatespec(b'', tmpl, None)
577 579
578 580
579 def templatepartsmap(spec, t, partnames): 581 def templatepartsmap(spec, t, partnames):
580 """Create a mapping of {part: ref}""" 582 """Create a mapping of {part: ref}"""
581 partsmap = {spec.ref: spec.ref} # initial ref must exist in t 583 partsmap = {spec.ref: spec.ref} # initial ref must exist in t
582 if spec.mapfile: 584 if spec.mapfile:
583 partsmap.update((p, p) for p in partnames if p in t) 585 partsmap.update((p, p) for p in partnames if p in t)
584 elif spec.ref: 586 elif spec.ref:
585 for part in partnames: 587 for part in partnames:
586 ref = '%s:%s' % (spec.ref, part) # select config sub-section 588 ref = b'%s:%s' % (spec.ref, part) # select config sub-section
587 if ref in t: 589 if ref in t:
588 partsmap[part] = ref 590 partsmap[part] = ref
589 return partsmap 591 return partsmap
590 592
591 593
603 ) 605 )
604 606
605 607
606 def maketemplater(ui, tmpl, defaults=None, resources=None, cache=None): 608 def maketemplater(ui, tmpl, defaults=None, resources=None, cache=None):
607 """Create a templater from a string template 'tmpl'""" 609 """Create a templater from a string template 'tmpl'"""
608 aliases = ui.configitems('templatealias') 610 aliases = ui.configitems(b'templatealias')
609 t = templater.templater( 611 t = templater.templater(
610 defaults=defaults, resources=resources, cache=cache, aliases=aliases 612 defaults=defaults, resources=resources, cache=cache, aliases=aliases
611 ) 613 )
612 t.cache.update( 614 t.cache.update(
613 (k, templater.unquotestring(v)) for k, v in ui.configitems('templates') 615 (k, templater.unquotestring(v)) for k, v in ui.configitems(b'templates')
614 ) 616 )
615 if tmpl: 617 if tmpl:
616 t.cache[''] = tmpl 618 t.cache[b''] = tmpl
617 return t 619 return t
618 620
619 621
620 # marker to denote a resource to be loaded on demand based on mapping values 622 # marker to denote a resource to be loaded on demand based on mapping values
621 # (e.g. (ctx, path) -> fctx) 623 # (e.g. (ctx, path) -> fctx)
625 class templateresources(templater.resourcemapper): 627 class templateresources(templater.resourcemapper):
626 """Resource mapper designed for the default templatekw and function""" 628 """Resource mapper designed for the default templatekw and function"""
627 629
628 def __init__(self, ui, repo=None): 630 def __init__(self, ui, repo=None):
629 self._resmap = { 631 self._resmap = {
630 'cache': {}, # for templatekw/funcs to store reusable data 632 b'cache': {}, # for templatekw/funcs to store reusable data
631 'repo': repo, 633 b'repo': repo,
632 'ui': ui, 634 b'ui': ui,
633 } 635 }
634 636
635 def availablekeys(self, mapping): 637 def availablekeys(self, mapping):
636 return { 638 return {
637 k for k in self.knownkeys() if self._getsome(mapping, k) is not None 639 k for k in self.knownkeys() if self._getsome(mapping, k) is not None
638 } 640 }
639 641
640 def knownkeys(self): 642 def knownkeys(self):
641 return {'cache', 'ctx', 'fctx', 'repo', 'revcache', 'ui'} 643 return {b'cache', b'ctx', b'fctx', b'repo', b'revcache', b'ui'}
642 644
643 def lookup(self, mapping, key): 645 def lookup(self, mapping, key):
644 if key not in self.knownkeys(): 646 if key not in self.knownkeys():
645 return None 647 return None
646 v = self._getsome(mapping, key) 648 v = self._getsome(mapping, key)
649 return v 651 return v
650 652
651 def populatemap(self, context, origmapping, newmapping): 653 def populatemap(self, context, origmapping, newmapping):
652 mapping = {} 654 mapping = {}
653 if self._hasnodespec(newmapping): 655 if self._hasnodespec(newmapping):
654 mapping['revcache'] = {} # per-ctx cache 656 mapping[b'revcache'] = {} # per-ctx cache
655 if self._hasnodespec(origmapping) and self._hasnodespec(newmapping): 657 if self._hasnodespec(origmapping) and self._hasnodespec(newmapping):
656 orignode = templateutil.runsymbol(context, origmapping, 'node') 658 orignode = templateutil.runsymbol(context, origmapping, b'node')
657 mapping['originalnode'] = orignode 659 mapping[b'originalnode'] = orignode
658 # put marker to override 'ctx'/'fctx' in mapping if any, and flag 660 # put marker to override 'ctx'/'fctx' in mapping if any, and flag
659 # its existence to be reported by availablekeys() 661 # its existence to be reported by availablekeys()
660 if 'ctx' not in newmapping and self._hasliteral(newmapping, 'node'): 662 if b'ctx' not in newmapping and self._hasliteral(newmapping, b'node'):
661 mapping['ctx'] = _placeholder 663 mapping[b'ctx'] = _placeholder
662 if 'fctx' not in newmapping and self._hasliteral(newmapping, 'path'): 664 if b'fctx' not in newmapping and self._hasliteral(newmapping, b'path'):
663 mapping['fctx'] = _placeholder 665 mapping[b'fctx'] = _placeholder
664 return mapping 666 return mapping
665 667
666 def _getsome(self, mapping, key): 668 def _getsome(self, mapping, key):
667 v = mapping.get(key) 669 v = mapping.get(key)
668 if v is not None: 670 if v is not None:
680 return None 682 return None
681 return v 683 return v
682 684
683 def _hasnodespec(self, mapping): 685 def _hasnodespec(self, mapping):
684 """Test if context revision is set or unset in the given mapping""" 686 """Test if context revision is set or unset in the given mapping"""
685 return 'node' in mapping or 'ctx' in mapping 687 return b'node' in mapping or b'ctx' in mapping
686 688
687 def _loadctx(self, mapping): 689 def _loadctx(self, mapping):
688 repo = self._getsome(mapping, 'repo') 690 repo = self._getsome(mapping, b'repo')
689 node = self._getliteral(mapping, 'node') 691 node = self._getliteral(mapping, b'node')
690 if repo is None or node is None: 692 if repo is None or node is None:
691 return 693 return
692 try: 694 try:
693 return repo[node] 695 return repo[node]
694 except error.RepoLookupError: 696 except error.RepoLookupError:
695 return None # maybe hidden/non-existent node 697 return None # maybe hidden/non-existent node
696 698
697 def _loadfctx(self, mapping): 699 def _loadfctx(self, mapping):
698 ctx = self._getsome(mapping, 'ctx') 700 ctx = self._getsome(mapping, b'ctx')
699 path = self._getliteral(mapping, 'path') 701 path = self._getliteral(mapping, b'path')
700 if ctx is None or path is None: 702 if ctx is None or path is None:
701 return None 703 return None
702 try: 704 try:
703 return ctx[path] 705 return ctx[path]
704 except error.LookupError: 706 except error.LookupError:
705 return None # maybe removed file? 707 return None # maybe removed file?
706 708
707 _loadermap = { 709 _loadermap = {
708 'ctx': _loadctx, 710 b'ctx': _loadctx,
709 'fctx': _loadfctx, 711 b'fctx': _loadfctx,
710 } 712 }
711 713
712 714
713 def formatter(ui, out, topic, opts): 715 def formatter(ui, out, topic, opts):
714 template = opts.get("template", "") 716 template = opts.get(b"template", b"")
715 if template == "cbor": 717 if template == b"cbor":
716 return cborformatter(ui, out, topic, opts) 718 return cborformatter(ui, out, topic, opts)
717 elif template == "json": 719 elif template == b"json":
718 return jsonformatter(ui, out, topic, opts) 720 return jsonformatter(ui, out, topic, opts)
719 elif template == "pickle": 721 elif template == b"pickle":
720 return pickleformatter(ui, out, topic, opts) 722 return pickleformatter(ui, out, topic, opts)
721 elif template == "debug": 723 elif template == b"debug":
722 return debugformatter(ui, out, topic, opts) 724 return debugformatter(ui, out, topic, opts)
723 elif template != "": 725 elif template != b"":
724 return templateformatter(ui, out, topic, opts) 726 return templateformatter(ui, out, topic, opts)
725 # developer config: ui.formatdebug 727 # developer config: ui.formatdebug
726 elif ui.configbool('ui', 'formatdebug'): 728 elif ui.configbool(b'ui', b'formatdebug'):
727 return debugformatter(ui, out, topic, opts) 729 return debugformatter(ui, out, topic, opts)
728 # deprecated config: ui.formatjson 730 # deprecated config: ui.formatjson
729 elif ui.configbool('ui', 'formatjson'): 731 elif ui.configbool(b'ui', b'formatjson'):
730 return jsonformatter(ui, out, topic, opts) 732 return jsonformatter(ui, out, topic, opts)
731 return plainformatter(ui, out, topic, opts) 733 return plainformatter(ui, out, topic, opts)
732 734
733 735
734 @contextlib.contextmanager 736 @contextlib.contextmanager
735 def openformatter(ui, filename, topic, opts): 737 def openformatter(ui, filename, topic, opts):
736 """Create a formatter that writes outputs to the specified file 738 """Create a formatter that writes outputs to the specified file
737 739
738 Must be invoked using the 'with' statement. 740 Must be invoked using the 'with' statement.
739 """ 741 """
740 with util.posixfile(filename, 'wb') as out: 742 with util.posixfile(filename, b'wb') as out:
741 with formatter(ui, out, topic, opts) as fm: 743 with formatter(ui, out, topic, opts) as fm:
742 yield fm 744 yield fm
743 745
744 746
745 @contextlib.contextmanager 747 @contextlib.contextmanager