3
3
# Licensed under the Apache license (see LICENSE)
4
4
import inspect
5
5
from contextlib import contextmanager , ExitStack
6
- from typing import Iterator , List , cast
6
+ from typing import Iterator , List , cast , Optional
7
7
8
8
import click
9
9
from markdown .extensions .toc import slugify
@@ -18,6 +18,7 @@ def make_command_docs(
18
18
style : str = "plain" ,
19
19
remove_ascii_art : bool = False ,
20
20
show_hidden : bool = False ,
21
+ list_subcommands : bool = False ,
21
22
has_attr_list : bool = False ,
22
23
) -> Iterator [str ]:
23
24
"""Create the Markdown lines for a command and its sub-commands."""
@@ -28,6 +29,7 @@ def make_command_docs(
28
29
style = style ,
29
30
remove_ascii_art = remove_ascii_art ,
30
31
show_hidden = show_hidden ,
32
+ list_subcommands = list_subcommands ,
31
33
has_attr_list = has_attr_list ,
32
34
):
33
35
if line .strip () == "\b " :
@@ -44,10 +46,11 @@ def _recursively_make_command_docs(
44
46
style : str = "plain" ,
45
47
remove_ascii_art : bool = False ,
46
48
show_hidden : bool = False ,
49
+ list_subcommands : bool = False ,
47
50
has_attr_list : bool = False ,
48
51
) -> Iterator [str ]:
49
52
"""Create the raw Markdown lines for a command and its sub-commands."""
50
- ctx = click . Context ( cast ( click . Command , command ), info_name = prog_name , parent = parent )
53
+ ctx = _build_command_context ( prog_name = prog_name , command = command , parent = parent )
51
54
52
55
if ctx .command .hidden and not show_hidden :
53
56
return
@@ -58,24 +61,43 @@ def _recursively_make_command_docs(
58
61
yield from _make_options (ctx , style , show_hidden = show_hidden )
59
62
60
63
subcommands = _get_sub_commands (ctx .command , ctx )
64
+ if len (subcommands ) == 0 :
65
+ return
66
+
67
+ subcommands .sort (key = lambda cmd : str (cmd .name ))
68
+
69
+ if list_subcommands :
70
+ yield from _make_subcommands_links (
71
+ subcommands ,
72
+ ctx ,
73
+ has_attr_list = has_attr_list ,
74
+ show_hidden = show_hidden ,
75
+ )
61
76
62
- for command in sorted ( subcommands , key = lambda cmd : cmd . name ): # type: ignore
77
+ for command in subcommands :
63
78
yield from _recursively_make_command_docs (
64
79
cast (str , command .name ),
65
80
command ,
66
81
parent = ctx ,
67
82
depth = depth + 1 ,
68
83
style = style ,
69
84
show_hidden = show_hidden ,
85
+ list_subcommands = list_subcommands ,
70
86
has_attr_list = has_attr_list ,
71
87
)
72
88
73
89
90
+ def _build_command_context (
91
+ prog_name : str , command : click .BaseCommand , parent : Optional [click .Context ]
92
+ ) -> click .Context :
93
+ return click .Context (cast (click .Command , command ), info_name = prog_name , parent = parent )
94
+
95
+
74
96
def _get_sub_commands (command : click .Command , ctx : click .Context ) -> List [click .Command ]:
75
97
"""Return subcommands of a Click command."""
76
98
subcommands = getattr (command , "commands" , {})
77
99
if subcommands :
78
- return subcommands .values () # type: ignore
100
+ return list ( subcommands .values ())
79
101
80
102
if not isinstance (command , click .MultiCommand ):
81
103
return []
@@ -131,26 +153,29 @@ def _make_description(ctx: click.Context, remove_ascii_art: bool = False) -> Ite
131
153
"""Create markdown lines based on the command's own description."""
132
154
help_string = ctx .command .help or ctx .command .short_help
133
155
134
- if help_string :
135
- # https://github.com/pallets/click/pull/2151
136
- help_string = inspect .cleandoc (help_string )
137
-
138
- if remove_ascii_art :
139
- skipped_ascii_art = True
140
- for i , line in enumerate (help_string .splitlines ()):
141
- if skipped_ascii_art is False :
142
- if not line .strip ():
143
- skipped_ascii_art = True
144
- continue
145
- elif i == 0 and line .strip () == "\b " :
146
- skipped_ascii_art = False
147
-
148
- if skipped_ascii_art :
149
- yield line
150
- else :
151
- yield from help_string .splitlines ()
156
+ if not help_string :
157
+ return
158
+
159
+ # https://github.com/pallets/click/pull/2151
160
+ help_string = inspect .cleandoc (help_string )
152
161
162
+ if not remove_ascii_art :
163
+ yield from help_string .splitlines ()
153
164
yield ""
165
+ return
166
+
167
+ skipped_ascii_art = True
168
+ for i , line in enumerate (help_string .splitlines ()):
169
+ if skipped_ascii_art is False :
170
+ if not line .strip ():
171
+ skipped_ascii_art = True
172
+ continue
173
+ elif i == 0 and line .strip () == "\b " :
174
+ skipped_ascii_art = False
175
+
176
+ if skipped_ascii_art :
177
+ yield line
178
+ yield ""
154
179
155
180
156
181
def _make_usage (ctx : click .Context ) -> Iterator [str ]:
@@ -300,3 +325,27 @@ def _make_table_options(ctx: click.Context, show_hidden: bool = False) -> Iterat
300
325
yield "| ---- | ---- | ----------- | ------- |"
301
326
yield from option_rows
302
327
yield ""
328
+
329
+
330
+ def _make_subcommands_links (
331
+ subcommands : List [click .Command ],
332
+ parent : click .Context ,
333
+ has_attr_list : bool ,
334
+ show_hidden : bool ,
335
+ ) -> Iterator [str ]:
336
+
337
+ yield "**Subcommands**"
338
+ yield ""
339
+ for command in subcommands :
340
+ command_name = cast (str , command .name )
341
+ ctx = _build_command_context (command_name , command , parent )
342
+ if ctx .command .hidden and not show_hidden :
343
+ continue
344
+ command_bullet = command_name if not has_attr_list else f"[{ command_name } ](#{ slugify (ctx .command_path , '-' )} )"
345
+ help_string = ctx .command .short_help or ctx .command .help
346
+ if help_string is not None :
347
+ help_string = help_string .splitlines ()[0 ]
348
+ else :
349
+ help_string = "*No description was provided with this command.*"
350
+ yield f"- *{ command_bullet } *: { help_string } "
351
+ yield ""
0 commit comments