• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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