Mercurial > public > mercurial-scm > hg
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) |