1
|
|
|
import os |
2
|
|
|
import sys |
3
|
|
|
from argparse import ArgumentParser |
4
|
|
|
|
5
|
|
|
from docutils import nodes |
6
|
|
|
from docutils.frontend import OptionParser |
7
|
|
|
from docutils.parsers.rst import Directive, Parser |
8
|
|
|
from docutils.parsers.rst.directives import flag, unchanged |
9
|
|
|
from docutils.statemachine import StringList |
10
|
|
|
from docutils.utils import new_document |
11
|
|
|
from sphinx.util.nodes import nested_parse_with_titles |
12
|
|
|
|
13
|
|
|
from sphinxarg.parser import parse_parser, parser_navigate |
14
|
|
|
|
15
|
|
|
from . import __version__ |
16
|
|
|
|
17
|
|
|
|
18
|
|
|
def map_nested_definitions(nested_content): |
19
|
|
|
if nested_content is None: |
20
|
|
|
raise Exception('Nested content should be iterable, not null') |
21
|
|
|
# build definition dictionary |
22
|
|
|
definitions = {} |
23
|
|
|
for item in nested_content: |
24
|
|
|
if not isinstance(item, nodes.definition_list): |
25
|
|
|
continue |
26
|
|
|
for subitem in item: |
27
|
|
|
if not isinstance(subitem, nodes.definition_list_item): |
28
|
|
|
continue |
29
|
|
|
if not len(subitem.children) > 0: |
30
|
|
|
continue |
31
|
|
|
classifier = '@after' |
32
|
|
|
idx = subitem.first_child_matching_class(nodes.classifier) |
33
|
|
|
if idx is not None: |
34
|
|
|
ci = subitem[idx] |
35
|
|
|
if len(ci.children) > 0: |
36
|
|
|
classifier = ci.children[0].astext() |
37
|
|
|
if classifier is not None and classifier not in ( |
38
|
|
|
'@replace', |
39
|
|
|
'@before', |
40
|
|
|
'@after', |
41
|
|
|
'@skip', |
42
|
|
|
): |
43
|
|
|
raise Exception(f'Unknown classifier: {classifier}') |
44
|
|
|
idx = subitem.first_child_matching_class(nodes.term) |
45
|
|
|
if idx is not None: |
46
|
|
|
term = subitem[idx] |
47
|
|
|
if len(term.children) > 0: |
48
|
|
|
term = term.children[0].astext() |
49
|
|
|
idx = subitem.first_child_matching_class(nodes.definition) |
50
|
|
|
if idx is not None: |
51
|
|
|
subcontent = [] |
52
|
|
|
for _ in subitem[idx]: |
53
|
|
|
if isinstance(_, nodes.definition_list): |
54
|
|
|
subcontent.append(_) |
55
|
|
|
definitions[term] = (classifier, subitem[idx], subcontent) |
56
|
|
|
|
57
|
|
|
return definitions |
58
|
|
|
|
59
|
|
|
|
60
|
|
|
def render_list(l, markdown_help, settings=None): |
61
|
|
|
""" |
62
|
|
|
Given a list of reStructuredText or MarkDown sections, return a docutils node list |
63
|
|
|
""" |
64
|
|
|
if len(l) == 0: |
65
|
|
|
return [] |
66
|
|
|
if markdown_help: |
67
|
|
|
from sphinxarg.markdown import parse_markdown_block |
68
|
|
|
|
69
|
|
|
return parse_markdown_block('\n\n'.join(l) + '\n') |
70
|
|
|
else: |
71
|
|
|
all_children = [] |
72
|
|
|
for element in l: |
73
|
|
|
if isinstance(element, str): |
74
|
|
|
if settings is None: |
75
|
|
|
settings = OptionParser(components=(Parser,)).get_default_values() |
76
|
|
|
document = new_document(None, settings) |
77
|
|
|
Parser().parse(element + '\n', document) |
78
|
|
|
all_children += document.children |
79
|
|
|
elif isinstance(element, nodes.definition): |
80
|
|
|
all_children += element |
81
|
|
|
|
82
|
|
|
return all_children |
83
|
|
|
|
84
|
|
|
|
85
|
|
|
def print_action_groups(data, nested_content, markdown_help=False, settings=None): |
86
|
|
|
""" |
87
|
|
|
Process all 'action groups', which are also include 'Options' and 'Required |
88
|
|
|
arguments'. A list of nodes is returned. |
89
|
|
|
""" |
90
|
|
|
definitions = map_nested_definitions(nested_content) |
91
|
|
|
nodes_list = [] |
92
|
|
|
if 'action_groups' in data: |
93
|
|
|
for action_group in data['action_groups']: |
94
|
|
|
# Every action group is comprised of a section, holding a title, the description, and the option group (members) |
95
|
|
|
section = nodes.section(ids=[action_group['title']]) |
96
|
|
|
section += nodes.title(action_group['title'], action_group['title']) |
97
|
|
|
|
98
|
|
|
desc = [] |
99
|
|
|
if action_group['description']: |
100
|
|
|
desc.append(action_group['description']) |
101
|
|
|
# Replace/append/prepend content to the description according to nested content |
102
|
|
|
subcontent = [] |
103
|
|
|
if action_group['title'] in definitions: |
104
|
|
|
classifier, s, subcontent = definitions[action_group['title']] |
105
|
|
|
if classifier == '@replace': |
106
|
|
|
desc = [s] |
107
|
|
|
elif classifier == '@after': |
108
|
|
|
desc.append(s) |
109
|
|
|
elif classifier == '@before': |
110
|
|
|
desc.insert(0, s) |
111
|
|
|
elif classifier == '@skip': |
112
|
|
|
continue |
113
|
|
|
if len(subcontent) > 0: |
114
|
|
|
for k, v in map_nested_definitions(subcontent).items(): |
115
|
|
|
definitions[k] = v |
116
|
|
|
# Render appropriately |
117
|
|
|
for element in render_list(desc, markdown_help): |
118
|
|
|
section += element |
119
|
|
|
|
120
|
|
|
local_definitions = definitions |
121
|
|
|
if len(subcontent) > 0: |
122
|
|
|
local_definitions = {k: v for k, v in definitions.items()} |
123
|
|
|
for k, v in map_nested_definitions(subcontent).items(): |
124
|
|
|
local_definitions[k] = v |
125
|
|
|
|
126
|
|
|
items = [] |
127
|
|
|
# Iterate over action group members |
128
|
|
|
for entry in action_group['options']: |
129
|
|
|
# Members will include: |
130
|
|
|
# default The default value. This may be ==SUPPRESS== |
131
|
|
|
# name A list of option names (e.g., ['-h', '--help'] |
132
|
|
|
# help The help message string |
133
|
|
|
# There may also be a 'choices' member. |
134
|
|
|
# Build the help text |
135
|
|
|
arg = [] |
136
|
|
|
if 'choices' in entry: |
137
|
|
|
arg.append(f"Possible choices: {', '.join(str(c) for c in entry['choices'])}\n") |
138
|
|
|
if 'help' in entry: |
139
|
|
|
arg.append(entry['help']) |
140
|
|
|
if entry['default'] is not None and entry['default'] not in [ |
141
|
|
|
'"==SUPPRESS=="', |
142
|
|
|
'==SUPPRESS==', |
143
|
|
|
]: |
144
|
|
|
if entry['default'] == '': |
145
|
|
|
arg.append('Default: ""') |
146
|
|
|
else: |
147
|
|
|
arg.append(f"Default: {entry['default']}") |
148
|
|
|
|
149
|
|
|
# Handle nested content, the term used in the dict has the comma removed for simplicity |
150
|
|
|
desc = arg |
151
|
|
|
term = ' '.join(entry['name']) |
152
|
|
|
if term in local_definitions: |
153
|
|
|
classifier, s, subcontent = local_definitions[term] |
154
|
|
|
if classifier == '@replace': |
155
|
|
|
desc = [s] |
156
|
|
|
elif classifier == '@after': |
157
|
|
|
desc.append(s) |
158
|
|
|
elif classifier == '@before': |
159
|
|
|
desc.insert(0, s) |
160
|
|
|
term = ', '.join(entry['name']) |
161
|
|
|
|
162
|
|
|
n = nodes.option_list_item( |
163
|
|
|
'', |
164
|
|
|
nodes.option_group('', nodes.option_string(text=term)), |
165
|
|
|
nodes.description('', *render_list(desc, markdown_help, settings)), |
166
|
|
|
) |
167
|
|
|
items.append(n) |
168
|
|
|
|
169
|
|
|
section += nodes.option_list('', *items) |
170
|
|
|
nodes_list.append(section) |
171
|
|
|
|
172
|
|
|
return nodes_list |
173
|
|
|
|
174
|
|
|
|
175
|
|
|
def print_subcommands(data, nested_content, markdown_help=False, settings=None): # noqa: N803 |
176
|
|
|
""" |
177
|
|
|
Each subcommand is a dictionary with the following keys: |
178
|
|
|
|
179
|
|
|
['usage', 'action_groups', 'bare_usage', 'name', 'help'] |
180
|
|
|
|
181
|
|
|
In essence, this is all tossed in a new section with the title 'name'. |
182
|
|
|
Apparently there can also be a 'description' entry. |
183
|
|
|
""" |
184
|
|
|
|
185
|
|
|
definitions = map_nested_definitions(nested_content) |
186
|
|
|
items = [] |
187
|
|
|
if 'children' in data: |
188
|
|
|
subcommands = nodes.section(ids=["Sub-commands:"]) |
189
|
|
|
subcommands += nodes.title('Sub-commands:', 'Sub-commands:') |
190
|
|
|
|
191
|
|
|
for child in data['children']: |
192
|
|
|
sec = nodes.section(ids=[child['name']]) |
193
|
|
|
sec += nodes.title(child['name'], child['name']) |
194
|
|
|
|
195
|
|
|
if 'description' in child and child['description']: |
196
|
|
|
desc = [child['description']] |
197
|
|
|
elif child['help']: |
198
|
|
|
desc = [child['help']] |
199
|
|
|
else: |
200
|
|
|
desc = ['Undocumented'] |
201
|
|
|
|
202
|
|
|
# Handle nested content |
203
|
|
|
subcontent = [] |
204
|
|
|
if child['name'] in definitions: |
205
|
|
|
classifier, s, subcontent = definitions[child['name']] |
206
|
|
|
if classifier == '@replace': |
207
|
|
|
desc = [s] |
208
|
|
|
elif classifier == '@after': |
209
|
|
|
desc.append(s) |
210
|
|
|
elif classifier == '@before': |
211
|
|
|
desc.insert(0, s) |
212
|
|
|
|
213
|
|
|
for element in render_list(desc, markdown_help): |
214
|
|
|
sec += element |
215
|
|
|
sec += nodes.literal_block(text=child['bare_usage']) |
216
|
|
|
for x in print_action_groups(child, nested_content + subcontent, markdown_help, settings=settings): |
217
|
|
|
sec += x |
218
|
|
|
|
219
|
|
|
for x in print_subcommands(child, nested_content + subcontent, markdown_help, settings=settings): |
220
|
|
|
sec += x |
221
|
|
|
|
222
|
|
|
if 'epilog' in child and child['epilog']: |
223
|
|
|
for element in render_list([child['epilog']], markdown_help): |
224
|
|
|
sec += element |
225
|
|
|
|
226
|
|
|
subcommands += sec |
227
|
|
|
items.append(subcommands) |
228
|
|
|
|
229
|
|
|
return items |
230
|
|
|
|
231
|
|
|
|
232
|
|
|
def ensure_unique_ids(items): |
233
|
|
|
""" |
234
|
|
|
If action groups are repeated, then links in the table of contents will |
235
|
|
|
just go to the first of the repeats. This may not be desirable, particularly |
236
|
|
|
in the case of subcommands where the option groups have different members. |
237
|
|
|
This function updates the title IDs by adding _repeatX, where X is a number |
238
|
|
|
so that the links are then unique. |
239
|
|
|
""" |
240
|
|
|
s = set() |
241
|
|
|
for item in items: |
242
|
|
|
for n in item.traverse(descend=True, siblings=True, ascend=False): |
243
|
|
|
if isinstance(n, nodes.section): |
244
|
|
|
ids = n['ids'] |
245
|
|
|
for idx, id in enumerate(ids): |
246
|
|
|
if id not in s: |
247
|
|
|
s.add(id) |
248
|
|
|
else: |
249
|
|
|
i = 1 |
250
|
|
|
while f"{id}_repeat{i}" in s: |
251
|
|
|
i += 1 |
252
|
|
|
ids[idx] = f"{id}_repeat{i}" |
253
|
|
|
s.add(ids[idx]) |
254
|
|
|
n['ids'] = ids |
255
|
|
|
|
256
|
|
|
|
257
|
|
|
class ArgParseDirective(Directive): |
258
|
|
|
has_content = True |
259
|
|
|
option_spec = dict( |
260
|
|
|
module=unchanged, |
261
|
|
|
func=unchanged, |
262
|
|
|
ref=unchanged, |
263
|
|
|
prog=unchanged, |
264
|
|
|
path=unchanged, |
265
|
|
|
nodefault=flag, |
266
|
|
|
nodefaultconst=flag, |
267
|
|
|
filename=unchanged, |
268
|
|
|
manpage=unchanged, |
269
|
|
|
nosubcommands=unchanged, |
270
|
|
|
passparser=flag, |
271
|
|
|
noepilog=unchanged, |
272
|
|
|
nodescription=unchanged, |
273
|
|
|
markdown=flag, |
274
|
|
|
markdownhelp=flag, |
275
|
|
|
) |
276
|
|
|
|
277
|
|
|
def _construct_manpage_specific_structure(self, parser_info): |
278
|
|
|
""" |
279
|
|
|
Construct a typical man page consisting of the following elements: |
280
|
|
|
NAME (automatically generated, out of our control) |
281
|
|
|
SYNOPSIS |
282
|
|
|
DESCRIPTION |
283
|
|
|
OPTIONS |
284
|
|
|
FILES |
285
|
|
|
SEE ALSO |
286
|
|
|
BUGS |
287
|
|
|
""" |
288
|
|
|
items = [] |
289
|
|
|
# SYNOPSIS section |
290
|
|
|
synopsis_section = nodes.section( |
291
|
|
|
'', |
292
|
|
|
nodes.title(text='Synopsis'), |
293
|
|
|
nodes.literal_block(text=parser_info["bare_usage"]), |
294
|
|
|
ids=['synopsis-section'], |
295
|
|
|
) |
296
|
|
|
items.append(synopsis_section) |
297
|
|
|
# DESCRIPTION section |
298
|
|
|
if 'nodescription' not in self.options: |
299
|
|
|
description_section = nodes.section( |
300
|
|
|
'', |
301
|
|
|
nodes.title(text='Description'), |
302
|
|
|
nodes.paragraph( |
303
|
|
|
text=parser_info.get( |
304
|
|
|
'description', |
305
|
|
|
parser_info.get('help', "undocumented").capitalize(), |
306
|
|
|
) |
307
|
|
|
), |
308
|
|
|
ids=['description-section'], |
309
|
|
|
) |
310
|
|
|
nested_parse_with_titles(self.state, self.content, description_section) |
311
|
|
|
items.append(description_section) |
312
|
|
|
if parser_info.get('epilog') and 'noepilog' not in self.options: |
313
|
|
|
# TODO: do whatever sphinx does to understand ReST inside |
314
|
|
|
# docstrings magically imported from other places. The nested |
315
|
|
|
# parse method invoked above seem to be able to do this but |
316
|
|
|
# I haven't found a way to do it for arbitrary text |
317
|
|
|
if description_section: |
318
|
|
|
description_section += nodes.paragraph(text=parser_info['epilog']) |
319
|
|
|
else: |
320
|
|
|
description_section = nodes.paragraph(text=parser_info['epilog']) |
321
|
|
|
items.append(description_section) |
322
|
|
|
# OPTIONS section |
323
|
|
|
options_section = nodes.section('', nodes.title(text='Options'), ids=['options-section']) |
324
|
|
|
if 'args' in parser_info: |
325
|
|
|
options_section += nodes.paragraph() |
326
|
|
|
options_section += nodes.subtitle(text='Positional arguments:') |
327
|
|
|
options_section += self._format_positional_arguments(parser_info) |
328
|
|
|
for action_group in parser_info['action_groups']: |
329
|
|
|
if 'options' in parser_info: |
330
|
|
|
options_section += nodes.paragraph() |
331
|
|
|
options_section += nodes.subtitle(text=action_group['title']) |
332
|
|
|
options_section += self._format_optional_arguments(action_group) |
333
|
|
|
|
334
|
|
|
# NOTE: we cannot generate NAME ourselves. It is generated by |
335
|
|
|
# docutils.writers.manpage |
336
|
|
|
# TODO: items.append(files) |
337
|
|
|
# TODO: items.append(see also) |
338
|
|
|
# TODO: items.append(bugs) |
339
|
|
|
|
340
|
|
|
if len(options_section.children) > 1: |
341
|
|
|
items.append(options_section) |
342
|
|
|
if 'nosubcommands' not in self.options: |
343
|
|
|
# SUBCOMMANDS section (non-standard) |
344
|
|
|
subcommands_section = nodes.section('', nodes.title(text='Sub-Commands'), ids=['subcommands-section']) |
345
|
|
|
if 'children' in parser_info: |
346
|
|
|
subcommands_section += self._format_subcommands(parser_info) |
347
|
|
|
if len(subcommands_section) > 1: |
348
|
|
|
items.append(subcommands_section) |
349
|
|
|
if os.getenv("INCLUDE_DEBUG_SECTION"): |
350
|
|
|
import json |
351
|
|
|
|
352
|
|
|
# DEBUG section (non-standard) |
353
|
|
|
debug_section = nodes.section( |
354
|
|
|
'', |
355
|
|
|
nodes.title(text="Argparse + Sphinx Debugging"), |
356
|
|
|
nodes.literal_block(text=json.dumps(parser_info, indent=' ')), |
357
|
|
|
ids=['debug-section'], |
358
|
|
|
) |
359
|
|
|
items.append(debug_section) |
360
|
|
|
return items |
361
|
|
|
|
362
|
|
|
def _format_positional_arguments(self, parser_info): |
363
|
|
|
assert 'args' in parser_info |
364
|
|
|
items = [] |
365
|
|
|
for arg in parser_info['args']: |
366
|
|
|
arg_items = [] |
367
|
|
|
if arg['help']: |
368
|
|
|
arg_items.append(nodes.paragraph(text=arg['help'])) |
369
|
|
|
elif 'choices' not in arg: |
370
|
|
|
arg_items.append(nodes.paragraph(text='Undocumented')) |
371
|
|
|
if 'choices' in arg: |
372
|
|
|
arg_items.append(nodes.paragraph(text='Possible choices: ' + ', '.join(arg['choices']))) |
373
|
|
|
items.append( |
374
|
|
|
nodes.option_list_item( |
375
|
|
|
'', |
376
|
|
|
nodes.option_group('', nodes.option('', nodes.option_string(text=arg['metavar']))), |
377
|
|
|
nodes.description('', *arg_items), |
378
|
|
|
) |
379
|
|
|
) |
380
|
|
|
return nodes.option_list('', *items) |
381
|
|
|
|
382
|
|
|
def _format_optional_arguments(self, parser_info): |
383
|
|
|
assert 'options' in parser_info |
384
|
|
|
items = [] |
385
|
|
|
for opt in parser_info['options']: |
386
|
|
|
names = [] |
387
|
|
|
opt_items = [] |
388
|
|
|
for name in opt['name']: |
389
|
|
|
option_declaration = [nodes.option_string(text=name)] |
390
|
|
|
if opt['default'] is not None and opt['default'] not in [ |
391
|
|
|
'"==SUPPRESS=="', |
392
|
|
|
'==SUPPRESS==', |
393
|
|
|
]: |
394
|
|
|
option_declaration += nodes.option_argument('', text='=' + str(opt['default'])) |
395
|
|
|
names.append(nodes.option('', *option_declaration)) |
396
|
|
|
if opt['help']: |
397
|
|
|
opt_items.append(nodes.paragraph(text=opt['help'])) |
398
|
|
|
elif 'choices' not in opt: |
399
|
|
|
opt_items.append(nodes.paragraph(text='Undocumented')) |
400
|
|
|
if 'choices' in opt: |
401
|
|
|
opt_items.append(nodes.paragraph(text='Possible choices: ' + ', '.join(opt['choices']))) |
402
|
|
|
items.append( |
403
|
|
|
nodes.option_list_item( |
404
|
|
|
'', |
405
|
|
|
nodes.option_group('', *names), |
406
|
|
|
nodes.description('', *opt_items), |
407
|
|
|
) |
408
|
|
|
) |
409
|
|
|
return nodes.option_list('', *items) |
410
|
|
|
|
411
|
|
|
def _format_subcommands(self, parser_info): |
412
|
|
|
assert 'children' in parser_info |
413
|
|
|
items = [] |
414
|
|
|
for subcmd in parser_info['children']: |
415
|
|
|
subcmd_items = [] |
416
|
|
|
if subcmd['help']: |
417
|
|
|
subcmd_items.append(nodes.paragraph(text=subcmd['help'])) |
418
|
|
|
else: |
419
|
|
|
subcmd_items.append(nodes.paragraph(text='Undocumented')) |
420
|
|
|
items.append( |
421
|
|
|
nodes.definition_list_item( |
422
|
|
|
'', |
423
|
|
|
nodes.term('', '', nodes.strong(text=subcmd['bare_usage'])), |
424
|
|
|
nodes.definition('', *subcmd_items), |
425
|
|
|
) |
426
|
|
|
) |
427
|
|
|
return nodes.definition_list('', *items) |
428
|
|
|
|
429
|
|
|
def _nested_parse_paragraph(self, text): |
430
|
|
|
content = nodes.paragraph() |
431
|
|
|
self.state.nested_parse(StringList(text.split("\n")), 0, content) |
432
|
|
|
return content |
433
|
|
|
|
434
|
|
|
def run(self): |
435
|
|
|
if 'module' in self.options and 'func' in self.options: |
436
|
|
|
module_name = self.options['module'] |
437
|
|
|
attr_name = self.options['func'] |
438
|
|
|
elif 'ref' in self.options: |
439
|
|
|
_parts = self.options['ref'].split('.') |
440
|
|
|
module_name = '.'.join(_parts[0:-1]) |
441
|
|
|
attr_name = _parts[-1] |
442
|
|
|
elif 'filename' in self.options and 'func' in self.options: |
443
|
|
|
mod = {} |
444
|
|
|
try: |
445
|
|
|
f = open(self.options['filename']) |
446
|
|
|
except OSError: |
447
|
|
|
# try open with abspath |
448
|
|
|
f = open(os.path.abspath(self.options['filename'])) |
449
|
|
|
code = compile(f.read(), self.options['filename'], 'exec') |
450
|
|
|
exec(code, mod) |
451
|
|
|
attr_name = self.options['func'] |
452
|
|
|
func = mod[attr_name] |
453
|
|
|
else: |
454
|
|
|
raise self.error(':module: and :func: should be specified, or :ref:, or :filename: and :func:') |
455
|
|
|
|
456
|
|
|
# Skip this if we're dealing with a local file, since it obviously can't be imported |
457
|
|
|
if 'filename' not in self.options: |
458
|
|
|
try: |
459
|
|
|
mod = __import__(module_name, globals(), locals(), [attr_name]) |
460
|
|
|
except ImportError: |
461
|
|
|
raise self.error(f'Failed to import "{attr_name}" from "{module_name}".\n{sys.exc_info()[1]}') |
462
|
|
|
|
463
|
|
|
if not hasattr(mod, attr_name): |
464
|
|
|
raise self.error(('Module "%s" has no attribute "%s"\n' 'Incorrect argparse :module: or :func: values?') % (module_name, attr_name)) |
465
|
|
|
func = getattr(mod, attr_name) |
466
|
|
|
|
467
|
|
|
if isinstance(func, ArgumentParser): |
468
|
|
|
parser = func |
469
|
|
|
elif 'passparser' in self.options: |
470
|
|
|
parser = ArgumentParser() |
471
|
|
|
func(parser) |
472
|
|
|
else: |
473
|
|
|
parser = func() |
474
|
|
|
if 'path' not in self.options: |
475
|
|
|
self.options['path'] = '' |
476
|
|
|
path = str(self.options['path']) |
477
|
|
|
if 'prog' in self.options: |
478
|
|
|
parser.prog = self.options['prog'] |
479
|
|
|
result = parse_parser( |
480
|
|
|
parser, |
481
|
|
|
skip_default_values='nodefault' in self.options, |
482
|
|
|
skip_default_const_values='nodefaultconst' in self.options, |
483
|
|
|
) |
484
|
|
|
result = parser_navigate(result, path) |
485
|
|
|
if 'manpage' in self.options: |
486
|
|
|
return self._construct_manpage_specific_structure(result) |
487
|
|
|
|
488
|
|
|
# Handle nested content, where markdown needs to be preprocessed |
489
|
|
|
items = [] |
490
|
|
|
nested_content = nodes.paragraph() |
491
|
|
|
if 'markdown' in self.options: |
492
|
|
|
from sphinxarg.markdown import parse_markdown_block |
493
|
|
|
|
494
|
|
|
items.extend(parse_markdown_block('\n'.join(self.content) + '\n')) |
495
|
|
|
else: |
496
|
|
|
self.state.nested_parse(self.content, self.content_offset, nested_content) |
497
|
|
|
nested_content = nested_content.children |
498
|
|
|
# add common content between |
499
|
|
|
for item in nested_content: |
500
|
|
|
if not isinstance(item, nodes.definition_list): |
501
|
|
|
items.append(item) |
502
|
|
|
|
503
|
|
|
markdown_help = False |
504
|
|
|
if 'markdownhelp' in self.options: |
505
|
|
|
markdown_help = True |
506
|
|
|
if 'description' in result and 'nodescription' not in self.options: |
507
|
|
|
if markdown_help: |
508
|
|
|
items.extend(render_list([result['description']], True)) |
509
|
|
|
else: |
510
|
|
|
items.append(self._nested_parse_paragraph(result['description'])) |
511
|
|
|
items.append(nodes.literal_block(text=result['usage'])) |
512
|
|
|
items.extend( |
513
|
|
|
print_action_groups( |
514
|
|
|
result, |
515
|
|
|
nested_content, |
516
|
|
|
markdown_help, |
517
|
|
|
settings=self.state.document.settings, |
518
|
|
|
) |
519
|
|
|
) |
520
|
|
|
if 'nosubcommands' not in self.options: |
521
|
|
|
items.extend( |
522
|
|
|
print_subcommands( |
523
|
|
|
result, |
524
|
|
|
nested_content, |
525
|
|
|
markdown_help, |
526
|
|
|
settings=self.state.document.settings, |
527
|
|
|
) |
528
|
|
|
) |
529
|
|
|
if 'epilog' in result and 'noepilog' not in self.options: |
530
|
|
|
items.append(self._nested_parse_paragraph(result['epilog'])) |
531
|
|
|
|
532
|
|
|
# Traverse the returned nodes, modifying the title IDs as necessary to avoid repeats |
533
|
|
|
ensure_unique_ids(items) |
534
|
|
|
|
535
|
|
|
return items |
536
|
|
|
|
537
|
|
|
|
538
|
|
|
def setup(app): |
539
|
|
|
app.add_directive('argparse', ArgParseDirective) |
540
|
|
|
return {'parallel_read_safe': True, 'version': __version__} |
541
|
|
|
|