diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b8d7bd..e260e52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,8 @@ Note: Breaking changes between versions are indicated by "💥". ## Unreleased -- [Improvement] Move ``-r/--root`` option to parent command level +- [Feature] Add plugin subcommands +- 💥[Improvement] Move ``-r/--root`` option to parent command level - [Bugfix] Fix course about page visibility - [Improvement] Print gunicorn access logs in the console - 💥[Improvement] Get rid of the `indexcourses` and `portainer` command (#269) diff --git a/docs/plugins.rst b/docs/plugins.rst index c67f0b6..101a2ff 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -175,6 +175,43 @@ Example:: With the above declaration, you can store plugin-specific templates in the ``templates/myplugin`` folder next to the ``plugin.py`` file. +``command`` +~~~~~~~~~~~ + +A plugin can provide custom command line commands. Commands are assumed to be `click.Command `__ objects. + +Example:: + + import click + + @click.command(help="I'm a plugin command") + def command(): + click.echo("Hello from myplugin!") + +Any user who installs the ``myplugin`` plugin can then run:: + + $ tutor myplugin + Hello from myplugin! + +You can even define subcommands by creating `command groups `__:: + + import click + + @click.group(help="I'm a plugin command group") + def command(): + pass + + @click.command(help="I'm a plugin subcommand") + def dosomething(): + click.echo("This subcommand is awesome") + +This would allow any user to run:: + + $ tutor myplugin dosomething + This subcommand is awesome + +See the official `click documentation `__ for more information. + Creating a new plugin --------------------- diff --git a/tutor/commands/cli.py b/tutor/commands/cli.py index 9f02e50..69a6b4b 100755 --- a/tutor/commands/cli.py +++ b/tutor/commands/cli.py @@ -11,7 +11,7 @@ from .dev import dev from .images import images_command from .k8s import k8s from .local import local -from .plugins import plugins_command +from .plugins import plugins_command, add_plugin_commands from .ui import ui from .webui import webui from ..__about__ import __version__ @@ -66,6 +66,7 @@ cli.add_command(ui) cli.add_command(webui) cli.add_command(print_help) cli.add_command(plugins_command) +add_plugin_commands(cli) if __name__ == "__main__": main() diff --git a/tutor/commands/plugins.py b/tutor/commands/plugins.py index c0806a9..7874180 100644 --- a/tutor/commands/plugins.py +++ b/tutor/commands/plugins.py @@ -62,14 +62,15 @@ def disable(context, plugin_names): ) -def iter_extra_plugin_commands(): +def add_plugin_commands(command_group): """ - TODO document this. Merge with plugins.iter_commands? It's good to keepo - click-related stuff outside of the plugins module. + Add commands provided by all plugins to the given command group. Each command is + added with a name that is equal to the plugin name. """ for plugin_name, command in plugins.iter_commands(): - command.name = plugin_name - yield command + if isinstance(command, click.Command): + command.name = plugin_name + command_group.add_command(command) plugins_command.add_command(list_command) diff --git a/tutor/plugins.py b/tutor/plugins.py index 79b6515..09d1c51 100644 --- a/tutor/plugins.py +++ b/tutor/plugins.py @@ -160,9 +160,7 @@ def iter_templates(config): def iter_commands(): """ - TODO doesn't this slow down the cli? (we need to import all plugins) - Also, do we really need the `config` argument? Do we want to make it possible for disabled plugins to be loaded? - TODO document this + Iterate over all plugins that provide a `command` attribute. """ for plugin_name, plugin in iter_installed(): command = getattr(plugin, "command", None)