comparison mercurial/ui.py @ 40547:840cd57cde32

ui: add config knob to redirect status messages to stderr (API) This option can be used to isolate structured output from status messages. For now, "stdio" (stdout/err pair) and "stderr" are supported. In future patches, I'll add the "channel" option which will send status messages to a separate command-server channel with some metadata attached, maybe in CBOR encoding. This is a part of the generic templating plan: https://www.mercurial-scm.org/wiki/GenericTemplatingPlan#Sanity_check_output .. api:: Status messages may be sent to a dedicated stream depending on configuration. Don't use ``ui.status()``, etc. as a shorthand for conditional writes. Use ``ui.write()`` for data output.
author Yuya Nishihara <yuya@tcha.org>
date Sat, 03 Nov 2018 19:42:50 +0900
parents 7bffbbe03e90
children db61a18148a4
comparison
equal deleted inserted replaced
40543:7bffbbe03e90 40547:840cd57cde32
232 232
233 if src: 233 if src:
234 self._fout = src._fout 234 self._fout = src._fout
235 self._ferr = src._ferr 235 self._ferr = src._ferr
236 self._fin = src._fin 236 self._fin = src._fin
237 self._fmsgout = src._fmsgout
238 self._fmsgerr = src._fmsgerr
237 self._finoutredirected = src._finoutredirected 239 self._finoutredirected = src._finoutredirected
238 self.pageractive = src.pageractive 240 self.pageractive = src.pageractive
239 self._disablepager = src._disablepager 241 self._disablepager = src._disablepager
240 self._tweaked = src._tweaked 242 self._tweaked = src._tweaked
241 243
257 self._blockedtimes = src._blockedtimes 259 self._blockedtimes = src._blockedtimes
258 else: 260 else:
259 self._fout = procutil.stdout 261 self._fout = procutil.stdout
260 self._ferr = procutil.stderr 262 self._ferr = procutil.stderr
261 self._fin = procutil.stdin 263 self._fin = procutil.stdin
264 self._fmsgout = self.fout # configurable
265 self._fmsgerr = self.ferr # configurable
262 self._finoutredirected = False 266 self._finoutredirected = False
263 self.pageractive = False 267 self.pageractive = False
264 self._disablepager = False 268 self._disablepager = False
265 self._tweaked = False 269 self._tweaked = False
266 270
414 raise 418 raise
415 self.warn(_("ignored: %s\n") % stringutil.forcebytestr(inst)) 419 self.warn(_("ignored: %s\n") % stringutil.forcebytestr(inst))
416 420
417 if self.plain(): 421 if self.plain():
418 for k in ('debug', 'fallbackencoding', 'quiet', 'slash', 422 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
419 'logtemplate', 'statuscopies', 'style', 423 'logtemplate', 'message-output', 'statuscopies', 'style',
420 'traceback', 'verbose'): 424 'traceback', 'verbose'):
421 if k in cfg['ui']: 425 if k in cfg['ui']:
422 del cfg['ui'][k] 426 del cfg['ui'][k]
423 for k, v in cfg.items('defaults'): 427 for k, v in cfg.items('defaults'):
424 del cfg['defaults'][k] 428 del cfg['defaults'][k]
467 p = os.path.normpath(os.path.join(root, p)) 471 p = os.path.normpath(os.path.join(root, p))
468 c.set("paths", n, p) 472 c.set("paths", n, p)
469 473
470 if section in (None, 'ui'): 474 if section in (None, 'ui'):
471 # update ui options 475 # update ui options
476 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
472 self.debugflag = self.configbool('ui', 'debug') 477 self.debugflag = self.configbool('ui', 'debug')
473 self.verbose = self.debugflag or self.configbool('ui', 'verbose') 478 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
474 self.quiet = not self.debugflag and self.configbool('ui', 'quiet') 479 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
475 if self.verbose and self.quiet: 480 if self.verbose and self.quiet:
476 self.quiet = self.verbose = False 481 self.quiet = self.verbose = False
889 return self._fout 894 return self._fout
890 895
891 @fout.setter 896 @fout.setter
892 def fout(self, f): 897 def fout(self, f):
893 self._fout = f 898 self._fout = f
899 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
894 900
895 @property 901 @property
896 def ferr(self): 902 def ferr(self):
897 return self._ferr 903 return self._ferr
898 904
899 @ferr.setter 905 @ferr.setter
900 def ferr(self, f): 906 def ferr(self, f):
901 self._ferr = f 907 self._ferr = f
908 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
902 909
903 @property 910 @property
904 def fin(self): 911 def fin(self):
905 return self._fin 912 return self._fin
906 913
1362 def prompt(self, msg, default="y"): 1369 def prompt(self, msg, default="y"):
1363 """Prompt user with msg, read response. 1370 """Prompt user with msg, read response.
1364 If ui is not interactive, the default is returned. 1371 If ui is not interactive, the default is returned.
1365 """ 1372 """
1366 if not self.interactive(): 1373 if not self.interactive():
1367 self.write(msg, ' ', label='ui.prompt') 1374 self._write(self._fmsgout, msg, ' ', label='ui.prompt')
1368 self.write(default or '', "\n", label='ui.promptecho') 1375 self._write(self._fmsgout, default or '', "\n",
1376 label='ui.promptecho')
1369 return default 1377 return default
1370 self._writenobuf(self._fout, msg, label='ui.prompt') 1378 self._writenobuf(self._fmsgout, msg, label='ui.prompt')
1371 self.flush() 1379 self.flush()
1372 try: 1380 try:
1373 r = self._readline() 1381 r = self._readline()
1374 if not r: 1382 if not r:
1375 r = default 1383 r = default
1376 if self.configbool('ui', 'promptecho'): 1384 if self.configbool('ui', 'promptecho'):
1377 self.write(r, "\n", label='ui.promptecho') 1385 self._write(self._fmsgout, r, "\n", label='ui.promptecho')
1378 return r 1386 return r
1379 except EOFError: 1387 except EOFError:
1380 raise error.ResponseExpected() 1388 raise error.ResponseExpected()
1381 1389
1382 @staticmethod 1390 @staticmethod
1422 resps = [r for r, t in choices] 1430 resps = [r for r, t in choices]
1423 while True: 1431 while True:
1424 r = self.prompt(msg, resps[default]) 1432 r = self.prompt(msg, resps[default])
1425 if r.lower() in resps: 1433 if r.lower() in resps:
1426 return resps.index(r.lower()) 1434 return resps.index(r.lower())
1427 self.write(_("unrecognized response\n")) 1435 # TODO: shouldn't it be a warning?
1436 self._write(self._fmsgout, _("unrecognized response\n"))
1428 1437
1429 def getpass(self, prompt=None, default=None): 1438 def getpass(self, prompt=None, default=None):
1430 if not self.interactive(): 1439 if not self.interactive():
1431 return default 1440 return default
1432 try: 1441 try:
1433 self.write_err(self.label(prompt or _('password: '), 'ui.prompt')) 1442 self._write(self._fmsgerr, prompt or _('password: '),
1443 label='ui.prompt')
1434 # disable getpass() only if explicitly specified. it's still valid 1444 # disable getpass() only if explicitly specified. it's still valid
1435 # to interact with tty even if fin is not a tty. 1445 # to interact with tty even if fin is not a tty.
1436 with self.timeblockedsection('stdio'): 1446 with self.timeblockedsection('stdio'):
1437 if self.configbool('ui', 'nontty'): 1447 if self.configbool('ui', 'nontty'):
1438 l = self._fin.readline() 1448 l = self._fin.readline()
1449 1459
1450 This adds an output label of "ui.status". 1460 This adds an output label of "ui.status".
1451 ''' 1461 '''
1452 if not self.quiet: 1462 if not self.quiet:
1453 opts[r'label'] = opts.get(r'label', '') + ' ui.status' 1463 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1454 self.write(*msg, **opts) 1464 self._write(self._fmsgout, *msg, **opts)
1455 1465
1456 def warn(self, *msg, **opts): 1466 def warn(self, *msg, **opts):
1457 '''write warning message to output (stderr) 1467 '''write warning message to output (stderr)
1458 1468
1459 This adds an output label of "ui.warning". 1469 This adds an output label of "ui.warning".
1460 ''' 1470 '''
1461 opts[r'label'] = opts.get(r'label', '') + ' ui.warning' 1471 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1462 self.write_err(*msg, **opts) 1472 self._write(self._fmsgerr, *msg, **opts)
1463 1473
1464 def error(self, *msg, **opts): 1474 def error(self, *msg, **opts):
1465 '''write error message to output (stderr) 1475 '''write error message to output (stderr)
1466 1476
1467 This adds an output label of "ui.error". 1477 This adds an output label of "ui.error".
1468 ''' 1478 '''
1469 opts[r'label'] = opts.get(r'label', '') + ' ui.error' 1479 opts[r'label'] = opts.get(r'label', '') + ' ui.error'
1470 self.write_err(*msg, **opts) 1480 self._write(self._fmsgerr, *msg, **opts)
1471 1481
1472 def note(self, *msg, **opts): 1482 def note(self, *msg, **opts):
1473 '''write note to output (if ui.verbose is True) 1483 '''write note to output (if ui.verbose is True)
1474 1484
1475 This adds an output label of "ui.note". 1485 This adds an output label of "ui.note".
1476 ''' 1486 '''
1477 if self.verbose: 1487 if self.verbose:
1478 opts[r'label'] = opts.get(r'label', '') + ' ui.note' 1488 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1479 self.write(*msg, **opts) 1489 self._write(self._fmsgout, *msg, **opts)
1480 1490
1481 def debug(self, *msg, **opts): 1491 def debug(self, *msg, **opts):
1482 '''write debug message to output (if ui.debugflag is True) 1492 '''write debug message to output (if ui.debugflag is True)
1483 1493
1484 This adds an output label of "ui.debug". 1494 This adds an output label of "ui.debug".
1485 ''' 1495 '''
1486 if self.debugflag: 1496 if self.debugflag:
1487 opts[r'label'] = opts.get(r'label', '') + ' ui.debug' 1497 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1488 self.write(*msg, **opts) 1498 self._write(self._fmsgout, *msg, **opts)
1489 1499
1490 def edit(self, text, user, extra=None, editform=None, pending=None, 1500 def edit(self, text, user, extra=None, editform=None, pending=None,
1491 repopath=None, action=None): 1501 repopath=None, action=None):
1492 if action is None: 1502 if action is None:
1493 self.develwarn('action is None but will soon be a required ' 1503 self.develwarn('action is None but will soon be a required '
1937 _progresssingleton = progress.progbar(ui) 1947 _progresssingleton = progress.progbar(ui)
1938 return _progresssingleton 1948 return _progresssingleton
1939 1949
1940 def haveprogbar(): 1950 def haveprogbar():
1941 return _progresssingleton is not None 1951 return _progresssingleton is not None
1952
1953 def _selectmsgdests(ui):
1954 name = ui.config(b'ui', b'message-output')
1955 if name == b'stdio':
1956 return ui.fout, ui.ferr
1957 if name == b'stderr':
1958 return ui.ferr, ui.ferr
1959 raise error.Abort(b'invalid ui.message-output destination: %s' % name)