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 | ############################# |