227 lines
6.9 KiB
Python
227 lines
6.9 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 command library support."""
|
|
|
|
from __future__ import absolute_import
|
|
from __future__ import division
|
|
from __future__ import unicode_literals
|
|
|
|
from googlecloudsdk.api_lib.util import apis_util
|
|
from googlecloudsdk.calliope import parser_completer
|
|
from googlecloudsdk.calliope import walker
|
|
from googlecloudsdk.command_lib.util import completers
|
|
from googlecloudsdk.core import exceptions
|
|
from googlecloudsdk.core import module_util
|
|
from googlecloudsdk.core import resources
|
|
from googlecloudsdk.core.cache import exceptions as cache_exceptions
|
|
from googlecloudsdk.core.cache import file_cache
|
|
from googlecloudsdk.core.cache import resource_cache
|
|
|
|
import six
|
|
|
|
|
|
_CACHE_RI_DEFAULT = 'resource://'
|
|
|
|
|
|
class Error(exceptions.Error):
|
|
"""Base cache exception."""
|
|
|
|
|
|
class NoTablesMatched(Error):
|
|
"""No table names matched the patterns."""
|
|
|
|
|
|
class GetCache(object):
|
|
"""Context manager for opening a cache given a cache identifier name."""
|
|
|
|
_TYPES = {
|
|
'file': file_cache.Cache,
|
|
'resource': resource_cache.ResourceCache,
|
|
}
|
|
|
|
def __init__(self, name, create=False):
|
|
"""Constructor.
|
|
|
|
Args:
|
|
name: The cache name to operate on. May be prefixed by "resource://" for
|
|
resource cache names or "file://" for persistent file cache names. If
|
|
only the prefix is specified then the default cache name for that prefix
|
|
is used.
|
|
create: Creates the persistent cache if it exists if True.
|
|
|
|
Raises:
|
|
CacheNotFound: If the cache does not exist.
|
|
|
|
Returns:
|
|
The cache object.
|
|
"""
|
|
self._name = name
|
|
self._create = create
|
|
self._cache = None
|
|
|
|
def _OpenCache(self, cache_class, name):
|
|
try:
|
|
return cache_class(name, create=self._create)
|
|
except cache_exceptions.Error as e:
|
|
raise Error(e)
|
|
|
|
def __enter__(self):
|
|
# Each cache_class has a default cache name. None or '' names that default.
|
|
if self._name:
|
|
for cache_id, cache_class in six.iteritems(self._TYPES):
|
|
if self._name.startswith(cache_id + '://'):
|
|
name = self._name[len(cache_id) + 3:]
|
|
if not name:
|
|
name = None
|
|
self._cache = self._OpenCache(cache_class, name)
|
|
return self._cache
|
|
self._cache = self._OpenCache(resource_cache.ResourceCache, self._name)
|
|
return self._cache
|
|
|
|
def __exit__(self, typ, value, traceback):
|
|
self._cache.Close(commit=typ is None)
|
|
|
|
|
|
def Delete():
|
|
"""Deletes the resource cache regardless of implementation."""
|
|
try:
|
|
resource_cache.Delete()
|
|
except cache_exceptions.Error as e:
|
|
raise Error(e)
|
|
return None
|
|
|
|
|
|
def AddCacheFlag(parser):
|
|
"""Adds the persistent cache flag to the parser."""
|
|
parser.add_argument(
|
|
'--cache',
|
|
metavar='CACHE_NAME',
|
|
default=_CACHE_RI_DEFAULT,
|
|
help=('The cache name to operate on. May be prefixed by "{}" for '
|
|
'resource cache names. If only the prefix is specified then the '
|
|
'default cache name for that prefix is used.'.format(
|
|
_CACHE_RI_DEFAULT)))
|
|
|
|
|
|
def _GetCompleterType(completer_class):
|
|
"""Returns the completer type name given its class."""
|
|
completer_type = None
|
|
try:
|
|
for t in completer_class.mro():
|
|
if t == completers.ResourceCompleter:
|
|
break
|
|
if t.__name__.endswith('Completer'):
|
|
completer_type = t.__name__
|
|
except AttributeError:
|
|
pass
|
|
if not completer_type and callable(completer_class):
|
|
completer_type = 'function'
|
|
return completer_type
|
|
|
|
|
|
class _CompleterModule(object):
|
|
|
|
def __init__(self, module_path, collection, api_version, completer_type):
|
|
self.module_path = module_path
|
|
self.collection = collection
|
|
self.api_version = api_version
|
|
self.type = completer_type
|
|
self.attachments = []
|
|
self._attachments_dict = {}
|
|
|
|
|
|
class _CompleterAttachment(object):
|
|
|
|
def __init__(self, command):
|
|
self.command = command
|
|
self.arguments = []
|
|
|
|
|
|
class _CompleterModuleGenerator(walker.Walker):
|
|
"""Constructs a CLI command dict tree."""
|
|
|
|
def __init__(self, cli):
|
|
super(_CompleterModuleGenerator, self).__init__(cli)
|
|
self._modules_dict = {}
|
|
|
|
def Visit(self, command, parent, is_group):
|
|
"""Visits each command in the CLI command tree to construct the module list.
|
|
|
|
Args:
|
|
command: group/command CommandCommon info.
|
|
parent: The parent Visit() return value, None at the top level.
|
|
is_group: True if command is a group, otherwise its is a command.
|
|
|
|
Returns:
|
|
The subtree module list.
|
|
"""
|
|
|
|
def _ActionKey(action):
|
|
return action.__repr__()
|
|
|
|
args = command.ai
|
|
for arg in sorted(args.flag_args + args.positional_args, key=_ActionKey):
|
|
try:
|
|
completer_class = arg.completer
|
|
except AttributeError:
|
|
continue
|
|
collection = None
|
|
api_version = None
|
|
if isinstance(completer_class, parser_completer.ArgumentCompleter):
|
|
completer_class = completer_class.completer_class
|
|
module_path = module_util.GetModulePath(completer_class)
|
|
if isinstance(completer_class, type):
|
|
try:
|
|
completer = completer_class()
|
|
try:
|
|
collection = completer.collection
|
|
except AttributeError:
|
|
pass
|
|
try:
|
|
api_version = completer.api_version
|
|
except AttributeError:
|
|
pass
|
|
except (apis_util.UnknownAPIError,
|
|
resources.InvalidCollectionException) as e:
|
|
collection = 'ERROR: {}'.format(e)
|
|
if arg.option_strings:
|
|
name = arg.option_strings[0]
|
|
else:
|
|
name = arg.dest.replace('_', '-')
|
|
module = self._modules_dict.get(module_path)
|
|
if not module:
|
|
module = _CompleterModule(
|
|
module_path=module_path,
|
|
collection=collection,
|
|
api_version=api_version,
|
|
completer_type=_GetCompleterType(completer_class),
|
|
)
|
|
self._modules_dict[module_path] = module
|
|
command_path = ' '.join(command.GetPath())
|
|
# pylint: disable=protected-access
|
|
attachment = module._attachments_dict.get(command_path)
|
|
if not attachment:
|
|
attachment = _CompleterAttachment(command_path)
|
|
module._attachments_dict[command_path] = attachment
|
|
module.attachments.append(attachment)
|
|
attachment.arguments.append(name)
|
|
return self._modules_dict
|
|
|
|
|
|
def ListAttachedCompleters(cli):
|
|
"""Returns the list of all attached CompleterModule objects in cli."""
|
|
return list(_CompleterModuleGenerator(cli).Walk().values())
|