mirror of
https://github.com/facebook/sapling.git
synced 2024-12-28 23:54:12 +03:00
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:
parent
51ef0b524e
commit
60e99a245c
@ -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"))
|
||||
|
@ -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():
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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:
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user