666 lines
26 KiB
Python
666 lines
26 KiB
Python
# -*- coding: utf-8 -*- #
|
|
# Copyright 2013 Google LLC. All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
"""Calliope argparse argument intercepts and extensions.
|
|
|
|
Refer to the calliope.parser_extensions module for a detailed overview.
|
|
"""
|
|
|
|
from __future__ import absolute_import
|
|
from __future__ import division
|
|
from __future__ import unicode_literals
|
|
|
|
import argparse
|
|
|
|
from googlecloudsdk.calliope import base
|
|
from googlecloudsdk.calliope import display_info
|
|
from googlecloudsdk.calliope import parser_errors
|
|
from googlecloudsdk.core.cache import completion_cache
|
|
|
|
|
|
# pylint: disable=protected-access
|
|
def _IsStoreTrueAction(action): # pylint: disable=invalid-name
|
|
return (action == 'store_true' or
|
|
isinstance(action, argparse._StoreTrueAction) or
|
|
(isinstance(action, type) and
|
|
issubclass(action, argparse._StoreTrueAction)))
|
|
|
|
|
|
# pylint: disable=protected-access
|
|
def _IsStoreFalseAction(action): # pylint: disable=invalid-name
|
|
return (action == 'store_false' or
|
|
isinstance(action, argparse._StoreFalseAction) or
|
|
(isinstance(action, type) and
|
|
issubclass(action, argparse._StoreFalseAction)))
|
|
|
|
|
|
def _IsStoreBoolAction(action): # pylint: disable=invalid-name
|
|
return _IsStoreTrueAction(action) or _IsStoreFalseAction(action)
|
|
|
|
|
|
class Argument(object):
|
|
"""Parsed argument base class with help generation attributess.
|
|
|
|
Attributes:
|
|
arguments: [ArgumentInterceptor], The group argument list if is_group is
|
|
true.
|
|
category: str, The argument help category name.
|
|
help: str, The argument help text.
|
|
is_global: bool, The argument is global to all commands.
|
|
is_hidden: bool, The argument help text is hidden.
|
|
is_group: bool, The argument is a group with arguments in self.arguments.
|
|
is_mutex: bool, This is a mutex argument group; at most one argument in
|
|
arguments may be specified.
|
|
is_positional: bool, The argument is a positional argument.
|
|
is_required: bool, The argument is required.
|
|
sort_args: bool, Whether to sort the arguments in this group when displaying
|
|
help/usage text. Applies only to this arg group (does not propagate to
|
|
nested groups).
|
|
disable_default_heading: bool, The default help heading text is hidden.
|
|
"""
|
|
|
|
# pylint: disable=redefined-builtin, Python can't keep help to itself
|
|
def __init__(self, arguments=None, hidden=False, is_group=False,
|
|
is_global=False, mutex=False, required=False,
|
|
help=None, category=None, sort_args=True,
|
|
disable_default_heading=False):
|
|
self.arguments = arguments or []
|
|
self.is_group = is_group or arguments
|
|
self.is_global = is_global
|
|
self._is_hidden = hidden
|
|
self.is_mutex = mutex
|
|
self.is_positional = False
|
|
self.is_required = required
|
|
self.help = help
|
|
self.category = category
|
|
self._sort_args = sort_args
|
|
self.disable_default_heading = disable_default_heading
|
|
|
|
@property
|
|
def is_hidden(self):
|
|
return self._is_hidden
|
|
|
|
@property
|
|
def sort_args(self):
|
|
return self._sort_args
|
|
|
|
|
|
class ArgumentInterceptor(Argument):
|
|
"""ArgumentInterceptor intercepts calls to argparse parsers.
|
|
|
|
The argparse module provides no public way to access the arguments that were
|
|
specified on the command line. Argparse itself does the validation when it is
|
|
run from the command line.
|
|
|
|
Attributes:
|
|
allow_positional: bool, Whether or not to allow positional arguments.
|
|
defaults: {str:obj}, A dict of {dest: default} for all the arguments added.
|
|
dests: [str], A list of the dests for all arguments.
|
|
flag_args: [argparse.Action], A list of the flag arguments.
|
|
parser: argparse.Parser, The parser whose methods are being intercepted.
|
|
positional_args: [argparse.Action], A list of the positional arguments.
|
|
required: [str], A list of the dests for all required arguments.
|
|
|
|
Raises:
|
|
ArgumentException: if a positional argument is made when allow_positional
|
|
is false.
|
|
"""
|
|
|
|
class ParserData(object):
|
|
"""Parser data for the entire command.
|
|
|
|
Attributes:
|
|
allow_positional: bool, Allow positional arguments if True.
|
|
ancestor_flag_args: [argparse.Action], The flags for all ancestor groups
|
|
in the cli tree.
|
|
cli_generator: cli.CLILoader, The builder used to generate this CLI.
|
|
command_name: [str], The parts of the command name path.
|
|
concept_handler: calliope.concepts.handlers.RuntimeHandler, a handler
|
|
for concept args.
|
|
defaults: {dest: default}, For all registered arguments.
|
|
dests: [str], A list of the dests for all arguments.
|
|
display_info: [display_info.DisplayInfo], The command display info object.
|
|
flag_args: [ArgumentInterceptor], The flag arguments.
|
|
positional_args: [ArgumentInterceptor], The positional args.
|
|
positional_completers: {Completer}, The set of completers for positionals.
|
|
required: [str], The dests for all required arguments.
|
|
"""
|
|
|
|
def __init__(self, command_name, cli_generator, allow_positional):
|
|
self.command_name = command_name
|
|
self.cli_generator = cli_generator
|
|
self.allow_positional = allow_positional
|
|
|
|
self.ancestor_flag_args = []
|
|
self.concept_handler = None
|
|
# Concepts v2
|
|
self.concepts = None
|
|
self.defaults = {}
|
|
self.dests = []
|
|
self.display_info = display_info.DisplayInfo()
|
|
self.flag_args = []
|
|
self.positional_args = []
|
|
self.positional_completers = set()
|
|
self.required = []
|
|
|
|
def __init__(self, parser, cli_generator=None, allow_positional=True,
|
|
data=None, **kwargs):
|
|
super(ArgumentInterceptor, self).__init__(is_group=True, **kwargs)
|
|
self.is_mutex = kwargs.pop('mutex', False)
|
|
self.help = kwargs.pop('help', None)
|
|
self.parser = parser
|
|
if parser:
|
|
# validate_specified_args() needs access to this argument interceptor,
|
|
# but it is called from parse_args() by argparse which passes the argparse
|
|
# internal "parser". We add parser.ai here to make it available.
|
|
parser.ai = self
|
|
# If this is an argument group within a command, use the data from the
|
|
# parser for the entire command. If it is the command itself, create a new
|
|
# data object and extract the command name from the parser.
|
|
if data:
|
|
self.data = data
|
|
else:
|
|
self.data = ArgumentInterceptor.ParserData(
|
|
# pylint: disable=protected-access
|
|
command_name=self.parser._calliope_command.GetPath(),
|
|
cli_generator=cli_generator,
|
|
allow_positional=allow_positional)
|
|
|
|
@property
|
|
def allow_positional(self):
|
|
return self.data.allow_positional
|
|
|
|
@property
|
|
def cli_generator(self):
|
|
return self.data.cli_generator
|
|
|
|
@property
|
|
def command_name(self):
|
|
return self.data.command_name
|
|
|
|
@property
|
|
def defaults(self):
|
|
return self.data.defaults
|
|
|
|
@property
|
|
def display_info(self):
|
|
return self.data.display_info
|
|
|
|
@property
|
|
def required(self):
|
|
return self.data.required
|
|
|
|
@property
|
|
def dests(self):
|
|
return self.data.dests
|
|
|
|
@property
|
|
def positional_args(self):
|
|
return self.data.positional_args
|
|
|
|
@property
|
|
def is_hidden(self):
|
|
if self._is_hidden:
|
|
return True
|
|
|
|
try:
|
|
next(a for a in self.arguments if not a.is_hidden)
|
|
return False
|
|
except StopIteration:
|
|
flags = []
|
|
for arg in self.arguments:
|
|
if hasattr(arg, 'option_strings'):
|
|
flags += arg.option_strings
|
|
raise parser_errors.ArgumentException(
|
|
'Groups with arguments and subgroups that are all hidden should be '
|
|
'marked hidden.\nCommand: [{}]\nGroup: [{}]\nFlags: [{}]'.format(
|
|
'.'.join(self.command_name), self.help, ', '.join(flags)))
|
|
|
|
@property
|
|
def flag_args(self):
|
|
return self.data.flag_args
|
|
|
|
@property
|
|
def positional_completers(self):
|
|
return self.data.positional_completers
|
|
|
|
@property
|
|
def ancestor_flag_args(self):
|
|
return self.data.ancestor_flag_args
|
|
|
|
@property
|
|
def concept_handler(self):
|
|
return self.data.concept_handler
|
|
|
|
@property
|
|
def concepts(self):
|
|
return self.data.concepts
|
|
|
|
def add_concepts(self, handler): # pylint: disable=invalid-name
|
|
# RuntimeParser is the v2 concepts handler.
|
|
# pylint: disable=g-import-not-at-top
|
|
from googlecloudsdk.command_lib.concepts import concept_managers
|
|
# pylint: enable=g-import-not-at-top
|
|
if isinstance(handler, concept_managers.RuntimeParser):
|
|
self.data.concepts = handler
|
|
return
|
|
if self.data.concept_handler:
|
|
raise AttributeError(
|
|
'It is not permitted to add two runtime handlers to a command class.')
|
|
self.data.concept_handler = handler
|
|
|
|
# pylint: disable=g-bad-name
|
|
def add_argument(self, *args, **kwargs):
|
|
"""add_argument intercepts calls to the parser to track arguments."""
|
|
name = args[0]
|
|
|
|
# The flag category name, None for no category. This is also used for help
|
|
# printing. Flags in the same category are grouped together in a section
|
|
# named "{category} FLAGS".
|
|
category = kwargs.pop('category', None)
|
|
# The unbound completer object or raw argcomplete completer function).
|
|
completer = kwargs.pop('completer', None)
|
|
# The default value.
|
|
default = kwargs.get('default')
|
|
# The namespace destination attribute name.
|
|
dest = kwargs.get('dest')
|
|
if not dest:
|
|
dest = name.lstrip(self.parser.prefix_chars).replace('-', '_')
|
|
# A flag that can only be supplied where it is defined and not propagated to
|
|
# subcommands.
|
|
do_not_propagate = kwargs.pop('do_not_propagate', False)
|
|
# hidden=True retains help but does not display it.
|
|
hidden = kwargs.pop('hidden', False) or self._is_hidden
|
|
help_text = kwargs.get('help')
|
|
if not help_text:
|
|
raise ValueError('Argument {} requires help text [hidden={}]'.format(
|
|
name, hidden))
|
|
if help_text == argparse.SUPPRESS:
|
|
raise ValueError('Argument {} needs hidden=True instead of '
|
|
'help=argparse.SUPPRESS.'.format(name))
|
|
# A flag that determines if when doing coverage we need to check for a unit
|
|
# test that exercises it. For example, list commands have the same flags and
|
|
# they have the same underlying implementation so they might not always be
|
|
# exclusively tested.
|
|
require_coverage_in_tests = kwargs.pop('require_coverage_in_tests', True)
|
|
# A global flag that is added at each level explicitly because each command
|
|
# has a different behavior (like -h).
|
|
is_replicated = kwargs.pop('is_replicated', False)
|
|
# This is used for help printing. A flag is considered global if it is
|
|
# added at the root of the CLI tree, or if it is explicitly added to every
|
|
# command level.
|
|
is_global = self.is_global or is_replicated
|
|
# The number positional args.
|
|
nargs = kwargs.get('nargs')
|
|
# The argument is required if True.
|
|
required = kwargs.get('required', False)
|
|
# Any alias this flag has for the purposes of the "did you mean"
|
|
# suggestions.
|
|
suggestion_aliases = kwargs.pop('suggestion_aliases', None)
|
|
if suggestion_aliases is None:
|
|
suggestion_aliases = []
|
|
# A subset of 'choices' that should be hidden from documentation.
|
|
hidden_choices = kwargs.pop('hidden_choices', None)
|
|
|
|
if self.is_global and category == base.COMMONLY_USED_FLAGS:
|
|
category = 'GLOBAL'
|
|
|
|
positional = not name.startswith('-')
|
|
if positional:
|
|
if not self.allow_positional:
|
|
raise parser_errors.ArgumentException(
|
|
'Illegal positional argument [{0}] for command [{1}]'.format(
|
|
name, '.'.join(self.data.command_name)))
|
|
if '-' in name:
|
|
raise parser_errors.ArgumentException(
|
|
"Positional arguments cannot contain a '-'. Illegal argument [{0}] "
|
|
'for command [{1}]'.format(name, '.'.join(self.data.command_name)))
|
|
if category:
|
|
raise parser_errors.ArgumentException(
|
|
'Positional argument [{0}] cannot have a category in '
|
|
'command [{1}]'.format(name, '.'.join(self.data.command_name)))
|
|
if suggestion_aliases:
|
|
raise parser_errors.ArgumentException(
|
|
'Positional argument [{0}] cannot have suggestion aliases in '
|
|
'command [{1}]'.format(name, '.'.join(self.data.command_name)))
|
|
|
|
self.defaults[dest] = default
|
|
if required:
|
|
self.required.append(dest)
|
|
self.dests.append(dest)
|
|
|
|
if positional and 'metavar' not in kwargs:
|
|
kwargs['metavar'] = name.upper()
|
|
if kwargs.get('nargs') is argparse.REMAINDER:
|
|
added_argument = self.parser.AddRemainderArgument(*args, **kwargs)
|
|
else:
|
|
added_argument = self.parser.add_argument(*args, **kwargs)
|
|
self._AttachCompleter(added_argument, completer, positional)
|
|
added_argument.require_coverage_in_tests = require_coverage_in_tests
|
|
added_argument.is_global = is_global
|
|
added_argument.is_group = False
|
|
added_argument.is_hidden = hidden
|
|
added_argument.is_required = required
|
|
added_argument.is_positional = positional
|
|
if hidden:
|
|
# argparse uses SUPPRESS -- cli_tree uses hidden_help to work around
|
|
added_argument.hidden_help = added_argument.help
|
|
added_argument.help = argparse.SUPPRESS
|
|
if positional:
|
|
if category:
|
|
raise parser_errors.ArgumentException(
|
|
'Positional argument [{0}] cannot have a category in '
|
|
'command [{1}]'.format(name, '.'.join(self.data.command_name)))
|
|
if (nargs is None or
|
|
nargs == '+' or
|
|
isinstance(nargs, int) and nargs > 0):
|
|
added_argument.is_required = True
|
|
self.positional_args.append(added_argument)
|
|
else:
|
|
if category and required:
|
|
raise parser_errors.ArgumentException(
|
|
'Required flag [{0}] cannot have a category in '
|
|
'command [{1}]'.format(name, '.'.join(self.data.command_name)))
|
|
if category == 'REQUIRED':
|
|
raise parser_errors.ArgumentException(
|
|
"Flag [{0}] cannot have category='REQUIRED' in "
|
|
'command [{1}]'.format(name, '.'.join(self.data.command_name)))
|
|
added_argument.category = category
|
|
added_argument.do_not_propagate = do_not_propagate
|
|
added_argument.is_replicated = is_replicated
|
|
added_argument.suggestion_aliases = suggestion_aliases
|
|
if isinstance(added_argument.choices, dict):
|
|
# choices is a name: description dict. Set the choices attribute to the
|
|
# keys for argparse and the choices_help attribute to the dict for
|
|
# the markdown generator.
|
|
setattr(added_argument, 'choices_help', added_argument.choices)
|
|
added_argument.choices = sorted(added_argument.choices.keys())
|
|
if hidden_choices is not None:
|
|
setattr(added_argument, 'hidden_choices', hidden_choices)
|
|
self.flag_args.append(added_argument)
|
|
|
|
inverted_flag = self._AddInvertedBooleanFlagIfNecessary(
|
|
added_argument, name, dest, kwargs)
|
|
if inverted_flag:
|
|
inverted_flag.category = category
|
|
inverted_flag.do_not_propagate = do_not_propagate
|
|
inverted_flag.is_replicated = is_replicated
|
|
inverted_flag.is_global = is_global
|
|
# Don't add suggestion aliases for the inverted flag. It can only map
|
|
# to one or the other.
|
|
self.flag_args.append(inverted_flag)
|
|
|
|
if (not getattr(added_argument, 'is_replicated', False) or
|
|
len(self.command_name) == 1):
|
|
self.arguments.append(added_argument)
|
|
return added_argument
|
|
|
|
# pylint: disable=redefined-builtin
|
|
def register(self, registry_name, value, object):
|
|
return self.parser.register(registry_name, value, object)
|
|
|
|
def set_defaults(self, **kwargs):
|
|
return self.parser.set_defaults(**kwargs)
|
|
|
|
def get_default(self, dest):
|
|
return self.parser.get_default(dest)
|
|
|
|
def parse_known_args(self, args=None, namespace=None):
|
|
"""Hooks ArgumentInterceptor into the argcomplete monkeypatch."""
|
|
return self.parser.parse_known_args(args=args, namespace=namespace)
|
|
|
|
def add_group(self, help=None, category=None, mutex=False, required=False,
|
|
hidden=False, sort_args=True, **kwargs):
|
|
"""Adds an argument group with mutex/required attributes to the parser.
|
|
|
|
Args:
|
|
help: str, The group help text description.
|
|
category: str, The group flag category name, None for no category.
|
|
mutex: bool, A mutually exclusive group if True.
|
|
required: bool, A required group if True.
|
|
hidden: bool, A hidden group if True.
|
|
sort_args: bool, Whether to sort the group's arguments in help/usage text.
|
|
NOTE - For ordering consistency across gcloud, generally prefer using
|
|
argument categories to organize information (instead of unsetting the
|
|
argument sorting).
|
|
**kwargs: Passed verbatim to ArgumentInterceptor().
|
|
|
|
Returns:
|
|
The added argument object.
|
|
"""
|
|
if 'description' in kwargs or 'title' in kwargs:
|
|
raise parser_errors.ArgumentException(
|
|
'parser.add_group(): description or title kwargs not supported '
|
|
'-- use help=... instead.')
|
|
# Look up the method on the parent class in case we're dealing with an
|
|
# argparse._ArgumentGroup. See b/289307337#comment3 for explanation.
|
|
new_parser = super(type(self.parser), self.parser).add_argument_group() # pylint: disable=bad-super-call
|
|
group = ArgumentInterceptor(parser=new_parser,
|
|
is_global=self.is_global,
|
|
cli_generator=self.cli_generator,
|
|
allow_positional=self.allow_positional,
|
|
data=self.data,
|
|
help=help,
|
|
category=category,
|
|
mutex=mutex,
|
|
required=required,
|
|
hidden=hidden or self._is_hidden,
|
|
sort_args=sort_args,
|
|
**kwargs)
|
|
self.arguments.append(group)
|
|
return group
|
|
|
|
def add_argument_group(self, help=None, **kwargs):
|
|
return self.add_group(help=help, **kwargs)
|
|
|
|
def add_mutually_exclusive_group(self, help=None, **kwargs):
|
|
return self.add_group(help=help, mutex=True, **kwargs)
|
|
|
|
def AddDynamicPositional(self, name, action, **kwargs):
|
|
"""Add a positional argument that adds new args on the fly when called.
|
|
|
|
Args:
|
|
name: The name/dest of the positional argument.
|
|
action: The argparse Action to use. It must be a subclass of
|
|
parser_extensions.DynamicPositionalAction.
|
|
**kwargs: Passed verbatim to the argparse.ArgumentParser.add_subparsers
|
|
method.
|
|
|
|
Returns:
|
|
argparse.Action, The added action.
|
|
"""
|
|
kwargs['dest'] = name
|
|
if 'metavar' not in kwargs:
|
|
kwargs['metavar'] = name.upper()
|
|
kwargs['parent_ai'] = self
|
|
|
|
action = self.parser.add_subparsers(action=action, **kwargs)
|
|
action.completer = action.Completions
|
|
action.is_group = False
|
|
action.is_hidden = kwargs.get('hidden', False)
|
|
action.is_positional = True
|
|
action.is_required = True
|
|
self.positional_args.append(action)
|
|
self.arguments.append(action)
|
|
return action
|
|
|
|
def _FlagArgExists(self, option_string):
|
|
"""If flag with the given option_string exists."""
|
|
for action in self.flag_args:
|
|
if option_string in action.option_strings:
|
|
return True
|
|
return False
|
|
|
|
def AddFlagActionFromAncestors(self, action):
|
|
"""Add a flag action to this parser, but segregate it from the others.
|
|
|
|
Segregating the action allows automatically generated help text to ignore
|
|
this flag.
|
|
|
|
Args:
|
|
action: argparse.Action, The action for the flag being added.
|
|
"""
|
|
# go/gcloud-project-flag-overwritable
|
|
# Do not add global --project if command already has --project
|
|
# argument in parser
|
|
for flag in ['--project', '--billing-project', '--universe-domain',
|
|
'--format']:
|
|
if self._FlagArgExists(flag) and flag in action.option_strings:
|
|
return
|
|
# pylint:disable=protected-access, simply no other way to do this.
|
|
self.parser._add_action(action)
|
|
# explicitly do this second, in case ._add_action() fails.
|
|
self.data.ancestor_flag_args.append(action)
|
|
|
|
def _AddInvertedBooleanFlagIfNecessary(self, added_argument, name, dest,
|
|
original_kwargs):
|
|
"""Determines whether to create the --no-* flag and adds it to the parser.
|
|
|
|
Args:
|
|
added_argument: The argparse argument that was previously created.
|
|
name: str, The name of the flag.
|
|
dest: str, The dest field of the flag.
|
|
original_kwargs: {str: object}, The original set of kwargs passed to the
|
|
ArgumentInterceptor.
|
|
|
|
Returns:
|
|
The new argument that was added to the parser or None, if it was not
|
|
necessary to create a new argument.
|
|
"""
|
|
action = original_kwargs.get('action')
|
|
wrapped_action = getattr(action, 'wrapped_action', None)
|
|
if wrapped_action is not None:
|
|
# If action is a wrapper, then we save the wrapper to subclass below and
|
|
# set action to the wrapped action so function can correctly interpret
|
|
# the what the intended action is.
|
|
action_wrapper = action
|
|
action = wrapped_action
|
|
|
|
# There are a few legitimate explicit --no-foo flags.
|
|
should_invert, prop = self._ShouldInvertBooleanFlag(name, action)
|
|
if not should_invert:
|
|
return
|
|
|
|
# Add hidden --no-foo for the --foo Boolean flag. The inverted flag will
|
|
# have the same dest and mutually exclusive group as the original flag.
|
|
# Explicit default=None yields the 'Use to disable.' text.
|
|
default = original_kwargs.get('default', False)
|
|
if prop:
|
|
inverted_synopsis = bool(prop.default)
|
|
elif default not in (True, None):
|
|
inverted_synopsis = False
|
|
elif default:
|
|
inverted_synopsis = bool(default)
|
|
else:
|
|
inverted_synopsis = False
|
|
|
|
kwargs = dict(original_kwargs)
|
|
if _IsStoreTrueAction(action):
|
|
action = 'store_false'
|
|
elif _IsStoreFalseAction(action):
|
|
action = 'store_true'
|
|
|
|
# This is a hacky workaround to get actions.DeprecationAction to properly
|
|
# generate an inverted boolean flag. The Action returned from
|
|
# actions.DeprecationAction will have a wrapped_action attribute which it
|
|
# uses at initialization to create an argparse Action. We check if this
|
|
# attribute exists, and if it does we create a new Action that subclasses
|
|
# from the action wrapper and use SetWrappedAction to change wrapped_action.
|
|
if wrapped_action is not None:
|
|
|
|
class NewAction(action_wrapper):
|
|
pass
|
|
|
|
NewAction.SetWrappedAction(action)
|
|
action = NewAction
|
|
|
|
kwargs['action'] = action
|
|
if not kwargs.get('dest'):
|
|
kwargs['dest'] = dest
|
|
|
|
inverted_argument = self.parser.add_argument(
|
|
name.replace('--', '--no-', 1), **kwargs)
|
|
if inverted_synopsis:
|
|
# flag.inverted_synopsis means display the inverted flag in the SYNOPSIS.
|
|
setattr(added_argument, 'inverted_synopsis', True)
|
|
inverted_argument.is_hidden = True
|
|
inverted_argument.is_required = added_argument.is_required
|
|
return inverted_argument
|
|
|
|
def _ShouldInvertBooleanFlag(self, name, action):
|
|
"""Checks if flag name with action is a Boolean flag to invert.
|
|
|
|
Args:
|
|
name: str, The flag name.
|
|
action: argparse.Action, The argparse action.
|
|
|
|
Returns:
|
|
(False, None) if flag is not a Boolean flag or should not be inverted,
|
|
(True, property) if flag is a Boolean flag associated with a property,
|
|
(False, property) if flag is a non-Boolean flag associated with a property
|
|
otherwise (True, None) if flag is a pure Boolean flag.
|
|
"""
|
|
if not name.startswith('--'):
|
|
return False, None
|
|
if name.startswith('--no-'):
|
|
# --no-no-* is a no no.
|
|
return False, None
|
|
if '--no-' + name[2:] in self.parser._option_string_actions: # pylint: disable=protected-access
|
|
# Don't override explicit --no-* inverted flag.
|
|
return False, None
|
|
if _IsStoreBoolAction(action):
|
|
return True, None
|
|
prop, kind, _ = getattr(action, 'store_property', (None, None, None))
|
|
if prop:
|
|
return kind == 'bool', prop
|
|
# Not a Boolean flag.
|
|
return False, None
|
|
|
|
def _AttachCompleter(self, arg, completer, positional):
|
|
"""Attaches a completer to arg if one is specified.
|
|
|
|
Args:
|
|
arg: The argument to attach the completer to.
|
|
completer: The completer Completer class or argcomplete function object.
|
|
positional: True if argument is a positional.
|
|
"""
|
|
# pylint: disable=g-import-not-at-top
|
|
from googlecloudsdk.calliope import parser_completer
|
|
# pylint: enable=g-import-not-at-top
|
|
if not completer:
|
|
return
|
|
if isinstance(completer, type):
|
|
# A completer class that will be instantiated at completion time.
|
|
if positional and issubclass(completer, completion_cache.Completer):
|
|
# The list of positional resource completers is used to determine
|
|
# parameters that must be present in the completions.
|
|
self.data.positional_completers.add(completer)
|
|
arg.completer = parser_completer.ArgumentCompleter(
|
|
completer, argument=arg)
|
|
else:
|
|
arg.completer = completer
|
|
|
|
def SetSortArgs(self, sort_args):
|
|
"""Sets whether or not to sort this group's arguments in help/usage text.
|
|
|
|
NOTE - For ordering consistency across gcloud, generally prefer using
|
|
argument categories to organize information (instead of unsetting the
|
|
argument sorting).
|
|
|
|
Args:
|
|
sort_args: bool, If arguments in this group should be sorted.
|
|
"""
|
|
self._sort_args = sort_args
|