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 HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Release History
+++++
* 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 `TagDecorator` 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
+++++
Expand Down
26 changes: 26 additions & 0 deletions knack/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -255,3 +257,27 @@ def parse_args(self, args=None, namespace=None):
"""
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)
2 changes: 1 addition & 1 deletion tests/test_cli_scenarios.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
# --------------------------------------------------------------------------------------------

import os
from collections import OrderedDict
import unittest
try:
import mock
except ImportError:
from unittest import mock
import mock

from collections import OrderedDict
from six import StringIO

from knack import CLI
Expand Down
6 changes: 2 additions & 4 deletions tests/test_command_with_configured_defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
7 changes: 5 additions & 2 deletions tests/test_deprecation.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def example_arg_handler(arg1, opt1, arg2=None, opt2=None, arg3=None,
pass


# pylint: disable=line-too-long
class TestCommandDeprecation(unittest.TestCase):

def setUp(self):
Expand Down Expand Up @@ -132,7 +133,8 @@ def test_deprecate_command_expired_execute(self):
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):
Expand Down Expand Up @@ -231,7 +233,8 @@ def test_deprecate_command_group_expired(self):
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):
Expand Down
5 changes: 0 additions & 5 deletions tests/test_introspection.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down