33# Licensed under the Apache license (see LICENSE)
44import inspect
55from contextlib import contextmanager , ExitStack
6- from typing import Iterator , List , cast
6+ from typing import Iterator , List , cast , Optional
77
88import click
99from markdown .extensions .toc import slugify
@@ -18,6 +18,7 @@ def make_command_docs(
1818 style : str = "plain" ,
1919 remove_ascii_art : bool = False ,
2020 show_hidden : bool = False ,
21+ list_subcommands : bool = False ,
2122 has_attr_list : bool = False ,
2223) -> Iterator [str ]:
2324 """Create the Markdown lines for a command and its sub-commands."""
@@ -28,6 +29,7 @@ def make_command_docs(
2829 style = style ,
2930 remove_ascii_art = remove_ascii_art ,
3031 show_hidden = show_hidden ,
32+ list_subcommands = list_subcommands ,
3133 has_attr_list = has_attr_list ,
3234 ):
3335 if line .strip () == "\b " :
@@ -44,10 +46,11 @@ def _recursively_make_command_docs(
4446 style : str = "plain" ,
4547 remove_ascii_art : bool = False ,
4648 show_hidden : bool = False ,
49+ list_subcommands : bool = False ,
4750 has_attr_list : bool = False ,
4851) -> Iterator [str ]:
4952 """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 )
5154
5255 if ctx .command .hidden and not show_hidden :
5356 return
@@ -58,24 +61,43 @@ def _recursively_make_command_docs(
5861 yield from _make_options (ctx , style , show_hidden = show_hidden )
5962
6063 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+ )
6176
62- for command in sorted ( subcommands , key = lambda cmd : cmd . name ): # type: ignore
77+ for command in subcommands :
6378 yield from _recursively_make_command_docs (
6479 cast (str , command .name ),
6580 command ,
6681 parent = ctx ,
6782 depth = depth + 1 ,
6883 style = style ,
6984 show_hidden = show_hidden ,
85+ list_subcommands = list_subcommands ,
7086 has_attr_list = has_attr_list ,
7187 )
7288
7389
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+
7496def _get_sub_commands (command : click .Command , ctx : click .Context ) -> List [click .Command ]:
7597 """Return subcommands of a Click command."""
7698 subcommands = getattr (command , "commands" , {})
7799 if subcommands :
78- return subcommands .values () # type: ignore
100+ return list ( subcommands .values ())
79101
80102 if not isinstance (command , click .MultiCommand ):
81103 return []
@@ -131,26 +153,29 @@ def _make_description(ctx: click.Context, remove_ascii_art: bool = False) -> Ite
131153 """Create markdown lines based on the command's own description."""
132154 help_string = ctx .command .help or ctx .command .short_help
133155
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 )
152161
162+ if not remove_ascii_art :
163+ yield from help_string .splitlines ()
153164 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 ""
154179
155180
156181def _make_usage (ctx : click .Context ) -> Iterator [str ]:
@@ -300,3 +325,27 @@ def _make_table_options(ctx: click.Context, show_hidden: bool = False) -> Iterat
300325 yield "| ---- | ---- | ----------- | ------- |"
301326 yield from option_rows
302327 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