|
|
@@ -545,6 +545,46 @@ def init_cli():
|
|
|
# Click is imported here to run it after check_environment()
|
|
|
import click
|
|
|
|
|
|
+ class DeprecationMessage(object):
|
|
|
+ """Construct deprecation notice for help messages"""
|
|
|
+
|
|
|
+ def __init__(self, deprecated=False):
|
|
|
+ self.deprecated = deprecated
|
|
|
+ self.since = None
|
|
|
+ self.removed = None
|
|
|
+ self.custom_message = ""
|
|
|
+
|
|
|
+ if isinstance(deprecated, dict):
|
|
|
+ self.custom_message = deprecated.get("message", "")
|
|
|
+ self.since = deprecated.get("since", None)
|
|
|
+ self.removed = deprecated.get("removed", None)
|
|
|
+ elif isinstance(deprecated, str):
|
|
|
+ self.custom_message = deprecated
|
|
|
+
|
|
|
+ def full_message(self, type="Option"):
|
|
|
+ return "%s is deprecated %sand will be removed in%s.%s" % (
|
|
|
+ type,
|
|
|
+ "since %s " % self.since if self.since else "",
|
|
|
+ " %s" % self.removed if self.removed else " future versions",
|
|
|
+ " %s" % self.custom_message if self.custom_message else ""
|
|
|
+ )
|
|
|
+
|
|
|
+ def help(self, text, type="Option", separator=" "):
|
|
|
+ text = text or ""
|
|
|
+ return self.full_message(type) + separator + text if self.deprecated else text
|
|
|
+
|
|
|
+ def short_help(self, text):
|
|
|
+ text = text or ""
|
|
|
+ return ("Deprecated! " + text) if self.deprecated else text
|
|
|
+
|
|
|
+ def print_deprecation_warning(ctx):
|
|
|
+ """Prints deprectation warnings for arguments in given context"""
|
|
|
+ for option in ctx.command.params:
|
|
|
+ default = () if option.multiple else option.default
|
|
|
+ if isinstance(option, Option) and option.deprecated and ctx.params[option.name] != default:
|
|
|
+ print("Warning: %s" % DeprecationMessage(option.deprecated).
|
|
|
+ full_message('Option "%s"' % option.name))
|
|
|
+
|
|
|
class Task(object):
|
|
|
def __init__(
|
|
|
self, callback, name, aliases, dependencies, order_dependencies, action_args
|
|
|
@@ -567,6 +607,7 @@ def init_cli():
|
|
|
self,
|
|
|
name=None,
|
|
|
aliases=None,
|
|
|
+ deprecated=False,
|
|
|
dependencies=None,
|
|
|
order_dependencies=None,
|
|
|
**kwargs
|
|
|
@@ -574,6 +615,7 @@ def init_cli():
|
|
|
super(Action, self).__init__(name, **kwargs)
|
|
|
|
|
|
self.name = self.name or self.callback.__name__
|
|
|
+ self.deprecated = deprecated
|
|
|
|
|
|
if aliases is None:
|
|
|
aliases = []
|
|
|
@@ -592,6 +634,11 @@ def init_cli():
|
|
|
# Show first line of help if short help is missing
|
|
|
self.short_help = self.short_help or self.help.split("\n")[0]
|
|
|
|
|
|
+ if deprecated:
|
|
|
+ deprecation = DeprecationMessage(deprecated)
|
|
|
+ self.short_help = deprecation.short_help(self.short_help)
|
|
|
+ self.help = deprecation.help(self.help, type="Command", separator="\n")
|
|
|
+
|
|
|
# Add aliases to help string
|
|
|
if aliases:
|
|
|
aliases_help = "Aliases: %s." % ", ".join(aliases)
|
|
|
@@ -614,8 +661,21 @@ def init_cli():
|
|
|
|
|
|
self.callback = wrapped_callback
|
|
|
|
|
|
+ def invoke(self, ctx):
|
|
|
+ if self.deprecated:
|
|
|
+ print("Warning: %s" % DeprecationMessage(self.deprecated).full_message('Command "%s"' % self.name))
|
|
|
+ self.deprecated = False # disable Click's built-in deprecation handling
|
|
|
+
|
|
|
+ # Print warnings for options
|
|
|
+ print_deprecation_warning(ctx)
|
|
|
+ return super(Action, self).invoke(ctx)
|
|
|
+
|
|
|
class Argument(click.Argument):
|
|
|
- """Positional argument"""
|
|
|
+ """
|
|
|
+ Positional argument
|
|
|
+
|
|
|
+ names - alias of 'param_decls'
|
|
|
+ """
|
|
|
|
|
|
def __init__(self, **kwargs):
|
|
|
names = kwargs.pop("names")
|
|
|
@@ -656,12 +716,28 @@ def init_cli():
|
|
|
class Option(click.Option):
|
|
|
"""Option that knows whether it should be global"""
|
|
|
|
|
|
- def __init__(self, scope=None, **kwargs):
|
|
|
+ def __init__(self, scope=None, deprecated=False, **kwargs):
|
|
|
+ """
|
|
|
+ Keyword arguments additional to Click's Option class:
|
|
|
+
|
|
|
+ names - alias of 'param_decls'
|
|
|
+ deprecated - marks option as deprecated. May be boolean, string (with custom deprecation message)
|
|
|
+ or dict with optional keys:
|
|
|
+ since: version of deprecation
|
|
|
+ removed: version when option will be removed
|
|
|
+ custom_message: Additional text to deprecation warning
|
|
|
+ """
|
|
|
+
|
|
|
kwargs["param_decls"] = kwargs.pop("names")
|
|
|
super(Option, self).__init__(**kwargs)
|
|
|
|
|
|
+ self.deprecated = deprecated
|
|
|
self.scope = Scope(scope)
|
|
|
|
|
|
+ if deprecated:
|
|
|
+ deprecation = DeprecationMessage(deprecated)
|
|
|
+ self.help = deprecation.help(self.help)
|
|
|
+
|
|
|
if self.scope.is_global:
|
|
|
self.help += " This option can be used at most once either globally, or for one subcommand."
|
|
|
|
|
|
@@ -823,6 +899,7 @@ def init_cli():
|
|
|
for task in tasks:
|
|
|
for key in list(task.action_args):
|
|
|
option = next((o for o in ctx.command.params if o.name == key), None)
|
|
|
+
|
|
|
if option and (option.scope.is_global or option.scope.is_shared):
|
|
|
local_value = task.action_args.pop(key)
|
|
|
global_value = global_args[key]
|
|
|
@@ -836,6 +913,9 @@ def init_cli():
|
|
|
if local_value != default:
|
|
|
global_args[key] = local_value
|
|
|
|
|
|
+ # Show warnings about global arguments
|
|
|
+ print_deprecation_warning(ctx)
|
|
|
+
|
|
|
# Validate global arguments
|
|
|
for action_callback in ctx.command.global_action_callbacks:
|
|
|
action_callback(ctx, global_args, tasks)
|