Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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