Python click 模块,Option() 实例源码
我们从Python开源项目中,提取了以下43个代码示例,用于说明如何使用click.Option()。
def get_freckles_option_set():
"""Helper method to create some common cli options."""
freckle_option = click.Option(param_decls=["--freckle", "-f"], required=False, multiple=True, type=RepoType(),
metavar=FRECKLE_ARG_METAVAR, help=FRECKLE_ARG_HELP)
target_option = click.Option(param_decls=["--target", "-t"], required=False, multiple=False, type=str,
metavar=TARGET_ARG_METAVAR,
help=TARGET_ARG_HELP)
include_option = click.Option(param_decls=["--include", "-i"],
help=INCLUDE_ARG_HELP,
type=str, metavar=INCLUDE_ARG_METAVAR, default=[], multiple=True)
exclude_option = click.Option(param_decls=["--exclude", "-e"],
help=EXCLUDE_ARG_HELP,
type=str, metavar=EXCLUDE_ARG_METAVAR, default=[], multiple=True)
ask_become_pass_option = click.Option(param_decls=["--ask-become-pass", "-pw"],
help=ASK_PW_HELP,
type=ASK_PW_CHOICES, default="true")
params = [freckle_option, target_option, include_option, exclude_option,
ask_become_pass_option]
return params
def generate_man_page(ctx, version=None):
"""
Generate documentation for the given command.
:param click.Context ctx: the click context for the
cli application.
:rtype: str
:returns: the generate man page from the given click Context.
"""
# Create man page with the details from the given context
man_page = ManPage(ctx.command_path)
man_page.version = version
man_page.short_help = ctx.command.short_help
man_page.description = ctx.command.help
man_page.synopsis = ' '.join(ctx.command.collect_usage_pieces(ctx))
man_page.options = [x.get_help_record(None) for x in ctx.command.params if isinstance(x, click.Option)]
commands = getattr(ctx.command, 'commands', None)
if commands:
man_page.commands = [(k, v.short_help) for k, v in commands.items()]
return str(man_page)
def get_click_options(self):
import click
import q2cli
import q2cli.core
name = '--' + self.cli_name
type = click.Path(exists=True, file_okay=True, dir_okay=False,
readable=True)
type = q2cli.core.MultipleType(type)
help = ('Metadata file or artifact viewable as metadata. This '
'option may be supplied multiple times to merge metadata.')
if self.default is None:
requirement = '[optional]'
else:
requirement = '[required]'
option = q2cli.Option([name], type=type, help=help, multiple=True)
yield self._add_description(option, requirement)
def get_click_options(self):
import q2cli
name = '--' + self.cli_name
type = str
help = ('Category from metadata file or artifact viewable as '
'metadata.')
if self.default is None:
requirement = '[optional]'
else:
requirement = '[required]'
option = q2cli.Option([name], type=type, help=help)
yield from self.metadata_handler.get_click_options()
yield self._add_description(option, requirement)
def extract_option_object(option):
"""Convert a click.option call into a click.Option object.
Parameters
----------
option : decorator
A click.option decorator.
Returns
-------
option_object : click.Option
The option object that this decorator will create.
"""
@option
def opt():
pass
return opt.__click_params__[0]
def hidden_option(*param_decls, **attrs):
"""Attaches a hidden option to the command. All positional arguments are
passed as parameter declarations to :class:`Option`; all keyword
arguments are forwarded unchanged. This is equivalent to creating an
:class:`Option` instance manually and attaching it to the
:attr:`Command.params` list.
"""
import inspect
from click.decorators import _param_memo
def decorator(f):
if 'help' in attrs:
attrs['help'] = inspect.cleandoc(attrs['help'])
_param_memo(f, HiddenOption(param_decls, **attrs))
return f
return decorator
#~~ helper for settings context options
def get_choices(cli, prog_name, args, incomplete):
ctx = resolve_ctx(cli, prog_name, args)
if ctx is None:
return
optctx = None
if args:
for param in ctx.command.get_params(ctx):
if isinstance(param, Option) and not param.is_flag and args[-1] in param.opts + param.secondary_opts:
optctx = param
choices = []
if optctx:
choices += [c if isinstance(c, tuple) else (c, None) for c in optctx.type.complete(ctx, incomplete)]
elif incomplete and not incomplete[:1].isalnum():
for param in ctx.command.get_params(ctx):
if not isinstance(param, Option):
continue
for opt in param.opts:
if startswith(opt, incomplete):
choices.append((opt, param.help))
for opt in param.secondary_opts:
if startswith(opt, incomplete):
# don't put the doc so fish won't group the primary and
# and secondary options
choices.append((opt, None))
elif isinstance(ctx.command, MultiCommand):
for name in ctx.command.list_commands(ctx):
if startswith(name, incomplete):
choices.append((name, ctx.command.get_command_short_help(ctx, name)))
else:
for param in ctx.command.get_params(ctx):
if isinstance(param, Argument):
choices += [c if isinstance(c, tuple) else (c, None) for c in param.type.complete(ctx, incomplete)]
for item, help in choices:
yield (item, help)
def set_help_from_yaml(cls, ctx, param, value):
"""
When attaching this function as `callback` argument to an Option (`click.Option`),
it will set
an automatic help for all Options of the same command, which do not have an `help`
specified and are found in the default config file for downloading
(currently `download.yaml`).
The Option having as callback this function must also have `is_eager=True`.
Example:
Assuming opt1, opt2, opt3 are variables of the config yaml file, and opt4 not, this
sets the default help for opt1 and opt2:
\@click.Option('--opt1', ..., callback=set_help_from_yaml, is_eager=True,...)
\@click.Option('--opt2'...)
\@click.Option('--opt3'..., help='my custom help do not set the config help')
\@click.Option('--opt4'...)
...
```
"""
cfg_doc = cls.DEFAULTDOC
for option in (opt for opt in ctx.command.params if opt.param_type_name == 'option'):
if option.help is None:
option.help = cfg_doc.get(option.name, None)
return value
```
def download(configfile, dburl, eventws, start, end, dataws, min_sample_rate, traveltimes_model,
timespan, update_metadata, retry_url_err, retry_mseed_err, retry_seg_not_found,
retry_client_err, retry_server_err, retry_timespan_err, inventory, eventws_query_args):
"""Download waveform data segments with quality metadata and relative events, stations and
channels metadata into a specified database.
The -c option (required) sets the defaults for all other options below, which are optional.
The argument 'eventws_query_args' is an optional list of space separated key and values to be
passed to the event web service query (example: minmag 5.5 minlon 34.5). All FDSN query
arguments are valid except 'start', 'end' (set them via -t0 and -t1) and 'format'
"""
try:
cfg_dict = yaml_load(configfile, **{k: v for k, v in locals().items()
if v not in ((), {}, None, configfile)})
# start and end might be integers. If we attach the conversion function
# `clickutils.valid_date` to the relative clikc Option 'type' argument, the
# function does not affect integer values in the config. Thus we need to set it here:
cfg_dict['start'] = clickutils.valid_date(cfg_dict['start'])
cfg_dict['end'] = clickutils.valid_date(cfg_dict['end'])
ret = main.download(isterminal=True, **cfg_dict)
sys.exit(ret)
except KeyboardInterrupt: # this except avoids printing traceback
sys.exit(1) # exit with 1 as normal python exceptions
def test_command_multi_registration(basicApp):
def _test_command(arg):
print(arg)
plugin = basicApp.plugins.get("CommandPlugin")
with pytest.raises(CommandExistException):
plugin.commands.register("test", "my test command", _test_command, params=[Option(("--arg", "-a"))])
plugin.commands.unregister("test")
plugin.commands.register("test", "my test command", _test_command, params=[Option(("--arg", "-a"))])
assert len(basicApp.commands.get()) == 1
plugin.commands.register("test2", "my test2 command", _test_command, params=[Option(("--arg", "-a"))])
assert len(basicApp.commands.get()) == 2
basicApp.plugins.deactivate(["CommandPlugin"])
print(basicApp.commands.get().keys())
assert len(basicApp.commands.get().keys()) == 0
def test_command_multi_plugin_registration(basicApp, EmptyCommandPlugin):
def _test_command(arg):
print(arg)
plugin = basicApp.plugins.get("CommandPlugin")
plugin2 = EmptyCommandPlugin(app=basicApp, name="CommandPlugin2")
plugin2.activate()
plugin2.commands.register("test2", "my test2 command", _test_command, params=[Option(("--arg", "-a"))])
assert len(basicApp.commands.get()) == 2
assert len(plugin.commands.get()) == 1
assert len(plugin2.commands.get()) == 1
basicApp.plugins.deactivate(["CommandPlugin2"])
assert len(basicApp.commands.get()) == 1
assert len(plugin.commands.get()) == 1
assert len(plugin2.commands.get()) == 0
def convert_param_to_option(self, parameter):
"""
Convert a Parameter into a click Option.
:type parameter: valohai_yaml.objs.Parameter
:rtype: click.Option
"""
assert isinstance(parameter, Parameter)
option = click.Option(
param_decls=[
'--%s' % parameter.name.replace('_', '-'),
],
required=(parameter.default is None and not parameter.optional),
default=parameter.default,
help=parameter.description,
type=self.parameter_type_map.get(parameter.type, click.STRING),
)
option.name = '~%s' % parameter.name # Tildify so we can pick these out of kwargs easily
return option
def convert_input_to_option(self, input):
"""
Convert an Input into a click Option.
:type input: valohai_yaml.objs.input.Input
:rtype: click.Option
"""
assert isinstance(input, Input)
option = click.Option(
param_decls=[
'--%s' % input.name.replace('_', '-'),
],
required=(input.default is None and not input.optional),
default=input.default,
metavar='URL',
help='Input "%s"' % humanize_identifier(input.name),
)
option.name = '^%s' % input.name # Caretize so we can pick these out of kwargs easily
return option
def get_choices(cli, prog_name, args, incomplete):
ctx = resolve_ctx(cli, prog_name, args)
if ctx is None:
return
optctx = None
if args:
for param in ctx.command.get_params(ctx):
if isinstance(param, Option) and not param.is_flag and args[-1] in param.opts + param.secondary_opts:
optctx = param
choices = []
if optctx:
choices += [c if isinstance(c, tuple) else (c, None) for c in optctx.type.complete(ctx, incomplete)]
elif incomplete and not incomplete[:1].isalnum():
for param in ctx.command.get_params(ctx):
if not isinstance(param, Option):
continue
for opt in param.opts:
if startswith(opt, incomplete):
choices.append((opt, param.help))
for opt in param.secondary_opts:
if startswith(opt, incomplete):
# don't put the doc so fish won't group the primary and
# and secondary options
choices.append((opt, None))
elif isinstance(ctx.command, MultiCommand):
for name in ctx.command.list_commands(ctx):
if startswith(name, incomplete):
choices.append((name, ctx.command.get_command_short_help(ctx, name)))
else:
for param in ctx.command.get_params(ctx):
if isinstance(param, Argument):
choices += [c if isinstance(c, tuple) else (c, None) for c in param.type.complete(ctx, incomplete)]
for item, help in choices:
yield (item, help)
def __init__(self, config, **kwargs):
"""Base class to provide a command-based (similar to e.g. git) cli for freckles.
This class parses the folders in the paths provided by the config
element for so-called 'freckle adapters'. A freckle adapter is a
collection of files that describe commandline-arguments and tasks lists
to execute on a folder (more information: XXX)
Args:
config (FrecklesConfig): the config wrapper object
kwargs (dict): additional arguments that are forwarded to the partent click.MultiCommand constructor
"""
click.MultiCommand.__init__(self, "freckles", result_callback=assemble_freckle_run, invoke_without_command=True,
**kwargs)
use_repo_option = click.Option(param_decls=["--use-repo", "-r"], required=False, multiple=True, help="extra context repos to use", is_eager=True, callback=download_extra_repos)
output_option = click.Option(param_decls=["--output", "-o"], required=False, default="default",
metavar="FORMAT", type=click.Choice(SUPPORTED_OUTPUT_FORMATS), is_eager=True,
help="format of the output")
no_run_option = click.Option(param_decls=["--no-run"],
help='don\'t execute frecklecute, only prepare environment and print task list',
type=bool, is_flag=True, default=False, required=False)
version_option = click.Option(param_decls=["--version"], help='prints the version of freckles', type=bool,
is_flag=True, is_eager=True, expose_value=False, callback=print_version)
self.params = get_freckles_option_set()
self.params.extend([ use_repo_option, output_option, no_run_option, version_option])
self.config = config
self.profile_repo = ProfileRepo(self.config)
# self.command_names.append(BREAK_COMMAND_NAME)
def __init__(self, current_command, config, **kwargs):
"""Base class to provide a command-based (similar to e.g. git) cli for frecklecute.
This class parses the folders in the paths provided by the config
element for so-called 'frecklecutables', (yaml) text files that contain a list of tasks and optionally command-line argument descriptions. More information: XXX
Args:
current_command (tuple): a tuple in the format (command_name, command_path), which is used for commands that are paths, instead of filenames in one of the known frecklecutable paths. Can be (None, None) if not a path.
config (FrecklesConfig): the config wrapper object
kwargs (dict): additional arguments that are forwarded to the partent click.MultiCommand constructor
"""
click.MultiCommand.__init__(self, "freckles", **kwargs)
output_option = click.Option(param_decls=["--output", "-o"], required=False, default="default",
metavar="FORMAT", type=click.Choice(SUPPORTED_OUTPUT_FORMATS),
help="format of the output", is_eager=True)
ask_become_pass_option = click.Option(param_decls=["--ask-become-pass", "-pw"],
help='whether to force ask for a password, force ask not to, or let try freckles decide (which might not always work)',
type=click.Choice(["auto", "true", "false"]), default="auto")
version_option = click.Option(param_decls=["--version"], help='prints the version of freckles', type=bool,
is_flag=True, is_eager=True, expose_value=False, callback=print_version)
no_run_option = click.Option(param_decls=["--no-run"],
help='don\'t execute frecklecute, only prepare environment and print task list',
type=bool, is_flag=True, default=False, required=False)
use_repo_option = click.Option(param_decls=["--use-repo", "-r"], required=False, multiple=True, help="extra context repos to use", is_eager=True, callback=download_extra_repos, expose_value=True)
self.params = [use_repo_option, output_option, ask_become_pass_option, no_run_option, version_option]
self.config = config
# .trusted_repos = DEFAULT_FRECKLES_CONFIG.trusted_repos
# local_paths = get_local_repos(trusted_repos, "frecklecutables")
self.command_repo = CommandRepo(config=self.config, additional_commands=[current_command])
self.current_command = current_command[0]
def generate_cli(spec):
origin_url = None
if isinstance(spec, str):
if spec.startswith('https://') or spec.startswith('http://'):
origin_url = spec
r = requests.get(spec)
r.raise_for_status()
spec = yaml.safe_load(r.text)
else:
with open(spec, 'rb') as fd:
spec = yaml.safe_load(fd.read())
spec = sanitize_spec(spec)
cli = clickclick.AliasedGroup(context_settings=CONTEXT_SETTINGS)
spec = Spec.from_dict(spec, origin_url=origin_url)
for res_name, res in spec.resources.items():
grp = clickclick.AliasedGroup(normalize_command_name(res_name), short_help='Manage {}'.format(res_name))
cli.add_command(grp)
for op_name, op in res.operations.items():
name = get_command_name(op)
cmd = click.Command(name, callback=partial(invoke, op=op), short_help=op.op_spec.get('summary'))
for param_name, param in op.params.items():
if param.required:
arg = click.Argument([param.name])
cmd.params.append(arg)
else:
arg = click.Option(['--' + param.name])
cmd.params.append(arg)
grp.add_command(cmd)
return cli
def _locate_value(self, arguments, fallback, multiple=False):
"""Default lookup procedure to find a click.Option provided by user"""
# TODO revisit this interaction between _locate_value, single vs.
# multiple options, and fallbacks. Perhaps handlers should always
# use tuples to store values, even for single options, in order to
# normalize single-vs-multiple option handling. Probably not worth
# revisiting until there are more unit + integration tests of q2cli
# since there's the potential to break things.
# Is it in args?
v = arguments[self.click_name]
missing_value = () if multiple else None
if v != missing_value:
return v
# Does our fallback know about it?
if fallback is not None:
try:
fallback_value = fallback(self.name, self.cli_name)
except ValueNotFoundException:
pass
else:
# TODO fallbacks don't know whether they're handling a single
# vs. multiple option, so the current expectation is that
# fallbacks will always return a single value. Revisit this
# expectation in the future; perhaps fallbacks should be aware
# of single-vs-multiple options, or perhaps they could always
# return a tuple.
if multiple:
fallback_value = (fallback_value,)
return fallback_value
# Do we have a default?
if self.default is not NoDefault:
return self.default
# Give up
self.missing.append(self.cli_name)
raise ValueNotFoundException()
def get_click_options(self):
import q2cli
# `is_flag` will set the default to `False`, but `self._locate_value`
# needs to distinguish between the presence or absence of the flag
# provided by the user.
yield q2cli.Option(
['--' + self.cli_name], is_flag=True, default=None,
help='Display verbose output to stdout and/or stderr during '
'execution of this action. [default: %s]' % self.default)
def get_click_options(self):
import q2cli
# `is_flag` will set the default to `False`, but `self._locate_value`
# needs to distinguish between the presence or absence of the flag
# provided by the user.
yield q2cli.Option(
['--' + self.cli_name], is_flag=True, default=None,
help='Silence output if execution is successful '
'(silence is golden). [default: %s]' % self.default)
def get_click_options(self):
import click
import q2cli
yield q2cli.Option(
['--' + self.cli_name],
type=click.Path(exists=False, dir_okay=True, file_okay=False,
writable=True),
help='Output unspecified results to a directory')
def get_click_options(self):
import click
import q2cli
yield q2cli.Option(
['--' + self.cli_name],
type=click.Path(exists=True, dir_okay=False, file_okay=True,
readable=True),
help='Use config file for command options')
def _error_with_duplicate_in_set(self, elements):
import click
import collections
counter = collections.Counter(elements)
dups = {name for name, count in counter.items() if count > 1}
ctx = click.get_current_context()
click.echo(ctx.get_usage() + '\n', err=True)
click.secho("Error: Option --%s was given these values: %r more than "
"one time, values passed should be unique."
% (self.cli_name, dups), err=True, fg='red', bold=True)
ctx.exit(1)
def get_click_options(self):
import q2cli
import q2cli.core
type = q2cli.core.ResultPath(repr=self.repr, exists=True,
file_okay=True, dir_okay=False,
readable=True)
if self.default is None:
requirement = '[optional]'
else:
requirement = '[required]'
option = q2cli.Option(['--' + self.cli_name], type=type, help="")
yield self._add_description(option, requirement)
def get_click_options(self):
import q2cli
type = q2cli.core.ResultPath(self.repr, exists=False, file_okay=True,
dir_okay=False, writable=True)
option = q2cli.Option(['--' + self.cli_name], type=type, help="")
yield self._add_description(
option, '[required if not passing --output-dir]')
def get_click_parameters(self):
# Handlers may provide more than one click.Option
for handler in self.generated_handlers.values():
yield from handler.get_click_options()
# Meta-Handlers' Options:
yield from self.output_dir_handler.get_click_options()
yield from self.cmd_config_handler.get_click_options()
yield from self.verbose_handler.get_click_options()
yield from self.quiet_handler.get_click_options()
def test_cls_override(self):
with self.assertRaisesRegex(ValueError, 'override `cls=q2cli.Option`'):
q2cli.option('--bar', cls=click.Option)
def option(*param_decls, **attrs):
"""``@click.option`` decorator with customized behavior for q2cli.
See docstring on ``q2cli.Option`` (above) for details.
"""
if 'cls' in attrs:
raise ValueError("Cannot override `cls=q2cli.Option` in `attrs`.")
attrs['cls'] = Option
def decorator(f):
return click.option(*param_decls, **attrs)(f)
return decorator
def type_cast_value(self, ctx, value):
# get the result of a normal type_cast
converted_val = super(OneUseOption, self).type_cast_value(ctx, value)
# if the option takes arguments (multiple was set to true)
# assert no more than one argument was gotten, and if an argument
# was gotten, take it out of the tuple and return it
if self.multiple:
if len(converted_val) > 1:
raise click.BadParameter(
"Option used multiple times.", ctx=ctx)
if len(converted_val):
return converted_val[0]
else:
return None
# if the option was a flag (converted to a count) assert that the flag
# count is no more than one, and type cast back to a bool
elif self.count:
if converted_val > 1:
raise click.BadParameter(
"Option used multiple times.", ctx=ctx)
return bool(converted_val)
else:
raise ValueError(("Internal error, OneUseOption expected either "
"multiple or count, but got neither."))
def one_use_option(*args, **kwargs):
"""
Wrapper of the click.option decorator that replaces any instances of
the Option class with the custom OneUseOption class
"""
# cannot force a multiple or count option to be single use
if "multiple" in kwargs or "count" in kwargs:
raise ValueError("Internal error, one_use_option cannot be used "
"with multiple or count.")
# cannot force a non Option Paramater (argument) to be a OneUseOption
if kwargs.get("cls"):
raise TypeError("Internal error, one_use_option cannot overwrite "
"cls {}.".format(kwargs.get("cls")))
# use our OneUseOption class instead of a normal Option
kwargs["cls"] = OneUseOption
# if dealing with a flag, switch to a counting option,
# and then assert if the count is not greater than 1 and cast to a bool
if kwargs.get("is_flag"):
kwargs["is_flag"] = False # mutually exclusive with count
kwargs["count"] = True
# if not a flag, this option takes an argument(s), switch to a multiple
# option, assert the len is 1, and treat the first element as the value
else:
kwargs["multiple"] = True
# decorate with the click.option decorator, but with our custom kwargs
def decorator(f):
return click.option(*args, **kwargs)(f)
return decorator
def confdir_defaults(ctx, param, value):
for p in ctx.command.params:
if isinstance(p, click.Option) and p.name.endswith('dir') and p != param:
p.default = os.path.join(value, SUBDIRS[p.name])
return value
def threshold_range(ctx, param, value):
for p in ctx.command.params:
if isinstance(p, click.Option) and p.name == 'threshold':
p.type.max = value
return value
def _format_option(opt):
"""Format the output for a `click.Option`."""
opt = _get_help_record(opt)
yield '.. option:: {}'.format(opt[0])
if opt[1]:
yield ''
for line in statemachine.string2lines(
opt[1], tab_width=4, convert_whitespace=True):
yield _indent(line)
def _format_options(ctx):
"""Format all `click.Option` for a `click.Command`."""
# the hidden attribute is part of click 7.x only hence use of getattr
params = [x for x in ctx.command.params if isinstance(x, click.Option)
and not getattr(x, 'hidden', False)]
for param in params:
for line in _format_option(param):
yield line
yield ''
def _format_envvar(param):
"""Format the envvars of a `click.Option` or `click.Argument`."""
yield '.. envvar:: {}'.format(param.envvar)
yield ''
if isinstance(param, click.Argument):
param_ref = param.human_readable_name
else:
# if a user has defined an opt with multiple "aliases", always use the
# first. For example, if '--foo' or '-f' are possible, use '--foo'.
param_ref = param.opts[0]
yield _indent('Provide a default for :option:`{}`'.format(param_ref))
def __init__(self, command_dir, *args, **kwargs):
cs = dict(help_option_names=['-h', '--help'])
params = [
click.Option(
param_decls=['-v', '--verbose'],
count=True,
help='Make commands verbose, use '
'multiple time for higher verbosity'
),
click.Option(
param_decls=['--log-file'],
help='When using the verbose flag, redirects '
'output to this file'
)
]
super(AerisCLI, self).__init__(context_settings=cs, params=params,
*args, **kwargs)
self.command_dir = command_dir
no_setup = 'AC_NO_ASSISTANT' in os.environ \
and os.environ['AC_NO_ASSISTANT'] == '1'
if not no_setup and sys.stdout.isatty() and not config.complete():
click.echo('''
It seems it is the first time you are launching AerisCloud, or new
configuration options were added since the last update. We can guide
you through a series of questions to setup your configuration.
''', err=True)
if not click.confirm('Run configuration assistant', default=True):
return
from .config import assistant
assistant()
def _options():
"""Collect all command line options"""
opts = sys.argv[1:]
return [click.Option((v.split('=')[0],)) for v in opts
if v[0] == '-' and v != '--help']
def click_options(ctx):
'''
Build and return a dictionary with the variable name from click
and the associated option used from the command line, for each
available option defined by the click.option decorator
ie. {'rootkey':'root'}
:param ctx: click context object
:return: dictionary
'''
return {opt.human_readable_name: opt.opts[1]
for opt in ctx.command.get_params(ctx)
if isinstance(opt, click.Option) and len(opt.opts) > 1}
def trait_to_option(name, trait):
if is_trait(trait):
if isinstance(trait, Int):
return click.Option(param_decls=('--' + name,),
default=trait.default_value,
type=click.IntRange(trait.min, trait.max))
elif isinstance(trait, Float):
return click.Option(param_decls=('--' + name,),
default=trait.default_value,
type=float)
elif isinstance(trait, Complex):
return click.Option(param_decls=('--' + name,),
default=trait.default_value,
type=complex)
elif isinstance(trait, Bool):
return click.Option(param_decls=('--' + name,),
default=trait.default_value,
is_flag=True)
elif isinstance(trait, Unicode):
return click.Option(param_decls=('--' + name,),
default=trait.default_value,
type=str)
elif isinstance(trait, Enum):
# FIXME: trait.values should be strings
return click.Option(param_decls=('--' + name,),
default=str(trait.default_value),
type=click.Choice(list(map(str, trait.values))))
elif isinstance(trait, Tuple):
return click.Option(param_decls=('--' + name,),
default=trait.default_value,
type=tuple(
trait_to_type(t) for t in trait._traits))
else:
raise InvalidValue('Trait conversion is not supported for: '
'{}'.format(trait))
else:
raise InvalidType('Trait should be instance of {}'.format(TraitType))
def activate(self):
self.commands.register("test", "my test command", self._test_command, params=[Option(("--arg", "-a"))])
def _generate_command_reply(cmd):
"""Recursively generate completion reply for this command and subcommands.
Parameters
----------
cmd : click.Command
Command to generate completion replies for (including its subcommands).
"""
import textwrap
import click
ctx = None
options = ['--help']
for param in cmd.params:
if isinstance(param, click.Option):
options.extend(param.opts)
options.extend(param.secondary_opts)
subcmd_names = []
if isinstance(cmd, click.MultiCommand):
subcmd_names.extend(cmd.list_commands(ctx))
subcmd_cases = []
for subcmd_name in subcmd_names:
subcmd_reply = _generate_command_reply(
cmd.get_command(ctx, subcmd_name))
subcmd_reply = textwrap.indent(subcmd_reply, ' ')
case = SUBCOMMAND_CASE_TEMPLATE.format(
subcmd_name=subcmd_name, subcmd_reply=subcmd_reply)
subcmd_cases.append(case)
subcmd_cases = textwrap.indent('\n'.join(subcmd_cases), ' ' * 6)
cmd_reply = COMMAND_REPLY_TEMPLATE.format(
options=' '.join(options), subcmd_names=' '.join(subcmd_names),
subcmd_cases=subcmd_cases)
return cmd_reply
# NOTE: using double braces to avoid `str.format` interpolation when bash needs
# curly braces in the generated code.
#
# NOTE: the handling of a negative COMP_CWORD is necessary in certain versions
# of bash (e.g. at least the bash shipped with OS X 10.9.5). When adding
# whitespace to the end of a command, and then moving the cursor backwards in
# the command and hitting <tab>, COMP_CWORD can be negative (I've only seen -2
# as its value). This is a bash bug and is not documented behavior. Other CLIs
# with tab completion suffer from the same issue, and each one deals with this
# bug differently (some not at all, e.g. `git`). The workaround used below
# seems to provide the least destructive completion behavior for our CLI.
#
# Bug report reference:
# https://lists.gnu.org/archive/html/bug-bash/2009-07/msg00108.html
def cmd_complete(out, name, cmd, level=1):
ctx = click.Context(cmd)
cmpl = {
'name': name,
'cmd': cmd,
'level': level,
'flags': [opts for param in cmd.params for opts in param.opts
if isinstance(param, click.Option)],
'complete_cmd': complete_cmd
}
params = [opts for param in cmd.params for opts in param.opts
if isinstance(param, click.Parameter) and
not isinstance(param, click.Option)]
if isinstance(cmd, click.MultiCommand):
cmds = cmd.list_commands(ctx)
for cmd_name in cmds:
cmd_complete(
out,
name + '_' + cmd_name,
cmd.get_command(ctx, cmd_name),
level + 1
)
cmpl['cmds'] = cmds
out.write(render_cli('autocomplete-multi', **cmpl))
else:
# TODO: we might want to move that list of params somewhere else
completable = [
'command',
'direction',
'endpoint',
'env',
'host',
'inventory',
'inventory_name',
'job',
'limit',
'organization_name',
'platform',
'project',
'server',
]
if len(params) == 1 and params[0] in completable:
cmpl['param'] = params[0]
elif len(params) > 1:
cmpl['params'] = [el for el in params if el in completable]
out.write(render_cli('autocomplete-single', **cmpl))
if level == 1:
out.write('complete -F _{0}_completion {0}\n'.format(name))
def create_command(self, provider_name):
config = node_manager.valid_node_types['providers'].get(provider_name)
if not config:
return
provider_options = [
click.Option(param_decls=['--name'], help='Compute node name', required=True)
]
for param, param_data in node_manager.get_node_params(provider_name).items():
argument_params = dict()
argument_params['help'] = ''
description = param_data.get('description', {}).get(DEFAULT_LANG)
default = param_data.get('default')
arg_type = param_data.get('type')
if description:
argument_params['help'] += '{} '.format(description)
if arg_type:
argument_params['help'] += '(type: {}) '.format(arg_type)
if default:
argument_params.update({
'default': default,
'required': False
})
argument_params['help'] += '(default: {}) '.format(default)
else:
argument_params['required'] = True
provider_options.append(click.Option(
param_decls=['--{}'.format(param)],
help=argument_params['help'],
default=default,
required=False if default else True
))
cmd = click.Command(
name=provider_name,
params=provider_options,
help=click.style(config['description'], fg='cyan'),
short_help=click.style(config['description'], fg='cyan'),
callback=self.create_node_callback
)
return cmd