File: //snap/google-cloud-cli/current/lib/surface/meta/cache/completers/run.py
# -*- 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