1- import inspect
1+ import collections . abc
22import functools
3+ import inspect
4+ import itertools
35import re
46import traceback
57import typing as ty
2123
2224LOG = logging .getLogger (__name__ )
2325
26+ NESTED_COMPLETE = 'complete'
2427NESTED_FULL = 'full'
2528NESTED_SHORT = 'short'
2629NESTED_NONE = 'none'
27- NestedT = ty .Literal ['full' , 'short' , 'none' , None ]
30+ NestedT = ty .Literal ['complete' , ' full' , 'short' , 'none' , None ]
2831
2932ANSI_ESC_SEQ_RE = re .compile (r'\x1B\[\d+(;\d+){0,2}m' , flags = re .MULTILINE )
3033
@@ -263,7 +266,7 @@ def _format_arguments(ctx: click.Context) -> ty.Generator[str, None, None]:
263266
264267def _format_envvar (
265268 ctx : click .Context ,
266- param : ty . Union [ click .core .Option , click . Argument ] ,
269+ param : click .core .Parameter ,
267270) -> ty .Generator [str , None , None ]:
268271 """Format the envvars of a `click.Option` or `click.Argument`."""
269272 command_name = _format_command_name (ctx )
@@ -367,21 +370,52 @@ def _filter_commands(
367370 return [lookup [command ] for command in commands if command in lookup ]
368371
369372
373+ def _format_header (ctx : click .Context ) -> ty .Generator [str , None , None ]:
374+ for line in _format_description (ctx ):
375+ yield line
376+
377+ yield '.. _{command_name}:' .format (
378+ command_name = _format_command_name (ctx ),
379+ )
380+ yield ''
381+ yield '.. program:: {}' .format (ctx .command_path )
382+
383+
384+ def _format_subcommand_summary (
385+ ctx : click .Context ,
386+ commands : ty .Optional [ty .List [str ]] = None ,
387+ ) -> ty .Generator [str , None , None ]:
388+ command_objs = _filter_commands (ctx , commands )
389+
390+ if command_objs :
391+ yield '.. rubric:: Commands'
392+ yield ''
393+
394+ for command_obj in command_objs :
395+ # Don't show hidden subcommands
396+ if command_obj .hidden :
397+ continue
398+
399+ for line in _format_subcommand (command_obj ):
400+ yield line
401+ yield ''
402+
403+
370404def _format_command (
371405 ctx : click .Context ,
372406 nested : NestedT ,
373407 commands : ty .Optional [ty .List [str ]] = None ,
408+ hide_header : bool = False ,
374409) -> ty .Generator [str , None , None ]:
375410 """Format the output of `click.Command`."""
376411 if ctx .command .hidden :
377412 return None
378413
379414 # description
380415
381- for line in _format_description (ctx ):
382- yield line
383-
384- yield '.. program:: {}' .format (ctx .command_path )
416+ if nested == NESTED_NONE or not hide_header :
417+ for line in _format_header (ctx ):
418+ yield line
385419
386420 # usage
387421
@@ -429,24 +463,34 @@ def _format_command(
429463 if nested in (NESTED_FULL , NESTED_NONE ):
430464 return
431465
432- command_objs = _filter_commands (ctx , commands )
466+ for line in _format_subcommand_summary (ctx , commands ):
467+ yield line
433468
434- if command_objs :
435- yield '.. rubric:: Commands'
436- yield ''
437469
438- for command_obj in command_objs :
439- # Don't show hidden subcommands
440- if command_obj .hidden :
441- continue
470+ def _format_summary (
471+ ctx : click .Context ,
472+ commands : ty .Optional [ty .List [str ]] = None ,
473+ hide_header : bool = False ,
474+ ) -> ty .Generator [str , None , None ]:
475+ """Format the output of `click.Command`."""
476+ if ctx .command .hidden :
477+ return
442478
443- for line in _format_subcommand (command_obj ):
479+ if not hide_header :
480+ # description
481+ for line in _format_header (ctx ):
444482 yield line
445- yield ''
483+
484+ # usage
485+ for line in _format_usage (ctx ):
486+ yield line
487+
488+ for line in _format_subcommand_summary (ctx , commands ):
489+ yield line
446490
447491
448492def nested (argument : ty .Optional [str ]) -> NestedT :
449- values = (NESTED_FULL , NESTED_SHORT , NESTED_NONE , None )
493+ values = (NESTED_COMPLETE , NESTED_FULL , NESTED_SHORT , NESTED_NONE , None )
450494
451495 if argument not in values :
452496 raise ValueError (
@@ -465,6 +509,7 @@ class ClickDirective(rst.Directive):
465509 'nested' : nested ,
466510 'commands' : directives .unchanged ,
467511 'show-nested' : directives .flag ,
512+ 'hide-header' : directives .flag ,
468513 }
469514
470515 def _load_module (self , module_path : str ) -> ty .Union [click .Command , click .Group ]:
@@ -513,7 +558,8 @@ def _generate_nodes(
513558 nested : NestedT ,
514559 commands : ty .Optional [ty .List [str ]] = None ,
515560 semantic_group : bool = False ,
516- ) -> ty .List [nodes .section ]:
561+ hide_header : bool = False ,
562+ ) -> ty .List [nodes .Element ]:
517563 """Generate the relevant Sphinx nodes.
518564
519565 Format a `click.Group` or `click.Command`.
@@ -526,65 +572,96 @@ def _generate_nodes(
526572 empty
527573 :param semantic_group: Display command as title and description for
528574 `click.CommandCollection`.
575+ :param hide_header: Hide the title and summary.
529576 :returns: A list of nested docutil nodes
530577 """
531578 ctx = click .Context (command , info_name = name , parent = parent )
532579
533580 if command .hidden :
534581 return []
535582
536- # Title
537-
538- section = nodes .section (
539- '' ,
540- nodes .title (text = name ),
541- ids = [nodes .make_id (ctx .command_path )],
542- names = [nodes .fully_normalize_name (ctx .command_path )],
543- )
544-
545583 # Summary
546584 source_name = ctx .command_path
547585 result = statemachine .StringList ()
548586
587+ lines : collections .abc .Iterator [str ] = iter (())
588+ hide_current_header = hide_header
589+ if nested == NESTED_COMPLETE :
590+ lines = itertools .chain (lines , _format_summary (ctx , commands , hide_header ))
591+ nested = ty .cast (NestedT , NESTED_FULL )
592+ hide_current_header = True
593+
549594 ctx .meta ["sphinx-click-env" ] = self .env
550595 if semantic_group :
551- lines = _format_description (ctx )
596+ lines = itertools . chain ( lines , _format_description (ctx ) )
552597 else :
553- lines = _format_command (ctx , nested , commands )
598+ lines = itertools .chain (
599+ lines , _format_command (ctx , nested , commands , hide_current_header )
600+ )
554601
555602 for line in lines :
556603 LOG .debug (line )
557604 result .append (line , source_name )
558605
559- sphinx_nodes .nested_parse_with_titles (self .state , result , section )
560-
561606 # Subcommands
562607
608+ subcommand_nodes = []
563609 if nested == NESTED_FULL :
564610 if isinstance (command , click .CommandCollection ):
565611 for source in command .sources :
566- section .extend (
612+ subcommand_nodes .extend (
567613 self ._generate_nodes (
568614 source .name ,
569615 source ,
570616 parent = ctx ,
571617 nested = nested ,
572618 semantic_group = True ,
619+ hide_header = False , # Hiding the header should not propagate to children
573620 )
574621 )
575622 else :
576623 commands = _filter_commands (ctx , commands )
577624 for command in commands :
578625 parent = ctx if not semantic_group else ctx .parent
579- section .extend (
626+ subcommand_nodes .extend (
580627 self ._generate_nodes (
581- command .name , command , parent = parent , nested = nested
628+ command .name ,
629+ command ,
630+ parent = parent ,
631+ nested = nested ,
632+ hide_header = False , # Hiding the header should not propagate to children
582633 )
583634 )
584635
585- return [section ]
636+ final_nodes : ty .List [nodes .Element ]
637+ section : nodes .Element
638+ if hide_header :
639+ final_nodes = subcommand_nodes
586640
587- def run (self ) -> ty .Sequence [nodes .section ]:
641+ if nested == NESTED_NONE or nested == NESTED_SHORT :
642+ section = nodes .paragraph ()
643+ self .state .nested_parse (result , 0 , section )
644+ final_nodes .insert (0 , section )
645+
646+ else :
647+ # Title
648+
649+ section = nodes .section (
650+ '' ,
651+ nodes .title (text = name ),
652+ ids = [nodes .make_id (ctx .command_path )],
653+ names = [nodes .fully_normalize_name (ctx .command_path )],
654+ )
655+
656+ sphinx_nodes .nested_parse_with_titles (self .state , result , section )
657+
658+ for node in subcommand_nodes :
659+ section .append (node )
660+ final_nodes = [section ]
661+
662+ return final_nodes
663+
664+ def run (self ) -> ty .Sequence [nodes .Element ]:
588665 self .env = self .state .document .settings .env
589666
590667 command = self ._load_module (self .arguments [0 ])
@@ -595,6 +672,7 @@ def run(self) -> ty.Sequence[nodes.section]:
595672 prog_name = self .options ['prog' ]
596673 show_nested = 'show-nested' in self .options
597674 nested = self .options .get ('nested' )
675+ hide_header = 'hide-header' in self .options
598676
599677 if show_nested :
600678 if nested :
@@ -614,7 +692,9 @@ def run(self) -> ty.Sequence[nodes.section]:
614692 command .strip () for command in self .options ['commands' ].split (',' )
615693 ]
616694
617- return self ._generate_nodes (prog_name , command , None , nested , commands )
695+ return self ._generate_nodes (
696+ prog_name , command , None , nested , commands , False , hide_header
697+ )
618698
619699
620700def setup (app : application .Sphinx ) -> ty .Dict [str , ty .Any ]:
0 commit comments