summaryrefslogtreecommitdiff
path: root/click/core.py
diff options
context:
space:
mode:
Diffstat (limited to 'click/core.py')
-rw-r--r--click/core.py117
1 files changed, 92 insertions, 25 deletions
diff --git a/click/core.py b/click/core.py
index d145c0f..107b347 100644
--- a/click/core.py
+++ b/click/core.py
@@ -8,10 +8,10 @@ from .types import convert_type, IntRange, BOOL
from .utils import make_str, make_default_short_help, echo
from .exceptions import ClickException, UsageError, BadParameter, Abort
from .termui import prompt, confirm
-from .formatting import HelpFormatter
+from .formatting import HelpFormatter, join_options
from .parser import OptionParser, split_opt
-from ._compat import PY2, isidentifier
+from ._compat import PY2, isidentifier, iteritems
_missing = object()
@@ -94,6 +94,10 @@ class Context(object):
A context can be used as context manager in which case it will call
:meth:`close` on teardown.
+ .. versionadded:: 2.0
+ Added the `resilient_parsing`, `help_option_names`,
+ `token_normalize_func` parameters.
+
:param command: the command class for this context.
:param parent: the parent context.
:param info_name: the info name for this invokation. Generally this
@@ -117,11 +121,19 @@ class Context(object):
parse without any interactivity or callback
invocation. This is useful for implementing
things such as completion support.
+ :param help_option_names: optionally a list of strings that define how
+ the default help parameter is named. The
+ default is ``['--help']``.
+ :param token_normalize_func: an optional function that is used to
+ normalize tokens (options, choices,
+ etc.). This for instance can be used to
+ implement case insensitive behavior.
"""
def __init__(self, command, parent=None, info_name=None, obj=None,
auto_envvar_prefix=None, default_map=None,
- terminal_width=None, resilient_parsing=False):
+ terminal_width=None, resilient_parsing=False,
+ help_option_names=None, token_normalize_func=None):
#: the parent context or `None` if none exists.
self.parent = parent
#: the :class:`Command` for this context.
@@ -151,6 +163,22 @@ class Context(object):
#: The width of the terminal (None is autodetection).
self.terminal_width = terminal_width
+ if help_option_names is None:
+ if parent is not None:
+ help_option_names = parent.help_option_names
+ else:
+ help_option_names = ['--help']
+
+ #: The name for the help options.
+ self.help_option_names = help_option_names
+
+ if token_normalize_func is None and parent is not None:
+ token_normalize_func = parent.token_normalize_func
+
+ #: An optional normalization function for tokens. This is
+ #: options, choices, commands etc.
+ self.token_normalize_func = token_normalize_func
+
#: Indicates if resilient parsing is enabled. In that case click
#: will do its best to not cause any failures.
self.resilient_parsing = resilient_parsing
@@ -326,15 +354,22 @@ class BaseCommand(object):
operations. For instance, they cannot be used with the decorators
usually and they have no built-in callback system.
+ .. versionchanged:: 2.0
+ Added the `context_defaults` parameter.
+
:param name: the name of the command to use unless a group overrides it.
+ :param context_defaults: an optional dictionary with defaults that are
+ passed to the context object.
"""
- def __init__(self, name):
+ def __init__(self, name, context_defaults=None):
#: the name the command thinks it has. Upon registering a command
#: on a :class:`Group` the group will default the command name
#: with this information. You should instead use the
#: :class:`Context`\'s :attr:`~Context.info_name` attribute.
self.name = name
+ #: an optional dictionary with defaults passed to the context.
+ self.context_defaults = context_defaults
def get_usage(self, ctx):
raise NotImplementedError('Base commands cannot get usage')
@@ -362,6 +397,11 @@ class BaseCommand(object):
if parent is not None and parent.default_map is not None:
default_map = parent.default_map.get(info_name)
extra['default_map'] = default_map
+
+ for key, value in iteritems(self.context_defaults or {}):
+ if key not in extra:
+ extra[key] = value
+
ctx = Context(self, info_name=info_name, parent=parent, **extra)
self.parse_args(ctx, args)
return ctx
@@ -456,7 +496,12 @@ class Command(BaseCommand):
click. A basic command handles command line parsing and might dispatch
more parsing to commands nested below it.
+ .. versionchanged:: 2.0
+ Added the `context_defaults` parameter.
+
:param name: the name of the command to use unless a group overrides it.
+ :param context_defaults: an optional dictionary with defaults that are
+ passed to the context object.
:param callback: the callback to invoke. This is optional.
:param params: the parameters to register with this command. This can
be either :class:`Option` or :class:`Argument` objects.
@@ -470,10 +515,10 @@ class Command(BaseCommand):
"""
allow_extra_args = False
- def __init__(self, name, callback=None, params=None, help=None,
- epilog=None, short_help=None,
+ def __init__(self, name, context_defaults=None, callback=None,
+ params=None, help=None, epilog=None, short_help=None,
options_metavar='[OPTIONS]', add_help_option=True):
- BaseCommand.__init__(self, name)
+ BaseCommand.__init__(self, name, context_defaults)
#: the callback to execute when the command fires. This might be
#: `None` in which case nothing happens.
self.callback = callback
@@ -487,12 +532,7 @@ class Command(BaseCommand):
if short_help is None and help:
short_help = make_default_short_help(help)
self.short_help = short_help
- if add_help_option:
- self.add_help_option()
-
- def add_help_option(self):
- """Adds a help option to the command."""
- help_option()(self)
+ self.add_help_option = add_help_option
def get_usage(self, ctx):
formatter = ctx.make_formatter()
@@ -513,11 +553,26 @@ class Command(BaseCommand):
rv.extend(param.get_usage_pieces(ctx))
return rv
+ def get_help_option_names(self, ctx):
+ """Returns the names for the help option."""
+ all_names = set(ctx.help_option_names)
+ for param in self.params:
+ all_names.difference_update(param.opts)
+ all_names.difference_update(param.secondary_opts)
+ return all_names
+
def make_parser(self, ctx):
"""Creates the underlying option parser for this command."""
parser = OptionParser(ctx)
for param in self.params:
param.add_to_parser(parser, ctx)
+
+ if self.add_help_option:
+ help_options = self.get_help_option_names(ctx)
+ if help_options:
+ parser.add_option(help_options, action='store_const',
+ const=True, dest='$help')
+
return parser
def get_help(self, ctx):
@@ -558,6 +613,12 @@ class Command(BaseCommand):
if rv is not None:
opts.append(rv)
+ if self.add_help_option:
+ help_options = self.get_help_option_names(ctx)
+ if help_options:
+ opts.append([join_options(help_options)[0],
+ 'Show this message and exit.'])
+
if opts:
with formatter.section('Options'):
formatter.write_dl(opts)
@@ -573,6 +634,10 @@ class Command(BaseCommand):
parser = self.make_parser(ctx)
opts, args, param_order = parser.parse_args(args=args)
+ if opts.get('$help') and not ctx.resilient_parsing:
+ echo(ctx.get_help())
+ ctx.exit()
+
for param in iter_params_for_processing(param_order, self.params):
value, args = param.handle_parse_result(ctx, opts, args)
@@ -665,8 +730,17 @@ class MultiCommand(Command):
ctx.fail('Missing command.')
cmd_name = make_str(ctx.args[0])
+ original_cmd_name = cmd_name
+
+ # Get the command
cmd = self.get_command(ctx, cmd_name)
+ # If we can't find the command but there is a normalization
+ # function available, we try with that one.
+ if cmd is None and ctx.token_normalize_func is not None:
+ cmd_name = ctx.token_normalize_func(cmd_name)
+ cmd = self.get_command(ctx, cmd_name)
+
# If we don't find the command we want to show an error message
# to the user that it was not provided. However, there is
# something else we should do: if the first argument looks like
@@ -676,7 +750,7 @@ class MultiCommand(Command):
if cmd is None:
if split_opt(cmd_name)[0]:
self.parse_args(ctx, ctx.args)
- ctx.fail('No such command "%s".' % cmd_name)
+ ctx.fail('No such command "%s".' % original_cmd_name)
return self.invoke_subcommand(ctx, cmd, cmd_name, ctx.args[1:])
@@ -1126,16 +1200,9 @@ class Option(Parameter):
any_prefix_is_slash = []
def _write_opts(opts):
- rv = []
- for opt in opts:
- prefix = split_opt(opt)[0]
- if prefix == '/':
- any_prefix_is_slash[:] = [True]
- rv.append((len(prefix), opt))
-
- rv.sort(key=lambda x: x[0])
-
- rv = ', '.join(x[1] for x in rv)
+ rv, any_slashes = join_options(opts)
+ if any_slashes:
+ any_prefix_is_slash[:] = [True]
if not self.is_flag:
rv += ' ' + self.make_metavar()
return rv
@@ -1264,4 +1331,4 @@ class Argument(Parameter):
# Circular dependency between decorators and core
-from .decorators import command, group, help_option
+from .decorators import command, group