Hello community,

here is the log from the commit of package python-knack for openSUSE:Factory 
checked in at 2019-05-27 08:38:23
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-knack (Old)
 and      /work/SRC/openSUSE:Factory/.python-knack.new.5148 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-knack"

Mon May 27 08:38:23 2019 rev:8 rq:705262 version:0.6.2

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-knack/python-knack.changes        
2019-05-22 11:16:43.162502081 +0200
+++ /work/SRC/openSUSE:Factory/.python-knack.new.5148/python-knack.changes      
2019-05-27 08:38:24.555070416 +0200
@@ -1,0 +2,12 @@
+Fri May 24 12:36:58 UTC 2019 - [email protected]
+
+- version update to 0.6.2
+ * Adds ability to declare that command groups, commands, and arguments
+   are in a preview status and therefore might change or be removed.
+   This is done by passing the kwarg `is_preview=True`.
+ * Adds a generic `StatusTag` class to `knack.util` that allows you
+   to create your own colorized tags like `[Preview]` and `[Deprecated]`.
+ * When an incorrect command name is entered, Knack will now attempt
+   to suggest the closest alternative.
+
+-------------------------------------------------------------------

Old:
----
  v0.6.1.tar.gz

New:
----
  v0.6.2.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-knack.spec ++++++
--- /var/tmp/diff_new_pack.6iAYgT/_old  2019-05-27 08:38:25.147070185 +0200
+++ /var/tmp/diff_new_pack.6iAYgT/_new  2019-05-27 08:38:25.151070184 +0200
@@ -18,7 +18,7 @@
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-knack
-Version:        0.6.1
+Version:        0.6.2
 Release:        0
 Summary:        A Command-Line Interface framework
 License:        MIT

++++++ v0.6.1.tar.gz -> v0.6.2.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/knack-0.6.1/HISTORY.rst new/knack-0.6.2/HISTORY.rst
--- old/knack-0.6.1/HISTORY.rst 1970-01-01 01:00:00.000000000 +0100
+++ new/knack-0.6.2/HISTORY.rst 2019-05-23 00:19:57.000000000 +0200
@@ -0,0 +1,106 @@
+.. :changelog:
+
+Release History
+===============
+
+0.6.2
++++++
+* Adds ability to declare that command groups, commands, and arguments are in 
a preview status and therefore might change or be removed. This is done by 
passing the kwarg `is_preview=True`.
+* Adds a generic `StatusTag` class to `knack.util` that allows you to create 
your own colorized tags like `[Preview]` and `[Deprecated]`.
+* When an incorrect command name is entered, Knack will now attempt to suggest 
the closest alternative.
+
+0.6.1
++++++
+* Always read from local for configured_default
+
+0.6.0
++++++
+* Support local context chained config file
+
+0.5.4
++++++
+* Allows the loading of text files using @filename syntax.
+* Adds the argument kwarg configured_default to support setting argument 
defaults via the config file's [defaults] section or an environment variable.
+
+0.5.3
++++++
+* Removes an incorrect check when adding arguments.
+
+0.5.2
++++++
+* Updates usages of yaml.load to use yaml.safe_load.
+
+0.5.1
++++++
+* Fix issue with some scenarios (no args and --version)
+
+0.5.0
++++++
+* Adds support for positional arguments with the .positional helper method on 
ArgumentsContext.
+* Removes the necessity for the type field in help.py. This information can be 
inferred from the class, so specifying it causes unnecessary crashes.
+* Adds support for examining the result of a command after a call to invoke. 
The raw object, error (if any) an exit code are accessible.
+* Adds support for accessing the command instance from inside custom commands 
by putting the special argument cmd in the signature.
+* Fixes an issue with the default config directory. It use to be .cli and is 
now based on the CLI name.
+* Fixes regression in knack 0.4.5 in behavior when cli_name --verbose/debug is 
used. Displays the welcome message as intended.
+* Adds ability to specify line width for help text display.
+
+0.4.5
++++++
+* Preserves logging verbosity and output format on the namespace for use by 
validators.
+
+0.4.4
++++++
+* Adds ability to set config file name.
+* Fixes bug with argument deprecations.
+
+0.4.3
++++++
+* Fixes issue where values were sometimes ignored when using deprecated 
options regardless of which option was given.
+
+0.4.2
++++++
+* Bug fix: disable number parse on table mode PR #88
+
+0.4.1
++++++
+* Fixes bug with deprecation mechanism.
+* Fixes an issue where the command group table would only be filled by calls 
to create CommandGroup classes. This resulted in some gaps in the command group 
table.
+
+0.4.0
++++++
+* Add mechanism to deprecate commands, command groups, arguments and argument 
options.
+* Improve help display support for Unicode.
+
+0.3.3
++++++
+* expose a callback to let client side perform extra logics (#80)
+* output: don't skip false value on auto-tabulating (#83)
+
+0.3.2
++++++
+* ArgumentsContext.ignore() should use hidden options_list (#76)
+* Consolidate exception handling (#66)
+
+0.3.1
++++++
+* Performance optimization - Delay import of platform and colorama (#47)
+* CLIError: Inherit from Exception directly (#65)
+* Explicitly state which packages to include (so exclude 'tests') (#68)
+
+0.2.0
++++++
+* Support command level and argument level validators.
+* knack.commands.CLICommandsLoader now accepts a command_cls argument so you 
can provide your own CLICommand class.
+* logging: make determine_verbose_level private method.
+* Allow overriding of NAMED_ARGUMENTS
+* Only pass valid argparse kwargs to argparse.ArgumentParser.add_argument and 
ignore the rest
+* logging: make determine_verbose_level private method
+* Remove cli_command, register_cli_argument, register_extra_cli_argument as 
ways to register commands and arguments.
+
+0.1.1
++++++
+* Add more types of command and argument loaders.
+
+0.1.0
++++++
+* Initial release
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/knack-0.6.1/MANIFEST.in new/knack-0.6.2/MANIFEST.in
--- old/knack-0.6.1/MANIFEST.in 2019-04-26 02:11:43.000000000 +0200
+++ new/knack-0.6.2/MANIFEST.in 2019-05-23 00:19:57.000000000 +0200
@@ -1 +1,3 @@
 include *.rst
+include LICENSE
+recursive-include tests *
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/knack-0.6.1/docs/cli.md new/knack-0.6.2/docs/cli.md
--- old/knack-0.6.1/docs/cli.md 2019-04-26 02:11:43.000000000 +0200
+++ new/knack-0.6.2/docs/cli.md 2019-05-23 00:19:57.000000000 +0200
@@ -9,7 +9,7 @@
 
 For example:
 `cli_name` - Name of CLI. Typically the executable name.
-`config_dir` - Path to config dir. e.g. `os.path.join('~', '.myconfig')`
+`config_dir` - Path to config dir. e.g. `os.path.expanduser(os.path.join('~', 
'.myconfig'))`
 `config_env_var_prefix` - A prefix for environment variables used in config 
e.g. `CLI_`.
 
 Use the `invoke()` method to invoke commands.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/knack-0.6.1/examples/exapp 
new/knack-0.6.2/examples/exapp
--- old/knack-0.6.1/examples/exapp      2019-04-26 02:11:43.000000000 +0200
+++ new/knack-0.6.2/examples/exapp      2019-05-23 00:19:57.000000000 +0200
@@ -79,7 +79,7 @@
 
 
 mycli = CLI(cli_name=cli_name,
-            config_dir=os.path.join('~', '.{}'.format(cli_name)),
+            config_dir=os.path.expanduser(os.path.join('~', 
'.{}'.format(cli_name))),
             config_env_var_prefix=cli_name,
             commands_loader_cls=MyCommandsLoader,
             help_cls=MyCLIHelp)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/knack-0.6.1/examples/exapp2 
new/knack-0.6.2/examples/exapp2
--- old/knack-0.6.1/examples/exapp2     2019-04-26 02:11:43.000000000 +0200
+++ new/knack-0.6.2/examples/exapp2     2019-05-23 00:19:57.000000000 +0200
@@ -86,7 +86,7 @@
 
 
 mycli = MyCLI(cli_name=cli_name,
-              config_dir=os.path.join('~', '.{}'.format(cli_name)),
+              config_dir=os.path.expanduser(os.path.join('~', 
'.{}'.format(cli_name))),
               config_env_var_prefix=cli_name,
               commands_loader_cls=MyCommandsLoader,
               help_cls=MyCLIHelp)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/knack-0.6.1/examples/test_exapp 
new/knack-0.6.2/examples/test_exapp
--- old/knack-0.6.1/examples/test_exapp 2019-04-26 02:11:43.000000000 +0200
+++ new/knack-0.6.2/examples/test_exapp 2019-05-23 00:19:57.000000000 +0200
@@ -47,7 +47,7 @@
 name = 'exapp4'
 
 mycli = CLI(cli_name=name,
-            config_dir=os.path.join('~', '.{}'.format(name)),
+            config_dir=os.path.expanduser(os.path.join('~', 
'.{}'.format(name))),
             config_env_var_prefix=name,
             commands_loader_cls=MyCommandsLoader)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/knack-0.6.1/knack/arguments.py 
new/knack-0.6.2/knack/arguments.py
--- old/knack-0.6.1/knack/arguments.py  2019-04-26 02:11:43.000000000 +0200
+++ new/knack-0.6.2/knack/arguments.py  2019-05-23 00:19:57.000000000 +0200
@@ -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
 
@@ -42,7 +43,7 @@
 
 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.
@@ -221,6 +222,55 @@
             action = _handle_option_deprecation(deprecated_opts)
         return action
 
+    def _handle_previews(self, argument_dest, **kwargs):
+
+        if not kwargs.get('is_preview', False):
+            return 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
+
+        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)
+
+        options_list = kwargs.get('options_list', None)
+        object_type = 'argument'
+
+        if options_list is None:
+            # convert argument dest
+            target = '--{}'.format(argument_dest.replace('_', '-'))
+        elif options_list:
+            target = sorted(options_list, key=len)[-1]
+        else:
+            # positional argument
+            target = kwargs.get('metavar', 
'<{}>'.format(argument_dest.upper()))
+            object_type = 'positional argument'
+
+        preview_info = PreviewItem(
+            target=target,
+            object_type=object_type,
+            message_func=_get_preview_arg_message
+        )
+        kwargs['preview_info'] = preview_info
+        kwargs['action'] = _handle_argument_preview(preview_info)
+        return kwargs
+
     # pylint: disable=inconsistent-return-statements
     def deprecate(self, **kwargs):
 
@@ -252,7 +302,8 @@
         :param arg_type: Predefined CLIArgumentType definition to register, as 
modified by any provided kwargs.
         :type arg_type: knack.arguments.CLIArgumentType
         :param kwargs: Possible values: `options_list`, `validator`, 
`completer`, `nargs`, `action`, `const`, `default`,
-                       `type`, `choices`, `required`, `help`, `metavar`. See 
/docs/arguments.md.
+                       `type`, `choices`, `required`, `help`, `metavar`, 
`is_preview`, `deprecate_info`.
+                       See /docs/arguments.md.
         """
         self._check_stale()
         if not self._applicable():
@@ -261,6 +312,8 @@
         deprecate_action = self._handle_deprecations(argument_dest, **kwargs)
         if deprecate_action:
             kwargs['action'] = deprecate_action
+
+        kwargs = self._handle_previews(argument_dest, **kwargs)
         
self.command_loader.argument_registry.register_cli_argument(self.command_scope,
                                                                     
argument_dest,
                                                                     arg_type,
@@ -274,7 +327,8 @@
         :param arg_type: Predefined CLIArgumentType definition to register, as 
modified by any provided kwargs.
         :type arg_type: knack.arguments.CLIArgumentType
         :param kwargs: Possible values: `validator`, `completer`, `nargs`, 
`action`, `const`, `default`,
-                       `type`, `choices`, `required`, `help`, `metavar`. See 
/docs/arguments.md.
+                       `type`, `choices`, `required`, `help`, `metavar`, 
`is_preview`, `deprecate_info`.
+                       See /docs/arguments.md.
         """
         self._check_stale()
         if not self._applicable():
@@ -293,11 +347,14 @@
             raise CLIError("command authoring error: commands may have, at 
most, one positional argument. '{}' already "
                            "has positional argument: 
{}.".format(self.command_scope, ' '.join(positional_args.keys())))
 
+        kwargs['options_list'] = []
+
         deprecate_action = self._handle_deprecations(argument_dest, **kwargs)
         if deprecate_action:
             kwargs['action'] = deprecate_action
 
-        kwargs['options_list'] = []
+        kwargs = self._handle_previews(argument_dest, **kwargs)
+
         
self.command_loader.argument_registry.register_cli_argument(self.command_scope,
                                                                     
argument_dest,
                                                                     arg_type,
@@ -323,7 +380,8 @@
         :param argument_dest: The destination argument to add this argument 
type to
         :type argument_dest: str
         :param kwargs: Possible values: `options_list`, `validator`, 
`completer`, `nargs`, `action`, `const`, `default`,
-                       `type`, `choices`, `required`, `help`, `metavar`. See 
/docs/arguments.md.
+                       `type`, `choices`, `required`, `help`, `metavar`, 
`is_preview`, `deprecate_info`.
+                       See /docs/arguments.md.
         """
         self._check_stale()
         if not self._applicable():
@@ -337,6 +395,9 @@
         deprecate_action = self._handle_deprecations(argument_dest, **kwargs)
         if deprecate_action:
             kwargs['action'] = deprecate_action
+
+        kwargs = self._handle_previews(argument_dest, **kwargs)
+
         
self.command_loader.extra_argument_registry[self.command_scope][argument_dest] 
= CLICommandArgument(
             argument_dest, **kwargs)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/knack-0.6.1/knack/commands.py 
new/knack-0.6.2/knack/commands.py
--- old/knack-0.6.1/knack/commands.py   2019-04-26 02:11:43.000000000 +0200
+++ new/knack-0.6.2/knack/commands.py   2019-05-23 00:19:57.000000000 +0200
@@ -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
@@ -27,7 +28,8 @@
     # 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
@@ -48,6 +50,8 @@
         :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
@@ -66,6 +70,7 @@
         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
 
@@ -295,6 +300,11 @@
         Deprecated.ensure_new_style_deprecation(self.command_loader.cli_ctx, 
self.group_kwargs, 'command group')
         if kwargs['deprecate_info']:
             kwargs['deprecate_info'].target = group_name
+        if kwargs.get('is_preview', False):
+            kwargs['preview_info'] = PreviewItem(
+                target=group_name,
+                object_type='command group'
+            )
         
command_loader._populate_command_group_table_with_subgroups(group_name)  # 
pylint: disable=protected-access
         self.command_loader.command_group_table[group_name] = self
 
@@ -313,7 +323,8 @@
         :type handler_name: str
         :param kwargs: Kwargs to apply to the command.
                        Possible values: `client_factory`, `arguments_loader`, 
`description_loader`, `description`,
-                       `formatter_class`, `table_transformer`, 
`deprecate_info`, `validator`, `confirmation`.
+                       `formatter_class`, `table_transformer`, 
`deprecate_info`, `validator`, `confirmation`,
+                       `is_preview`.
         """
         import copy
 
@@ -322,6 +333,11 @@
         command_kwargs.update(kwargs)
         # don't inherit deprecation info from command group
         command_kwargs['deprecate_info'] = kwargs.get('deprecate_info', None)
+        if kwargs.get('is_preview', False):
+            command_kwargs['preview_info'] = PreviewItem(
+                self.command_loader.cli_ctx,
+                object_type='command'
+            )
 
         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(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/knack-0.6.1/knack/config.py 
new/knack-0.6.2/knack/config.py
--- old/knack-0.6.1/knack/config.py     2019-04-26 02:11:43.000000000 +0200
+++ new/knack-0.6.2/knack/config.py     2019-05-23 00:19:57.000000000 +0200
@@ -21,7 +21,7 @@
                        '0': False, 'no': False, 'false': False, 'off': False}
 
     _DEFAULT_CONFIG_ENV_VAR_PREFIX = 'CLI'
-    _DEFAULT_CONFIG_DIR = os.path.join('~', '.{}'.format('cli'))
+    _DEFAULT_CONFIG_DIR = os.path.expanduser(os.path.join('~', 
'.{}'.format('cli')))
     _DEFAULT_CONFIG_FILE_NAME = 'config'
     _CONFIG_DEFAULTS_SECTION = 'defaults'
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/knack-0.6.1/knack/deprecation.py 
new/knack-0.6.2/knack/deprecation.py
--- old/knack-0.6.1/knack/deprecation.py        2019-04-26 02:11:43.000000000 
+0200
+++ new/knack-0.6.2/knack/deprecation.py        2019-05-23 00:19:57.000000000 
+0200
@@ -5,6 +5,7 @@
 
 from six import string_types as STRING_TYPES
 
+from .util import StatusTag
 
 DEFAULT_DEPRECATED_TAG = '[Deprecated]'
 
@@ -29,25 +30,8 @@
     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):
+class Deprecated(StatusTag):
 
     @staticmethod
     def ensure_new_style_deprecation(cli_ctx, kwargs, object_type):
@@ -62,7 +46,7 @@
         return deprecate_info
 
     def __init__(self, cli_ctx=None, object_type='', target=None, 
redirect=None, hide=False, expiration=None,
-                 tag_func=None, message_func=None):
+                 tag_func=None, message_func=None, **kwargs):
         """ Create a collection of deprecation metadata.
 
         :param cli_ctx: The CLI context associated with the deprecated item.
@@ -87,13 +71,6 @@
                              Omit to use the default.
         :type message_func: callable
         """
-        self.cli_ctx = cli_ctx
-        self.object_type = object_type
-        self.target = target
-        self.redirect = redirect
-        self.hide = hide
-        self.expiration = expiration
-
         def _default_get_message(self):
             msg = "This {} has been deprecated and will be removed 
".format(self.object_type)
             if self.expiration:
@@ -104,24 +81,18 @@
                 msg += " Use '{}' instead.".format(self.redirect)
             return msg
 
-        self._get_tag = tag_func or (lambda _: DEFAULT_DEPRECATED_TAG)
-        self._get_message = message_func or _default_get_message
-
-    def __deepcopy__(self, memo):
-        import copy
+        self.redirect = redirect
+        self.hide = hide
+        self.expiration = expiration
 
-        cls = self.__class__
-        result = cls.__new__(cls)
-        memo[id(self)] = result
-        for k, v in self.__dict__.items():
-            try:
-                setattr(result, k, copy.deepcopy(v, memo))
-            except TypeError:
-                if k == 'cli_ctx':
-                    setattr(result, k, self.cli_ctx)
-                else:
-                    raise
-        return result
+        super(Deprecated, self).__init__(
+            cli_ctx=cli_ctx,
+            object_type=object_type,
+            target=target,
+            color='yellow',
+            tag_func=tag_func or (lambda _: DEFAULT_DEPRECATED_TAG),
+            message_func=message_func or _default_get_message
+        )
 
     # pylint: disable=no-self-use
     def _version_less_than_or_equal_to(self, v1, v2):
@@ -148,16 +119,6 @@
     def show_in_help(self):
         return not self.hidden() and not self.expired()
 
-    @property
-    def tag(self):
-        """ Returns a tag object. """
-        return ColorizedString(self._get_tag(self), 'yellow')
-
-    @property
-    def message(self):
-        """ Returns a tuple with the formatted message string and the message 
length. """
-        return ColorizedString(self._get_message(self), 'yellow')
-
 
 class ImplicitDeprecated(Deprecated):
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/knack-0.6.1/knack/help.py 
new/knack-0.6.2/knack/help.py
--- old/knack-0.6.1/knack/help.py       2019-04-26 02:11:43.000000000 +0200
+++ new/knack-0.6.2/knack/help.py       2019-05-23 00:19:57.000000000 +0200
@@ -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
 
@@ -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
 
@@ -159,6 +153,26 @@
             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'
+            self.preview_info = ImplicitPreviewItem(**preview_kwargs)
+
     def load(self, options):
         description = getattr(options, 'description', None)
         try:
@@ -208,7 +222,6 @@
 
         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):
@@ -244,6 +257,7 @@
                 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,
@@ -280,7 +294,8 @@
             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))
 
@@ -304,7 +319,7 @@
 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))
@@ -317,6 +332,7 @@
         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'):
@@ -367,6 +383,8 @@
                 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
@@ -381,15 +399,18 @@
         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(' ')
             ])
@@ -488,15 +509,18 @@
             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(' ')
             ])
@@ -573,6 +597,9 @@
             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])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/knack-0.6.1/knack/invocation.py 
new/knack-0.6.2/knack/invocation.py
--- old/knack-0.6.1/knack/invocation.py 2019-04-26 02:11:43.000000000 +0200
+++ new/knack-0.6.2/knack/invocation.py 2019-05-23 00:19:57.000000000 +0200
@@ -10,6 +10,7 @@
 from collections import defaultdict
 
 from .deprecation import ImplicitDeprecated, resolve_deprecate_info
+from .preview import ImplicitPreviewItem, resolve_preview_info
 from .util import CLIError, CtxTypeError, CommandResultItem, todict
 from .parser import CLICommandParser
 from .commands import CLICommandsLoader
@@ -117,6 +118,7 @@
             err = sys.exc_info()[1]
             getattr(parsed_ns, '_parser', 
self.parser).validation_error(str(err))
 
+    # pylint: disable=too-many-statements
     def execute(self, args):
         """ Executes the command invocation
 
@@ -164,6 +166,10 @@
         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
@@ -180,9 +186,23 @@
             del deprecate_kwargs['_get_message']
             deprecations.append(ImplicitDeprecated(**deprecate_kwargs))
 
+        # search for implicit preview
+        path_comps = cmd.name.split()[:-1]
+        implicit_preview_info = None
+        while path_comps and not implicit_preview_info:
+            implicit_preview_info = resolve_preview_info(self.cli_ctx, ' 
'.join(path_comps))
+            del path_comps[-1]
+
+        if implicit_preview_info:
+            preview_kwargs = implicit_preview_info.__dict__.copy()
+            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)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/knack-0.6.1/knack/parser.py 
new/knack-0.6.2/knack/parser.py
--- old/knack-0.6.1/knack/parser.py     2019-04-26 02:11:43.000000000 +0200
+++ new/knack-0.6.2/knack/parser.py     2019-05-23 00:19:57.000000000 +0200
@@ -3,6 +3,8 @@
 # Licensed under the MIT License. See License.txt in the project root for 
license information.
 # 
--------------------------------------------------------------------------------------------
 
+from __future__ import print_function
+
 import argparse
 
 from .deprecation import Deprecated
@@ -170,6 +172,7 @@
                     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,
@@ -254,3 +257,27 @@
         """
         self._expand_prefixed_files(args)
         return super(CLICommandParser, self).parse_args(args)
+
+    def _check_value(self, action, value):
+        # Override to customize the error message when a argument is not among 
the available choices
+        # converted value must be one of the choices (if specified)
+        import difflib
+        import sys
+
+        if action.choices is not None and value not in action.choices:
+            # parser has no `command_source`, value is part of command itself
+            error_msg = "{prog}: '{value}' is not in the '{prog}' command 
group. See '{prog} --help'.".format(
+                prog=self.prog, value=value)
+            logger.error(error_msg)
+            candidates = difflib.get_close_matches(value, action.choices, 
cutoff=0.7)
+            if candidates:
+                print_args = {
+                    's': 's' if len(candidates) > 1 else '',
+                    'verb': 'are' if len(candidates) > 1 else 'is',
+                    'value': value
+                }
+                suggestion_msg = "\nThe most similar choice{s} to '{value}' 
{verb}:\n".format(**print_args)
+                suggestion_msg += '\n'.join(['\t' + candidate for candidate in 
candidates])
+                print(suggestion_msg, file=sys.stderr)
+
+            self.exit(2)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/knack-0.6.1/knack/preview.py 
new/knack-0.6.2/knack/preview.py
--- old/knack-0.6.1/knack/preview.py    1970-01-01 01:00:00.000000000 +0100
+++ new/knack-0.6.2/knack/preview.py    2019-05-23 00:19:57.000000000 +0200
@@ -0,0 +1,77 @@
+# 
--------------------------------------------------------------------------------------------
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License. See License.txt in the project root for 
license information.
+# 
--------------------------------------------------------------------------------------------
+
+from .util import StatusTag
+
+_PREVIEW_TAG = '[Preview]'
+_preview_kwarg = 'preview_info'
+
+
+def resolve_preview_info(cli_ctx, name):
+
+    def _get_command(name):
+        return cli_ctx.invocation.commands_loader.command_table[name]
+
+    def _get_command_group(name):
+        return 
cli_ctx.invocation.commands_loader.command_group_table.get(name, None)
+
+    preview_info = None
+    try:
+        command = _get_command(name)
+        preview_info = getattr(command, _preview_kwarg, None)
+    except KeyError:
+        command_group = _get_command_group(name)
+        group_kwargs = getattr(command_group, 'group_kwargs', None)
+        if group_kwargs:
+            preview_info = group_kwargs.get(_preview_kwarg, None)
+    return preview_info
+
+
+# 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):
+        """ Create a collection of preview metadata.
+
+        :param cli_ctx: The CLI context associated with the preview item.
+        :type cli_ctx: knack.cli.CLI
+        :param object_type: A label describing the type of object in preview.
+        :type: object_type: str
+        :param target: The name of the object in preview.
+        :type target: str
+        :param tag_func: Callable which returns the desired unformatted tag 
string for the preview item.
+                         Omit to use the default.
+        :type tag_func: callable
+        :param message_func: Callable which returns the desired unformatted 
message string for the preview item.
+                             Omit to use the default.
+        :type message_func: callable
+        """
+
+        def _default_get_message(self):
+            return "This {} is in preview. It may be changed/removed in a 
future release.".format(self.object_type)
+
+        super(PreviewItem, self).__init__(
+            cli_ctx=cli_ctx,
+            object_type=object_type,
+            target=target,
+            color='cyan',
+            tag_func=tag_func or (lambda _: _PREVIEW_TAG),
+            message_func=message_func or _default_get_message
+        )
+
+
+class ImplicitPreviewItem(PreviewItem):
+
+    def __init__(self, **kwargs):
+
+        def get_implicit_preview_message(self):
+            return "Command group '{}' is in preview. It may be 
changed/removed " \
+                   "in a future release.".format(self.target)
+
+        kwargs.update({
+            'tag_func': lambda _: '',
+            'message_func': get_implicit_preview_message
+        })
+        super(ImplicitPreviewItem, self).__init__(**kwargs)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/knack-0.6.1/knack/util.py 
new/knack-0.6.2/knack/util.py
--- old/knack-0.6.1/knack/util.py       2019-04-26 02:11:43.000000000 +0200
+++ new/knack-0.6.2/knack/util.py       2019-05-23 00:19:57.000000000 +0200
@@ -35,6 +35,68 @@
                                                                                
    obj.__class__.__name__))
 
 
+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
+
+
+class StatusTag(object):
+
+    # pylint: disable=unused-argument
+    def __init__(self, cli_ctx, object_type, target, tag_func, message_func, 
color, **kwargs):
+        self.cli_ctx = cli_ctx
+        self.object_type = object_type
+        self.target = target
+        self._color = color
+        self._get_tag = tag_func
+        self._get_message = message_func
+
+    def __deepcopy__(self, memo):
+        import copy
+
+        cls = self.__class__
+        result = cls.__new__(cls)
+        memo[id(self)] = result
+        for k, v in self.__dict__.items():
+            try:
+                setattr(result, k, copy.deepcopy(v, memo))
+            except TypeError:
+                if k == 'cli_ctx':
+                    setattr(result, k, self.cli_ctx)
+                else:
+                    raise
+        return result
+
+    # pylint: disable=no-self-use
+    def hidden(self):
+        return False
+
+    def show_in_help(self):
+        return not self.hidden()
+
+    @property
+    def tag(self):
+        """ Returns a tag object. """
+        return ColorizedString(self._get_tag(self), self._color)
+
+    @property
+    def message(self):
+        """ Returns a tuple with the formatted message string and the message 
length. """
+        return ColorizedString(self._get_message(self), self._color)
+
+
 def ensure_dir(d):
     """ Create a directory if it doesn't exist """
     if not os.path.isdir(d):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/knack-0.6.1/setup.py new/knack-0.6.2/setup.py
--- old/knack-0.6.1/setup.py    2019-04-26 02:11:43.000000000 +0200
+++ new/knack-0.6.2/setup.py    2019-05-23 00:19:57.000000000 +0200
@@ -9,7 +9,7 @@
 from codecs import open
 from setuptools import setup, find_packages
 
-VERSION = '0.6.1'
+VERSION = '0.6.2'
 
 DEPENDENCIES = [
     'argcomplete',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/knack-0.6.1/tests/test_cli_scenarios.py 
new/knack-0.6.2/tests/test_cli_scenarios.py
--- old/knack-0.6.1/tests/test_cli_scenarios.py 2019-04-26 02:11:43.000000000 
+0200
+++ new/knack-0.6.2/tests/test_cli_scenarios.py 2019-05-23 00:19:57.000000000 
+0200
@@ -4,6 +4,7 @@
 # 
--------------------------------------------------------------------------------------------
 
 import os
+from collections import OrderedDict
 import unittest
 try:
     import mock
@@ -11,7 +12,6 @@
     from unittest import mock
 import mock
 
-from collections import OrderedDict
 from six import StringIO
 
 from knack import CLI
@@ -87,7 +87,7 @@
                 self.command_table['abc list'] = CLICommand(self.cli_ctx, 'abc 
list', a_test_command_handler)
                 return OrderedDict(self.command_table)
 
-        mycli = CLI(cli_name='exapp1', config_dir=os.path.join('~', 
'.exapp1'), commands_loader_cls=MyCommandsLoader)
+        mycli = CLI(cli_name='exapp1', 
config_dir=os.path.expanduser(os.path.join('~', '.exapp1')), 
commands_loader_cls=MyCommandsLoader)
 
         expected_output = """[
   {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/knack-0.6.1/tests/test_command_with_configured_defaults.py 
new/knack-0.6.2/tests/test_command_with_configured_defaults.py
--- old/knack-0.6.1/tests/test_command_with_configured_defaults.py      
2019-04-26 02:11:43.000000000 +0200
+++ new/knack-0.6.2/tests/test_command_with_configured_defaults.py      
2019-05-23 00:19:57.000000000 +0200
@@ -5,17 +5,15 @@
 from __future__ import print_function
 import os
 import logging
+import sys
 import unittest
 try:
     import mock
 except ImportError:
     from unittest import mock
-from six import StringIO
-import sys
 
 from knack.arguments import ArgumentsContext
-from knack.commands import CLICommandsLoader, CLICommand, CommandGroup
-from knack.config import CLIConfig
+from knack.commands import CLICommandsLoader, CommandGroup
 from tests.util import DummyCLI, redirect_io
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/knack-0.6.1/tests/test_deprecation.py 
new/knack-0.6.2/tests/test_deprecation.py
--- old/knack-0.6.1/tests/test_deprecation.py   2019-04-26 02:11:43.000000000 
+0200
+++ new/knack-0.6.2/tests/test_deprecation.py   2019-05-23 00:19:57.000000000 
+0200
@@ -28,6 +28,7 @@
     pass
 
 
+# pylint: disable=line-too-long
 class TestCommandDeprecation(unittest.TestCase):
 
     def setUp(self):
@@ -132,7 +133,8 @@
         with self.assertRaises(SystemExit):
             self.cli_ctx.invoke('cmd5 -h'.split())
         actual = self.io.getvalue()
-        self.assertTrue(u'invalid choice' in actual and u'cmd5' in actual)
+        expected = """The most similar choices to 'cmd5'"""
+        self.assertIn(expected, actual)
 
 
 class TestCommandGroupDeprecation(unittest.TestCase):
@@ -231,7 +233,8 @@
         with self.assertRaises(SystemExit):
             self.cli_ctx.invoke('group5 -h'.split())
         actual = self.io.getvalue()
-        self.assertTrue(u'invalid choice' in actual and u'group5' in actual)
+        expected = """The most similar choices to 'group5'"""
+        self.assertIn(expected, actual)
 
     @redirect_io
     def test_deprecate_command_implicitly(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/knack-0.6.1/tests/test_introspection.py 
new/knack-0.6.2/tests/test_introspection.py
--- old/knack-0.6.1/tests/test_introspection.py 2019-04-26 02:11:43.000000000 
+0200
+++ new/knack-0.6.2/tests/test_introspection.py 2019-05-23 00:19:57.000000000 
+0200
@@ -3,12 +3,7 @@
 # Licensed under the MIT License. See License.txt in the project root for 
license information.
 # 
--------------------------------------------------------------------------------------------
 
-import os
-import stat
 import unittest
-import tempfile
-import mock
-from six.moves import configparser
 
 from knack.introspection import extract_full_summary_from_signature, 
option_descriptions, extract_args_from_signature
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/knack-0.6.1/tests/test_preview.py 
new/knack-0.6.2/tests/test_preview.py
--- old/knack-0.6.1/tests/test_preview.py       1970-01-01 01:00:00.000000000 
+0100
+++ new/knack-0.6.2/tests/test_preview.py       2019-05-23 00:19:57.000000000 
+0200
@@ -0,0 +1,196 @@
+# 
--------------------------------------------------------------------------------------------
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License. See License.txt in the project root for 
license information.
+# 
--------------------------------------------------------------------------------------------
+
+from __future__ import unicode_literals
+
+import unittest
+try:
+    import mock
+except ImportError:
+    from unittest import mock
+from threading import Lock
+
+from knack.arguments import ArgumentsContext
+from knack.commands import CLICommand, CLICommandsLoader, CommandGroup
+
+from tests.util import DummyCLI, redirect_io
+
+
+def example_handler(arg1, arg2=None, arg3=None):
+    """ Short summary here. Long summary here. Still long summary. """
+    pass
+
+
+def example_arg_handler(arg1, opt1, arg2=None, opt2=None, arg3=None,
+                        opt3=None, arg4=None, opt4=None, arg5=None, opt5=None):
+    pass
+
+
+class TestCommandPreview(unittest.TestCase):
+
+    def setUp(self):
+
+        from knack.help_files import helps
+
+        class PreviewTestCommandLoader(CLICommandsLoader):
+            def load_command_table(self, args):
+                super(PreviewTestCommandLoader, self).load_command_table(args)
+                with CommandGroup(self, '', '{}#{{}}'.format(__name__)) as g:
+                    g.command('cmd1', 'example_handler', is_preview=True)
+
+                with CommandGroup(self, 'grp1', '{}#{{}}'.format(__name__), 
is_preview=True) as g:
+                    g.command('cmd1', 'example_handler')
+
+                return self.command_table
+
+            def load_arguments(self, command):
+                with ArgumentsContext(self, '') as c:
+                    c.argument('arg1', options_list=['--arg', '-a'], 
required=False, type=int, choices=[1, 2, 3])
+                    c.argument('arg2', options_list=['-b'], required=True, 
choices=['a', 'b', 'c'])
+
+                super(PreviewTestCommandLoader, self).load_arguments(command)
+
+        helps['grp1'] = """
+    type: group
+    short-summary: A group.
+"""
+        self.cli_ctx = DummyCLI(commands_loader_cls=PreviewTestCommandLoader)
+
+    @redirect_io
+    def test_preview_command_group_help(self):
+        """ Ensure preview commands appear correctly in group help view. """
+        with self.assertRaises(SystemExit):
+            self.cli_ctx.invoke('-h'.split())
+        actual = self.io.getvalue()
+        expected = u"""
+Group
+    {}
+
+Subgroups:
+    grp1 [Preview] : A group.
+
+Commands:
+    cmd1 [Preview] : Short summary here.
+
+""".format(self.cli_ctx.name)
+        self.assertEqual(expected, actual)
+
+    @redirect_io
+    def test_preview_command_plain_execute(self):
+        """ Ensure general warning displayed when running preview command. """
+        self.cli_ctx.invoke('cmd1 -b b'.split())
+        actual = self.io.getvalue()
+        expected = "This command is in preview. It may be changed/removed in a 
future release."
+        self.assertIn(expected, actual)
+
+
+class TestCommandGroupPreview(unittest.TestCase):
+
+    def setUp(self):
+
+        from knack.help_files import helps
+
+        class PreviewTestCommandLoader(CLICommandsLoader):
+            def load_command_table(self, args):
+                super(PreviewTestCommandLoader, self).load_command_table(args)
+
+                with CommandGroup(self, 'group1', '{}#{{}}'.format(__name__), 
is_preview=True) as g:
+                    g.command('cmd1', 'example_handler')
+
+                return self.command_table
+
+            def load_arguments(self, command):
+                with ArgumentsContext(self, '') as c:
+                    c.argument('arg1', options_list=['--arg', '-a'], 
required=False, type=int, choices=[1, 2, 3])
+                    c.argument('arg2', options_list=['-b'], required=True, 
choices=['a', 'b', 'c'])
+
+                super(PreviewTestCommandLoader, self).load_arguments(command)
+
+        helps['group1'] = """
+    type: group
+    short-summary: A group.
+"""
+        self.cli_ctx = DummyCLI(commands_loader_cls=PreviewTestCommandLoader)
+
+    @redirect_io
+    def test_preview_command_group_help_plain(self):
+        """ Ensure help warnings appear for preview command group help. """
+        with self.assertRaises(SystemExit):
+            self.cli_ctx.invoke('group1 -h'.split())
+        actual = self.io.getvalue()
+        expected = """
+Group
+    cli group1 : A group.
+        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)
+
+    @redirect_io
+    def test_preview_command_implicitly(self):
+        """ Ensure help warning displayed for command in preview because of a 
preview parent group. """
+        with self.assertRaises(SystemExit):
+            self.cli_ctx.invoke('group1 cmd1 -h'.split())
+        actual = self.io.getvalue()
+        expected = """
+Command
+    {} group1 cmd1 : Short summary here.
+        Long summary here. Still long summary. Command group 'group1' is in 
preview. It may be
+        changed/removed in a future release.
+""".format(self.cli_ctx.name)
+        self.assertIn(expected, actual)
+
+
+class TestArgumentPreview(unittest.TestCase):
+
+    def setUp(self):
+
+        from knack.help_files import helps
+
+        class PreviewTestCommandLoader(CLICommandsLoader):
+            def load_command_table(self, args):
+                super(PreviewTestCommandLoader, self).load_command_table(args)
+                with CommandGroup(self, '', '{}#{{}}'.format(__name__)) as g:
+                    g.command('arg-test', 'example_arg_handler')
+                return self.command_table
+
+            def load_arguments(self, command):
+                with ArgumentsContext(self, 'arg-test') as c:
+                    c.argument('arg1', help='Arg1', is_preview=True)
+
+                super(PreviewTestCommandLoader, self).load_arguments(command)
+
+        helps['grp1'] = """
+    type: group
+    short-summary: A group.
+"""
+        self.cli_ctx = DummyCLI(commands_loader_cls=PreviewTestCommandLoader)
+
+    @redirect_io
+    def test_preview_arguments_command_help(self):
+        """ Ensure preview arguments appear correctly in command help view. """
+        with self.assertRaises(SystemExit):
+            self.cli_ctx.invoke('arg-test -h'.split())
+        actual = self.io.getvalue()
+        expected = """
+Arguments
+    --arg1 [Preview] [Required] : Arg1.
+        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. """
+        self.cli_ctx.invoke('arg-test --arg1 foo --opt1 bar'.split())
+        actual = self.io.getvalue()
+        expected = "Argument '--arg1' is in preview. It may be changed/removed 
in a future release."
+        self.assertIn(expected, actual)
+
+
+if __name__ == '__main__':
+    unittest.main()


Reply via email to