209 lines
7.0 KiB
Python
209 lines
7.0 KiB
Python
# -*- coding: utf-8 -*- #
|
|
# Copyright 2015 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.
|
|
|
|
"""A module for walking the Cloud SDK CLI tree."""
|
|
|
|
from __future__ import absolute_import
|
|
from __future__ import division
|
|
from __future__ import unicode_literals
|
|
|
|
from typing import Any
|
|
|
|
from googlecloudsdk.core.console import console_io
|
|
from googlecloudsdk.core.console import progress_tracker
|
|
import six
|
|
|
|
|
|
class Walker(object):
|
|
"""Base class for walking the Cloud SDK CLI tree.
|
|
|
|
Attributes:
|
|
_roots: The root elements of the CLI tree that will be walked.
|
|
_num_nodes: The total number of nodes in the tree.
|
|
_num_visited: The count of visited nodes so far.
|
|
_progress_callback: The progress bar function to call to update progress.
|
|
"""
|
|
|
|
def __init__(
|
|
self, cli, progress_callback=None, ignore_load_errors=False, restrict=None
|
|
):
|
|
"""Constructor.
|
|
|
|
Args:
|
|
cli: The Cloud SDK CLI object.
|
|
progress_callback: f(float), The function to call to update the progress
|
|
bar or None for no progress bar.
|
|
ignore_load_errors: bool, True to ignore command load failures. This
|
|
should only be used when it is not critical that all data is returned,
|
|
like for optimizations like static tab completion.
|
|
restrict: Restricts the walk to the command/group dotted paths in this
|
|
list. For example, restrict=['gcloud.alpha.test', 'gcloud.topic']
|
|
restricts the walk to the 'gcloud topic' and 'gcloud alpha test'
|
|
commands/groups. When provided here, any groups above the restrictions
|
|
in the tree will not be loaded or visited.
|
|
"""
|
|
top = cli._TopElement() # pylint: disable=protected-access
|
|
if restrict:
|
|
roots = [self._GetSubElement(top, r) for r in restrict]
|
|
self._roots = [r for r in roots if r]
|
|
else:
|
|
self._roots = [top]
|
|
|
|
self._num_nodes = 0
|
|
if progress_callback:
|
|
with progress_tracker.ProgressTracker('Loading CLI Tree'):
|
|
for root in self._roots:
|
|
self._num_nodes += 1.0 + root.LoadAllSubElements(
|
|
recursive=True, ignore_load_errors=ignore_load_errors
|
|
)
|
|
else:
|
|
for root in self._roots:
|
|
self._num_nodes += 1.0 + root.LoadAllSubElements(
|
|
recursive=True, ignore_load_errors=ignore_load_errors
|
|
)
|
|
|
|
self._num_visited = 0
|
|
self._progress_callback = (
|
|
progress_callback or console_io.DefaultProgressBarCallback
|
|
)
|
|
|
|
def _GetSubElement(self, top_element, path):
|
|
parts = path.split('.')[1:]
|
|
current = top_element
|
|
for part in parts:
|
|
current = current.LoadSubElement(part)
|
|
if not current:
|
|
return None
|
|
return current
|
|
|
|
def Walk(self, hidden=False, universe_compatible=False, restrict=None):
|
|
"""Calls self.Visit() on each node in the CLI tree.
|
|
|
|
The walk is DFS, ordered by command name for reproducability.
|
|
|
|
Args:
|
|
hidden: Include hidden groups and commands if True.
|
|
universe_compatible: Exclusively include commands which are marked
|
|
universe compatible.
|
|
restrict: Restricts the walk to the command/group dotted paths in this
|
|
list. For example, restrict=['gcloud.alpha.test', 'gcloud.topic']
|
|
restricts the walk to the 'gcloud topic' and 'gcloud alpha test'
|
|
commands/groups. When provided here, parent groups will still be visited
|
|
as the walk progresses down to these leaves, but only parent groups
|
|
between the restrictions and the root.
|
|
|
|
Returns:
|
|
The return value of the top level Visit() call.
|
|
"""
|
|
|
|
def _IsUniverseCompatible(command: Any) -> bool:
|
|
"""Determines if a command is universe compatible.
|
|
|
|
Args:
|
|
command: CommandCommon command node.
|
|
|
|
Returns:
|
|
True if command is universe compatible.
|
|
"""
|
|
return not isinstance(command, dict) and (command.IsUniverseCompatible())
|
|
|
|
def _Include(command, traverse=False):
|
|
"""Determines if command should be included in the walk.
|
|
|
|
Args:
|
|
command: CommandCommon command node.
|
|
traverse: If True then check traversal through group to subcommands.
|
|
|
|
Returns:
|
|
True if command should be included in the walk.
|
|
"""
|
|
if not hidden and command.IsHidden():
|
|
return False
|
|
if universe_compatible and not _IsUniverseCompatible(command):
|
|
return False
|
|
if not restrict:
|
|
return True
|
|
path = '.'.join(command.GetPath())
|
|
for item in restrict:
|
|
if path.startswith(item):
|
|
return True
|
|
if traverse and item.startswith(path):
|
|
return True
|
|
return False
|
|
|
|
def _Walk(node, parent):
|
|
"""Walk() helper that calls self.Visit() on each node in the CLI tree.
|
|
|
|
Args:
|
|
node: CommandCommon tree node.
|
|
parent: The parent Visit() return value, None at the top level.
|
|
|
|
Returns:
|
|
The return value of the outer Visit() call.
|
|
"""
|
|
if not node.is_group:
|
|
self._Visit(node, parent, is_group=False)
|
|
return parent
|
|
|
|
parent = self._Visit(node, parent, is_group=True)
|
|
commands_and_groups = []
|
|
|
|
if node.commands:
|
|
for name, command in six.iteritems(node.commands):
|
|
if _Include(command):
|
|
commands_and_groups.append((name, command, False))
|
|
if node.groups:
|
|
for name, command in six.iteritems(node.groups):
|
|
if _Include(command, traverse=True):
|
|
commands_and_groups.append((name, command, True))
|
|
for _, command, is_group in sorted(commands_and_groups):
|
|
if is_group:
|
|
_Walk(command, parent)
|
|
else:
|
|
self._Visit(command, parent, is_group=False)
|
|
return parent
|
|
|
|
self._num_visited = 0
|
|
parent = None
|
|
for root in self._roots:
|
|
parent = _Walk(root, None)
|
|
self.Done()
|
|
return parent
|
|
|
|
def _Visit(self, node, parent, is_group):
|
|
self._num_visited += 1
|
|
self._progress_callback(self._num_visited // self._num_nodes)
|
|
return self.Visit(node, parent, is_group)
|
|
|
|
def Visit(self, node, parent, is_group):
|
|
"""Visits each node in the CLI command tree.
|
|
|
|
Called preorder by WalkCLI() using DFS.
|
|
|
|
Args:
|
|
node: group/command CommandCommon info.
|
|
parent: The parent Visit() return value, None at the top level.
|
|
is_group: True if node is a group, otherwise its is a command.
|
|
|
|
Returns:
|
|
A new parent value for the node subtree. This value is the parent arg
|
|
for the Visit() calls for the children of this node.
|
|
"""
|
|
pass
|
|
|
|
def Done(self):
|
|
"""Cleans up after all nodes in the CLI tree have been visited."""
|
|
pass
|