ui: add labelled prefixes to ui.write

Summary:
Add optional prefix keyword arguments to `ui.write` (and thus `ui.status`,
`ui.warn`, etc.).  These prefixes can be used to indicate an error condition,
some notice to the user, or to mark a message as coming from a particular
component.  The prefixes are labelled so that they can be colored to stand
out from the surrounding text.  The default colors are red for errors, yellow
for notices, and cyan for components.

Add a component parameter to `error.Abort` (and thus any exception that
inherits from it) that can be used by callers to mark which component is
responsible for the exception.   When the error is printed, the component is
passed as the component prefix of the error message.

Reviewed By: mitrandir77

Differential Revision: D15201946

fbshipit-source-id: 02e3da40e9563704fa1d7ce81366e6e7f66c7f34
This commit is contained in:
Mark Thomas 2019-05-09 06:50:03 -07:00 committed by Facebook Github Bot
parent 51ef0b524e
commit 60e99a245c
9 changed files with 111 additions and 98 deletions

View File

@ -403,10 +403,8 @@ def getdag(ui, repo, revs, master):
try:
results.sort(key=lambda x: order[x[0]], reverse=True)
except ValueError: # Happened when 'order' is empty
msg = _("note: smartlog encountered an error\n")
hint = _("(so the sorting might be wrong.\n\n)")
ui.warn(msg)
ui.warn(hint)
ui.warn(_("smartlog encountered an error\n"), notice=_("note"))
ui.warn(_("(so the sorting might be wrong.\n\n)"))
results.reverse()
# indent the top non-public stack
@ -686,7 +684,8 @@ def _smartlog(ui, repo, *pats, **opts):
pass
if hiddenchanges:
msg = _("note: hiding %s old heads without bookmarks\n") % hiddenchanges
hint = _("(use --all to see them)\n")
ui.warn(msg)
ui.warn(hint)
ui.warn(
_("hiding %s old heads without bookmarks\n") % hiddenchanges,
notice=_("note"),
)
ui.warn(_("(use --all to see them)\n"))

View File

@ -118,7 +118,6 @@ _defaultstyles = {
"formatvariant.config.special": "yellow",
"formatvariant.config.default": "green",
"formatvariant.default": "",
"hint.prefix": "yellow",
"histedit.remaining": "red bold",
"log.changeset": "yellow",
"processtree.descendants": "green",
@ -149,6 +148,9 @@ _defaultstyles = {
"tags.normal": "green",
"tags.local": "black bold",
"ui.metrics": "#777:color242:dim",
"ui.prefix.component": "cyan",
"ui.prefix.error": "brightred:red",
"ui.prefix.notice": "yellow",
}
@ -552,7 +554,7 @@ if pycompat.iswindows:
else:
origattr = csbi.wAttributes
ansire = re.compile(
"\033\[([^m]*)m([^\033]*)(.*)", re.MULTILINE | re.DOTALL
"\033\\[([^m]*)m([^\033]*)(.*)", re.MULTILINE | re.DOTALL
)
def win32print(ui, writefunc, *msgs, **opts):
@ -560,7 +562,6 @@ if pycompat.iswindows:
_win32print(ui, text, writefunc, **opts)
def _win32print(ui, text, writefunc, **opts):
label = opts.get(r"label", "")
attr = origattr
def mapcolor(val, attr):
@ -573,36 +574,24 @@ if pycompat.iswindows:
else:
return (val & 0x07) | (attr & 0xF8)
# determine console attributes based on labels
for l in label.split():
style = ui._styles.get(l, "")
for effect in style.split():
try:
attr = mapcolor(w32effects[effect], attr)
except KeyError:
# w32effects could not have certain attributes so we skip
# them if not found
pass
# hack to ensure regexp finds data
if not text.startswith("\033["):
text = "\033[m" + text
# Look for ANSI-like codes embedded in text
m = re.match(ansire, text)
try:
while m:
for sattr in m.group(1).split(";"):
if sattr:
attr = mapcolor(int(sattr), attr)
if m:
try:
while m:
for sattr in m.group(1).split(";"):
if sattr:
attr = mapcolor(int(sattr), attr)
ui.flush()
_kernel32.SetConsoleTextAttribute(stdout, attr)
writefunc(m.group(2), **opts)
m = re.match(ansire, m.group(3))
finally:
# Explicitly reset original attributes
ui.flush()
_kernel32.SetConsoleTextAttribute(stdout, attr)
writefunc(m.group(2), **opts)
m = re.match(ansire, m.group(3))
finally:
# Explicitly reset original attributes
ui.flush()
_kernel32.SetConsoleTextAttribute(stdout, origattr)
_kernel32.SetConsoleTextAttribute(stdout, origattr)
else:
writefunc(text, **opts)
def supportedcolors():

View File

@ -38,6 +38,18 @@ class Hint(object):
super(Hint, self).__init__(*args, **kw)
class Component(object):
"""Mix-in to privide component identity of an error
This should come before Exception in the inheritance list to consume the
component name and pass the remaining arguments to the exception class.
"""
def __init__(self, *args, **kw):
self.component = kw.pop(r"component", None)
super(Component, self).__init__(*args, **kw)
class RevlogError(Hint, Exception):
__bytes__ = _tobytes
@ -86,7 +98,7 @@ class InterventionRequired(Hint, Exception):
__bytes__ = _tobytes
class Abort(Hint, Exception):
class Abort(Hint, Component, Exception):
"""Raised if a command needs to print an error and exit."""
__bytes__ = _tobytes

View File

@ -52,11 +52,6 @@ def trigger(name, *args, **kwargs):
messages.append((name, msg.rstrip()))
def _prefix(ui, name):
"""Return "hint[%s]" % name, colored"""
return ui.label(_("hint[%s]: ") % (name,), "hint.prefix")
def show(ui):
"""Show all triggered hint messages"""
if ui.plain("hint"):
@ -76,13 +71,11 @@ def show(ui):
names = []
for name, msg in messages:
if not isacked(name):
prefix = _prefix(ui, name)
ui.write_err(("%s%s\n") % (prefix, msg.rstrip()))
ui.write_err(("%s\n") % msg.rstrip(), notice=_("hint[%s]") % name)
names.append(name)
if names and not isacked("hint-ack"):
prefix = _prefix(ui, "hint-ack")
msg = _("use 'hg hint --ack %s' to silence these hints\n") % " ".join(names)
ui.write_err(prefix + msg)
ui.write_err(msg, notice=_("hint[%s]") % "hint-ack")
def silence(ui, names):

View File

@ -143,30 +143,31 @@ def callcatch(ui, func):
reason = _("timed out waiting for lock held by %r") % inst.lockinfo
else:
reason = _("lock held by %r") % inst.lockinfo
ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
ui.warn(_("%s: %s\n") % (inst.desc or inst.filename, reason), error=_("abort"))
if not inst.lockinfo:
ui.warn(_("(lock might be very busy)\n"))
except error.LockUnavailable as inst:
ui.warn(
_("abort: could not lock %s: %s\n")
% (inst.desc or inst.filename, encoding.strtolocal(inst.strerror))
_("could not lock %s: %s\n")
% (inst.desc or inst.filename, encoding.strtolocal(inst.strerror)),
error=_("abort"),
)
except error.OutOfBandError as inst:
if inst.args:
msg = _("abort: remote error:\n")
msg = _("remote error:\n")
else:
msg = _("abort: remote error\n")
ui.warn(msg)
msg = _("remote error\n")
ui.warn(msg, error=_("abort"))
if inst.args:
ui.warn("".join(inst.args))
if inst.hint:
ui.warn("(%s)\n" % inst.hint)
except error.RepoError as inst:
ui.warn(_("abort: %s!\n") % inst)
ui.warn(_("%s!\n") % inst, error=_("abort"))
if inst.hint:
ui.warn(_("(%s)\n") % inst.hint)
except error.ResponseError as inst:
ui.warn(_("abort: %s") % inst.args[0])
ui.warn(inst.args[0], error=_("abort"))
if not isinstance(inst.args[1], basestring): # noqa
ui.warn(" %r\n" % (inst.args[1],))
elif not inst.args[1]:
@ -174,23 +175,23 @@ def callcatch(ui, func):
else:
ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
except error.CensoredNodeError as inst:
ui.warn(_("abort: file censored %s!\n") % inst)
ui.warn(_("file censored %s!\n") % inst, error=_("abort"))
except error.RevlogError as inst:
ui.warn(_("abort: %s!\n") % inst)
ui.warn(_("%s!\n") % inst, error=_("abort"))
except error.InterventionRequired as inst:
ui.warn("%s\n" % inst)
if inst.hint:
ui.warn(_("(%s)\n") % inst.hint)
return 1
except error.WdirUnsupported:
ui.warn(_("abort: working directory revision cannot be specified\n"))
ui.warn(_("working directory revision cannot be specified\n"), error=_("abort"))
except error.Abort as inst:
ui.warn(_("abort: %s\n") % inst)
ui.warn(_("%s\n") % inst, error=_("abort"), component=inst.component)
if inst.hint:
ui.warn(_("(%s)\n") % inst.hint)
return inst.exitcode
except ImportError as inst:
ui.warn(_("abort: %s!\n") % inst)
ui.warn(_("%s!\n") % inst, error=_("abort"))
m = str(inst).split()[-1]
if m in "mpatch bdiff".split():
ui.warn(_("(did you forget to compile extensions?)\n"))
@ -198,7 +199,7 @@ def callcatch(ui, func):
ui.warn(_("(is your Python install correct?)\n"))
except IOError as inst:
if util.safehasattr(inst, "code"):
ui.warn(_("abort: %s\n") % inst)
ui.warn(_("%s\n") % inst, error=_("abort"))
elif util.safehasattr(inst, "reason"):
try: # usually it is in the form (errno, strerror)
reason = inst.reason.args[1]
@ -208,7 +209,7 @@ def callcatch(ui, func):
if isinstance(reason, unicode): # noqa
# SSLError of Python 2.7.9 contains a unicode
reason = encoding.unitolocal(reason)
ui.warn(_("abort: error: %s\n") % reason)
ui.warn(_("error: %s\n") % reason, error=_("abort"))
elif (
util.safehasattr(inst, "args") and inst.args and inst.args[0] == errno.EPIPE
):
@ -216,29 +217,31 @@ def callcatch(ui, func):
elif getattr(inst, "strerror", None):
if getattr(inst, "filename", None):
ui.warn(
_("abort: %s: %s\n")
% (encoding.strtolocal(inst.strerror), inst.filename)
_("%s: %s\n") % (encoding.strtolocal(inst.strerror), inst.filename),
error=_("abort"),
)
else:
ui.warn(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
ui.warn(
_("%s\n") % encoding.strtolocal(inst.strerror), error=_("abort")
)
else:
ui.warn(_("abort: %s\n") % inst)
ui.warn(_("%s\n") % inst, error=_("abort"))
except OSError as inst:
if getattr(inst, "filename", None) is not None:
ui.warn(
_("abort: %s: '%s'\n")
% (encoding.strtolocal(inst.strerror), inst.filename)
_("%s: '%s'\n") % (encoding.strtolocal(inst.strerror), inst.filename),
error=_("abort"),
)
else:
ui.warn(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
ui.warn(_("%s\n") % encoding.strtolocal(inst.strerror), error=_("abort"))
except MemoryError:
ui.warn(_("abort: out of memory\n"))
ui.warn(_("out of memory\n"), error=_("abort"))
except SystemExit as inst:
# Commands shouldn't sys.exit directly, but give a return code.
# Just in case catch this and pass exit code to caller.
return inst.code
except socket.error as inst:
ui.warn(_("abort: %s\n") % inst.args[-1])
ui.warn(_("%s\n") % inst.args[-1], error=_("abort"))
return -1
@ -274,7 +277,7 @@ def checkportable(ui, f):
msg = "%s: %s" % (msg, util.shellquote(f))
if abort:
raise error.Abort(msg)
ui.warn(_("warning: %s\n") % msg)
ui.warn(_("%s\n") % msg, notice=_("warning"))
def checkportabilityalert(ui):
@ -326,7 +329,7 @@ class casecollisionauditor(object):
msg = _("possible case-folding collision for %s") % f
if self._abort:
raise error.Abort(msg)
self._ui.warn(_("warning: %s\n") % msg)
self._ui.warn(_("%s\n") % msg, notice=_("warning"))
self._newfiles.add(f)

View File

@ -802,6 +802,21 @@ class ui(object):
return "".join(self._buffers.pop())
def _addprefixesandlabels(self, args, opts, addlabels):
msgs = []
for item in r"error", r"notice", r"component":
itemvalue = opts.get(item)
if itemvalue:
itemvalue = "%s:" % itemvalue
if addlabels:
itemvalue = self.label(itemvalue, "ui.prefix.%s" % item)
msgs.extend((itemvalue, " "))
msgs.extend(args)
if addlabels:
label = opts.get(r"label", "")
msgs = [self.label(m, label) for m in msgs]
return msgs
def write(self, *args, **opts):
"""write args to output
@ -817,23 +832,22 @@ class ui(object):
When labeling output for a specific command, a label of
"cmdname.type" is recommended. For example, status issues
a label of "status.modified" for modified files.
The output can optionally be prefixed by an error prefix, warning prefix
note prefix, or a component name if the corresponding keyword argument
is set. The prefix will be labelled with the "ui.prefix.PREFIXNAME"
label.
"""
if self._buffers and not opts.get(r"prompt", False):
if self._bufferapplylabels:
label = opts.get(r"label", "")
self._buffers[-1].extend(self.label(a, label) for a in args)
else:
self._buffers[-1].extend(args)
elif self._colormode == "win32":
# windows color printing is its own can of crab, defer to
# the color module and that is it.
color.win32print(self, self._write, *args, **opts)
msgs = self._addprefixesandlabels(args, opts, self._bufferapplylabels)
self._buffers[-1].extend(msgs)
else:
msgs = args
if self._colormode is not None:
label = opts.get(r"label", "")
msgs = [self.label(a, label) for a in args]
self._write(*msgs, **opts)
msgs = self._addprefixesandlabels(args, opts, self._colormode)
if self._colormode == "win32":
# windows color printing is its own can of crab
color.win32print(self, self._write, *msgs, **opts)
else:
self._write(*msgs, **opts)
def _write(self, *msgs, **opts):
with progress.suspend():
@ -851,16 +865,13 @@ class ui(object):
with progress.suspend():
if self._bufferstates and self._bufferstates[-1][0]:
self.write(*args, **opts)
elif self._colormode == "win32":
# windows color printing is its own can of crab, defer to
# the color module and that is it.
color.win32print(self, self._write_err, *args, **opts)
else:
msgs = args
if self._colormode is not None:
label = opts.get(r"label", "")
msgs = [self.label(a, label) for a in args]
self._write_err(*msgs, **opts)
msgs = self._addprefixesandlabels(args, opts, self._colormode)
if self._colormode == "win32":
# windows color printing is its own can of crab
color.win32print(self, self._write_err, *msgs, **opts)
else:
self._write_err(*msgs, **opts)
def _write_err(self, *msgs, **opts):
try:

View File

@ -14705,6 +14705,9 @@ msgstr "Hinweis: Nicht synchronisierte entfernte Änderungen!\n"
msgid "abort: %s\n"
msgstr "Abbruch: %s\n"
msgid "abort"
msgstr "Abbruch"
#, python-format
msgid "hg: parse error at %s: %s\n"
msgstr "hg: Parserfehler bei %s: %s\n"

View File

@ -18083,6 +18083,9 @@ msgstr "comando %s desconhecido"
msgid "abort: %s\n"
msgstr "abortado: %s\n"
msgid "abort"
msgstr "abortado"
#, python-format
msgid "listening at %s\n"
msgstr "ouvindo em %s\n"

View File

@ -23,7 +23,7 @@ Traceback has color:
\x1b[0;31;1m File "$TESTTMP/repocrash.py", line 3, in reposetup\x1b[0m (esc)
\x1b[0;31;1m raise error.Abort('.')\x1b[0m (esc)
\x1b[0;31;1mAbort: .\x1b[0m (esc)
abort: .
\x1b[0;91mabort:\x1b[0m . (esc)
Uncaught exception has color: