Skip to content

Commit

Permalink
Allow disabling color (#171)
Browse files Browse the repository at this point in the history
  • Loading branch information
jiasli committed Mar 16, 2020
1 parent f539a58 commit 82875fb
Show file tree
Hide file tree
Showing 12 changed files with 156 additions and 35 deletions.
1 change: 1 addition & 0 deletions knack/arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ def _get_preview_arg_message(self):
object_type = 'positional argument'

preview_info = PreviewItem(
cli_ctx=self.command_loader.cli_ctx,
target=target,
object_type=object_type,
message_func=_get_preview_arg_message
Expand Down
11 changes: 10 additions & 1 deletion knack/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ def __init__(self,
self.output = self.output_cls(cli_ctx=self)
self.result = None
self.query = query_cls(cli_ctx=self)
self.enable_color = not self.config.get('core', 'no_color', fallback=False)

@staticmethod
def _should_show_version(args):
Expand Down Expand Up @@ -187,6 +188,13 @@ def invoke(self, args, initial_invocation_data=None, out_file=None):
raise TypeError('args should be a list or tuple.')
exit_code = 0
try:
if self.enable_color:
import colorama
colorama.init()
if self.out_file == sys.__stdout__:
# point out_file to the new sys.stdout which is overwritten by colorama
self.out_file = sys.stdout

args = self.completion.get_completion_args() or args
out_file = out_file or self.out_file

Expand Down Expand Up @@ -218,6 +226,7 @@ def invoke(self, args, initial_invocation_data=None, out_file=None):
exit_code = self.exception_handler(ex)
self.result = CommandResultItem(None, error=ex)
finally:
pass
if self.enable_color:
colorama.deinit()
self.result.exit_code = exit_code
return exit_code
1 change: 1 addition & 0 deletions knack/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ def __init__(self, command_loader, group_name, operations_tmpl, **kwargs):
kwargs['deprecate_info'].target = group_name
if kwargs.get('is_preview', False):
kwargs['preview_info'] = PreviewItem(
cli_ctx=self.command_loader.cli_ctx,
target=group_name,
object_type='command group'
)
Expand Down
2 changes: 0 additions & 2 deletions knack/help.py
Original file line number Diff line number Diff line change
Expand Up @@ -684,8 +684,6 @@ def show_welcome(self, parser):
self.print_description_list(help_file.children)

def show_help(self, cli_name, nouns, parser, is_group):
import colorama
colorama.init(autoreset=True)
delimiters = ' '.join(nouns)
help_file = self.command_help_cls(self, delimiters, parser) if not is_group \
else self.group_help_cls(self, delimiters, parser)
Expand Down
3 changes: 0 additions & 3 deletions knack/invocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,6 @@ def execute(self, args):
:return: The command result
:rtype: knack.util.CommandResultItem
"""
import colorama

self.cli_ctx.raise_event(EVENT_INVOKER_PRE_CMD_TBL_CREATE, args=args)
cmd_tbl = self.commands_loader.load_command_table(args)
Expand Down Expand Up @@ -198,12 +197,10 @@ def execute(self, args):
preview_kwargs['object_type'] = 'command'
previews.append(ImplicitPreviewItem(**preview_kwargs))

colorama.init()
for d in deprecations:
print(d.message, file=sys.stderr)
for p in previews:
print(p.message, file=sys.stderr)
colorama.deinit()

cmd_result = parsed_args.func(params)
cmd_result = todict(cmd_result)
Expand Down
24 changes: 6 additions & 18 deletions knack/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,24 +54,10 @@ def wrap_msg_with_color(msg):

return cls.COLOR_MAP.get(level, None)

def _should_enable_color(self):
try:
# Color if tty stream available
if self.stream.isatty():
return True
except AttributeError:
pass
return False

def __init__(self, log_level_config, log_format):
import platform
import colorama

def __init__(self, log_level_config, log_format, enable_color):
logging.StreamHandler.__init__(self)
self.setLevel(log_level_config)
if platform.system() == 'Windows':
self.stream = colorama.AnsiToWin32(self.stream).stream
self.enable_color = self._should_enable_color()
self.enable_color = enable_color
self.setFormatter(logging.Formatter(log_format[self.enable_color]))

def format(self, record):
Expand Down Expand Up @@ -153,9 +139,11 @@ def _determine_verbose_level(self, args):

def _init_console_handlers(self, root_logger, cli_logger, log_level_config):
root_logger.addHandler(_CustomStreamHandler(log_level_config['root'],
self.console_log_format['root']))
self.console_log_format['root'],
self.cli_ctx.enable_color))
cli_logger.addHandler(_CustomStreamHandler(log_level_config[CLI_LOGGER_NAME],
self.console_log_format[CLI_LOGGER_NAME]))
self.console_log_format[CLI_LOGGER_NAME],
self.cli_ctx.enable_color))

def _init_logfile_handlers(self, root_logger, cli_logger):
ensure_dir(self.log_dir)
Expand Down
5 changes: 0 additions & 5 deletions knack/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,6 @@ def out(self, obj, formatter=None, out_file=None): # pylint: disable=no-self-us
if not isinstance(obj, CommandResultItem):
raise TypeError('Expected {} got {}'.format(CommandResultItem.__name__, type(obj)))

import platform
import colorama

if platform.system() == 'Windows':
out_file = colorama.AnsiToWin32(out_file).stream
output = formatter(obj)
try:
print(output, file=out_file, end='')
Expand Down
2 changes: 1 addition & 1 deletion knack/preview.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def _get_command_group(name):
# pylint: disable=too-many-instance-attributes
class PreviewItem(StatusTag):

def __init__(self, cli_ctx=None, object_type='', target=None, tag_func=None, message_func=None, **kwargs):
def __init__(self, cli_ctx, object_type='', target=None, tag_func=None, message_func=None, **kwargs):
""" Create a collection of preview metadata.
:param cli_ctx: The CLI context associated with the preview item.
Expand Down
7 changes: 5 additions & 2 deletions knack/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from datetime import date, time, datetime, timedelta
from enum import Enum

NO_COLOR_VARIABLE_NAME = 'KNACK_NO_COLOR'


class CommandResultItem(object): # pylint: disable=too-few-public-methods
def __init__(self, result, table_transformer=None, is_query_active=False,
Expand Down Expand Up @@ -90,12 +92,13 @@ def show_in_help(self):
@property
def tag(self):
""" Returns a tag object. """
return ColorizedString(self._get_tag(self), self._color)
return ColorizedString(self._get_tag(self), self._color) if self.cli_ctx.enable_color else self._get_tag(self)

@property
def message(self):
""" Returns a tuple with the formatted message string and the message length. """
return ColorizedString(self._get_message(self), self._color)
return ColorizedString(self._get_message(self), self._color) if self.cli_ctx.enable_color \
else "WARNING: " + self._get_message(self)


def ensure_dir(d):
Expand Down
47 changes: 45 additions & 2 deletions tests/test_deprecation.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from knack.arguments import ArgumentsContext
from knack.commands import CLICommand, CLICommandsLoader, CommandGroup

from tests.util import DummyCLI, redirect_io
from tests.util import DummyCLI, redirect_io, disable_color


def example_handler(arg1, arg2=None, arg3=None):
Expand Down Expand Up @@ -128,13 +128,32 @@ def test_deprecate_command_expiring_execute(self):
expected = "This command has been deprecated and will be removed in version '1.0.0'. Use 'alt-cmd4' instead."
self.assertIn(expected, actual)

@redirect_io
def test_deprecate_command_expiring_execute_no_color(self):
""" Ensure warning is displayed without color. """
self.cli_ctx.enable_color = False
self.cli_ctx.invoke('cmd4 -b b'.split())
actual = self.io.getvalue()
expected = "WARNING: This command has been deprecated and will be removed in version '1.0.0'"
self.assertIn(expected, actual)

@redirect_io
def test_deprecate_command_expired_execute(self):
""" Ensure expired command cannot be reached. """
with self.assertRaises(SystemExit):
self.cli_ctx.invoke('cmd5 -h'.split())
actual = self.io.getvalue()
expected = """The most similar choices to 'cmd5'"""
expected = """cli: 'cmd5' is not in the 'cli' command group."""
self.assertIn(expected, actual)

@redirect_io
@disable_color
def test_deprecate_command_expired_execute_no_color(self):
""" Ensure error is displayed without color. """
with self.assertRaises(SystemExit):
self.cli_ctx.invoke('cmd5 -h'.split())
actual = self.io.getvalue()
expected = """ERROR: cli: 'cmd5' is not in the 'cli' command group."""
self.assertIn(expected, actual)


Expand Down Expand Up @@ -228,6 +247,21 @@ def test_deprecate_command_group_help_expiring(self):
""".format(self.cli_ctx.name)
self.assertIn(expected, actual)

@redirect_io
@disable_color
def test_deprecate_command_group_help_expiring_no_color(self):
""" Ensure warning is displayed without color. """
with self.assertRaises(SystemExit):
self.cli_ctx.invoke('group4 -h'.split())
actual = self.io.getvalue()
expected = """
Group
cli group4
WARNING: This command group has been deprecated and will be removed in version \'1.0.0\'. Use
'alt-group4' instead.
""".format(self.cli_ctx.name)
self.assertIn(expected, actual)

@redirect_io
def test_deprecate_command_group_expired(self):
""" Ensure expired command cannot be reached. """
Expand Down Expand Up @@ -411,6 +445,15 @@ def test_deprecate_options_execute_expiring(self):
expected = "Option '--alt4' has been deprecated and will be removed in version '1.0.0'. Use '--opt4' instead."
self.assertIn(expected, actual)

@redirect_io
@disable_color
def test_deprecate_options_execute_expiring_no_color(self):
""" Ensure error is displayed without color. """
self.cli_ctx.invoke('arg-test --arg1 foo --opt1 bar --alt4 bar'.split())
actual = self.io.getvalue()
expected = "WARNING: Option '--alt4' has been deprecated and will be removed in version '1.0.0'. Use '--opt4' instead."
self.assertIn(expected, actual)

@redirect_io
def test_deprecate_options_execute_expiring_non_deprecated(self):
""" Ensure non-expiring options can be used without warning. """
Expand Down
71 changes: 70 additions & 1 deletion tests/test_preview.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from __future__ import unicode_literals, print_function

import os
import unittest
try:
import mock
Expand All @@ -17,7 +18,7 @@
from knack.arguments import ArgumentsContext
from knack.commands import CLICommandsLoader, CommandGroup

from tests.util import DummyCLI, redirect_io
from tests.util import DummyCLI, redirect_io, disable_color


def example_handler(arg1, arg2=None, arg3=None):
Expand Down Expand Up @@ -87,6 +88,31 @@ def test_preview_command_plain_execute(self):
expected = "This command is in preview. It may be changed/removed in a future release."
self.assertIn(expected, actual)

@redirect_io
@disable_color
def test_preview_command_plain_execute_no_color(self):
""" Ensure warning is displayed without color. """
self.cli_ctx.invoke('cmd1 -b b'.split())
actual = self.io.getvalue()
self.assertIn("WARNING: This command is in preview. It may be changed/removed in a future release.", actual)

@redirect_io
def test_preview_command_implicitly_execute(self):
""" Ensure general warning displayed when running command from a preview parent group. """
self.cli_ctx.invoke('grp1 cmd1 -b b'.split())
actual = self.io.getvalue()
expected = "Command group 'grp1' is in preview. It may be changed/removed in a future release."
self.assertIn(expected, actual)

@redirect_io
@disable_color
def test_preview_command_implicitly_no_color(self):
""" Ensure warning is displayed without color. """
self.cli_ctx.invoke('grp1 cmd1 -b b'.split())
actual = self.io.getvalue()
expected = "WARNING: Command group 'grp1' is in preview. It may be changed/removed in a future release."
self.assertIn(expected, actual)


class TestCommandGroupPreview(unittest.TestCase):

Expand Down Expand Up @@ -129,6 +155,23 @@ def test_preview_command_group_help_plain(self):
Commands:
cmd1 : Short summary here.
""".format(self.cli_ctx.name)
self.assertEqual(expected, actual)

@redirect_io
@disable_color
def test_preview_command_group_help_plain_no_color(self):
""" Ensure warning is displayed without color. """
with self.assertRaises(SystemExit):
self.cli_ctx.invoke('group1 -h'.split())
actual = self.io.getvalue()
expected = """
Group
cli group1 : A group.
WARNING: This command group is in preview. It may be changed/removed in a future release.
Commands:
cmd1 : Short summary here.
""".format(self.cli_ctx.name)
self.assertEqual(expected, actual)

Expand Down Expand Up @@ -190,6 +233,20 @@ def test_preview_arguments_command_help(self):
""".format(self.cli_ctx.name)
self.assertIn(expected, actual)

@redirect_io
@disable_color
def test_preview_arguments_command_help_no_color(self):
""" Ensure warning is displayed without color. """
with self.assertRaises(SystemExit):
self.cli_ctx.invoke('arg-test -h'.split())
actual = self.io.getvalue()
expected = """
Arguments
--arg1 [Preview] [Required] : Arg1.
WARNING: Argument '--arg1' is in preview. It may be changed/removed in a future release.
""".format(self.cli_ctx.name)
self.assertIn(expected, actual)

@redirect_io
def test_preview_arguments_execute(self):
""" Ensure deprecated arguments can be used. """
Expand All @@ -201,6 +258,18 @@ def test_preview_arguments_execute(self):
action_expected = "Side-effect from some original action!"
self.assertIn(action_expected, actual)

@redirect_io
@disable_color
def test_preview_arguments_execute_no_color(self):
""" Ensure warning is displayed without color. """
self.cli_ctx.invoke('arg-test --arg1 foo --opt1 bar'.split())
actual = self.io.getvalue()
preview_expected = "WARNING: Argument '--arg1' is in preview. It may be changed/removed in a future release."
self.assertIn(preview_expected, actual)

action_expected = "Side-effect from some original action!"
self.assertIn(action_expected, actual)


if __name__ == '__main__':
unittest.main()
17 changes: 17 additions & 0 deletions tests/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import shutil
import os
from six import StringIO
import logging
from knack.log import CLI_LOGGER_NAME

from knack.cli import CLI, CLICommandsLoader, CommandInvoker

Expand All @@ -29,6 +31,21 @@ def wrapper(self):
self.io.close()
sys.stdout = original_stderr
sys.stderr = original_stderr

# Remove the handlers added by CLI, so that the next invoke call init them again with the new stderr
# Otherwise, the handlers will write to a closed StringIO from a preview test
root_logger = logging.getLogger()
cli_logger = logging.getLogger(CLI_LOGGER_NAME)
root_logger.handlers = root_logger.handlers[:-1]
cli_logger.handlers = cli_logger.handlers[:-1]
return wrapper


def disable_color(func):
def wrapper(self):
self.cli_ctx.enable_color = False
func(self)
self.cli_ctx.enable_color = True
return wrapper


Expand Down

0 comments on commit 82875fb

Please sign in to comment.