diff --git a/docs/modules/cli.rst b/docs/modules/cli.rst
new file mode 100644
index 00000000..c3ea7186
--- /dev/null
+++ b/docs/modules/cli.rst
@@ -0,0 +1,31 @@
+.. _sec-modules-cli:
+
+octoprint.cli
+-------------
+
+.. automodule:: octoprint.cli
+ :members:
+
+.. _sec-modules-cli-devel:
+
+octoprint.cli.devel
+-------------------
+
+.. automodule:: octoprint.cli.devel
+ :members:
+
+.. _sec-modules-cli-plugins:
+
+octoprint.cli.plugins
+---------------------
+
+.. automodule:: octoprint.cli.plugins
+ :members:
+
+.. _sec-modules-cli-server:
+
+octoprint.cli.server
+--------------------
+
+.. automodule:: octoprint.cli.server
+ :members:
diff --git a/docs/modules/index.rst b/docs/modules/index.rst
index db82bdde..1cdb36a0 100644
--- a/docs/modules/index.rst
+++ b/docs/modules/index.rst
@@ -7,6 +7,7 @@ Internal Modules
.. toctree::
:maxdepth: 3
+ cli.rst
filemanager.rst
plugin.rst
printer.rst
diff --git a/docs/plugins/hooks.rst b/docs/plugins/hooks.rst
index da8e270d..2d5b7699 100644
--- a/docs/plugins/hooks.rst
+++ b/docs/plugins/hooks.rst
@@ -46,20 +46,87 @@ octoprint.cli.commands
By providing a handler for this hook plugins may register commands on OctoPrint's command line interface (CLI).
- .. todo::
+ Handlers are expected to return a list of callables annotated as `Click commands `_ to register with the
+ CLI.
- * More documentation
- * Example
+ The custom ``MultiCommand`` instance :class:`~octoprint.cli.plugins.OctoPrintPluginCommands` is provided
+ as parameter. Via that object handlers may access the *global* :class:`~octoprint.settings.Settings`
+ and the :class:`~octoprint.plugin.core.PluginManager` instance as ``cli_group.settings`` and ``cli_group.plugin_manager``.
+
+ **Example:**
+
+ Registers two new commands, ``custom_cli_command:greet`` and ``custom_cli_command:random`` with
+ OctoPrint:
+
+ .. onlineinclude:: https://raw.githubusercontent.com/OctoPrint/Plugin-Examples/master/custom_cli_command.py
+ :linenos:
+ :tab-width: 4
+ :caption: `custom_cli_command.py `_
+
+ Calling ``octoprint --help`` shows the two new commands:
+
+ .. code-block:: none
+
+ $ octoprint --help
+ Usage: octoprint [OPTIONS] COMMAND [ARGS]...
+
+ Options:
+ -b, --basedir PATH Specify the basedir to use for uploads, timelapses etc.
+ -c, --config PATH Specify the config file to use.
+ -v, --verbose Increase logging verbosity
+ --version Show the version and exit.
+ --help Show this message and exit.
+
+ Commands:
+ custom_cli_command:greet Greet someone by name, the greeting can be...
+ custom_cli_command:random Greet someone by name with a random greeting.
+ daemon Starts, stops or restarts in daemon mode.
+ devel:newplugin Creates a new plugin based on the OctoPrint...
+ serve Starts the OctoPrint server.
+
+ Each also has an individual help output:
+
+ .. code-block:: none
+
+ $ octoprint custom_cli_command:greet --help
+ Usage: octoprint custom_cli_command:greet [OPTIONS] [NAME]
+
+ Greet someone by name, the greeting can be customized.
+
+ Options:
+ -g, --greeting TEXT The greeting to use
+ --help Show this message and exit.
+
+ $ octoprint custom_cli_command:random --help
+ Usage: octoprint custom_cli_command:random [OPTIONS] [NAME]
+
+ Greet someone by name with a random greeting.
+
+ Options:
+ --help Show this message and exit.
+
+ And of course they work too:
+
+ .. code-block:: none
+
+ $ octoprint custom_cli_command:greet
+ Hello World!
+
+ $ octoprint custom_cli_command:greet --greeting "Good morning"
+ Good morning World!
+
+ $ octoprint custom_cli_command:random stranger
+ Hola stranger!
.. note::
If your hook handler is an instance method of a plugin mixin implementation, be aware that the hook will be
called without OctoPrint initializing your implementation instance. That means that **none** of the
:ref:`injected properties ` will be available and also the
- :meth:`~octoprint.plugin.Plugin.initialize` method will not be called.
+ :method:`~octoprint.plugin.Plugin.initialize` method will not be called.
- Your hook handler will have access to the plugin manager as ``cli_group._plugin_manager`` and to the
- *global* settings as ``cli_group._settings``. You can have your handler turn the latter into a
+ Your hook handler will have access to the plugin manager as ``cli_group.plugin_manager`` and to the
+ *global* settings as ``cli_group.settings``. You can have your handler turn the latter into a
:class:`~octoprint.plugin.PluginSettings` instance by using :func:`octoprint.plugin.plugin_settings_from_settings_plugin`
if your plugin's implementation implements the :class:`~octoprint.plugin.SettingsPlugin` mixin and inject
that and the plugin manager instance yourself:
@@ -92,7 +159,7 @@ octoprint.cli.commands
OctoPrint server context, so there is absolutely no way to access a printer connection, the event bus or
anything else like that. The only things available are the settings and the plugin manager.
- :return: A list of `Click commands or groups ` to provide on
+ :return: A list of `Click commands or groups `_ to provide on
OctoPrint's CLI.
:rtype: list
diff --git a/src/octoprint/cli/__init__.py b/src/octoprint/cli/__init__.py
index 42799573..9e63b12f 100644
--- a/src/octoprint/cli/__init__.py
+++ b/src/octoprint/cli/__init__.py
@@ -11,16 +11,20 @@ import octoprint
#~~ click context
class OctoPrintContext(object):
- def __init__(self, configfile=None, basedir=None, debug=False, verbosity=0):
+ """Custom context wrapping the standard options."""
+
+ def __init__(self, configfile=None, basedir=None, verbosity=0):
self.configfile = configfile
self.basedir = basedir
- self.debug = debug
self.verbosity = verbosity
+
pass_octoprint_ctx = click.make_pass_decorator(OctoPrintContext, ensure=True)
+"""Decorator to pass in the :class:`OctoPrintContext` instance."""
#~~ Custom click option to hide from help
class HiddenOption(click.Option):
+ """Custom option sub class with empty help."""
def get_help_record(self, ctx):
pass
@@ -55,6 +59,13 @@ def set_ctx_obj_option(ctx, param, value):
#~~ helper for setting a lot of bulk options
def bulk_options(options):
+ """
+ Utility decorator to decorate a function with a list of click decorators.
+
+ The provided list of ``options`` will be reversed to ensure correct
+ processing order (inverse from what would be intuitive).
+ """
+
def decorator(f):
options.reverse()
for option in options:
@@ -65,6 +76,14 @@ def bulk_options(options):
#~~ helper for setting --basedir, --config and --verbose options
def standard_options(hidden=False):
+ """
+ Decorator to add the standard options shared among all "octoprint" commands.
+
+ Adds the options ``--basedir``, ``--config`` and ``--verbose``. If ``hidden``
+ is set to ``True``, the options will be available on the command but not
+ listed in its help page.
+ """
+
factory = click.option
if hidden:
factory = hidden_option
@@ -91,6 +110,9 @@ legacy_options = bulk_options([
hidden_option("--pid", type=click.Path(), default="/tmp/octoprint.pid"),
hidden_option("--iknowwhatimdoing", "allow_root", is_flag=True),
])
+"""Legacy options available directly on the "octoprint" command in earlier versions.
+ Kept available for reasons of backwards compatibility, but hidden from the
+ generated help pages."""
#~~ "octoprint" command, merges server_commands and plugin_commands groups
diff --git a/src/octoprint/cli/devel.py b/src/octoprint/cli/devel.py
index 30e1c393..c4625e6e 100644
--- a/src/octoprint/cli/devel.py
+++ b/src/octoprint/cli/devel.py
@@ -9,8 +9,12 @@ __copyright__ = "Copyright (C) 2015 The OctoPrint Project - Released under terms
import click
import logging
-
class OctoPrintDevelCommands(click.MultiCommand):
+ """
+ Custom `click.MultiCommand `_
+ implementation that provides commands relevant for (plugin) development
+ based on availability of development dependencies.
+ """
sep = ":"
diff --git a/src/octoprint/cli/plugins.py b/src/octoprint/cli/plugins.py
index 41c12b21..0fbeea56 100644
--- a/src/octoprint/cli/plugins.py
+++ b/src/octoprint/cli/plugins.py
@@ -14,21 +14,32 @@ from octoprint.cli import pass_octoprint_ctx, OctoPrintContext
class OctoPrintPluginCommands(click.MultiCommand):
"""
- Custom `click.MultiCommand` implementation that collects commands from
- the plugin hook "octoprint.cli.commands".
+ Custom `click.MultiCommand `_
+ implementation that collects commands from the plugin hook
+ :ref:`octoprint.cli.commands `.
+
+ .. attribute:: settings
+
+ The global :class:`~octoprint.settings.Settings` instance.
+
+ .. attribute:: plugin_manager
+
+ The :class:`~octoprint.plugin.core.PluginManager` instance.
"""
sep = ":"
- """Separator for commands between plugin name and command name."""
def __init__(self, *args, **kwargs):
click.MultiCommand.__init__(self, *args, **kwargs)
- self._settings = None
- self._plugin_manager = None
+
+ self.settings = None
+ self.plugin_manager = None
+
self._logger = logging.getLogger(__name__)
+ self._initialized = False
def _initialize(self, ctx):
- if self._settings is not None:
+ if self._initialized:
return
if ctx.obj is None:
@@ -37,11 +48,13 @@ class OctoPrintPluginCommands(click.MultiCommand):
# initialize settings and plugin manager based on provided
# context (basedir and configfile)
from octoprint import init_settings, init_pluginsystem
- self._settings = init_settings(ctx.obj.basedir, ctx.obj.configfile)
- self._plugin_manager = init_pluginsystem(self._settings)
+ self.settings = init_settings(ctx.obj.basedir, ctx.obj.configfile)
+ self.plugin_manager = init_pluginsystem(self.settings)
# fetch registered hooks
- self._hooks = self._plugin_manager.get_hooks("octoprint.cli.commands")
+ self.hooks = self.plugin_manager.get_hooks("octoprint.cli.commands")
+
+ self._initialized = True
def list_commands(self, ctx):
self._initialize(ctx)
@@ -60,7 +73,7 @@ class OctoPrintPluginCommands(click.MultiCommand):
import collections
result = collections.OrderedDict()
- for name, hook in self._hooks.items():
+ for name, hook in self.hooks.items():
try:
commands = hook(self, pass_octoprint_ctx)
for command in commands:
diff --git a/src/octoprint/cli/server.py b/src/octoprint/cli/server.py
index d0010848..e5737ffc 100644
--- a/src/octoprint/cli/server.py
+++ b/src/octoprint/cli/server.py
@@ -12,6 +12,8 @@ import sys
from octoprint.cli import pass_octoprint_ctx, bulk_options, standard_options
def run_server(basedir, configfile, host, port, debug, allow_root, logging_config, verbosity):
+ """Initializes the environment and starts up the server."""
+
from octoprint import init_platform, __display_version__
def log_startup(_):
@@ -39,10 +41,8 @@ def run_server(basedir, configfile, host, port, debug, allow_root, logging_confi
octoprint_server = Server(settings=settings, plugin_manager=plugin_manager, host=host, port=port, debug=debug, allow_root=allow_root)
octoprint_server.run()
-
#~~ server options
-
server_options = bulk_options([
click.option("--host", type=click.STRING,
help="Specify the host on which to bind the server."),
@@ -54,6 +54,8 @@ server_options = bulk_options([
help="Allow OctoPrint to run as user root."),
click.option("--debug", is_flag=True, help="Enable debug mode"),
])
+"""Decorator to add the options shared among the server commands: ``--host``, ``--port``,
+ ``--logging``, ``--iknowwhatimdoing`` and ``--debug``."""
#~~ "octoprint serve" and "octoprint daemon" commands