Blob Blame Raw
commit a61fa7385cace7c290c44c31ad733bdf2f504860
Author: Jiri Kuncar <jiri.kuncar@gmail.com>
Date:   Tue Nov 14 09:24:07 2017 +0100

    Add :commands: option for click directive
    
    Improves control over which commands should be shown in documentation
    and in which order.
    
    Closes #18
    
    Signed-off-by: Jiri Kuncar <jiri.kuncar@gmail.com>

diff --git a/docs/usage.rst b/docs/usage.rst
index 9966c61..9c7bd43 100644
--- a/docs/usage.rst
+++ b/docs/usage.rst
@@ -30,6 +30,9 @@ directive`__.
    `:show-nested:`
      Enable full documentation for sub-commands.
 
+   `:commands:`
+     Document only listed commands.
+
 __ http://www.sphinx-doc.org/en/stable/extdev/markupapi.html
 __ http://click.pocoo.org/
 
@@ -54,6 +57,12 @@ module:
         """Greet a user."""
         click.echo('Hello %s' % user)
 
+    @greet.command()
+    def world(user):
+        """Greet the world."""
+        click.echo('Hello world!')
+
+
 To document this, use the following:
 
 .. code-block:: rst
@@ -70,6 +79,14 @@ output, add the ``show-nested`` flag.
       :prog: hello-world
       :show-nested:
 
+You can also document only selected commands by using ``:commands:`` option.
+
+.. code-block:: rst
+
+    .. click:: hello_world:greet
+      :prog: hello-world
+      :commands: hello
+
 Modifying ``sys.path``
 ----------------------
 
diff --git a/sphinx_click/ext.py b/sphinx_click/ext.py
index bb13765..c0420c5 100644
--- a/sphinx_click/ext.py
+++ b/sphinx_click/ext.py
@@ -153,16 +153,26 @@ def _format_envvars(ctx):
 
 def _format_subcommand(command):
     """Format a sub-command of a `click.Command` or `click.Group`."""
-    yield '.. object:: {}'.format(command[0])
+    yield '.. object:: {}'.format(command.name)
 
-    if command[1].short_help:
+    if command.short_help:
         yield ''
         for line in statemachine.string2lines(
-                command[1].short_help, tab_width=4, convert_whitespace=True):
+                command.short_help, tab_width=4, convert_whitespace=True):
             yield _indent(line)
 
+def _filter_commands(ctx, commands=None):
+    """Return list of used commands."""
+    if commands is None:
+        return sorted(getattr(ctx.command, 'commands', {}).values(),
+                      key=lambda item: item.name)
+    else:
+        names = [name.strip() for name in commands.split(',')]
+        lookup = getattr(ctx.command, 'commands', {})
+        return [lookup[name] for name in names if name in lookup]
+
 
-def _format_command(ctx, show_nested):
+def _format_command(ctx, show_nested, commands=None):
     """Format the output of `click.Command`."""
 
     # description
@@ -213,7 +223,7 @@ def _format_command(ctx, show_nested):
     if show_nested:
         return
 
-    commands = sorted(getattr(ctx.command, 'commands', {}).items())
+    commands = _filter_commands(ctx, commands)
 
     if commands:
         yield '.. rubric:: Commands'
@@ -232,6 +242,7 @@ class ClickDirective(rst.Directive):
     option_spec = {
         'prog': directives.unchanged_required,
         'show-nested': directives.flag,
+        'commands': directives.unchanged,
     }
 
     def _load_module(self, module_path):
@@ -266,7 +277,7 @@ class ClickDirective(rst.Directive):
 
         return getattr(mod, attr_name)
 
-    def _generate_nodes(self, name, command, parent=None, show_nested=False):
+    def _generate_nodes(self, name, command, parent=None, show_nested=False, commands=None):
         """Generate the relevant Sphinx nodes.
 
         Format a `click.Group` or `click.Command`.
@@ -275,6 +286,7 @@ class ClickDirective(rst.Directive):
         :param command: Instance of `click.Group` or `click.Command`
         :param parent: Instance of `click.Context`, or None
         :param show_nested: Whether subcommands should be included in output
+        :param commands: Display only listed commands or skip the section if empty
         :returns: A list of nested docutil nodes
         """
         ctx = click.Context(command, info_name=name, parent=parent)
@@ -292,7 +304,7 @@ class ClickDirective(rst.Directive):
         source_name = ctx.command_path
         result = statemachine.ViewList()
 
-        lines = _format_command(ctx, show_nested)
+        lines = _format_command(ctx, show_nested, commands)
         for line in lines:
             result.append(line, source_name)
 
@@ -301,11 +313,11 @@ class ClickDirective(rst.Directive):
         # Subcommands
 
         if show_nested:
-            commands = getattr(ctx.command, 'commands', {})
-            for command_name, command_obj in sorted(commands.items()):
+            commands = _filter_commands(ctx, commands)
+            for command in commands:
                 section.extend(self._generate_nodes(
-                    command_name,
-                    command_obj,
+                    command.name,
+                    command,
                     ctx,
                     show_nested))
 
@@ -322,8 +334,10 @@ class ClickDirective(rst.Directive):
             raise self.error(':prog: must be specified')
 
         show_nested = 'show-nested' in self.options
+        commands = self.options.get('commands')
 
-        return self._generate_nodes(prog_name, command, None, show_nested)
+        return self._generate_nodes(
+            prog_name, command, None, show_nested, commands)
 
 
 def setup(app):
diff --git a/tests/test_formatter.py b/tests/test_formatter.py
index 9288e01..db75f86 100644
--- a/tests/test_formatter.py
+++ b/tests/test_formatter.py
@@ -24,6 +24,8 @@ class GroupTestCase(unittest.TestCase):
         output = list(ext._format_command(ctx, show_nested=False))
 
         self.assertEqual(textwrap.dedent("""
+        A sample command group.
+
         .. program:: cli
         .. code-block:: shell
 
@@ -48,6 +50,8 @@ class GroupTestCase(unittest.TestCase):
         output = list(ext._format_command(ctx, show_nested=False))
 
         self.assertEqual(textwrap.dedent("""
+        A sample command group.
+
         .. program:: cli
         .. code-block:: shell
 
@@ -104,6 +108,8 @@ class NestedCommandsTestCase(unittest.TestCase):
         output = list(ext._format_command(ctx, show_nested=False))
 
         self.assertEqual(textwrap.dedent("""
+        A sample command group.
+
         .. program:: cli
         .. code-block:: shell
 
@@ -126,8 +132,71 @@ class NestedCommandsTestCase(unittest.TestCase):
         output = list(ext._format_command(ctx, show_nested=True))
 
         self.assertEqual(textwrap.dedent("""
+        A sample command group.
+
+        .. program:: cli
+        .. code-block:: shell
+
+            cli [OPTIONS] COMMAND [ARGS]...
+        """).lstrip(), '\n'.join(output))
+
+
+class CommandFilterTestCase(unittest.TestCase):
+
+    @staticmethod
+    def _get_ctx():
+
+        @click.group()
+        def cli():
+            """A sample command group."""
+
+        @cli.command()
+        def hello():
+            """A sample command."""
+
+        @cli.command()
+        def world():
+            """A world command."""
+
+        return click.Context(cli, info_name='cli')
+
+    def test_no_commands(self):
+        """Validate an empty command group."""
+
+        ctx = self._get_ctx()
+        output = list(ext._format_command(ctx, show_nested=False, commands=''))
+
+        self.assertEqual(textwrap.dedent("""
+        A sample command group.
+
+        .. program:: cli
+        .. code-block:: shell
+
+            cli [OPTIONS] COMMAND [ARGS]...
+        """).lstrip(), '\n'.join(output))
+
+
+    def test_order_of_commands(self):
+        """Validate the order of commands."""
+
+        ctx = self._get_ctx()
+        output = list(ext._format_command(ctx, show_nested=False, commands='world, hello'))
+
+        self.assertEqual(textwrap.dedent("""
+        A sample command group.
+
         .. program:: cli
         .. code-block:: shell
 
             cli [OPTIONS] COMMAND [ARGS]...
+
+        .. rubric:: Commands
+
+        .. object:: world
+
+            A world command.
+
+        .. object:: hello
+
+            A sample command.
         """).lstrip(), '\n'.join(output))