diff --git a/pyproject.toml b/pyproject.toml index 216a232..acb06ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,7 @@ Issues = "https://forge.monodon.me/Gnarwhal/sshare/issues" [project.scripts] sshare = "sshare.main:main" +sshare-validate = "sshare.validator:main" [tool.setuptools_scm] version_file = "src/sshare/version.py" diff --git a/src/sshare/plugin/plugin.py b/src/sshare/plugin/plugin.py index dde368b..8eacc52 100644 --- a/src/sshare/plugin/plugin.py +++ b/src/sshare/plugin/plugin.py @@ -21,11 +21,11 @@ from sshare import config_directory class PluginLoader: @staticmethod - def all(command_line=False, logger=None, config=None, flags=None): + def all(command_line=False, logger=None, config=dict(), flags=dict()): return PluginLoader.internal(command_line, logger, config, flags) + PluginLoader.external(logger, config, flags) @staticmethod - def internal(command_line=False, logger=None, config=None, flags=None): + def internal(command_line=False, logger=None, config=dict(), flags=dict()): return [ Plugin.internal(plugin, logger, config, flags) for plugin @@ -41,7 +41,7 @@ class PluginLoader: ] @staticmethod - def external(logger=None, config=None, flags=None): + def external(logger=None, config=dict(), flags=dict()): return [ Plugin.external(plugin, logger, config, flags) for plugin @@ -49,7 +49,7 @@ class PluginLoader: ] @staticmethod - def at(logger=None, config=None, flags=None, *args): + def at(logger=None, config=dict(), flags=dict(), *args): return [ Plugin.external(plugin, logger, config, flags) for plugin @@ -91,8 +91,19 @@ class PluginManager: class Plugin: @staticmethod - def types(): - return [ "logger", "source", "name", "upload", "location", "feedback" ] + def types(methods=False): + types = { + "logger": { "info", "warn", "error" }, + "source": { "get_source" }, + "name": { "get_name" }, + "upload": { "upload" }, + "location": { "get_location" }, + "feedback": { "give_feedback"}, + } + if methods: + return types + else: + return types.keys() def __init__(self, name, module, logger, external_config, external_flags): self.__dict__ = module.__dict__ @@ -169,11 +180,11 @@ class Plugin: return activate @staticmethod - def internal(name=None, logger=None, config=None, flags=None): + def internal(name=None, logger=None, config=dict(), flags=dict()): return Plugin(name, importlib.import_module(f"sshare.plugins.{name}"), logger, config.get(name, dict()), flags.get(name, dict())) @staticmethod - def external(path, logger=None, config=None, flags=None): + def external(path, logger=None, config=dict(), flags=dict()): module_spec = importlib.util.spec_from_file_location( path.stem, path.as_posix(), diff --git a/src/sshare/validator.py b/src/sshare/validator.py new file mode 100644 index 0000000..f4e21fd --- /dev/null +++ b/src/sshare/validator.py @@ -0,0 +1,132 @@ +# This file is part of SSHare. +# +# SSHare is free software: you can redistribute it and/or modify it under the terms of +# the GNU General Public License as published by the Free Software Foundation, +# either version 3 of the License, or (at your option) any later version. +# +# SSHare is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# SSHare. If not, see . + +import argparse + +from sshare import config_directory +from sshare.plugin import Plugin +from sshare.plugin import PluginLoader +from sshare.version import version + +def validate(plugin, quiet=False): + def log(message): + if not quiet: + print(message) + + error_count = 0 + def error(message, end=False): + nonlocal error_count + nonlocal log + + if error_count == 0: + log("[\033[91mFAILED\033[0m]") + error_count = error_count + 1 + log(f"\033[91m => {message}\033[0m") + + def success(): + nonlocal log + + log(f"[\033[92mOK\033[0m]") + + mandatory_keys = dict() + def validate_type_and_add_keys(type): + nonlocal mandatory_keys + + types = Plugin.types(methods=True) + if type in types: + mandatory_keys[type] = types[type] + + else: + error(f"plugin_type '{type}' is not a valid plugin type") + if not hasattr(plugin, "plugin_type"): + error("must define plugin_type") + else: + if isinstance(plugin.plugin_type, set): + for index, type in enumerate(plugin.plugin_type): + validate_type_and_add_keys(type) + elif isinstance(plugin.plugin_type, str): + validate_type_and_add_keys(plugin.plugin_type) + else: + error("plugin_type must be either a string or a set of strings") + + for type, keys in mandatory_keys.items(): + for key in keys: + if not hasattr(plugin, key): + error(f"plugin type '{type}' requires definition of '{key}' ") + + if hasattr(plugin, "activate"): + def validate_activate(args, type=None): + def validate_activate_arg(arg): + nonlocal plugin + nonlocal type + + if hasattr(plugin, "args"): + if arg in plugin.args.keys(): + return + error(f"activate arg '{arg}' does not exist in 'args'") + if isinstance(args, set): + for arg in args: + validate_activate_arg(arg) + else: + validate_activate_arg(arg) + + if isinstance(plugin.activate, dict): + for type, args in plugin.activate.items(): + if not type in Plugin.types(): + error(f"activate specified for unrecognized plugin type '{type}'") + validate_activate(args, type) + else: + validate_activate(plugin.activate) + + if error_count == 0: + success() + return error_count + +def main(): + arg_parser = argparse.ArgumentParser( + prog="sshare-validate", + description="Validate sshare plugins", + ) + arg_parser.add_argument( + "-v", + "--version", + action="version", + version=f"%(prog)s version {version}", + ) + arg_parser.add_argument( + "--dev", + action="store_const", + const=True, + help="Validate all internal and external plugins" + ) + arg_parser.add_argument( + "plugins", + nargs="*", + help="plugin(s) to be validated", + ) + arguments = arg_parser.parse_args() + if arguments.dev: + to_be_validated = PluginLoader.all(command_line=True) + elif len(arguments.plugins) > 0: + to_be_validated = PluginLoader.at(*arguments.plugins) + else: + to_be_validated = PluginLoader.external() + + longest = 0 + for plugin in to_be_validated: + if len(plugin.name) > longest: + longest = len(plugin.name) + for plugin in to_be_validated: + print(f"{plugin.name}{"".join([ " " for _ in range(0, longest-len(plugin.name))])} ", end="") + validate(plugin)