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