Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Command/Argument Preview Mechanism #152

Merged
merged 6 commits into from
May 22, 2019
Merged
Show file tree
Hide file tree
Changes from 3 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
49 changes: 48 additions & 1 deletion knack/arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from collections import defaultdict

from .deprecation import Deprecated
from .preview import PreviewItem
from .log import get_logger
from .util import CLIError

Expand Down Expand Up @@ -42,7 +43,7 @@ def update(self, other=None, **kwargs):

class CLICommandArgument(object):

NAMED_ARGUMENTS = ['options_list', 'validator', 'completer', 'arg_group', 'deprecate_info']
NAMED_ARGUMENTS = ['options_list', 'validator', 'completer', 'arg_group', 'deprecate_info', 'preview_info']

def __init__(self, dest=None, argtype=None, **kwargs):
"""An argument that has a specific destination parameter.
Expand Down Expand Up @@ -221,6 +222,34 @@ def __call__(self, parser, namespace, values, option_string=None):
action = _handle_option_deprecation(deprecated_opts)
return action

def _handle_previews(self, argument_dest, **kwargs):

def _handle_argument_preview(preview_info):

parent_class = self._get_parent_class(**kwargs)

class PreviewArgumentAction(parent_class):

def __call__(self, parser, namespace, values, option_string=None):
if not hasattr(namespace, '_argument_previews'):
setattr(namespace, '_argument_previews', [preview_info])
else:
namespace._argument_previews.append(preview_info) # pylint: disable=protected-access
try:
super(PreviewArgumentAction, self).__call__(parser, namespace, values, option_string)
except NotImplementedError:
setattr(namespace, self.dest, values)

return PreviewArgumentAction

action = kwargs.get('action', None)

preview_info = kwargs.get('preview_info', None)
if preview_info:
preview_info.target = preview_info.target or argument_dest
action = _handle_argument_preview(preview_info)
return action

# pylint: disable=inconsistent-return-statements
def deprecate(self, **kwargs):

Expand All @@ -244,6 +273,21 @@ def _get_deprecated_arg_message(self):
kwargs['message_func'] = _get_deprecated_arg_message
return Deprecated(self.command_loader.cli_ctx, **kwargs)

# pylint: disable=inconsistent-return-statements
def preview(self, **kwargs):

def _get_preview_arg_message(self):
return "{} '{}' is in preview. It may be changed/removed in a future release.".format(
self.object_type.capitalize(), self.target)

self._check_stale()
if not self._applicable():
return

kwargs['object_type'] = 'argument'
kwargs['message_func'] = _get_preview_arg_message
return PreviewItem(self.command_loader.cli_ctx, **kwargs)

def argument(self, argument_dest, arg_type=None, **kwargs):
""" Register an argument for the given command scope using a knack.arguments.CLIArgumentType

Expand All @@ -261,6 +305,9 @@ def argument(self, argument_dest, arg_type=None, **kwargs):
deprecate_action = self._handle_deprecations(argument_dest, **kwargs)
if deprecate_action:
kwargs['action'] = deprecate_action
preview_action = self._handle_previews(argument_dest, **kwargs)
if preview_action:
kwargs['action'] = preview_action
self.command_loader.argument_registry.register_cli_argument(self.command_scope,
argument_dest,
arg_type,
Expand Down
16 changes: 15 additions & 1 deletion knack/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import six

from .deprecation import Deprecated
from .preview import PreviewItem
from .prompting import prompt_y_n, NoTTYException
from .util import CLIError, CtxTypeError
from .arguments import ArgumentRegistry, CLICommandArgument
Expand All @@ -27,7 +28,8 @@ class CLICommand(object): # pylint:disable=too-many-instance-attributes
# pylint: disable=unused-argument
def __init__(self, cli_ctx, name, handler, description=None, table_transformer=None,
arguments_loader=None, description_loader=None,
formatter_class=None, deprecate_info=None, validator=None, confirmation=None, **kwargs):
formatter_class=None, deprecate_info=None, validator=None, confirmation=None, preview_info=None,
**kwargs):
""" The command object that goes into the command table.

:param cli_ctx: CLI Context
Expand All @@ -48,6 +50,8 @@ def __init__(self, cli_ctx, name, handler, description=None, table_transformer=N
:type formatter_class: class
:param deprecate_info: Deprecation message to display when this command is invoked
:type deprecate_info: str
:param preview_info: Indicates a command is in preview
:type preview_info: bool
:param validator: The command validator
:param confirmation: User confirmation required for command
:type confirmation: bool, str, callable
Expand All @@ -66,6 +70,7 @@ def __init__(self, cli_ctx, name, handler, description=None, table_transformer=N
self.table_transformer = table_transformer
self.formatter_class = formatter_class
self.deprecate_info = deprecate_info
self.preview_info = preview_info
self.confirmation = confirmation
self.validator = validator

Expand Down Expand Up @@ -272,6 +277,10 @@ def deprecate(self, **kwargs):
kwargs['object_type'] = 'command group'
return Deprecated(self.cli_ctx, **kwargs)

def preview(self, **kwargs):
kwargs['object_type'] = 'command group'
return PreviewItem(self.cli_ctx, **kwargs)


class CommandGroup(object):

Expand Down Expand Up @@ -322,6 +331,7 @@ def command(self, name, handler_name, **kwargs):
command_kwargs.update(kwargs)
# don't inherit deprecation info from command group
command_kwargs['deprecate_info'] = kwargs.get('deprecate_info', None)
command_kwargs['preview_info'] = kwargs.get('preview_info', None)

self.command_loader._populate_command_group_table_with_subgroups(' '.join(command_name.split()[:-1])) # pylint: disable=protected-access
self.command_loader.command_table[command_name] = self.command_loader.create_command(
Expand All @@ -332,3 +342,7 @@ def command(self, name, handler_name, **kwargs):
def deprecate(self, **kwargs):
kwargs['object_type'] = 'command'
return Deprecated(self.command_loader.cli_ctx, **kwargs)

def preview(self, **kwargs):
kwargs['object_type'] = 'command'
return PreviewItem(self.command_loader.cli_ctx, **kwargs)
18 changes: 1 addition & 17 deletions knack/deprecation.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from six import string_types as STRING_TYPES

from .util import ColorizedString

DEFAULT_DEPRECATED_TAG = '[Deprecated]'

Expand All @@ -29,23 +30,6 @@ def _get_command_group(name):
return deprecate_info


class ColorizedString(object):

def __init__(self, message, color):
import colorama
self._message = message
self._color = getattr(colorama.Fore, color.upper(), None)

def __len__(self):
return len(self._message)

def __str__(self):
import colorama
if not self._color:
return self._message
return self._color + self._message + colorama.Fore.RESET


# pylint: disable=too-many-instance-attributes
class Deprecated(object):

Expand Down
67 changes: 49 additions & 18 deletions knack/help.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from .deprecation import ImplicitDeprecated, resolve_deprecate_info
from .log import get_logger
from .preview import ImplicitPreviewItem, resolve_preview_info
from .util import CtxTypeError
from .help_files import _load_help_file

Expand All @@ -21,13 +22,6 @@
REQUIRED_TAG = '[Required]'


def _get_preview_tag():
import colorama
PREVIEW_TAG = '{}[Preview]{}'.format(colorama.Fore.CYAN, colorama.Fore.RESET)
PREVIEW_TAG_LEN = len(PREVIEW_TAG) - 2 * len(colorama.Fore.RESET)
return (PREVIEW_TAG, PREVIEW_TAG_LEN)


def _get_hanging_indent(max_length, indent):
return max_length + (indent * 4) + len(FIRST_LINE_PREFIX) - 1

Expand Down Expand Up @@ -159,6 +153,30 @@ def __init__(self, help_ctx, delimiters):
del deprecate_kwargs['_get_message']
self.deprecate_info = ImplicitDeprecated(**deprecate_kwargs)

# resolve preview info
direct_preview_info = resolve_preview_info(help_ctx.cli_ctx, delimiters)
if direct_preview_info:
self.preview_info = direct_preview_info

# search for implicit preview
path_comps = delimiters.split()[:-1]
implicit_preview_info = None
while path_comps and not implicit_preview_info:
implicit_preview_info = resolve_preview_info(help_ctx.cli_ctx, ' '.join(path_comps))
del path_comps[-1]

if implicit_preview_info:
preview_kwargs = implicit_preview_info.__dict__.copy()
if delimiters in help_ctx.cli_ctx.invocation.commands_loader.command_table:
preview_kwargs['object_type'] = 'command'
else:
preview_kwargs['object_type'] = 'command group'

del preview_kwargs['_get_tag']
del preview_kwargs['_get_message']
self.preview_info = ImplicitPreviewItem(**preview_kwargs)


def load(self, options):
description = getattr(options, 'description', None)
try:
Expand Down Expand Up @@ -208,7 +226,6 @@ def __init__(self, help_ctx, delimiters, parser):

super(GroupHelpFile, self).__init__(help_ctx, delimiters)
self.type = 'group'
self.preview_info = getattr(parser, 'preview_info', None)

self.children = []
if getattr(parser, 'choices', None):
Expand Down Expand Up @@ -244,6 +261,7 @@ def __init__(self, help_ctx, delimiters, parser):
param_kwargs = {
'name_source': [action.metavar or action.dest],
'deprecate_info': getattr(action, 'deprecate_info', None),
'preview_info': getattr(action, 'preview_info', None),
'description': action.help,
'choices': action.choices,
'required': False,
Expand Down Expand Up @@ -280,7 +298,8 @@ def _add_parameter_help(self, param):
self.parameters.append(HelpParameter(**param_kwargs))
param_kwargs.update({
'name_source': normal_options,
'deprecate_info': getattr(param, 'deprecate_info', None)
'deprecate_info': getattr(param, 'deprecate_info', None),
'preview_info': getattr(param, 'preview_info', None)
})
self.parameters.append(HelpParameter(**param_kwargs))

Expand All @@ -304,7 +323,7 @@ def _load_from_data(self, data):
class HelpParameter(HelpObject): # pylint: disable=too-many-instance-attributes

def __init__(self, name_source, description, required, choices=None,
default=None, group_name=None, deprecate_info=None):
default=None, group_name=None, deprecate_info=None, preview_info=None):
super(HelpParameter, self).__init__()
self.name_source = name_source
self.name = ' '.join(sorted(name_source))
Expand All @@ -317,6 +336,7 @@ def __init__(self, name_source, description, required, choices=None,
self.default = default
self.group_name = group_name
self.deprecate_info = deprecate_info
self.preview_info = preview_info

def update_from_data(self, data):
if self.name != data.get('name'):
Expand Down Expand Up @@ -367,6 +387,8 @@ def _build_long_summary(item):
lines.append(item.long_summary)
if item.deprecate_info:
lines.append(str(item.deprecate_info.message))
if item.preview_info:
lines.append(str(item.preview_info.message))
return ' '.join(lines)

indent += 1
Expand All @@ -381,15 +403,18 @@ def _print_groups(self, help_file):
self.max_line_len = 0

def _build_tags_string(item):
PREVIEW_TAG, PREVIEW_TAG_LEN = _get_preview_tag()

preview_info = getattr(item, 'preview_info', None)
preview = preview_info.tag if preview_info else ''

deprecate_info = getattr(item, 'deprecate_info', None)
deprecated = deprecate_info.tag if deprecate_info else ''
preview = PREVIEW_TAG if getattr(item, 'preview_info', None) else ''

required = REQUIRED_TAG if getattr(item, 'required', None) else ''
tags = ' '.join([x for x in [str(deprecated), preview, required] if x])
tags = ' '.join([x for x in [str(deprecated), str(preview), required] if x])
tags_len = sum([
len(deprecated),
PREVIEW_TAG_LEN if preview else 0,
len(preview),
len(required),
tags.count(' ')
])
Expand Down Expand Up @@ -488,15 +513,18 @@ def _print_arguments(self, help_file): # pylint: disable=too-many-statements
return None

def _build_tags_string(item):
PREVIEW_TAG, PREVIEW_TAG_LEN = _get_preview_tag()

preview_info = getattr(item, 'preview_info', None)
preview = preview_info.tag if preview_info else ''

deprecate_info = getattr(item, 'deprecate_info', None)
deprecated = deprecate_info.tag if deprecate_info else ''
preview = PREVIEW_TAG if getattr(item, 'preview_info', None) else ''

required = REQUIRED_TAG if getattr(item, 'required', None) else ''
tags = ' '.join([x for x in [str(deprecated), preview, required] if x])
tags = ' '.join([x for x in [str(deprecated), str(preview), required] if x])
tags_len = sum([
len(deprecated),
PREVIEW_TAG_LEN if preview else 0,
len(preview),
len(required),
tags.count(' ')
])
Expand Down Expand Up @@ -573,6 +601,9 @@ def _build_long_summary(item):
deprecate_info = getattr(item, 'deprecate_info', None)
if deprecate_info:
lines.append(str(item.deprecate_info.message))
preview_info = getattr(item, 'preview_info', None)
if preview_info:
lines.append(str(item.preview_info.message))
return ' '.join(lines)

group_registry = ArgumentGroupRegistry([p.group_name for p in help_file.parameters if p.group_name])
Expand Down
8 changes: 8 additions & 0 deletions knack/invocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,10 @@ def execute(self, args):
if cmd.deprecate_info:
deprecations.append(cmd.deprecate_info)

previews = getattr(parsed_args, '_argument_previews', [])
if cmd.preview_info:
previews.append(cmd.preview_info)

params = self._filter_params(parsed_args)

# search for implicit deprecation
Expand All @@ -180,9 +184,13 @@ def execute(self, args):
del deprecate_kwargs['_get_message']
deprecations.append(ImplicitDeprecated(**deprecate_kwargs))

# TODO: search for implicit preview?
marstr marked this conversation as resolved.
Show resolved Hide resolved

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)
Expand Down
1 change: 1 addition & 0 deletions knack/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ def load_command_table(self, command_loader):
param = CLICommandParser._add_argument(command_parser, arg)
param.completer = arg.completer
param.deprecate_info = arg.deprecate_info
param.preview_info = arg.preview_info
command_parser.set_defaults(
func=metadata,
command=command_name,
Expand Down
Loading