Repository: pallets/click
Pull request #1005 (2018-05-15 15:18:35)

Rebase Added deprecation flag to commands
continued from #682


Patch:
diff --git a/click/core.py b/click/core.py
index e1cf001..1d2bf22 100644
--- a/click/core.py
+++ b/click/core.py
@@ -10,7 +10,7 @@ from .types import convert_type, IntRange, BOOL
 from .utils import make_str, make_default_short_help, echo, get_os_args
 from .exceptions import ClickException, UsageError, BadParameter, Abort, \
      MissingParameter
-from .termui import prompt, confirm
+from .termui import prompt, confirm, style
 from .formatting import HelpFormatter, join_options
 from .parser import OptionParser, split_opt
 from .globals import push_context, pop_context
@@ -25,6 +25,15 @@ _missing = object()
 SUBCOMMAND_METAVAR = 'COMMAND [ARGS]...'
 SUBCOMMANDS_METAVAR = 'COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]...'
 
+DEPRECATED_HELP_NOTICE = ' (DEPRECATED)'
+DEPRECATED_INVOKE_NOTICE = 'DeprecationWarning: ' + \
+                           'The command %(name)s is deprecated.'
+
+
+def _maybe_show_deprecated_notice(cmd):
+    if cmd.deprecated:
+        echo(style(DEPRECATED_INVOKE_NOTICE % {'name': cmd.name}, fg='red'), err=True)
+
 
 def fast_exit(code):
     """Exit without garbage collection, this speeds up exit by about 10ms for
@@ -752,12 +761,15 @@ class Command(BaseCommand):
     :param add_help_option: by default each command registers a ``--help``
                             option.  This can be disabled by this parameter.
     :param hidden: hide this command from help outputs.
+
+    :param deprecated: issues a message indicating that
+                             the command is deprecated.
     """
 
     def __init__(self, name, context_settings=None, callback=None,
                  params=None, help=None, epilog=None, short_help=None,
                  options_metavar='[OPTIONS]', add_help_option=True,
-                 hidden=False):
+                 hidden=False, deprecated=False):
         BaseCommand.__init__(self, name, context_settings)
         #: the callback to execute when the command fires.  This might be
         #: `None` in which case nothing happens.
@@ -779,6 +791,7 @@ class Command(BaseCommand):
         self.short_help = short_help
         self.add_help_option = add_help_option
         self.hidden = hidden
+        self.deprecated = deprecated
 
     def get_usage(self, ctx):
         formatter = ctx.make_formatter()
@@ -864,7 +877,14 @@ class Command(BaseCommand):
         if self.help:
             formatter.write_paragraph()
             with formatter.indentation():
-                formatter.write_text(self.help)
+                help_text = self.help
+                if self.deprecated:
+                    help_text += DEPRECATED_HELP_NOTICE
+                formatter.write_text(help_text)
+        elif self.deprecated:
+            formatter.write_paragraph()
+            with formatter.indentation():
+                formatter.write_text(DEPRECATED_HELP_NOTICE)
 
     def format_options(self, ctx, formatter):
         """Writes all the options into the formatter if they exist."""
@@ -905,6 +925,7 @@ class Command(BaseCommand):
         """Given a context, this invokes the attached callback (if it exists)
         in the right way.
         """
+        _maybe_show_deprecated_notice(self)
         if self.callback is not None:
             return ctx.invoke(self.callback, **ctx.params)
 
@@ -1020,6 +1041,8 @@ class MultiCommand(Command):
                 continue
 
             help = cmd.short_help or ''
+            if cmd.deprecated:
+                help += DEPRECATED_HELP_NOTICE
             rows.append((subcommand, help))
 
         if rows:
diff --git a/click/types.py b/click/types.py
index b0fb437..f594005 100644
--- a/click/types.py
+++ b/click/types.py
@@ -133,15 +133,11 @@ class Choice(ParamType):
     generators) may lead to surprising results.
 
     See :ref:`choice-opts` for an example.
-
-    :param case_sensitive: Set to false to make choices case insensitive.
-    Defaults to true.
     """
     name = 'choice'
 
-    def __init__(self, choices, case_sensitive=True):
+    def __init__(self, choices):
         self.choices = choices
-        self.case_sensitive = case_sensitive
 
     def get_metavar(self, param):
         return '[%s]' % '|'.join(self.choices)
@@ -154,25 +150,13 @@ class Choice(ParamType):
         if value in self.choices:
             return value
 
-        # Match through normalization and case sensitivity
-        # first do token_normalize_func, then lowercase
-        # preserve original `value` to produce an accurate message in
-        # `self.fail`
-        normed_value = value
-        normed_choices = self.choices
-
+        # Match through normalization
         if ctx is not None and \
            ctx.token_normalize_func is not None:
-            normed_value = ctx.token_normalize_func(value)
-            normed_choices = [ctx.token_normalize_func(choice) for choice in
-                              self.choices]
-
-        if not self.case_sensitive:
-            normed_value = normed_value.lower()
-            normed_choices = [choice.lower() for choice in normed_choices]
-
-        if normed_value in normed_choices:
-            return normed_value
+            value = ctx.token_normalize_func(value)
+            for choice in self.choices:
+                if ctx.token_normalize_func(choice) == value:
+                    return choice
 
         self.fail('invalid choice: %s. (choose from %s)' %
                   (value, ', '.join(self.choices)), param, ctx)


Test patch:
diff --git a/tests/test_commands.py b/tests/test_commands.py
index af58e7a..ee6006b 100644
--- a/tests/test_commands.py
+++ b/tests/test_commands.py
@@ -263,61 +263,27 @@ def test_unprocessed_options(runner):
     ]
 
 
-def test_subcommand_naming(runner):
-    @click.group()
-    def cli():
+def test_deprecated_in_help_messages(runner):
+    @click.command(deprecated=True)
+    def cmd_with_help():
+        """CLI HELP"""
         pass
 
-    @cli.command()
-    def foo_bar():
-        click.echo('foo-bar')
+    result = runner.invoke(cmd_with_help, ['--help'])
+    assert '(DEPRECATED)' in result.output
 
-    result = runner.invoke(cli, ['foo-bar'])
-    assert not result.exception
-    assert result.output.splitlines() == ['foo-bar']
-
-
-def test_environment_variables(runner):
-    @click.group()
-    def cli():
+    @click.command(deprecated=True)
+    def cmd_without_help():
         pass
 
-    @cli.command()
-    @click.option('--name', envvar='CLICK_NAME')
-    def foo(name):
-        click.echo(name)
-
-    result = runner.invoke(cli, ['foo'], env={'CLICK_NAME': 'environment'})
+    result = runner.invoke(cmd_without_help, ['--help'])
+    assert '(DEPRECATED)' in result.output
 
-    assert not result.exception
-    assert result.output == 'environment\n'
 
+def test_deprecated_in_invocation(runner):
+    @click.command(deprecated=True)
+    def deprecated_cmd():
+        debug()
 
-# Ensures the variables are read in the following order:
-# 1. CLI
-# 2. Environment
-# 3. Defaults
-variable_precedence_testdata = [
-    (['foo', '--name=cli'], {'CLICK_NAME': 'environment'}, 'cli\n'),
-    (['foo'], {'CLICK_NAME': 'environment'}, 'environment\n'),
-    (['foo'], None, 'defaults\n'),
-]
-
-
-@pytest.mark.parametrize("command,environment,expected",
-                          variable_precedence_testdata)
-def test_variable_precendence_00(runner, command, environment, expected):
-    @click.group()
-    def cli():
-        pass
-
-    @cli.command()
-    @click.option('--name', envvar='CLICK_NAME')
-    def foo(name):
-        click.echo(name)
-
-    defaults = {'foo': {'name': 'defaults'}}
-    result = runner.invoke(cli, command, default_map=defaults, env=environment)
-
-    assert not result.exception
-    assert result.output == expected
+    result = runner.invoke(deprecated_cmd)
+    assert 'DeprecationWarning:' in result.output
diff --git a/tests/test_options.py b/tests/test_options.py
index 06931f1..e8057fa 100644
--- a/tests/test_options.py
+++ b/tests/test_options.py
@@ -310,37 +310,6 @@ def test_missing_choice(runner):
         in result.output
 
 
-def test_case_insensitive_choice(runner):
-    @click.command()
-    @click.option('--foo', type=click.Choice(
-        ['Orange', 'Apple'], case_sensitive=False))
-    def cmd(foo):
-        click.echo(foo)
-
-    result = runner.invoke(cmd, ['--foo', 'apple'])
-    assert result.exit_code == 0
-
-    result = runner.invoke(cmd, ['--foo', 'oRANGe'])
-    assert result.exit_code == 0
-
-    result = runner.invoke(cmd, ['--foo', 'Apple'])
-    assert result.exit_code == 0
-
-    @click.command()
-    @click.option('--foo', type=click.Choice(['Orange', 'Apple']))
-    def cmd2(foo):
-        click.echo(foo)
-
-    result = runner.invoke(cmd2, ['--foo', 'apple'])
-    assert result.exit_code == 2
-
-    result = runner.invoke(cmd2, ['--foo', 'oRANGe'])
-    assert result.exit_code == 2
-
-    result = runner.invoke(cmd2, ['--foo', 'Apple'])
-    assert result.exit_code == 0
-
-
 def test_multiline_help(runner):
     @click.command()
     @click.option('--foo', help="""