288 lines
12 KiB
Python
288 lines
12 KiB
Python
# -*- coding: utf-8 -*- #
|
|
# Copyright 2017 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.
|
|
|
|
"""The meta cache completers run command."""
|
|
|
|
from __future__ import absolute_import
|
|
from __future__ import division
|
|
from __future__ import print_function
|
|
from __future__ import unicode_literals
|
|
|
|
import sys
|
|
|
|
from googlecloudsdk.calliope import arg_parsers
|
|
from googlecloudsdk.calliope import base
|
|
from googlecloudsdk.calliope import parser_extensions
|
|
from googlecloudsdk.command_lib.meta import cache_util
|
|
from googlecloudsdk.command_lib.util import parameter_info_lib
|
|
from googlecloudsdk.command_lib.util.concepts import concept_parsers
|
|
from googlecloudsdk.command_lib.util.concepts import presentation_specs
|
|
from googlecloudsdk.core import exceptions
|
|
from googlecloudsdk.core import log
|
|
from googlecloudsdk.core import module_util
|
|
from googlecloudsdk.core.console import console_io
|
|
|
|
import six
|
|
|
|
|
|
class _FunctionCompleter(object):
|
|
"""Convert an argparse function completer to a resource_cache completer."""
|
|
|
|
def __init__(self, completer):
|
|
self._completer = completer
|
|
self.parameters = None
|
|
|
|
def ParameterInfo(self, parsed_args, argument):
|
|
del argument
|
|
return parsed_args
|
|
|
|
def Complete(self, prefix, parameter_info):
|
|
return self._completer(prefix, parsed_args=parameter_info)
|
|
|
|
|
|
def _GetPresentationSpec(resource_spec_path, **kwargs):
|
|
"""Build a presentation spec."""
|
|
resource_spec = module_util.ImportModule(resource_spec_path)
|
|
if callable(resource_spec):
|
|
resource_spec = resource_spec()
|
|
flag_name_overrides = kwargs.pop('flag_name_overrides', '')
|
|
flag_name_overrides = {
|
|
o.split(':')[0]: o.split(':')[1] if ':' in o else ''
|
|
for o in flag_name_overrides.split(';')
|
|
if o}
|
|
prefixes = kwargs.pop('prefixes', False)
|
|
return presentation_specs.ResourcePresentationSpec(
|
|
kwargs.pop('name', resource_spec.name),
|
|
resource_spec,
|
|
'help text',
|
|
flag_name_overrides=flag_name_overrides,
|
|
prefixes=prefixes,
|
|
**kwargs)
|
|
|
|
|
|
def _GetCompleter(module_path, cache=None, qualify=None,
|
|
resource_spec=None, presentation_kwargs=None, attribute=None,
|
|
**kwargs):
|
|
"""Returns an instantiated completer for module_path."""
|
|
presentation_kwargs = presentation_kwargs or {}
|
|
if resource_spec:
|
|
presentation_spec = _GetPresentationSpec(resource_spec,
|
|
**presentation_kwargs)
|
|
completer = module_util.ImportModule(module_path)(
|
|
presentation_spec.concept_spec,
|
|
attribute)
|
|
|
|
else:
|
|
completer = module_util.ImportModule(module_path)
|
|
if not isinstance(completer, type):
|
|
return _FunctionCompleter(completer)
|
|
try:
|
|
return completer(
|
|
cache=cache,
|
|
qualified_parameter_names=qualify,
|
|
**kwargs)
|
|
except TypeError:
|
|
return _FunctionCompleter(completer())
|
|
|
|
|
|
class AddCompleterResourceFlags(parser_extensions.DynamicPositionalAction):
|
|
"""Adds resource argument flags based on the completer."""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(AddCompleterResourceFlags, self).__init__(*args, **kwargs)
|
|
self.__argument = None
|
|
self.__completer = None
|
|
|
|
def GenerateArgs(self, namespace, module_path):
|
|
args = []
|
|
presentation_kwargs = namespace.resource_presentation_kwargs or {}
|
|
# Add the args that correspond to the resource arg, but make them
|
|
# non-required.
|
|
if namespace.resource_spec_path:
|
|
spec = _GetPresentationSpec(namespace.resource_spec_path,
|
|
**presentation_kwargs)
|
|
info = concept_parsers.ConceptParser([spec]).GetInfo(spec.name)
|
|
for arg in info.GetAttributeArgs():
|
|
if arg.name.startswith('--'):
|
|
arg.kwargs['required'] = False
|
|
else:
|
|
arg.kwargs['nargs'] = '?' if not spec.plural else '*'
|
|
args.append(arg)
|
|
kwargs = namespace.kwargs or {}
|
|
self.__completer = _GetCompleter(
|
|
module_path, qualify=namespace.qualify,
|
|
resource_spec=namespace.resource_spec_path,
|
|
presentation_kwargs=presentation_kwargs,
|
|
attribute=namespace.attribute,
|
|
**kwargs)
|
|
if self.__completer.parameters:
|
|
for parameter in self.__completer.parameters:
|
|
dest = parameter_info_lib.GetDestFromParam(parameter.name)
|
|
if hasattr(namespace, dest):
|
|
# Don't add if its already been added.
|
|
continue
|
|
flag = parameter_info_lib.GetFlagFromDest(dest)
|
|
arg = base.Argument(
|
|
flag,
|
|
dest=dest,
|
|
category='RESOURCE COMPLETER',
|
|
help='{} `{}` parameter value.'.format(
|
|
self.__completer.__class__.__name__, parameter.name))
|
|
args.append(arg)
|
|
self.__argument = base.Argument(
|
|
'resource_to_complete',
|
|
nargs='?',
|
|
help=('The partial resource name to complete. Omit to enter an '
|
|
'interactive loop that reads a partial resource name from the '
|
|
'input and lists the possible prefix matches on the output '
|
|
'or displays an ERROR message.'))
|
|
args.append(self.__argument)
|
|
return args
|
|
|
|
def Completions(self, prefix, parsed_args, **kwargs):
|
|
parameter_info = self.__completer.ParameterInfo(
|
|
parsed_args, self.__argument)
|
|
return self.__completer.Complete(prefix, parameter_info)
|
|
|
|
|
|
class Run(base.Command):
|
|
"""Cloud SDK completer module tester.
|
|
|
|
*{command}* is an ideal way to debug completer modules without interference
|
|
from the shell. Shells typically ignore completer errors by disabling all
|
|
standard output, standard error and exception messaging. Specify
|
|
`--verbosity=INFO` to enable completion and resource cache tracing.
|
|
"""
|
|
|
|
@staticmethod
|
|
def Args(parser):
|
|
# Add a concept handler that will be stuffed dynamically with information.
|
|
concept_parsers.ConceptParser([]).AddToParser(parser)
|
|
parser.add_argument(
|
|
'--resource-spec-path',
|
|
help=('The resource spec path for a resource argument auto-generated '
|
|
'completer.'))
|
|
parser.add_argument(
|
|
'--attribute',
|
|
help=('The name of the resource attribute for a resource argument '
|
|
'auto-generated completer.'))
|
|
parser.add_argument(
|
|
'--resource-presentation-kwargs',
|
|
type=arg_parsers.ArgDict(
|
|
spec={
|
|
'name': str,
|
|
'flag_name_overrides': str,
|
|
'plural': bool,
|
|
'prefixes': bool,
|
|
'required': bool}),
|
|
help=('Dict of kwargs to be passed to the presentation spec for the '
|
|
'resource argument for which a completer is being tested, such '
|
|
'as name, prefixes, plural, flag name overrides (format as a '
|
|
'list of semicolon-separated key:value pairs). Prefixes is False '
|
|
'by default. Name is the resource spec name by default.'))
|
|
cache_util.AddCacheFlag(parser)
|
|
parser.add_argument(
|
|
'--qualify',
|
|
metavar='NAME',
|
|
type=arg_parsers.ArgList(),
|
|
help=('A list of resource parameter names that must always be '
|
|
'qualified. This is a manual setting for testing. The CLI sets '
|
|
'this automatically.'))
|
|
parser.add_argument(
|
|
'--kwargs',
|
|
metavar='NAME=VALUE',
|
|
type=arg_parsers.ArgDict(),
|
|
help=('Keyword arg dict passed to the completer constructor. For '
|
|
'example, use this to set the resource collection and '
|
|
'list command for `DeprecatedListCommandCompleter`:\n\n'
|
|
' --kwargs=collection=...,foo="..."'))
|
|
parser.add_argument(
|
|
'--stack-trace',
|
|
action='store_true',
|
|
default=True,
|
|
help=('Enable all exception stack traces, including Cloud SDK core '
|
|
'exceptions.'))
|
|
parser.AddDynamicPositional(
|
|
'module_path',
|
|
action=AddCompleterResourceFlags,
|
|
help=('The completer module path. Run $ gcloud meta completers list` '
|
|
'to list the module paths of the available completers. A '
|
|
'completer module may declare additional flags. Specify `--help` '
|
|
'after _MODULE_PATH_ for details on the module specific flags.'
|
|
'\n\nNOTE: To test resource argument completers, use the '
|
|
'module path "googlecloudsdk.command_lib.util.completers:'
|
|
'CompleterForAttribute". The flags `--resource-spec-path`, '
|
|
'`--attribute`, and (if desired) `--resource-presentation-'
|
|
'kwargs` must be provided BEFORE the positional. Unlike with '
|
|
'most gcloud commands, the arguments are generated on the fly '
|
|
'using the completer you provide, so all the information to '
|
|
'create a resource completer needs to be provided up-front. For '
|
|
'example:\n\n $ {command} --resource-spec-path MODULE_PATH:'
|
|
'SPEC_OBJECT --attribute ATTRIBUTE_NAME --resource-presentation-'
|
|
'kwargs flag_name_overrides=ATTRIBUTE1:FLAG1;ATTRIBUTE2:FLAG2 '
|
|
'googlecloudsdk.command_lib.util.completers:CompleterForAttribute'
|
|
))
|
|
|
|
def Run(self, args):
|
|
"""Returns the results for one completion."""
|
|
presentation_kwargs = args.resource_presentation_kwargs or {}
|
|
with cache_util.GetCache(args.cache, create=True) as cache:
|
|
log.info('cache name {}'.format(cache.name))
|
|
if not args.kwargs:
|
|
args.kwargs = {}
|
|
# Create the ResourceInfo object that is used to hook up the parameter
|
|
# info to the argparse namespace for resource argument completers.
|
|
if args.resource_spec_path:
|
|
spec = _GetPresentationSpec(
|
|
args.resource_spec_path,
|
|
**presentation_kwargs)
|
|
spec.required = False
|
|
resource_info = concept_parsers.ConceptParser([spec]).GetInfo(spec.name)
|
|
# Since the argument being completed doesn't have the correct
|
|
# dest, make sure the handler always gives the same ResourceInfo
|
|
# object.
|
|
def ResourceInfoMonkeyPatch(*args, **kwargs):
|
|
del args, kwargs
|
|
return resource_info
|
|
args.CONCEPTS.ArgNameToConceptInfo = ResourceInfoMonkeyPatch
|
|
|
|
completer = _GetCompleter(
|
|
args.module_path, cache=cache, qualify=args.qualify,
|
|
resource_spec=args.resource_spec_path,
|
|
presentation_kwargs=presentation_kwargs,
|
|
attribute=args.attribute,
|
|
**args.kwargs)
|
|
parameter_info = completer.ParameterInfo(
|
|
args, args.GetPositionalArgument('resource_to_complete'))
|
|
if args.resource_to_complete is not None:
|
|
matches = completer.Complete(args.resource_to_complete, parameter_info)
|
|
return [matches]
|
|
while True:
|
|
name = console_io.PromptResponse('COMPLETE> ')
|
|
if name is None:
|
|
break
|
|
try:
|
|
completions = completer.Complete(name, parameter_info)
|
|
except (Exception, SystemExit) as e: # pylint: disable=broad-except
|
|
if args.stack_trace:
|
|
exceptions.reraise(Exception(e))
|
|
else:
|
|
log.error(six.text_type(e))
|
|
continue
|
|
if completions:
|
|
print('\n'.join(completions))
|
|
sys.stderr.write('\n')
|
|
return None
|