@@ -126,8 +126,8 @@ def _make_id(self) -> Callable[[str], str]:
126126 return make_id_lower if "force_refs_lower" in self .options else make_id
127127
128128 def _load_sub_parsers (
129- self , sub_parser : _SubParsersAction [ArgumentParser ]
130- ) -> Iterator [tuple [list [str ], str , ArgumentParser ]]:
129+ self , sub_parser : _SubParsersAction [ArgumentParser ], parent_cmd : str = ""
130+ ) -> Iterator [tuple [list [str ], str , ArgumentParser , str ]]:
131131 parser_to_args : dict [int , list [str ]] = defaultdict (list )
132132 for key , parser in sub_parser ._name_parser_map .items (): # noqa: SLF001
133133 parser_to_args [id (parser )].append (key )
@@ -144,12 +144,13 @@ def _load_sub_parsers(
144144 help_msg = next ((a .help for a in sub_parser ._choices_actions if a .dest == name ), None ) # noqa: SLF001
145145 if help_msg == SUPPRESS :
146146 continue
147- yield aliases , help_msg or "" , parser
147+ sub_cmd = f"{ parent_cmd } { name } " .strip ()
148+ yield aliases , help_msg or "" , parser , sub_cmd
148149
149150 if (sub_sub_parser := _sub_parser_action (parser )) is not None :
150- yield from self ._load_sub_parsers (sub_sub_parser )
151+ yield from self ._load_sub_parsers (sub_sub_parser , sub_cmd )
151152
152- def _iter_sub_commands (self ) -> Iterator [tuple [list [str ], str , ArgumentParser ]]:
153+ def _iter_sub_commands (self ) -> Iterator [tuple [list [str ], str , ArgumentParser , str ]]:
153154 if (sub_parser := _sub_parser_action (self .parser )) is not None :
154155 yield from self ._load_sub_parsers (sub_parser )
155156
@@ -177,10 +178,9 @@ def run(self) -> list[Node]:
177178 actions ,
178179 self .parser ,
179180 prefix = self .parser .prog .split ("/" )[- 1 ],
180- prog = self .parser .prog .split ("/" )[- 1 ],
181181 )
182- for aliases , help_msg , parser in self ._iter_sub_commands ():
183- home_section += self ._mk_sub_command (aliases , help_msg , parser )
182+ for aliases , help_msg , parser , sub_cmd in self ._iter_sub_commands ():
183+ home_section += self ._mk_sub_command (aliases , help_msg , parser , sub_cmd )
184184
185185 if epilog := self ._pre_format (self .options .get ("epilog" , self .parser .epilog ), self .parser ):
186186 home_section += epilog
@@ -204,13 +204,19 @@ def _pre_format(self, block: str | None, parser: ArgumentParser) -> paragraph |
204204 return para
205205
206206 def _mk_option_group (
207- self , group : _ArgumentGroup , actions : list [Action ], parser : ArgumentParser , prefix : str , prog : str
207+ self ,
208+ group : _ArgumentGroup ,
209+ actions : list [Action ],
210+ parser : ArgumentParser ,
211+ prefix : str ,
212+ sub_cmd : str | None = None ,
208213 ) -> section :
214+ prog = self .parser .prog .split ("/" )[- 1 ]
209215 sub_title_prefix : str = self .options .get ("group_sub_title_prefix" )
210216 title_prefix = self .options .get ("group_title_prefix" )
211217 # an untitled group borrows its description as heading so its anchor stays unique
212218 group_title = group .title or group .description or "arguments"
213- title_text = self ._build_opt_grp_title ( group_title , prefix , prog , sub_title_prefix , title_prefix )
219+ title_text = self ._resolve_prefix ( prog , sub_cmd , prefix , title_prefix , sub_title_prefix ) + group_title
214220 title_ref : str = f"{ prefix } { ' ' if prefix else '' } { group_title } "
215221 ref_id = self ._make_id (title_ref )
216222 # the text sadly needs to be prefixed, because otherwise the autosectionlabel will conflict
@@ -225,12 +231,6 @@ def _mk_option_group(
225231 group_section += opt_group
226232 return group_section
227233
228- def _build_opt_grp_title (
229- self , group_title : str , prefix : str , prog : str , sub_title_prefix : str , title_prefix : str
230- ) -> str :
231- sub_cmd = prefix [len (prog ) :].strip () or None if prefix != prog else None
232- return self ._resolve_prefix (prog , sub_cmd , prefix , title_prefix , sub_title_prefix ) + group_title
233-
234234 def _mk_option_line (self , parser : ArgumentParser , action : Action , prefix : str ) -> list_item :
235235 line = paragraph ()
236236 if action .option_strings :
@@ -308,15 +308,16 @@ def _register_ref(
308308 self ._std_domain .anonlabels [name ] = doc_name , ref_name
309309 self ._std_domain .labels [name ] = doc_name , ref_name , ref_title
310310
311- def _mk_sub_command (self , aliases : list [str ], help_msg : str , parser : ArgumentParser ) -> section :
311+ def _mk_sub_command (self , aliases : list [str ], help_msg : str , parser : ArgumentParser , sub_cmd : str ) -> section :
312312 sub_title_prefix : str = self .options .get ("group_sub_title_prefix" )
313313 title_prefix : str = self .options .get ("group_title_prefix" )
314314
315315 if sys .version_info >= (3 , 14 ): # pragma: >=3.14 cover
316316 # https://github.com/python/cpython/issues/139809
317317 parser .prog = _strip_ansi_colors (parser .prog )
318318
319- title_text = self ._build_sub_cmd_title (parser , sub_title_prefix , title_prefix )
319+ root_prog = self .parser .prog .split ("/" )[- 1 ]
320+ title_text = self ._resolve_prefix (root_prog , sub_cmd , parser .prog , title_prefix , sub_title_prefix ).rstrip ()
320321 title_ref : str = parser .prog
321322 if aliases :
322323 aliases_text : str = f" ({ ', ' .join (aliases )} )"
@@ -338,18 +339,11 @@ def _mk_sub_command(self, aliases: list[str], help_msg: str, parser: ArgumentPar
338339
339340 for group in parser ._action_groups : # noqa: SLF001
340341 if actions := _visible_actions (group ):
341- group_section += self ._mk_option_group (
342- group , actions , parser , prefix = parser .prog , prog = self .parser .prog .split ("/" )[- 1 ]
343- )
342+ group_section += self ._mk_option_group (group , actions , parser , prefix = parser .prog , sub_cmd = sub_cmd )
344343 if epilog := self ._pre_format (parser .epilog , parser ):
345344 group_section += epilog
346345 return group_section
347346
348- def _build_sub_cmd_title (self , parser : ArgumentParser , sub_title_prefix : str , title_prefix : str ) -> str :
349- root_prog = self .parser .prog .split ("/" )[- 1 ]
350- sub_cmd = parser .prog [len (root_prog ) :].strip ().split (" " , maxsplit = 1 )[0 ]
351- return self ._resolve_prefix (root_prog , sub_cmd , parser .prog , title_prefix , sub_title_prefix ).rstrip ()
352-
353347 def _resolve_prefix (
354348 self ,
355349 prog_name : str ,
0 commit comments