From efbdd8cff4e7ad7511b60462f01f44f4cb95bb70 Mon Sep 17 00:00:00 2001
From: Ben Sturmfels <ben@sturm.com.au>
Date: Fri, 22 Nov 2019 15:43:10 +1100
Subject: [PATCH] Allow plugins to register custom gmg commands.
This change adds a `register_commands` function to the plugins API to be called
during plugin setup hook. The `sampleplugin` now includes a `samplecommand`.
---
mediagoblin/gmg_commands/__init__.py | 62 ++++++++++++++++----
mediagoblin/plugins/sampleplugin/__init__.py | 20 ++++++-
mediagoblin/tools/pluginapi.py | 27 +++++++++
3 files changed, 98 insertions(+), 11 deletions(-)
diff --git a/mediagoblin/gmg_commands/__init__.py b/mediagoblin/gmg_commands/__init__.py
index 0034fd98..ba5339ca 100644
|
a
|
b
|
import shutil
|
| 20 | 20 | |
| 21 | 21 | import six |
| 22 | 22 | |
| | 23 | from mediagoblin.init import plugins, setup_global_and_app_config |
| | 24 | from mediagoblin.tools.pluginapi import PluginManager |
| 23 | 25 | from mediagoblin.tools.common import import_component |
| 24 | 26 | |
| 25 | 27 | import logging |
| … |
… |
SUBCOMMAND_MAP = {
|
| 90 | 92 | } |
| 91 | 93 | |
| 92 | 94 | |
| 93 | | def main_cli(): |
| | 95 | def argparser_with_conf_file(add_help=True): |
| | 96 | """Makes an base ArgumentParser including the --conf_file option. |
| | 97 | |
| | 98 | Factored out to avoid duplication caused by initial parsing before plugins |
| | 99 | are loaded. |
| | 100 | |
| | 101 | """ |
| 94 | 102 | parser = argparse.ArgumentParser( |
| 95 | | description='GNU MediaGoblin utilities.') |
| | 103 | description='GNU MediaGoblin utilities.', |
| | 104 | add_help=add_help) |
| 96 | 105 | parser.add_argument( |
| 97 | 106 | '-cf', '--conf_file', default=None, |
| 98 | 107 | help=( |
| 99 | 108 | "Config file used to set up environment. " |
| 100 | 109 | "Default to mediagoblin_local.ini if readable, " |
| 101 | 110 | "otherwise mediagoblin.ini")) |
| | 111 | return parser |
| | 112 | |
| | 113 | |
| | 114 | def find_conf_file(conf_file=None): |
| | 115 | """Find the config file when not explictly specified. |
| | 116 | |
| | 117 | Factored out to avoid duplication caused by initial parsing before plugins |
| | 118 | are loaded. |
| | 119 | |
| | 120 | TODO: Give that mediagoblin_local.ini is now deprecated, this could be |
| | 121 | removed entirely. How would we warn people that are still using |
| | 122 | mediagoblin_local.ini? |
| | 123 | |
| | 124 | """ |
| | 125 | if conf_file is None: |
| | 126 | if os.path.exists('mediagoblin_local.ini') \ |
| | 127 | and os.access('mediagoblin_local.ini', os.R_OK): |
| | 128 | conf_file = 'mediagoblin_local.ini' |
| | 129 | else: |
| | 130 | conf_file = 'mediagoblin.ini' |
| | 131 | return conf_file |
| | 132 | |
| 102 | 133 | |
| | 134 | def main_cli(): |
| | 135 | # Initial argument parse to load config file and set up plugins. |
| | 136 | # |
| | 137 | # Until the config file has been read, we don't know what plugins will be |
| | 138 | # activated and what custom gmg commands they define. Here we custom |
| | 139 | # commands using "parse_known_args". The command line help is also not |
| | 140 | # useful yet, so that's disabled. |
| | 141 | initial_parser = argparser_with_conf_file(add_help=False) |
| | 142 | initial_args, _ = initial_parser.parse_known_args() |
| | 143 | initial_args.conf_file = find_conf_file(initial_args.conf_file) |
| | 144 | global_config, app_config = setup_global_and_app_config(initial_args.conf_file) |
| | 145 | plugins.setup_plugins() |
| | 146 | |
| | 147 | # Full argument parse including any gmg commands from now loaded plugins. |
| | 148 | parser = argparser_with_conf_file() |
| 103 | 149 | subparsers = parser.add_subparsers(help='sub-command help') |
| 104 | | for command_name, command_struct in six.iteritems(SUBCOMMAND_MAP): |
| | 150 | # Merge command dicts without mutating - ugly in Python < 3.9. |
| | 151 | subcommands = {**SUBCOMMAND_MAP, **PluginManager().get_commands()} |
| | 152 | for command_name, command_struct in six.iteritems(subcommands): |
| 105 | 153 | if 'help' in command_struct: |
| 106 | 154 | subparser = subparsers.add_parser( |
| 107 | 155 | command_name, help=command_struct['help']) |
| … |
… |
def main_cli():
|
| 116 | 164 | subparser.set_defaults(func=exec_func) |
| 117 | 165 | |
| 118 | 166 | args = parser.parse_args() |
| 119 | | args.orig_conf_file = args.conf_file |
| 120 | | if args.conf_file is None: |
| 121 | | if os.path.exists('mediagoblin_local.ini') \ |
| 122 | | and os.access('mediagoblin_local.ini', os.R_OK): |
| 123 | | args.conf_file = 'mediagoblin_local.ini' |
| 124 | | else: |
| 125 | | args.conf_file = 'mediagoblin.ini' |
| | 167 | args.conf_file = initial_args.conf_file |
| 126 | 168 | |
| 127 | 169 | # This is a hopefully TEMPORARY hack for adding a mediagoblin.ini |
| 128 | 170 | # if none exists, to make up for a deficiency as we are migrating |
diff --git a/mediagoblin/plugins/sampleplugin/__init__.py b/mediagoblin/plugins/sampleplugin/__init__.py
index 2cd077a2..9f83a069 100644
|
a
|
b
|
|
| 17 | 17 | |
| 18 | 18 | import logging |
| 19 | 19 | |
| 20 | | from mediagoblin.tools.pluginapi import get_config |
| | 20 | from mediagoblin.gmg_commands import util as commands_util |
| | 21 | from mediagoblin.tools.pluginapi import get_config, register_commands |
| 21 | 22 | |
| 22 | 23 | |
| 23 | 24 | _log = logging.getLogger(__name__) |
| … |
… |
def setup_plugin():
|
| 36 | 37 | _log.info('There is no configuration set.') |
| 37 | 38 | _setup_plugin_called += 1 |
| 38 | 39 | |
| | 40 | register_commands({ |
| | 41 | 'samplecommand': { |
| | 42 | 'setup': 'mediagoblin.plugins.sampleplugin:samplecommand_parser_setup', |
| | 43 | 'func': 'mediagoblin.plugins.sampleplugin:samplecommand', |
| | 44 | 'help': 'Do something'}, |
| | 45 | }) |
| 39 | 46 | |
| 40 | 47 | hooks = { |
| 41 | 48 | 'setup': setup_plugin |
| 42 | 49 | } |
| | 50 | |
| | 51 | |
| | 52 | def samplecommand_parser_setup(subparser): |
| | 53 | subparser.add_argument( |
| | 54 | '--username','-u', |
| | 55 | help="Username to greet") |
| | 56 | |
| | 57 | |
| | 58 | def samplecommand(args): |
| | 59 | args.username = commands_util.prompt_if_not_set(args.username, "Username:") |
| | 60 | print("Hello {args.username}, I'm a command!".format(args=args)) |
diff --git a/mediagoblin/tools/pluginapi.py b/mediagoblin/tools/pluginapi.py
index 1eabe9f1..9071f201 100644
|
a
|
b
|
class PluginManager(object):
|
| 88 | 88 | |
| 89 | 89 | # list of registered routes |
| 90 | 90 | "routes": [], |
| | 91 | |
| | 92 | # dictionary of commands |
| | 93 | "commands": {}, |
| 91 | 94 | } |
| 92 | 95 | |
| 93 | 96 | def clear(self): |
| … |
… |
class PluginManager(object):
|
| 146 | 149 | def get_template_hooks(self, hook_name): |
| 147 | 150 | return self.template_hooks.get(hook_name, []) |
| 148 | 151 | |
| | 152 | def register_commands(self, commands): |
| | 153 | self.commands.update(commands) |
| | 154 | |
| | 155 | def get_commands(self): |
| | 156 | return self.commands |
| | 157 | |
| 149 | 158 | |
| 150 | 159 | def register_routes(routes): |
| 151 | 160 | """Registers one or more routes |
| … |
… |
def get_hook_templates(hook_name):
|
| 274 | 283 | return PluginManager().get_template_hooks(hook_name) |
| 275 | 284 | |
| 276 | 285 | |
| | 286 | def register_commands(commands): |
| | 287 | """ |
| | 288 | Register a list of gmg commands. |
| | 289 | |
| | 290 | Takes a dictionary of gmg subcommands, in the same format as mediagoblin.gmg_commands.SUBCOMMAND_MAP. |
| | 291 | |
| | 292 | Example: |
| | 293 | |
| | 294 | .. code-block:: python |
| | 295 | |
| | 296 | {'batchaddmedia': { |
| | 297 | 'setup': 'mediagoblin.gmg_commands.batchaddmedia:parser_setup', |
| | 298 | 'func': 'mediagoblin.gmg_commands.batchaddmedia:batchaddmedia', |
| | 299 | 'help': 'Add many media entries at once'}} |
| | 300 | """ |
| | 301 | PluginManager().register_commands(commands) |
| | 302 | |
| | 303 | |
| 277 | 304 | ############################# |
| 278 | 305 | ## Hooks: The Next Generation |
| 279 | 306 | ############################# |