Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aws
GitHub Repository: aws/aws-cli
Path: blob/develop/awscli/clidocs.py
2624 views
1
# Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
#
3
# Licensed under the Apache License, Version 2.0 (the "License"). You
4
# may not use this file except in compliance with the License. A copy of
5
# the License is located at
6
#
7
# http://aws.amazon.com/apache2.0/
8
#
9
# or in the "license" file accompanying this file. This file is
10
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11
# ANY KIND, either express or implied. See the License for the specific
12
# language governing permissions and limitations under the License.
13
import logging
14
import os
15
import re
16
17
from botocore.model import StringShape
18
from botocore.utils import is_json_value_header
19
20
from awscli import SCALAR_TYPES, __version__ as AWS_CLI_VERSION
21
from awscli.argprocess import ParamShorthandDocGen
22
from awscli.bcdoc.docevents import DOC_EVENTS
23
from awscli.topictags import TopicTagDB
24
from awscli.utils import (
25
find_service_and_method_in_event_name,
26
is_document_type,
27
is_streaming_blob_type,
28
is_tagged_union_type,
29
operation_uses_document_types,
30
)
31
32
LOG = logging.getLogger(__name__)
33
EXAMPLES_DIR = os.path.join(
34
os.path.dirname(os.path.abspath(__file__)), 'examples'
35
)
36
GLOBAL_OPTIONS_FILE = os.path.join(EXAMPLES_DIR, 'global_options.rst')
37
GLOBAL_OPTIONS_SYNOPSIS_FILE = os.path.join(
38
EXAMPLES_DIR, 'global_synopsis.rst'
39
)
40
41
42
class CLIDocumentEventHandler:
43
def __init__(self, help_command):
44
self.help_command = help_command
45
self.register(help_command.session, help_command.event_class)
46
self._arg_groups = self._build_arg_table_groups(help_command)
47
self._documented_arg_groups = []
48
49
def _build_arg_table_groups(self, help_command):
50
arg_groups = {}
51
for arg in help_command.arg_table.values():
52
if arg.group_name is not None:
53
arg_groups.setdefault(arg.group_name, []).append(arg)
54
return arg_groups
55
56
def _get_argument_type_name(self, shape, default):
57
if is_json_value_header(shape):
58
return 'JSON'
59
if is_document_type(shape):
60
return 'document'
61
if is_streaming_blob_type(shape):
62
return 'streaming blob'
63
if is_tagged_union_type(shape):
64
return 'tagged union structure'
65
return default
66
67
def _map_handlers(self, session, event_class, mapfn):
68
for event in DOC_EVENTS:
69
event_handler_name = event.replace('-', '_')
70
if hasattr(self, event_handler_name):
71
event_handler = getattr(self, event_handler_name)
72
format_string = DOC_EVENTS[event]
73
num_args = len(format_string.split('.')) - 2
74
format_args = (event_class,) + ('*',) * num_args
75
event_string = event + format_string % format_args
76
unique_id = event_class + event_handler_name
77
mapfn(event_string, event_handler, unique_id)
78
79
def register(self, session, event_class):
80
"""
81
The default register iterates through all of the
82
available document events and looks for a corresponding
83
handler method defined in the object. If it's there, that
84
handler method will be registered for the all events of
85
that type for the specified ``event_class``.
86
"""
87
self._map_handlers(session, event_class, session.register)
88
89
def unregister(self):
90
"""
91
The default unregister iterates through all of the
92
available document events and looks for a corresponding
93
handler method defined in the object. If it's there, that
94
handler method will be unregistered for the all events of
95
that type for the specified ``event_class``.
96
"""
97
self._map_handlers(
98
self.help_command.session,
99
self.help_command.event_class,
100
self.help_command.session.unregister,
101
)
102
103
# These are default doc handlers that apply in the general case.
104
105
def doc_breadcrumbs(self, help_command, **kwargs):
106
doc = help_command.doc
107
if doc.target != 'man':
108
cmd_names = help_command.event_class.split('.')
109
doc.write('[ ')
110
doc.write(':ref:`aws <cli:aws>`')
111
full_cmd_list = ['aws']
112
for cmd in cmd_names[:-1]:
113
doc.write(' . ')
114
full_cmd_list.append(cmd)
115
full_cmd_name = ' '.join(full_cmd_list)
116
doc.write(f':ref:`{cmd} <cli:{full_cmd_name}>`')
117
doc.writeln(' ]')
118
doc.writeln('')
119
120
def doc_title(self, help_command, **kwargs):
121
doc = help_command.doc
122
doc.style.new_paragraph()
123
reference = help_command.event_class.replace('.', ' ')
124
if reference != 'aws':
125
reference = 'aws ' + reference
126
doc.writeln(f'.. _cli:{reference}:')
127
doc.style.h1(help_command.name)
128
129
def doc_description(self, help_command, **kwargs):
130
doc = help_command.doc
131
doc.style.h2('Description')
132
doc.include_doc_string(help_command.description)
133
doc.style.new_paragraph()
134
135
def doc_synopsis_start(self, help_command, **kwargs):
136
self._documented_arg_groups = []
137
doc = help_command.doc
138
doc.style.h2('Synopsis')
139
doc.style.start_codeblock()
140
doc.writeln(help_command.name)
141
142
def doc_synopsis_option(self, arg_name, help_command, **kwargs):
143
doc = help_command.doc
144
argument = help_command.arg_table[arg_name]
145
if argument.group_name in self._arg_groups:
146
if argument.group_name in self._documented_arg_groups:
147
# This arg is already documented so we can move on.
148
return
149
option_str = ' | '.join(
150
a.cli_name for a in self._arg_groups[argument.group_name]
151
)
152
self._documented_arg_groups.append(argument.group_name)
153
elif argument.cli_name.startswith('--'):
154
option_str = f'{argument.cli_name} <value>'
155
else:
156
option_str = f'<{argument.cli_name}>'
157
if not (
158
argument.required
159
or getattr(argument, '_DOCUMENT_AS_REQUIRED', False)
160
):
161
option_str = f'[{option_str}]'
162
doc.writeln(option_str)
163
164
def doc_synopsis_end(self, help_command, **kwargs):
165
doc = help_command.doc
166
# Append synopsis for global options.
167
doc.write_from_file(GLOBAL_OPTIONS_SYNOPSIS_FILE)
168
doc.style.end_codeblock()
169
# Reset the documented arg groups for other sections
170
# that may document args (the detailed docs following
171
# the synopsis).
172
self._documented_arg_groups = []
173
174
def doc_options_start(self, help_command, **kwargs):
175
doc = help_command.doc
176
doc.style.h2('Options')
177
if not help_command.arg_table:
178
doc.write('*None*\n')
179
180
def doc_option(self, arg_name, help_command, **kwargs):
181
doc = help_command.doc
182
argument = help_command.arg_table[arg_name]
183
if argument.group_name in self._arg_groups:
184
if argument.group_name in self._documented_arg_groups:
185
# This arg is already documented so we can move on.
186
return
187
name = ' | '.join(
188
f'``{a.cli_name}``' for a in self._arg_groups[argument.group_name]
189
)
190
self._documented_arg_groups.append(argument.group_name)
191
else:
192
name = f'``{argument.cli_name}``'
193
argument_type_name = self._get_argument_type_name(
194
argument.argument_model, argument.cli_type_name
195
)
196
doc.write(f'{name} ({argument_type_name})\n')
197
doc.style.indent()
198
doc.include_doc_string(argument.documentation)
199
if is_streaming_blob_type(argument.argument_model):
200
self._add_streaming_blob_note(doc)
201
if is_tagged_union_type(argument.argument_model):
202
self._add_tagged_union_note(argument.argument_model, doc)
203
if hasattr(argument, 'argument_model'):
204
self._document_enums(argument.argument_model, doc)
205
self._document_nested_structure(argument.argument_model, doc)
206
doc.style.dedent()
207
doc.style.new_paragraph()
208
209
def doc_global_option(self, help_command, **kwargs):
210
doc = help_command.doc
211
doc.style.h2('Global Options')
212
doc.write_from_file(GLOBAL_OPTIONS_FILE)
213
214
def doc_relateditems_start(self, help_command, **kwargs):
215
if help_command.related_items:
216
doc = help_command.doc
217
doc.style.h2('See Also')
218
219
def doc_relateditem(self, help_command, related_item, **kwargs):
220
doc = help_command.doc
221
doc.write('* ')
222
doc.style.sphinx_reference_label(
223
label=f'cli:{related_item}', text=related_item
224
)
225
doc.write('\n')
226
227
def doc_meta_description(self, help_command, **kwargs):
228
pass
229
230
def _document_enums(self, model, doc):
231
"""Documents top-level parameter enums"""
232
if isinstance(model, StringShape):
233
if model.enum:
234
doc.style.new_paragraph()
235
doc.write('Possible values:')
236
doc.style.start_ul()
237
for enum in model.enum:
238
doc.style.li(f'``{enum}``')
239
doc.style.end_ul()
240
241
def _document_nested_structure(self, model, doc):
242
"""Recursively documents parameters in nested structures"""
243
member_type_name = getattr(model, 'type_name', None)
244
if member_type_name == 'structure':
245
for member_name, member_shape in model.members.items():
246
self._doc_member(
247
doc, member_name, member_shape, stack=[model.name]
248
)
249
elif member_type_name == 'list':
250
self._doc_member(doc, '', model.member, stack=[model.name])
251
elif member_type_name == 'map':
252
key_shape = model.key
253
key_name = key_shape.serialization.get('name', 'key')
254
self._doc_member(doc, key_name, key_shape, stack=[model.name])
255
value_shape = model.value
256
value_name = value_shape.serialization.get('name', 'value')
257
self._doc_member(doc, value_name, value_shape, stack=[model.name])
258
259
def _doc_member(self, doc, member_name, member_shape, stack):
260
if member_shape.name in stack:
261
# Document the recursion once, otherwise just
262
# note the fact that it's recursive and return.
263
if stack.count(member_shape.name) > 1:
264
if member_shape.type_name == 'structure':
265
doc.write('( ... recursive ... )')
266
return
267
stack.append(member_shape.name)
268
try:
269
self._do_doc_member(doc, member_name, member_shape, stack)
270
finally:
271
stack.pop()
272
273
def _do_doc_member(self, doc, member_name, member_shape, stack):
274
docs = member_shape.documentation
275
type_name = self._get_argument_type_name(
276
member_shape, member_shape.type_name
277
)
278
if member_name:
279
doc.write(f'{member_name} -> ({type_name})')
280
else:
281
doc.write(f'({type_name})')
282
doc.style.indent()
283
doc.style.new_paragraph()
284
doc.include_doc_string(docs)
285
if is_tagged_union_type(member_shape):
286
self._add_tagged_union_note(member_shape, doc)
287
doc.style.new_paragraph()
288
member_type_name = member_shape.type_name
289
if member_type_name == 'structure':
290
for sub_name, sub_shape in member_shape.members.items():
291
self._doc_member(doc, sub_name, sub_shape, stack)
292
elif member_type_name == 'map':
293
key_shape = member_shape.key
294
key_name = key_shape.serialization.get('name', 'key')
295
self._doc_member(doc, key_name, key_shape, stack)
296
value_shape = member_shape.value
297
value_name = value_shape.serialization.get('name', 'value')
298
self._doc_member(doc, value_name, value_shape, stack)
299
elif member_type_name == 'list':
300
self._doc_member(doc, '', member_shape.member, stack)
301
doc.style.dedent()
302
doc.style.new_paragraph()
303
304
def _add_streaming_blob_note(self, doc):
305
doc.style.start_note()
306
msg = (
307
"This argument is of type: streaming blob. "
308
"Its value must be the path to a file "
309
"(e.g. ``path/to/file``) and must **not** "
310
"be prefixed with ``file://`` or ``fileb://``"
311
)
312
doc.writeln(msg)
313
doc.style.end_note()
314
315
def _add_tagged_union_note(self, shape, doc):
316
doc.style.start_note()
317
members_str = ", ".join(f'``{key}``' for key in shape.members.keys())
318
doc.writeln(
319
"This is a Tagged Union structure. Only one of the "
320
f"following top level keys can be set: {members_str}."
321
)
322
doc.style.end_note()
323
324
325
class ProviderDocumentEventHandler(CLIDocumentEventHandler):
326
def doc_breadcrumbs(self, help_command, event_name, **kwargs):
327
pass
328
329
def doc_synopsis_start(self, help_command, **kwargs):
330
doc = help_command.doc
331
doc.style.h2('Synopsis')
332
doc.style.codeblock(help_command.synopsis)
333
doc.include_doc_string(help_command.help_usage)
334
335
def doc_synopsis_option(self, arg_name, help_command, **kwargs):
336
pass
337
338
def doc_synopsis_end(self, help_command, **kwargs):
339
doc = help_command.doc
340
doc.style.new_paragraph()
341
342
def doc_options_start(self, help_command, **kwargs):
343
pass
344
345
def doc_option(self, arg_name, help_command, **kwargs):
346
pass
347
348
def doc_subitems_start(self, help_command, **kwargs):
349
doc = help_command.doc
350
doc.style.h2('Available Services')
351
doc.style.toctree()
352
353
def doc_subitem(self, command_name, help_command, **kwargs):
354
doc = help_command.doc
355
doc.style.tocitem(command_name, file_name=f"{command_name}/index")
356
357
358
class ServiceDocumentEventHandler(CLIDocumentEventHandler):
359
# A service document has no synopsis.
360
def doc_synopsis_start(self, help_command, **kwargs):
361
pass
362
363
def doc_synopsis_option(self, arg_name, help_command, **kwargs):
364
pass
365
366
def doc_synopsis_end(self, help_command, **kwargs):
367
pass
368
369
# A service document has no option section.
370
def doc_options_start(self, help_command, **kwargs):
371
pass
372
373
def doc_option(self, arg_name, help_command, **kwargs):
374
pass
375
376
def doc_option_example(self, arg_name, help_command, **kwargs):
377
pass
378
379
def doc_options_end(self, help_command, **kwargs):
380
pass
381
382
def doc_global_option(self, help_command, **kwargs):
383
pass
384
385
def doc_description(self, help_command, **kwargs):
386
doc = help_command.doc
387
service_model = help_command.obj
388
doc.style.h2('Description')
389
# TODO: need a documentation attribute.
390
doc.include_doc_string(service_model.documentation)
391
392
def doc_subitems_start(self, help_command, **kwargs):
393
doc = help_command.doc
394
doc.style.h2('Available Commands')
395
doc.style.toctree()
396
397
def doc_subitem(self, command_name, help_command, **kwargs):
398
doc = help_command.doc
399
subcommand = help_command.command_table[command_name]
400
subcommand_table = getattr(subcommand, 'subcommand_table', {})
401
# If the subcommand table has commands in it,
402
# direct the subitem to the command's index because
403
# it has more subcommands to be documented.
404
if len(subcommand_table) > 0:
405
doc.style.tocitem(command_name, file_name=f"{command_name}/index")
406
else:
407
doc.style.tocitem(command_name)
408
409
def doc_meta_description(self, help_command, **kwargs):
410
doc = help_command.doc
411
reference = help_command.event_class.replace('.', ' ')
412
doc.writeln(".. meta::")
413
doc.writeln(f" :description: Learn about the AWS CLI {AWS_CLI_VERSION} {reference} commands.")
414
doc.writeln("")
415
416
417
class OperationDocumentEventHandler(CLIDocumentEventHandler):
418
AWS_DOC_BASE = 'https://docs.aws.amazon.com/goto/WebAPI'
419
420
def doc_description(self, help_command, **kwargs):
421
doc = help_command.doc
422
operation_model = help_command.obj
423
doc.style.h2('Description')
424
doc.include_doc_string(operation_model.documentation)
425
self._add_webapi_crosslink(help_command)
426
self._add_note_for_document_types_if_used(help_command)
427
428
def _add_webapi_crosslink(self, help_command):
429
doc = help_command.doc
430
operation_model = help_command.obj
431
service_model = operation_model.service_model
432
service_uid = service_model.metadata.get('uid')
433
if service_uid is None:
434
# If there's no service_uid in the model, we can't
435
# be certain if the generated cross link will work
436
# so we don't generate any crosslink info.
437
return
438
doc.style.new_paragraph()
439
doc.write("See also: ")
440
link = f'{self.AWS_DOC_BASE}/{service_uid}/{operation_model.name}'
441
doc.style.external_link(title="AWS API Documentation", link=link)
442
doc.writeln('')
443
444
def _add_note_for_document_types_if_used(self, help_command):
445
if operation_uses_document_types(help_command.obj):
446
help_command.doc.style.new_paragraph()
447
help_command.doc.writeln(
448
f'``{help_command.name}`` uses document type values. Document '
449
'types follow the JSON data model where valid values are: '
450
'strings, numbers, booleans, null, arrays, and objects. For '
451
'command input, options and nested parameters that are labeled '
452
'with the type ``document`` must be provided as JSON. '
453
'Shorthand syntax does not support document types.'
454
)
455
456
def _json_example_value_name(
457
self, argument_model, include_enum_values=True
458
):
459
# If include_enum_values is True, then the valid enum values
460
# are included as the sample JSON value.
461
if isinstance(argument_model, StringShape):
462
if argument_model.enum and include_enum_values:
463
choices = argument_model.enum
464
return '|'.join(f'"{c}"' for c in choices)
465
else:
466
return '"string"'
467
elif argument_model.type_name == 'boolean':
468
return 'true|false'
469
else:
470
return argument_model.type_name
471
472
def _json_example(self, doc, argument_model, stack):
473
if argument_model.name in stack:
474
# Document the recursion once, otherwise just
475
# note the fact that it's recursive and return.
476
if stack.count(argument_model.name) > 1:
477
if argument_model.type_name == 'structure':
478
doc.write('{ ... recursive ... }')
479
return
480
stack.append(argument_model.name)
481
try:
482
self._do_json_example(doc, argument_model, stack)
483
finally:
484
stack.pop()
485
486
def _do_json_example(self, doc, argument_model, stack):
487
if argument_model.type_name == 'list':
488
doc.write('[')
489
if argument_model.member.type_name in SCALAR_TYPES:
490
example_name = self._json_example_value_name(argument_model.member)
491
doc.write(f'{example_name}, ...')
492
else:
493
doc.style.indent()
494
doc.style.new_line()
495
self._json_example(doc, argument_model.member, stack)
496
doc.style.new_line()
497
doc.write('...')
498
doc.style.dedent()
499
doc.style.new_line()
500
doc.write(']')
501
elif argument_model.type_name == 'map':
502
doc.write('{')
503
doc.style.indent()
504
key_string = self._json_example_value_name(argument_model.key)
505
doc.write(f'{key_string}: ')
506
if argument_model.value.type_name in SCALAR_TYPES:
507
doc.write(self._json_example_value_name(argument_model.value))
508
else:
509
doc.style.indent()
510
self._json_example(doc, argument_model.value, stack)
511
doc.style.dedent()
512
doc.style.new_line()
513
doc.write('...')
514
doc.style.dedent()
515
doc.write('}')
516
elif argument_model.type_name == 'structure':
517
if argument_model.is_document_type:
518
self._doc_document_member(doc)
519
else:
520
self._doc_input_structure_members(doc, argument_model, stack)
521
522
def _doc_document_member(self, doc):
523
doc.write('{...}')
524
525
def _doc_input_structure_members(self, doc, argument_model, stack):
526
doc.write('{')
527
doc.style.indent()
528
doc.style.new_line()
529
members = argument_model.members
530
for i, member_name in enumerate(members):
531
member_model = members[member_name]
532
member_type_name = member_model.type_name
533
if member_type_name in SCALAR_TYPES:
534
example_name = self._json_example_value_name(member_model)
535
doc.write(f'"{member_name}": {example_name}')
536
elif member_type_name == 'structure':
537
doc.write(f'"{member_name}": ')
538
self._json_example(doc, member_model, stack)
539
elif member_type_name == 'map':
540
doc.write(f'"{member_name}": ')
541
self._json_example(doc, member_model, stack)
542
elif member_type_name == 'list':
543
doc.write(f'"{member_name}": ')
544
self._json_example(doc, member_model, stack)
545
if i < len(members) - 1:
546
doc.write(',')
547
doc.style.new_line()
548
doc.style.dedent()
549
doc.style.new_line()
550
doc.write('}')
551
552
def doc_option_example(self, arg_name, help_command, event_name, **kwargs):
553
service_id, operation_name = find_service_and_method_in_event_name(
554
event_name
555
)
556
doc = help_command.doc
557
cli_argument = help_command.arg_table[arg_name]
558
if cli_argument.group_name in self._arg_groups:
559
if cli_argument.group_name in self._documented_arg_groups:
560
# Args with group_names (boolean args) don't
561
# need to generate example syntax.
562
return
563
argument_model = cli_argument.argument_model
564
docgen = ParamShorthandDocGen()
565
if docgen.supports_shorthand(cli_argument.argument_model):
566
example_shorthand_syntax = docgen.generate_shorthand_example(
567
cli_argument, service_id, operation_name
568
)
569
if example_shorthand_syntax is None:
570
# If the shorthand syntax returns a value of None,
571
# this indicates to us that there is no example
572
# needed for this param so we can immediately
573
# return.
574
return
575
if example_shorthand_syntax:
576
doc.style.new_paragraph()
577
doc.write('Shorthand Syntax')
578
doc.style.start_codeblock()
579
for example_line in example_shorthand_syntax.splitlines():
580
doc.writeln(example_line)
581
doc.style.end_codeblock()
582
if (
583
argument_model is not None
584
and argument_model.type_name == 'list'
585
and argument_model.member.type_name in SCALAR_TYPES
586
):
587
# A list of scalars is special. While you *can* use
588
# JSON ( ["foo", "bar", "baz"] ), you can also just
589
# use the argparse behavior of space separated lists.
590
# "foo" "bar" "baz". In fact we don't even want to
591
# document the JSON syntax in this case.
592
member = argument_model.member
593
doc.style.new_paragraph()
594
doc.write('Syntax')
595
doc.style.start_codeblock()
596
example_type = self._json_example_value_name(
597
member, include_enum_values=False
598
)
599
doc.write(f'{example_type} {example_type} ...')
600
if isinstance(member, StringShape) and member.enum:
601
# If we have enum values, we can tell the user
602
# exactly what valid values they can provide.
603
self._write_valid_enums(doc, member.enum)
604
doc.style.end_codeblock()
605
doc.style.new_paragraph()
606
elif cli_argument.cli_type_name not in SCALAR_TYPES:
607
doc.style.new_paragraph()
608
doc.write('JSON Syntax')
609
doc.style.start_codeblock()
610
self._json_example(doc, argument_model, stack=[])
611
doc.style.end_codeblock()
612
doc.style.new_paragraph()
613
614
def _write_valid_enums(self, doc, enum_values):
615
doc.style.new_paragraph()
616
doc.write("Where valid values are:\n")
617
for value in enum_values:
618
doc.write(f" {value}\n")
619
doc.write("\n")
620
621
def doc_output(self, help_command, event_name, **kwargs):
622
doc = help_command.doc
623
doc.style.h2('Output')
624
operation_model = help_command.obj
625
output_shape = operation_model.output_shape
626
if output_shape is None or not output_shape.members:
627
doc.write('None')
628
else:
629
for member_name, member_shape in output_shape.members.items():
630
self._doc_member(doc, member_name, member_shape, stack=[])
631
632
def doc_meta_description(self, help_command, **kwargs):
633
doc = help_command.doc
634
reference = help_command.event_class.replace('.', ' ')
635
doc.writeln(".. meta::")
636
doc.writeln(f" :description: Use the AWS CLI {AWS_CLI_VERSION} to run the {reference} command.")
637
doc.writeln("")
638
639
class TopicListerDocumentEventHandler(CLIDocumentEventHandler):
640
DESCRIPTION = (
641
'This is the AWS CLI Topic Guide. It gives access to a set '
642
'of topics that provide a deeper understanding of the CLI. To access '
643
'the list of topics from the command line, run ``aws help topics``. '
644
'To access a specific topic from the command line, run '
645
'``aws help [topicname]``, where ``topicname`` is the name of the '
646
'topic as it appears in the output from ``aws help topics``.'
647
)
648
649
def __init__(self, help_command):
650
self.help_command = help_command
651
self.register(help_command.session, help_command.event_class)
652
self._topic_tag_db = TopicTagDB()
653
self._topic_tag_db.load_json_index()
654
655
def doc_breadcrumbs(self, help_command, **kwargs):
656
doc = help_command.doc
657
if doc.target != 'man':
658
doc.write('[ ')
659
doc.style.sphinx_reference_label(label='cli:aws', text='aws')
660
doc.write(' ]')
661
662
def doc_title(self, help_command, **kwargs):
663
doc = help_command.doc
664
doc.style.new_paragraph()
665
doc.style.link_target_definition(
666
refname=f'cli:aws help {self.help_command.name}', link=''
667
)
668
doc.style.h1('AWS CLI Topic Guide')
669
670
def doc_description(self, help_command, **kwargs):
671
doc = help_command.doc
672
doc.style.h2('Description')
673
doc.include_doc_string(self.DESCRIPTION)
674
doc.style.new_paragraph()
675
676
def doc_synopsis_start(self, help_command, **kwargs):
677
pass
678
679
def doc_synopsis_end(self, help_command, **kwargs):
680
pass
681
682
def doc_options_start(self, help_command, **kwargs):
683
pass
684
685
def doc_options_end(self, help_command, **kwargs):
686
pass
687
688
def doc_global_option(self, help_command, **kwargs):
689
pass
690
691
def doc_subitems_start(self, help_command, **kwargs):
692
doc = help_command.doc
693
doc.style.h2('Available Topics')
694
695
categories = self._topic_tag_db.query('category')
696
topic_names = self._topic_tag_db.get_all_topic_names()
697
698
# Sort the categories
699
category_names = sorted(categories.keys())
700
for category_name in category_names:
701
doc.style.h3(category_name)
702
doc.style.new_paragraph()
703
# Write out the topic and a description for each topic under
704
# each category.
705
for topic_name in sorted(categories[category_name]):
706
description = self._topic_tag_db.get_tag_single_value(
707
topic_name, 'description'
708
)
709
doc.write('* ')
710
doc.style.sphinx_reference_label(
711
label=f'cli:aws help {topic_name}', text=topic_name
712
)
713
doc.write(f': {description}\n')
714
# Add a hidden toctree to make sure everything is connected in
715
# the document.
716
doc.style.hidden_toctree()
717
for topic_name in topic_names:
718
doc.style.hidden_tocitem(topic_name)
719
720
721
class TopicDocumentEventHandler(TopicListerDocumentEventHandler):
722
def doc_breadcrumbs(self, help_command, **kwargs):
723
doc = help_command.doc
724
if doc.target != 'man':
725
doc.write('[ ')
726
doc.style.sphinx_reference_label(label='cli:aws', text='aws')
727
doc.write(' . ')
728
doc.style.sphinx_reference_label(
729
label='cli:aws help topics', text='topics'
730
)
731
doc.write(' ]')
732
733
def doc_title(self, help_command, **kwargs):
734
doc = help_command.doc
735
doc.style.new_paragraph()
736
doc.style.link_target_definition(
737
refname=f'cli:aws help {self.help_command.name}', link=''
738
)
739
title = self._topic_tag_db.get_tag_single_value(
740
help_command.name, 'title'
741
)
742
doc.style.h1(title)
743
744
def doc_description(self, help_command, **kwargs):
745
doc = help_command.doc
746
topic_filename = os.path.join(
747
self._topic_tag_db.topic_dir, f'{help_command.name}.rst'
748
)
749
contents = self._remove_tags_from_content(topic_filename)
750
doc.writeln(contents)
751
doc.style.new_paragraph()
752
753
def _remove_tags_from_content(self, filename):
754
with open(filename) as f:
755
lines = f.readlines()
756
757
content_begin_index = 0
758
for i, line in enumerate(lines):
759
# If a line is encountered that does not begin with the tag
760
# end the search for tags and mark where tags end.
761
if not self._line_has_tag(line):
762
content_begin_index = i
763
break
764
765
# Join all of the non-tagged lines back together.
766
return ''.join(lines[content_begin_index:])
767
768
def _line_has_tag(self, line):
769
for tag in self._topic_tag_db.valid_tags:
770
if line.startswith(f':{tag}:'):
771
return True
772
return False
773
774
def doc_subitems_start(self, help_command, **kwargs):
775
pass
776
777
778
class GlobalOptionsDocumenter:
779
"""Documenter used to pre-generate global options docs."""
780
781
def __init__(self, help_command):
782
self._help_command = help_command
783
784
def _remove_multilines(self, s):
785
return re.sub(r'\n+', '\n', s)
786
787
def doc_global_options(self):
788
help_command = self._help_command
789
for arg in help_command.arg_table:
790
argument = help_command.arg_table.get(arg)
791
help_command.doc.writeln(
792
f"``{argument.cli_name}`` ({argument.cli_type_name})"
793
)
794
help_command.doc.style.indent()
795
help_command.doc.style.new_paragraph()
796
help_command.doc.include_doc_string(argument.documentation)
797
if argument.choices:
798
help_command.doc.style.start_ul()
799
for choice in argument.choices:
800
help_command.doc.style.li(choice)
801
help_command.doc.style.end_ul()
802
help_command.doc.style.dedent()
803
help_command.doc.style.new_paragraph()
804
global_options = help_command.doc.getvalue().decode('utf-8')
805
return self._remove_multilines(global_options)
806
807
def doc_global_synopsis(self):
808
help_command = self._help_command
809
for arg in help_command.arg_table:
810
argument = help_command.arg_table.get(arg)
811
if argument.cli_type_name == 'boolean':
812
arg_synopsis = f"[{argument.cli_name}]"
813
else:
814
arg_synopsis = f"[{argument.cli_name} <value>]"
815
help_command.doc.writeln(arg_synopsis)
816
global_synopsis = help_command.doc.getvalue().decode('utf-8')
817
return self._remove_multilines(global_synopsis)
818
819