@@ -130,8 +130,8 @@ def _raw_format(self) -> bool:
130130 return isinstance (formatter , type ) and issubclass (formatter , RawDescriptionHelpFormatter )
131131
132132 def _load_sub_parsers (
133- self , sub_parser : _SubParsersAction [ArgumentParser ]
134- ) -> Iterator [tuple [list [str ], str , ArgumentParser ]]:
133+ self , sub_parser : _SubParsersAction [ArgumentParser ], parent_cmd : str = ""
134+ ) -> Iterator [tuple [list [str ], str , ArgumentParser , str ]]:
135135 parser_to_args : dict [int , list [str ]] = defaultdict (list )
136136 for key , parser in sub_parser ._name_parser_map .items (): # noqa: SLF001
137137 parser_to_args [id (parser )].append (key )
@@ -146,14 +146,15 @@ def _load_sub_parsers(
146146 aliases .remove (name )
147147 # help is stored in a pseudo action
148148 help_msg = next ((a .help for a in sub_parser ._choices_actions if a .dest == name ), None ) or "" # noqa: SLF001
149- yield aliases , help_msg , parser
149+ sub_cmd = f"{ parent_cmd } { name } " .strip ()
150+ yield aliases , help_msg , parser , sub_cmd
150151
151152 if parser ._subparsers : # noqa: SLF001
152153 sub_sub_parser : _SubParsersAction [ArgumentParser ] = parser ._subparsers ._group_actions [0 ] # type: ignore[assignment] # noqa: SLF001
153154 if isinstance (sub_sub_parser , _SubParsersAction ):
154- yield from self ._load_sub_parsers (sub_sub_parser )
155+ yield from self ._load_sub_parsers (sub_sub_parser , sub_cmd )
155156
156- def _iter_sub_commands (self ) -> Iterator [tuple [list [str ], str , ArgumentParser ]]:
157+ def _iter_sub_commands (self ) -> Iterator [tuple [list [str ], str , ArgumentParser , str ]]:
157158 top_sub_parser = self .parser ._subparsers # noqa: SLF001
158159 if not top_sub_parser :
159160 return
@@ -183,8 +184,8 @@ def run(self) -> list[Node]:
183184 home_section += self ._mk_option_group (
184185 group , prefix = self .parser .prog .split ("/" )[- 1 ], prog = self .parser .prog .split ("/" )[- 1 ]
185186 )
186- for aliases , help_msg , parser in self ._iter_sub_commands ():
187- home_section += self ._mk_sub_command (aliases , help_msg , parser )
187+ for aliases , help_msg , parser , sub_cmd in self ._iter_sub_commands ():
188+ home_section += self ._mk_sub_command (aliases , help_msg , parser , sub_cmd )
188189
189190 if epilog := self ._pre_format (self .options .get ("epilog" , self .parser .epilog )):
190191 home_section += epilog
@@ -205,10 +206,10 @@ def _pre_format(self, block: str | None) -> paragraph | literal_block | None:
205206 _protect_option_dashes (para )
206207 return para
207208
208- def _mk_option_group (self , group : _ArgumentGroup , prefix : str , prog : str ) -> section :
209+ def _mk_option_group (self , group : _ArgumentGroup , prefix : str , prog : str , sub_cmd : str | None = None ) -> section :
209210 sub_title_prefix : str = self .options .get ("group_sub_title_prefix" )
210211 title_prefix = self .options .get ("group_title_prefix" )
211- title_text = self ._build_opt_grp_title ( group , prefix , prog , sub_title_prefix , title_prefix )
212+ title_text = self ._resolve_prefix ( prog , sub_cmd , prefix , title_prefix , sub_title_prefix ) + ( group . title or "" )
212213 title_ref : str = f"{ prefix } { ' ' if prefix else '' } { group .title } "
213214 ref_id = self ._make_id (title_ref )
214215 # the text sadly needs to be prefixed, because otherwise the autosectionlabel will conflict
@@ -226,14 +227,6 @@ def _mk_option_group(self, group: _ArgumentGroup, prefix: str, prog: str) -> sec
226227 group_section += opt_group
227228 return group_section
228229
229- def _build_opt_grp_title (
230- self , group : _ArgumentGroup , prefix : str , prog : str , sub_title_prefix : str , title_prefix : str
231- ) -> str :
232- sub_cmd = prefix [len (prog ) :].strip () or None if prefix != prog else None
233- title_text = self ._resolve_prefix (prog , sub_cmd , prefix , title_prefix , sub_title_prefix )
234- title_text += group .title or ""
235- return title_text
236-
237230 def _mk_option_line (self , action : Action , prefix : str ) -> list_item :
238231 line = paragraph ()
239232 as_key = action .dest
@@ -310,15 +303,16 @@ def _register_ref(
310303 self ._std_domain .anonlabels [name ] = doc_name , ref_name
311304 self ._std_domain .labels [name ] = doc_name , ref_name , ref_title
312305
313- def _mk_sub_command (self , aliases : list [str ], help_msg : str , parser : ArgumentParser ) -> section :
306+ def _mk_sub_command (self , aliases : list [str ], help_msg : str , parser : ArgumentParser , sub_cmd : str ) -> section :
314307 sub_title_prefix : str = self .options .get ("group_sub_title_prefix" )
315308 title_prefix : str = self .options .get ("group_title_prefix" )
316309
317310 if sys .version_info >= (3 , 14 ): # pragma: >=3.14 cover
318311 # https://github.com/python/cpython/issues/139809
319312 parser .prog = _strip_ansi_colors (parser .prog )
320313
321- title_text = self ._build_sub_cmd_title (parser , sub_title_prefix , title_prefix )
314+ root_prog = self .parser .prog .split ("/" )[- 1 ]
315+ title_text = self ._resolve_prefix (root_prog , sub_cmd , parser .prog , title_prefix , sub_title_prefix ).rstrip ()
322316 title_ref : str = parser .prog
323317 if aliases :
324318 aliases_text : str = f" ({ ', ' .join (aliases )} )"
@@ -346,14 +340,9 @@ def _mk_sub_command(self, aliases: list[str], help_msg: str, parser: ArgumentPar
346340 continue
347341 if isinstance (group ._group_actions [0 ], _SubParsersAction ): # noqa: SLF001
348342 continue
349- group_section += self ._mk_option_group (group , prefix = parser .prog , prog = self . parser . prog . split ( "/" )[ - 1 ] )
343+ group_section += self ._mk_option_group (group , prefix = parser .prog , prog = root_prog , sub_cmd = sub_cmd )
350344 return group_section
351345
352- def _build_sub_cmd_title (self , parser : ArgumentParser , sub_title_prefix : str , title_prefix : str ) -> str :
353- root_prog = self .parser .prog .split ("/" )[- 1 ]
354- sub_cmd = parser .prog [len (root_prog ) :].strip ().split (" " , maxsplit = 1 )[0 ]
355- return self ._resolve_prefix (root_prog , sub_cmd , parser .prog , title_prefix , sub_title_prefix ).rstrip ()
356-
357346 def _resolve_prefix (
358347 self ,
359348 prog_name : str ,
0 commit comments