1# -*- coding: utf-8 -*- # 2# Copyright 2017 Google LLC. All Rights Reserved. 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15 16"""The cli_tree command help document markdown generator. 17 18This module generates command help markdown from the tree generated by: 19 20 gcloud --quiet alpha # make sure the alpha component is installed 21 gcloud --quiet beta # make sure the beta component is installed 22 gcloud meta list-gcloud --format=json | 23 python -c " 24 import json 25 import sys 26 data = json.load(sys.stdin) 27 print 'gcloud_tree =', data" > gcloud_tree.py 28 29Usage: 30 31 from googlecloudsdk.calliope import cli_tree_markdown 32 from googlecloudsdk.command_lib.shell import gcloud_tree 33 34 command = <command node in gcloud tree> 35 flag = <flag node in gcloud tree> 36 generator = cli_tree_markdown.CliTreeMarkdownGenerator(command, gcloud_tree) 37 generator.PrintSynopsisSection() 38 generator.PrintFlagDefinition(flag) 39 ... 40 markdown = generator.Edit() 41""" 42 43from __future__ import absolute_import 44from __future__ import division 45from __future__ import unicode_literals 46 47from googlecloudsdk.calliope import arg_parsers 48from googlecloudsdk.calliope import base 49from googlecloudsdk.calliope import cli_tree 50from googlecloudsdk.calliope import markdown 51from googlecloudsdk.calliope import usage_text 52from googlecloudsdk.core import properties 53 54import six 55 56 57if six.PY2: 58 FLAG_TYPE_NAME = b'flag' 59 POSITIONAL_TYPE_NAME = b'positional' 60 GROUP_TYPE_NAME = b'group' 61else: 62 FLAG_TYPE_NAME = 'flag' 63 POSITIONAL_TYPE_NAME = 'positional' 64 GROUP_TYPE_NAME = 'group' 65 66 67def _GetReleaseTrackFromId(release_id): 68 """Returns the base.ReleaseTrack for release_id.""" 69 if release_id == 'INTERNAL': 70 release_id = 'GA' 71 return base.ReleaseTrack.FromId(release_id) 72 73 74def Flag(d): 75 """Returns a flag object suitable for the calliope.markdown module.""" 76 flag = type(FLAG_TYPE_NAME, (object,), d) 77 flag.is_group = False 78 flag.is_hidden = d.get(cli_tree.LOOKUP_IS_HIDDEN, d.get('hidden', False)) 79 flag.hidden = flag.is_hidden 80 flag.is_positional = False 81 flag.is_required = d.get(cli_tree.LOOKUP_IS_REQUIRED, 82 d.get(cli_tree.LOOKUP_REQUIRED, False)) 83 flag.required = flag.is_required 84 flag.help = flag.description 85 flag.dest = flag.name.lower().replace('-', '_') 86 flag.metavar = flag.value 87 flag.option_strings = [flag.name] 88 if not hasattr(flag, 'default'): 89 flag.default = None 90 91 if flag.type == 'bool': 92 flag.nargs = 0 93 elif flag.nargs not in ('?', '*', '+'): 94 flag.nargs = 1 95 if flag.type == 'dict': 96 flag.type = arg_parsers.ArgDict() 97 elif flag.type == 'list': 98 flag.type = arg_parsers.ArgList() 99 elif flag.type == 'string': 100 flag.type = None 101 102 if flag.attr.get(cli_tree.LOOKUP_INVERTED_SYNOPSIS): 103 flag.inverted_synopsis = True 104 prop = flag.attr.get('property') 105 if prop: 106 if cli_tree.LOOKUP_VALUE in prop: 107 kind = 'value' 108 value = prop[cli_tree.LOOKUP_VALUE] 109 else: 110 value = None 111 kind = 'bool' if flag.type == 'bool' else None 112 flag.store_property = (properties.FromString( 113 prop[cli_tree.LOOKUP_NAME]), kind, value) 114 115 return flag 116 117 118def Positional(d): 119 """Returns a positional object suitable for the calliope.markdown module.""" 120 positional = type(POSITIONAL_TYPE_NAME, (object,), d) 121 positional.help = positional.description 122 positional.is_group = False 123 positional.is_hidden = False 124 positional.is_positional = True 125 positional.is_required = positional.nargs != '*' 126 positional.dest = positional.value.lower().replace('-', '_') 127 positional.metavar = positional.value 128 positional.option_strings = [] 129 try: 130 positional.nargs = int(positional.nargs) 131 except ValueError: 132 pass 133 return positional 134 135 136def Argument(d): 137 """Returns an argument object suitable for the calliope.markdown module.""" 138 if d.get(cli_tree.LOOKUP_IS_POSITIONAL, False): 139 return Positional(d) 140 if not d.get(cli_tree.LOOKUP_IS_GROUP, False): 141 return Flag(d) 142 group = type(GROUP_TYPE_NAME, (object,), d) 143 group.arguments = [Argument(a) for a in d.get(cli_tree.LOOKUP_ARGUMENTS, [])] 144 group.category = None 145 group.help = group.description 146 group.is_global = False 147 group.is_hidden = False 148 return group 149 150 151class CliTreeMarkdownGenerator(markdown.MarkdownGenerator): 152 """cli_tree command help markdown document generator. 153 154 Attributes: 155 _capsule: The help text capsule. 156 _command: The tree node for command. 157 _command_path: The command path list. 158 _tree: The (sub)tree root. 159 _sections: The help text sections indexed by SECTION name. 160 _subcommands: The dict of subcommand help indexed by subcommand name. 161 _subgroups: The dict of subgroup help indexed by subcommand name. 162 """ 163 164 def __init__(self, command, tree): 165 """Constructor. 166 167 Args: 168 command: The command node in the root tree. 169 tree: The (sub)tree root. 170 """ 171 self._tree = tree 172 self._command = command 173 self._command_path = command[cli_tree.LOOKUP_PATH] 174 super(CliTreeMarkdownGenerator, self).__init__( 175 self._command_path, 176 _GetReleaseTrackFromId(self._command[cli_tree.LOOKUP_RELEASE]), 177 self._command.get(cli_tree.LOOKUP_IS_HIDDEN, 178 self._command.get('hidden', False))) 179 self._capsule = self._command[cli_tree.LOOKUP_CAPSULE] 180 self._sections = self._command[cli_tree.LOOKUP_SECTIONS] 181 self._subcommands = self.GetSubCommandHelp() 182 self._subgroups = self.GetSubGroupHelp() 183 184 def _GetCommandFromPath(self, command_path): 185 """Returns the command node for command_path.""" 186 path = self._tree[cli_tree.LOOKUP_PATH] 187 if path: 188 # self._tree is not a super root. The first path name must match. 189 if command_path[:1] != path: 190 return None 191 # Already checked the first name. 192 command_path = command_path[1:] 193 command = self._tree 194 for name in command_path: 195 commands = command[cli_tree.LOOKUP_COMMANDS] 196 if name not in commands: 197 return None 198 command = commands[name] 199 return command 200 201 def IsValidSubPath(self, command_path): 202 """Returns True if the given command path after the top is valid.""" 203 return self._GetCommandFromPath([cli_tree.DEFAULT_CLI_NAME] + 204 command_path) is not None 205 206 def GetArguments(self): 207 """Returns the command arguments.""" 208 command = self._GetCommandFromPath(self._command_path) 209 try: 210 return [Argument(a) for a in 211 command[cli_tree.LOOKUP_CONSTRAINTS][cli_tree.LOOKUP_ARGUMENTS]] 212 except (KeyError, TypeError): 213 return [] 214 215 def GetArgDetails(self, arg, depth=None): 216 """Returns the help text with auto-generated details for arg. 217 218 The help text was already generated on the cli_tree generation side. 219 220 Args: 221 arg: The arg to auto-generate help text for. 222 depth: The indentation depth at which the details should be printed. 223 Added here only to maintain consistency with superclass during testing. 224 225 Returns: 226 The help text with auto-generated details for arg. 227 """ 228 return arg.help 229 230 def _GetSubHelp(self, is_group=False): 231 """Returns the help dict indexed by command for sub commands or groups.""" 232 return {name: usage_text.HelpInfo( 233 help_text=subcommand[cli_tree.LOOKUP_CAPSULE], 234 is_hidden=subcommand.get(cli_tree.LOOKUP_IS_HIDDEN, 235 subcommand.get('hidden', False)), 236 release_track=_GetReleaseTrackFromId( 237 subcommand[cli_tree.LOOKUP_RELEASE])) 238 for name, subcommand in six.iteritems(self._command[ 239 cli_tree.LOOKUP_COMMANDS]) 240 if subcommand[cli_tree.LOOKUP_IS_GROUP] == is_group} 241 242 def GetSubCommandHelp(self): 243 """Returns the subcommand help dict indexed by subcommand.""" 244 return self._GetSubHelp(is_group=False) 245 246 def GetSubGroupHelp(self): 247 """Returns the subgroup help dict indexed by subgroup.""" 248 return self._GetSubHelp(is_group=True) 249 250 def PrintFlagDefinition(self, flag, disable_header=False): 251 """Prints a flags definition list item.""" 252 if isinstance(flag, dict): 253 flag = Flag(flag) 254 super(CliTreeMarkdownGenerator, self).PrintFlagDefinition( 255 flag, disable_header=disable_header) 256 257 def _ExpandHelpText(self, doc): 258 """{...} references were done when the tree was generated.""" 259 return doc 260 261 262def Markdown(command, tree): 263 """Returns the help markdown document string for the command node in tree. 264 265 Args: 266 command: The command node in the root tree. 267 tree: The (sub)tree root. 268 269 Returns: 270 The markdown document string. 271 """ 272 return CliTreeMarkdownGenerator(command, tree).Generate() 273