From 3c2d2b579d0a1c1e3f20137582135f7a4452655c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Thu, 29 Oct 2015 17:37:05 +0100 Subject: [PATCH] Documention for CLI and its development --- docs/modules/cli.rst | 31 ++++++++++++++ docs/modules/index.rst | 1 + docs/plugins/hooks.rst | 81 ++++++++++++++++++++++++++++++++--- src/octoprint/cli/__init__.py | 26 ++++++++++- src/octoprint/cli/devel.py | 6 ++- src/octoprint/cli/plugins.py | 33 +++++++++----- src/octoprint/cli/server.py | 6 ++- 7 files changed, 162 insertions(+), 22 deletions(-) create mode 100644 docs/modules/cli.rst 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