1# -*- coding: utf-8 -*- 2# Copyright 2011 Google Inc. 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"""Implementation of gsutil help command.""" 16 17from __future__ import absolute_import 18 19import itertools 20import os 21import pkgutil 22import re 23from subprocess import PIPE 24from subprocess import Popen 25 26import gslib.addlhelp 27from gslib.command import Command 28from gslib.command import OLD_ALIAS_MAP 29import gslib.commands 30from gslib.exception import CommandException 31from gslib.help_provider import HelpProvider 32from gslib.help_provider import MAX_HELP_NAME_LEN 33from gslib.util import IsRunningInteractively 34 35_SYNOPSIS = """ 36 gsutil help [command or topic] 37""" 38 39_DETAILED_HELP_TEXT = (""" 40<B>SYNOPSIS</B> 41""" + _SYNOPSIS + """ 42 43 44<B>DESCRIPTION</B> 45 Running: 46 47 gsutil help 48 49 will provide a summary of all commands and additional topics on which 50 help is available. 51 52 Running: 53 54 gsutil help command or topic 55 56 will provide help about the specified command or topic. 57 58 Running: 59 60 gsutil help command sub-command 61 62 will provide help about the specified sub-command. For example, running: 63 64 gsutil help acl set 65 66 will provide help about the "set" subcommand of the "acl" command. 67 68 If you set the PAGER environment variable to the path to a pager program 69 (such as /bin/less on Linux), long help sections will be piped through 70 the specified pager. 71""") 72 73top_level_usage_string = ( 74 'Usage: gsutil [-D] [-DD] [-h header]... ' 75 '[-m] [-o] [-q] [command [opts...] args...]' 76) 77 78 79class HelpCommand(Command): 80 """Implementation of gsutil help command.""" 81 82 # Command specification. See base class for documentation. 83 command_spec = Command.CreateCommandSpec( 84 'help', 85 command_name_aliases=['?', 'man'], 86 usage_synopsis=_SYNOPSIS, 87 min_args=0, 88 max_args=2, 89 supported_sub_args='', 90 file_url_ok=True, 91 provider_url_ok=False, 92 urls_start_arg=0, 93 ) 94 # Help specification. See help_provider.py for documentation. 95 help_spec = Command.HelpSpec( 96 help_name='help', 97 help_name_aliases=['?'], 98 help_type='command_help', 99 help_one_line_summary='Get help about commands and topics', 100 help_text=_DETAILED_HELP_TEXT, 101 subcommand_help_text={}, 102 ) 103 104 def RunCommand(self): 105 """Command entry point for the help command.""" 106 (help_type_map, help_name_map) = self._LoadHelpMaps() 107 output = [] 108 if not self.args: 109 output.append('%s\nAvailable commands:\n' % top_level_usage_string) 110 format_str = ' %-' + str(MAX_HELP_NAME_LEN) + 's%s\n' 111 for help_prov in sorted(help_type_map['command_help'], 112 key=lambda hp: hp.help_spec.help_name): 113 output.append(format_str % ( 114 help_prov.help_spec.help_name, 115 help_prov.help_spec.help_one_line_summary)) 116 output.append('\nAdditional help topics:\n') 117 for help_prov in sorted(help_type_map['additional_help'], 118 key=lambda hp: hp.help_spec.help_name): 119 output.append(format_str % ( 120 help_prov.help_spec.help_name, 121 help_prov.help_spec.help_one_line_summary)) 122 output.append('\nUse gsutil help <command or topic> for detailed help.') 123 else: 124 invalid_subcommand = False 125 arg = self.args[0] 126 if arg not in help_name_map: 127 output.append('No help available for "%s"' % arg) 128 else: 129 help_prov = help_name_map[arg] 130 help_name = None 131 if len(self.args) > 1: # We also have a subcommand argument. 132 subcommand_map = help_prov.help_spec.subcommand_help_text 133 if subcommand_map and self.args[1] in subcommand_map: 134 help_name = arg + ' ' + self.args[1] 135 help_text = subcommand_map[self.args[1]] 136 else: 137 invalid_subcommand = True 138 if not subcommand_map: 139 output.append(( 140 'The "%s" command has no subcommands. You can ask for the ' 141 'full help by running:\n\n\tgsutil help %s\n') % 142 (arg, arg)) 143 else: 144 subcommand_examples = [] 145 for subcommand in subcommand_map: 146 subcommand_examples.append( 147 '\tgsutil help %s %s' % (arg, subcommand)) 148 output.append( 149 ('Subcommand "%s" does not exist for command "%s".\n' 150 'You can either ask for the full help about the command by ' 151 'running:\n\n\tgsutil help %s\n\n' 152 'Or you can ask for help about one of the subcommands:\n\n%s' 153 ) % (self.args[1], arg, arg, '\n'.join(subcommand_examples))) 154 if not invalid_subcommand: 155 if not help_name: # No subcommand or invalid subcommand. 156 help_name = help_prov.help_spec.help_name 157 help_text = help_prov.help_spec.help_text 158 159 output.append('<B>NAME</B>\n') 160 output.append(' %s - %s\n' % ( 161 help_name, help_prov.help_spec.help_one_line_summary)) 162 output.append('\n\n') 163 output.append(help_text.strip('\n')) 164 new_alias = OLD_ALIAS_MAP.get(arg, [None])[0] 165 if new_alias: 166 deprecation_warning = """ 167 The "%s" alias is deprecated, and will eventually be removed completely. 168 Please use the "%s" command instead.""" % (arg, new_alias) 169 170 output.append('\n\n\n<B>DEPRECATION WARNING</B>\n') 171 output.append(deprecation_warning) 172 self._OutputHelp(''.join(output)) 173 return 0 174 175 def _OutputHelp(self, help_str): 176 """Outputs simply formatted string. 177 178 This function paginates if the string is too long, PAGER is defined, and 179 the output is a tty. 180 181 Args: 182 help_str: String to format. 183 """ 184 # Replace <B> and </B> with terminal formatting strings if connected to tty. 185 if not IsRunningInteractively(): 186 help_str = re.sub('<B>', '', help_str) 187 help_str = re.sub('</B>', '', help_str) 188 print help_str 189 return 190 help_str = re.sub('<B>', '\033[1m', help_str) 191 help_str = re.sub('</B>', '\033[0;0m', help_str) 192 num_lines = len(help_str.split('\n')) 193 if 'PAGER' in os.environ and num_lines >= gslib.util.GetTermLines(): 194 # Use -r option for less to make bolding work right. 195 pager = os.environ['PAGER'].split(' ') 196 if pager[0].endswith('less'): 197 pager.append('-r') 198 try: 199 Popen(pager, stdin=PIPE).communicate(input=help_str) 200 except OSError, e: 201 raise CommandException('Unable to open pager (%s): %s' % 202 (' '.join(pager), e)) 203 else: 204 print help_str 205 206 def _LoadHelpMaps(self): 207 """Returns tuple of help type and help name. 208 209 help type is a dict with key: help type 210 value: list of HelpProviders 211 help name is a dict with key: help command name or alias 212 value: HelpProvider 213 214 Returns: 215 (help type, help name) 216 """ 217 218 # Import all gslib.commands submodules. 219 for _, module_name, _ in pkgutil.iter_modules(gslib.commands.__path__): 220 __import__('gslib.commands.%s' % module_name) 221 # Import all gslib.addlhelp submodules. 222 for _, module_name, _ in pkgutil.iter_modules(gslib.addlhelp.__path__): 223 __import__('gslib.addlhelp.%s' % module_name) 224 225 help_type_map = {} 226 help_name_map = {} 227 for s in gslib.help_provider.ALL_HELP_TYPES: 228 help_type_map[s] = [] 229 # Only include HelpProvider subclasses in the dict. 230 for help_prov in itertools.chain( 231 HelpProvider.__subclasses__(), Command.__subclasses__()): 232 if help_prov is Command: 233 # Skip the Command base class itself; we just want its subclasses, 234 # where the help command text lives (in addition to non-Command 235 # HelpProviders, like naming.py). 236 continue 237 gslib.help_provider.SanityCheck(help_prov, help_name_map) 238 help_name_map[help_prov.help_spec.help_name] = help_prov 239 for help_name_aliases in help_prov.help_spec.help_name_aliases: 240 help_name_map[help_name_aliases] = help_prov 241 help_type_map[help_prov.help_spec.help_type].append(help_prov) 242 return (help_type_map, help_name_map) 243