diff --git a/supervisor/colors.py b/supervisor/colors.py new file mode 100644 index 000000000..a0bba337f --- /dev/null +++ b/supervisor/colors.py @@ -0,0 +1,31 @@ +ESC = '\033[' + + +class ansicolors: + class fg: + black = ESC + '30m' + red = ESC + '31m' + green = ESC + '32m' + yellow = ESC + '33m' + blue = ESC + '34m' + magenta = ESC + '35m' + cyan = ESC + '36m' + white = ESC + '37m' + reset = ESC + '39m' + + class bg: + black = ESC + '40m' + red = ESC + '41m' + green = ESC + '42m' + yellow = ESC + '43m' + blue = ESC + '44m' + magenta = ESC + '45m' + cyan = ESC + '46m' + white = ESC + '47m' + reset = ESC + '49m' + + class style: + bright = ESC + '1m' + dim = ESC + '2m' + normal = ESC + '22m' + reset_all = ESC + '0m' diff --git a/supervisor/options.py b/supervisor/options.py index f3cce2680..3c5ad4dc7 100644 --- a/supervisor/options.py +++ b/supervisor/options.py @@ -1557,6 +1557,7 @@ class ClientOptions(Options): username = None password = None history_file = None + colorize_status = False def __init__(self): Options.__init__(self, require_configfile=False) @@ -1568,6 +1569,7 @@ def __init__(self): self.configroot.supervisorctl.username = None self.configroot.supervisorctl.password = None self.configroot.supervisorctl.history_file = None + self.configroot.supervisorctl.colorize_status = False from supervisor.supervisorctl import DefaultControllerPlugin default_factory = ('default', DefaultControllerPlugin, {}) @@ -1578,6 +1580,8 @@ def __init__(self): self.add("interactive", "supervisorctl.interactive", "i", "interactive", flag=1, default=0) self.add("prompt", "supervisorctl.prompt", default="supervisor") + self.add("colorize_status", "supervisorctl.colorize_status", + default=False) self.add("serverurl", "supervisorctl.serverurl", "s:", "serverurl=", url, default="http://localhost:9001") self.add("username", "supervisorctl.username", "u:", "username=") @@ -1627,6 +1631,7 @@ def read_config(self, fp): # The defaults used below are really set in __init__ (since # section==self.configroot.supervisorctl) section.prompt = config.getdefault('prompt', section.prompt) + section.colorize_status = config.getdefault('colorize_status', section.colorize_status) section.username = config.getdefault('username', section.username) section.password = config.getdefault('password', section.password) history_file = config.getdefault('history_file', section.history_file) diff --git a/supervisor/supervisorctl.py b/supervisor/supervisorctl.py index 048d3e00c..f59ad518a 100755 --- a/supervisor/supervisorctl.py +++ b/supervisor/supervisorctl.py @@ -30,11 +30,14 @@ import errno import threading +from supervisor.colors import ansicolors from supervisor.compat import xmlrpclib from supervisor.compat import urlparse from supervisor.compat import unicode from supervisor.compat import raw_input +from supervisor.datatypes import boolean + from supervisor.medusa import asyncore_25 as asyncore from supervisor.options import ClientOptions @@ -592,28 +595,65 @@ def help_quit(self): def help_exit(self): self.ctl.output("exit\tExit the supervisor shell.") - def _show_statuses(self, process_infos): + def _show_statuses(self, process_infos, colorize_output=True): namespecs, maxlen = [], 30 for i, info in enumerate(process_infos): namespecs.append(make_namespec(info['group'], info['name'])) if len(namespecs[i]) > maxlen: maxlen = len(namespecs[i]) - template = '%(namespec)-' + str(maxlen+3) + 's%(state)-10s%(desc)s' + template = self._get_status_template(colorize_output, maxlen) + for i, info in enumerate(process_infos): + state = info['statename'] + + if colorize_output: + state = self._colorize_state(state) + line = template % {'namespec': namespecs[i], - 'state': info['statename'], + 'state': state, 'desc': info['description']} self.ctl.output(line) + def _get_status_template(self, colorize_output, maxlen): + state_col_width = 24 if colorize_output else 10 + template = ('%(namespec)-' + str(maxlen+3) + 's' + + '%(state)-' + str(state_col_width) + + 's%(desc)s') + return template + + def _colorize_state(self, statename): + colors = { + 'RUNNING': ansicolors.style.bright + ansicolors.fg.green, + 'STOPPED': ansicolors.style.bright + ansicolors.fg.yellow, + 'BACKOFF': ansicolors.style.bright + ansicolors.fg.red, + 'FATAL': ansicolors.style.bright + ansicolors.fg.red, + } + color = colors.get(statename, '') + return '%s%s%s' % (color, statename, ansicolors.fg.reset) + def do_status(self, arg): if not self.ctl.upcheck(): return supervisor = self.ctl.get_supervisor() all_infos = supervisor.getAllProcessInfo() + options = self.ctl.options + if options.colorize_status == 'auto': + colorize_output = sys.stdout.isatty() + else: + colorize_output = boolean(options.colorize_status) - names = arg.split() + args = arg.split() + + options = [arg for arg in args if arg.startswith('--')] + for option in options: + if option.startswith('--color'): + colorize_output = True + elif option.startswith('--no-color'): + colorize_output = False + + names = [arg for arg in args if not arg.startswith('--')] if not names or "all" in names: matching_infos = all_infos else: @@ -638,7 +678,7 @@ def do_status(self, arg): else: msg = "%s: ERROR (no such process)" % name self.ctl.output(msg) - self._show_statuses(matching_infos) + self._show_statuses(matching_infos, colorize_output) def help_status(self): self.ctl.output("status \t\tGet status for a single process") diff --git a/supervisor/tests/test_supervisorctl.py b/supervisor/tests/test_supervisorctl.py index 38034ea8e..c2a115b3b 100644 --- a/supervisor/tests/test_supervisorctl.py +++ b/supervisor/tests/test_supervisorctl.py @@ -1832,6 +1832,7 @@ def do_help(self, arg): class DummyClientOptions: def __init__(self): self.prompt = 'supervisor' + self.colorize_status = False self.serverurl = 'http://localhost:65532' self.username = 'chrism' self.password = '123'