I would like to be able to specify help on positional arguments.
@click.group()
def cli():
click.echo(u'shellfoundry - CloudShell shell command-line tool')
pass
@cli.command()
@click.argument(u'name', help='Shell name to be created')
@click.argument(u'template', default=u'default', help='Template to be used')
def new(name, template):
"""
Create a new shell based on a template.\r\n
"""
NewCommandExecutor().new(name, template)
The above code throws the below exception:
Traceback (most recent call last):
File "C:\Python27lib\runpy.py", line 162, in _run_module_as_main
"__main__", fname, loader, pkg_name)
File "C:\Python27lib\runpy.py", line 72, in _run_code
exec code in run_globals
File "c:\work\GitHub\shellfoundry\shellfoundry__main__.py", line 6, in
from bootstrap import cli
File "shellfoundry\bootstrap.py", line 36, in
@click.argument(u'template', default=u'default', help='Template to be used')
File "C:\Python27lib\site-packages\click\decorators.py", line 151, in decorator
_param_memo(f, ArgumentClass(param_decls, attrs))
File "C:\Python27lib\site-packages\click\core.py", line 1693, in __init__
Parameter.init(self, param_decls, required=required, *attrs)
TypeError: *init() got an unexpected keyword argument 'help'
I just encountered this.
http://click.pocoo.org/6/documentation/
Arguments cannot be documented this way. This is to follow the general convention of Unix tools of using arguments for only the most necessary things and to document them in the introduction text by referring to them by name.
Is this to say the best practice would be to document the argument in the docstring itself?
I think they mean to put a reference to the argument in the command long-help. For example, heres a script with command show that accepts an argument PATH.
[evan@blackbox ~] ph show -h
Usage: ph show [OPTIONS] PATH
show the contents of an entry, where PATH is the entry path
optional arguments:
-h, --help show this help message and exit
--field FIELD show the contents of a specific field as plaintext
So in this case you'd want to use both help and short_help, where short_help is a shorter command description that doesn't contain information about PATH.
The following code implements this feature request (with a few other related customizations), and acts as a drop-in replacement module for import click. (Click seems to me to actually be very flexible, and I would guess that using the following strategy would allow most of the internals to be overridden/extended if you wanted to.)
Example Usage:
from your_package import click
@click.command()
@click.argument('name', help='The name to print.')
@click.option('--count', default=1, help='The number of greetings.')
def hello(name, count):
"""
This script prints "Hello <name>!" COUNT times.
"""
for x in range(count):
click.echo('Hello %s!' % name)
What the output looks like:
$ hello --help
Usage: hello <name> [OPTIONS]
This script prints "Hello <name>!" COUNT times.
Arguments:
name: <name> The name to print. [required]
[OPTIONS]:
--count INTEGER The number of greetings.
-h, --help Show this message and exit.
And of course, the code itself:
(Upstream docstrings omitted for brevity.)
# your_package/click.py
import click
import inspect as _inspect
from click import *
from click.formatting import join_options as _join_options
DEFAULT_CONTEXT_SETTINGS = dict(help_option_names=('-h', '--help'))
def _update_ctx_settings(context_settings):
rv = DEFAULT_CONTEXT_SETTINGS.copy()
if not context_settings:
return rv
rv.update(context_settings)
return rv
class Command(click.Command):
"""
:param options_metavar: The options metavar to display in the usage.
Defaults to ``[OPTIONS]``.
:param args_before_options: Whether or not to display the options
metavar before the arguments.
Defaults to False.
"""
def __init__(self, name, context_settings=None, callback=None, params=None,
help=None, epilog=None, short_help=None, add_help_option=True,
options_metavar='[OPTIONS]', args_before_options=True):
super().__init__(
name, callback=callback, params=params, help=help, epilog=epilog,
short_help=short_help, add_help_option=add_help_option,
context_settings=_update_ctx_settings(context_settings),
options_metavar=options_metavar)
self.args_before_options = args_before_options
# overridden to support displaying args before the options metavar
def collect_usage_pieces(self, ctx):
rv = [] if self.args_before_options else [self.options_metavar]
for param in self.get_params(ctx):
rv.extend(param.get_usage_pieces(ctx))
if self.args_before_options:
rv.append(self.options_metavar)
return rv
# overridden to group arguments separately from options
def format_options(self, ctx, formatter):
args = []
opts = []
for param in self.get_params(ctx):
rv = param.get_help_record(ctx)
if rv is not None:
if isinstance(param, click.Argument):
args.append(rv)
else:
opts.append(rv)
def print_args():
if args:
with formatter.section('Arguments'):
formatter.write_dl(args)
def print_opts():
if opts:
with formatter.section(self.options_metavar):
formatter.write_dl(opts)
if self.args_before_options:
print_args()
print_opts()
else:
print_opts()
print_args()
# overridden to make sure our custom classes propagate recursively in trees of commands
class Group(click.Group):
def __init__(self, *args, **kwargs):
super().__init__(*args, context_settings=_update_ctx_settings(
kwargs.pop('context_settings', None)), **kwargs)
def command(self, *args, **kwargs):
return super().command(
*args, cls=kwargs.pop('cls', Command) or Command, **kwargs)
def group(self, *args, **kwargs):
return super().group(
*args, cls=kwargs.pop('cls', Group) or Group, **kwargs)
class Argument(click.Argument):
"""
:param help: the help string.
:param hidden: hide this option from help outputs.
Default is True, unless ``help`` is given.
"""
def __init__(self, param_decls, required=None, help=None, hidden=None, **attrs):
super().__init__(param_decls, required=required, **attrs)
self.help = help
self.hidden = hidden if hidden is not None else not help
# overridden to customize the automatic formatting of metavars
# for example, given self.name = 'query':
# upstream | (optional) | this-method | (optional)
# default behavior:
# QUERY | [QUERY] | <query> | [<query>]
# when nargs > 1:
# QUERY... | [QUERY...] | <query>, ... | [<query>, ...]
def make_metavar(self):
if self.metavar is not None:
return self.metavar
var = '' if self.required else '['
var += '<' + self.name + '>'
if self.nargs != 1:
var += ', ...'
if not self.required:
var += ']'
return var
# this code is 90% copied from click.Option.get_help_record
def get_help_record(self, ctx):
if self.hidden:
return
any_prefix_is_slash = []
def _write_opts(opts):
rv, any_slashes = _join_options(opts)
if any_slashes:
any_prefix_is_slash[:] = [True]
rv += ': ' + self.make_metavar()
return rv
rv = [_write_opts(self.opts)]
if self.secondary_opts:
rv.append(_write_opts(self.secondary_opts))
help = self.help or ''
extra = []
if self.default is not None:
if isinstance(self.default, (list, tuple)):
default_string = ', '.join('%s' % d for d in self.default)
elif _inspect.isfunction(self.default):
default_string = "(dynamic)"
else:
default_string = self.default
extra.append('default: {}'.format(default_string))
if self.required:
extra.append('required')
if extra:
help = '%s[%s]' % (help and help + ' ' or '', '; '.join(extra))
return ((any_prefix_is_slash and '; ' or ' / ').join(rv), help)
def command(name=None, cls=None, **attrs):
return click.command(name=name, cls=cls or Command, **attrs)
def group(name=None, cls=None, **attrs):
return click.group(name=name, cls=cls or Group, **attrs)
def argument(*param_decls, cls=None, **attrs):
return click.argument(*param_decls, cls=cls or Argument, **attrs)
EDIT: Here's an example of what it looks like integrated with Flask.
I'd be happy to open a PR (leaving the current Click behaviour as default; different from the sample code above) if upstream is interested in supporting this feature officially.
Please do, I've also just been hit with this.
Would love to see this feature as well!
As the docs quoted earlier say, Click intentionally does not implement this.
I know this is closed, but I'll just +1 this feature request. It could be completely optional.
I'm making a project right now where this would be really helpful.
Most helpful comment
The following code implements this feature request (with a few other related customizations), and acts as a drop-in replacement module for
import click. (Click seems to me to actually be very flexible, and I would guess that using the following strategy would allow most of the internals to be overridden/extended if you wanted to.)Example Usage:
What the output looks like:
And of course, the code itself:
(Upstream docstrings omitted for brevity.)
EDIT: Here's an example of what it looks like integrated with Flask.
I'd be happy to open a PR (leaving the current Click behaviour as default; different from the sample code above) if upstream is interested in supporting this feature officially.