Skip to content

Commit 7f9ac3d

Browse files
committed
Include global options in command docs
We get a lot of requests from users to include global options in a command's help page. This PR fulfils them by pre-generating .rst files for global options and synopsis and writing them to commands' help docs. An alternative that was considered attempted to plumb the global arg table from the CLIDriver to any help command that would need it. However, we abandoned the approach because it was too invasive to the existing interfaces and there was no easy way to apply the changes to customizations.
1 parent ff9b332 commit 7f9ac3d

12 files changed

Lines changed: 322 additions & 117 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"type": "enhancement",
3+
"category": "docs",
4+
"description": "Improve AWS CLI docs to include global options available to service commands."
5+
}

awscli/bcdoc/docevents.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
'doc-option': '.%s.%s',
2424
'doc-option-example': '.%s.%s',
2525
'doc-options-end': '.%s',
26+
'doc-global-option': '.%s',
2627
'doc-examples': '.%s',
2728
'doc-output': '.%s',
2829
'doc-subitems-start': '.%s',
@@ -74,6 +75,8 @@ def generate_events(session, help_command):
7475
arg_name=arg_name, help_command=help_command)
7576
session.emit('doc-options-end.%s' % help_command.event_class,
7677
help_command=help_command)
78+
session.emit('doc-global-option.%s' % help_command.event_class,
79+
help_command=help_command)
7780
session.emit('doc-subitems-start.%s' % help_command.event_class,
7881
help_command=help_command)
7982
if help_command.command_table:

awscli/bcdoc/restdoc.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,11 @@ def remove_last_doc_string(self):
102102
start, end = self._last_doc_string
103103
del self._writes[start:end]
104104

105+
def write_from_file(self, filename):
106+
with open(filename, 'r') as f:
107+
for line in f.readlines():
108+
self.writeln(line.strip())
109+
105110

106111
class DocumentStructure(ReSTDocument):
107112
def __init__(self, name, section_names=None, target='man', context=None):

awscli/clidocs.py

Lines changed: 59 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# language governing permissions and limitations under the License.
1313
import logging
1414
import os
15+
import re
1516
from botocore import xform_name
1617
from botocore.model import StringShape
1718
from botocore.utils import is_json_value_header
@@ -26,6 +27,11 @@
2627
)
2728

2829
LOG = logging.getLogger(__name__)
30+
EXAMPLES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)),
31+
'examples')
32+
GLOBAL_OPTIONS_FILE = os.path.join(EXAMPLES_DIR, 'global_options.rst')
33+
GLOBAL_OPTIONS_SYNOPSIS_FILE = os.path.join(EXAMPLES_DIR,
34+
'global_synopsis.rst')
2935

3036

3137
class CLIDocumentEventHandler(object):
@@ -146,6 +152,8 @@ def doc_synopsis_option(self, arg_name, help_command, **kwargs):
146152

147153
def doc_synopsis_end(self, help_command, **kwargs):
148154
doc = help_command.doc
155+
# Append synopsis for global options.
156+
doc.write_from_file(GLOBAL_OPTIONS_SYNOPSIS_FILE)
149157
doc.style.end_codeblock()
150158
# Reset the documented arg groups for other sections
151159
# that may document args (the detailed docs following
@@ -183,6 +191,11 @@ def doc_option(self, arg_name, help_command, **kwargs):
183191
doc.style.dedent()
184192
doc.style.new_paragraph()
185193

194+
def doc_global_option(self, help_command, **kwargs):
195+
doc = help_command.doc
196+
doc.style.h2('Global Options')
197+
doc.write_from_file(GLOBAL_OPTIONS_FILE)
198+
186199
def doc_relateditems_start(self, help_command, **kwargs):
187200
if help_command.related_items:
188201
doc = help_command.doc
@@ -297,20 +310,10 @@ def doc_synopsis_end(self, help_command, **kwargs):
297310
doc.style.new_paragraph()
298311

299312
def doc_options_start(self, help_command, **kwargs):
300-
doc = help_command.doc
301-
doc.style.h2('Options')
313+
pass
302314

303315
def doc_option(self, arg_name, help_command, **kwargs):
304-
doc = help_command.doc
305-
argument = help_command.arg_table[arg_name]
306-
doc.writeln('``%s`` (%s)' % (argument.cli_name,
307-
argument.cli_type_name))
308-
doc.include_doc_string(argument.documentation)
309-
if argument.choices:
310-
doc.style.start_ul()
311-
for choice in argument.choices:
312-
doc.style.li(choice)
313-
doc.style.end_ul()
316+
pass
314317

315318
def doc_subitems_start(self, help_command, **kwargs):
316319
doc = help_command.doc
@@ -348,6 +351,9 @@ def doc_option_example(self, arg_name, help_command, **kwargs):
348351
def doc_options_end(self, help_command, **kwargs):
349352
pass
350353

354+
def doc_global_option(self, help_command, **kwargs):
355+
pass
356+
351357
def doc_description(self, help_command, **kwargs):
352358
doc = help_command.doc
353359
service_model = help_command.obj
@@ -384,17 +390,8 @@ def doc_description(self, help_command, **kwargs):
384390
doc.style.h2('Description')
385391
doc.include_doc_string(operation_model.documentation)
386392
self._add_webapi_crosslink(help_command)
387-
self._add_top_level_args_reference(help_command)
388393
self._add_note_for_document_types_if_used(help_command)
389394

390-
def _add_top_level_args_reference(self, help_command):
391-
help_command.doc.writeln('')
392-
help_command.doc.write("See ")
393-
help_command.doc.style.internal_link(
394-
title="'aws help'",
395-
page='/reference/index'
396-
)
397-
help_command.doc.writeln(' for descriptions of global parameters.')
398395

399396
def _add_webapi_crosslink(self, help_command):
400397
doc = help_command.doc
@@ -592,9 +589,6 @@ def doc_output(self, help_command, event_name, **kwargs):
592589
for member_name, member_shape in output_shape.members.items():
593590
self._doc_member(doc, member_name, member_shape, stack=[])
594591

595-
def doc_options_end(self, help_command, **kwargs):
596-
self._add_top_level_args_reference(help_command)
597-
598592

599593
class TopicListerDocumentEventHandler(CLIDocumentEventHandler):
600594
DESCRIPTION = (
@@ -729,3 +723,44 @@ def _line_has_tag(self, line):
729723

730724
def doc_subitems_start(self, help_command, **kwargs):
731725
pass
726+
727+
728+
class GlobalOptionsDocumenter:
729+
"""Documenter used to pre-generate global options docs."""
730+
731+
def __init__(self, help_command):
732+
self._help_command = help_command
733+
734+
def _remove_multilines(self, s):
735+
return re.sub(r'\n+', '\n', s)
736+
737+
def doc_global_options(self):
738+
help_command = self._help_command
739+
for arg in help_command.arg_table:
740+
argument = help_command.arg_table.get(arg)
741+
help_command.doc.writeln(
742+
f"``{argument.cli_name}`` ({argument.cli_type_name})")
743+
help_command.doc.style.indent()
744+
help_command.doc.style.new_paragraph()
745+
help_command.doc.include_doc_string(argument.documentation)
746+
if argument.choices:
747+
help_command.doc.style.start_ul()
748+
for choice in argument.choices:
749+
help_command.doc.style.li(choice)
750+
help_command.doc.style.end_ul()
751+
help_command.doc.style.dedent()
752+
help_command.doc.style.new_paragraph()
753+
global_options = help_command.doc.getvalue().decode('utf-8')
754+
return self._remove_multilines(global_options)
755+
756+
def doc_global_synopsis(self):
757+
help_command = self._help_command
758+
for arg in help_command.arg_table:
759+
argument = help_command.arg_table.get(arg)
760+
if argument.cli_type_name == 'boolean':
761+
arg_synopsis = f"[{argument.cli_name}]"
762+
else:
763+
arg_synopsis = f"[{argument.cli_name} <value>]"
764+
help_command.doc.writeln(arg_synopsis)
765+
global_synopsis = help_command.doc.getvalue().decode('utf-8')
766+
return self._remove_multilines(global_synopsis)

awscli/customizations/commands.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,6 @@ def doc_description(self, help_command, **kwargs):
370370
self.doc.style.h2('Description')
371371
self.doc.write(help_command.description)
372372
self.doc.style.new_paragraph()
373-
self._add_top_level_args_reference(help_command)
374373

375374
def doc_synopsis_start(self, help_command, **kwargs):
376375
if not help_command.synopsis:
@@ -411,12 +410,16 @@ def doc_synopsis_option(self, arg_name, help_command, **kwargs):
411410
pass
412411

413412
def doc_synopsis_end(self, help_command, **kwargs):
414-
if not help_command.synopsis:
413+
if not help_command.synopsis and not help_command.command_table:
415414
super(BasicDocHandler, self).doc_synopsis_end(
416415
help_command=help_command, **kwargs)
417416
else:
418417
self.doc.style.end_codeblock()
419418

419+
def doc_global_option(self, help_command, **kwargs):
420+
if not help_command.command_table:
421+
super().doc_global_option(help_command, **kwargs)
422+
420423
def doc_examples(self, help_command, **kwargs):
421424
if help_command.examples:
422425
self.doc.style.h2('Examples')
@@ -438,6 +441,3 @@ def doc_subitems_end(self, help_command, **kwargs):
438441

439442
def doc_output(self, help_command, event_name, **kwargs):
440443
pass
441-
442-
def doc_options_end(self, help_command, **kwargs):
443-
self._add_top_level_args_reference(help_command)

awscli/examples/global_options.rst

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
``--debug`` (boolean)
2+
3+
Turn on debug logging.
4+
5+
``--endpoint-url`` (string)
6+
7+
Override command's default URL with the given URL.
8+
9+
``--no-verify-ssl`` (boolean)
10+
11+
By default, the AWS CLI uses SSL when communicating with AWS services. For each SSL connection, the AWS CLI will verify SSL certificates. This option overrides the default behavior of verifying SSL certificates.
12+
13+
``--no-paginate`` (boolean)
14+
15+
Disable automatic pagination.
16+
17+
``--output`` (string)
18+
19+
The formatting style for command output.
20+
21+
22+
* json
23+
24+
* text
25+
26+
* table
27+
28+
29+
``--query`` (string)
30+
31+
A JMESPath query to use in filtering the response data.
32+
33+
``--profile`` (string)
34+
35+
Use a specific profile from your credential file.
36+
37+
``--region`` (string)
38+
39+
The region to use. Overrides config/env settings.
40+
41+
``--version`` (string)
42+
43+
Display the version of this tool.
44+
45+
``--color`` (string)
46+
47+
Turn on/off color output.
48+
49+
50+
* on
51+
52+
* off
53+
54+
* auto
55+
56+
57+
``--no-sign-request`` (boolean)
58+
59+
Do not sign requests. Credentials will not be loaded if this argument is provided.
60+
61+
``--ca-bundle`` (string)
62+
63+
The CA certificate bundle to use when verifying SSL certificates. Overrides config/env settings.
64+
65+
``--cli-read-timeout`` (int)
66+
67+
The maximum socket read time in seconds. If the value is set to 0, the socket read will be blocking and not timeout. The default value is 60 seconds.
68+
69+
``--cli-connect-timeout`` (int)
70+
71+
The maximum socket connect time in seconds. If the value is set to 0, the socket connect will be blocking and not timeout. The default value is 60 seconds.
72+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[--debug]
2+
[--endpoint-url <value>]
3+
[--no-verify-ssl]
4+
[--no-paginate]
5+
[--output <value>]
6+
[--query <value>]
7+
[--profile <value>]
8+
[--region <value>]
9+
[--version <value>]
10+
[--color <value>]
11+
[--no-sign-request]
12+
[--ca-bundle <value>]
13+
[--cli-read-timeout <value>]
14+
[--cli-connect-timeout <value>]
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#!/usr/bin/env python
2+
"""Generate a global options section.
3+
4+
The purpose of this script is to pre-generate
5+
global options for the help docs.
6+
The output of this script is loaded and applied to
7+
every subcommand's help docs.
8+
9+
"""
10+
11+
import os
12+
13+
from awscli.clidriver import create_clidriver
14+
from awscli.clidocs import (
15+
EXAMPLES_DIR, GLOBAL_OPTIONS_FILE,
16+
GLOBAL_OPTIONS_SYNOPSIS_FILE, GlobalOptionsDocumenter
17+
)
18+
19+
20+
def main():
21+
if not os.path.isdir(EXAMPLES_DIR):
22+
os.makedirs(EXAMPLES_DIR)
23+
driver = create_clidriver()
24+
options_help_command = driver.create_help_command()
25+
synopsis_help_command = driver.create_help_command()
26+
options_documenter = GlobalOptionsDocumenter(options_help_command)
27+
synopsis_documenter = GlobalOptionsDocumenter(synopsis_help_command)
28+
29+
with open(GLOBAL_OPTIONS_FILE, 'w') as f:
30+
for line in options_documenter.doc_global_options():
31+
f.write(line)
32+
with open(GLOBAL_OPTIONS_SYNOPSIS_FILE, 'w') as f:
33+
for line in synopsis_documenter.doc_global_synopsis():
34+
f.write(line)
35+
36+
37+
if __name__ == "__main__":
38+
main()

tests/functional/docs/test_help_output.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@ def test_output(self):
4646
self.assert_contains('``--no-paginate``')
4747
# Arg with choices
4848
self.assert_contains('``--color``')
49-
self.assert_contains('* on')
50-
self.assert_contains('* off')
51-
self.assert_contains('* auto')
49+
self.assert_contains('* on')
50+
self.assert_contains('* off')
51+
self.assert_contains('* auto')
5252
# Then we should see the services.
5353
self.assert_contains('* ec2')
5454
self.assert_contains('* s3api')
@@ -80,22 +80,23 @@ def test_operation_help_output(self):
8080
self.assert_contains('Launches the specified number of instances')
8181
self.assert_contains('``--count`` (string)')
8282

83-
def test_waiter_does_not_have_duplicate_global_params_link(self):
84-
self.driver.main(['ec2', 'wait', 'help'])
85-
self.assert_contains_with_count(
86-
'for descriptions of global parameters', 1)
87-
8883
def test_custom_service_help_output(self):
8984
self.driver.main(['s3', 'help'])
9085
self.assert_contains('.. _cli:aws s3:')
9186
self.assert_contains('high-level S3 commands')
9287
self.assert_contains('* cp')
9388

89+
def test_waiter_does_not_have_global_args(self):
90+
self.driver.main(['ec2', 'wait', 'help'])
91+
self.assert_not_contains('--debug')
92+
self.assert_not_contains('Global Options')
93+
9494
def test_custom_operation_help_output(self):
9595
self.driver.main(['s3', 'ls', 'help'])
9696
self.assert_contains('.. _cli:aws s3 ls:')
9797
self.assert_contains('List S3 objects')
9898
self.assert_contains('--summarize')
99+
self.assert_contains('--debug')
99100

100101
def test_topic_list_help_output(self):
101102
self.driver.main(['help', 'topics'])

0 commit comments

Comments
 (0)