summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArmin Ronacher <armin.ronacher@active-4.com>2014-04-29 22:07:15 +0200
committerArmin Ronacher <armin.ronacher@active-4.com>2014-04-29 22:07:15 +0200
commit055a8e6054700951c95aac3d9709e4538ec7c0b8 (patch)
treea3cc3a2a9e4a6ce5022201439c4b8ad5dc54e10a
parent51ebc82f828d392cb5b56ed202f60855ffb12733 (diff)
downloadclick-055a8e6054700951c95aac3d9709e4538ec7c0b8.tar.gz
Implemented basic command collections
-rw-r--r--click/__init__.py8
-rw-r--r--click/core.py29
-rw-r--r--docs/api.rst3
-rw-r--r--docs/commands.rst49
4 files changed, 85 insertions, 4 deletions
diff --git a/click/__init__.py b/click/__init__.py
index 43244bd..624a5ca 100644
--- a/click/__init__.py
+++ b/click/__init__.py
@@ -15,8 +15,8 @@
"""
# Core classes
-from .core import Context, Command, MultiCommand, Group, Parameter, \
- Option, Argument
+from .core import Context, Command, MultiCommand, Group, CommandCollection, \
+ Parameter, Option, Argument
# Decorators
from .decorators import pass_context, pass_obj, make_pass_decorator, \
@@ -35,8 +35,8 @@ from .exceptions import UsageError, Abort
__all__ = [
# Core classes
- 'Context', 'Command', 'MultiCommand', 'Group', 'Parameter',
- 'Option', 'Argument',
+ 'Context', 'Command', 'MultiCommand', 'Group', 'CommandCollection',
+ 'Parameter', 'Option', 'Argument',
# Decorators
'pass_context', 'pass_obj', 'make_pass_decorator', 'command', 'group',
diff --git a/click/core.py b/click/core.py
index eba72ad..7a72c48 100644
--- a/click/core.py
+++ b/click/core.py
@@ -471,6 +471,35 @@ class Group(MultiCommand):
return sorted(self.commands)
+class CommandCollection(MultiCommand):
+ """A command collection is a multi command that merges multiple multi
+ commands together into one. This is a straightforward implementation
+ that accepts a list of different multi commands as sources and
+ provides all the commands for each of them.
+ """
+
+ def __init__(self, name=None, sources=None, **attrs):
+ MultiCommand.__init__(self, name, **attrs)
+ #: The list of registered multi commands.
+ self.sources = sources or []
+
+ def add_source(self, multi_cmd):
+ """Adds a new multi command to the chain dispatcher."""
+ self.sources.append(multi_cmd)
+
+ def get_command(self, ctx, cmd_name):
+ for source in self.sources:
+ rv = source.get_command(ctx, cmd_name)
+ if rv is not None:
+ return rv
+
+ def list_commands(self, ctx):
+ rv = set()
+ for source in self.sources:
+ rv.update(source.list_commands(ctx))
+ return sorted(rv)
+
+
class Parameter(object):
"""A parameter to a command comes in two versions: they are either
:class:`Option`\s or :class:`Argument`\s. Other subclasses are currently
diff --git a/docs/api.rst b/docs/api.rst
index a957053..79fa046 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -52,6 +52,9 @@ Commands
.. autoclass:: Group
:members:
+.. autoclass:: CommandCollection
+ :members:
+
Parameters
----------
diff --git a/docs/commands.rst b/docs/commands.rst
index ec3a5b5..23427cf 100644
--- a/docs/commands.rst
+++ b/docs/commands.rst
@@ -205,3 +205,52 @@ These custom classes can also be used with decorators:
@click.command(cls=MyCLI)
def cli():
pass
+
+Merging Multi Commands
+----------------------
+
+In addition to implementing custom multi commands it can also be
+interesting to merge multiple together into one script. While this is
+generally not as recommended as nesting one below the other the merging
+approach can be useful in some circumstances for a nicer shell experience.
+
+A default implementation for such a merging system is the
+:class:`CommandCollection` class. It accepts a list of other multi
+commands and makes the commands available on the same class class. It
+accepts a list of other multi commands and makes the commands available on
+the same level.
+
+Example usage:
+
+.. click:example::
+
+ import click
+
+ @click.group()
+ def cli1():
+ pass
+
+ @cli1.command()
+ def cmd1():
+ """Command on cli1"""
+
+ @click.group()
+ def cli2():
+ pass
+
+ @cli1.command()
+ def cmd2():
+ """Command on cli2"""
+
+ cli = click.CommandCollection(sources=[cli1, cli2])
+
+ if __name__ == '__main__':
+ cli()
+
+And what it looks like:
+
+.. click:run::
+
+ invoke(cli, prog_name='cli', args=['--help'])
+
+In case a command exists on more than one source, the first source wins.