mirror of
https://github.com/craigerl/aprsd.git
synced 2025-04-18 01:08:36 -04:00
234 lines
7.4 KiB
Python
234 lines
7.4 KiB
Python
# The base plugin class
|
|
import abc
|
|
import fnmatch
|
|
import importlib
|
|
import inspect
|
|
import logging
|
|
import os
|
|
import re
|
|
|
|
import pluggy
|
|
from thesmuggler import smuggle
|
|
|
|
# setup the global logger
|
|
LOG = logging.getLogger("APRSD")
|
|
|
|
hookspec = pluggy.HookspecMarker("aprsd")
|
|
hookimpl = pluggy.HookimplMarker("aprsd")
|
|
|
|
CORE_PLUGINS = [
|
|
"aprsd.plugins.email.EmailPlugin",
|
|
"aprsd.plugins.fortune.FortunePlugin",
|
|
"aprsd.plugins.location.LocationPlugin",
|
|
"aprsd.plugins.ping.PingPlugin",
|
|
"aprsd.plugins.query.QueryPlugin",
|
|
"aprsd.plugins.time.TimePlugin",
|
|
"aprsd.plugins.weather.WeatherPlugin",
|
|
"aprsd.plugins.version.VersionPlugin",
|
|
]
|
|
|
|
|
|
class APRSDCommandSpec:
|
|
"""A hook specification namespace."""
|
|
|
|
@hookspec
|
|
def run(self, fromcall, message, ack):
|
|
"""My special little hook that you can customize."""
|
|
pass
|
|
|
|
|
|
class APRSDPluginBase(metaclass=abc.ABCMeta):
|
|
def __init__(self, config):
|
|
"""The aprsd config object is stored."""
|
|
self.config = config
|
|
|
|
@property
|
|
def command_name(self):
|
|
"""The usage string help."""
|
|
raise NotImplementedError
|
|
|
|
@property
|
|
def command_regex(self):
|
|
"""The regex to match from the caller"""
|
|
raise NotImplementedError
|
|
|
|
@property
|
|
def version(self):
|
|
"""Version"""
|
|
raise NotImplementedError
|
|
|
|
@hookimpl
|
|
def run(self, fromcall, message, ack):
|
|
if re.search(self.command_regex, message):
|
|
return self.command(fromcall, message, ack)
|
|
|
|
@abc.abstractmethod
|
|
def command(self, fromcall, message, ack):
|
|
"""This is the command that runs when the regex matches.
|
|
To reply with a message over the air, return a string
|
|
to send.
|
|
"""
|
|
pass
|
|
|
|
|
|
class PluginManager:
|
|
# The singleton instance object for this class
|
|
_instance = None
|
|
|
|
# the pluggy PluginManager
|
|
_pluggy_pm = None
|
|
|
|
# aprsd config dict
|
|
config = None
|
|
|
|
def __new__(cls, *args, **kwargs):
|
|
"""This magic turns this into a singleton."""
|
|
if cls._instance is None:
|
|
cls._instance = super().__new__(cls)
|
|
# Put any initialization here.
|
|
return cls._instance
|
|
|
|
def __init__(self, config=None):
|
|
self.obj_list = []
|
|
if config:
|
|
self.config = config
|
|
|
|
def load_plugins_from_path(self, module_path):
|
|
if not os.path.exists(module_path):
|
|
LOG.error("plugin path '{}' doesn't exist.".format(module_path))
|
|
return None
|
|
|
|
dir_path = os.path.realpath(module_path)
|
|
pattern = "*.py"
|
|
|
|
self.obj_list = []
|
|
|
|
for path, _subdirs, files in os.walk(dir_path):
|
|
for name in files:
|
|
if fnmatch.fnmatch(name, pattern):
|
|
LOG.debug("MODULE? '{}' '{}'".format(name, path))
|
|
module = smuggle("{}/{}".format(path, name))
|
|
for mem_name, obj in inspect.getmembers(module):
|
|
if inspect.isclass(obj) and self.is_plugin(obj):
|
|
self.obj_list.append(
|
|
{"name": mem_name, "obj": obj(self.config)},
|
|
)
|
|
|
|
return self.obj_list
|
|
|
|
def is_plugin(self, obj):
|
|
for c in inspect.getmro(obj):
|
|
if issubclass(c, APRSDPluginBase):
|
|
return True
|
|
|
|
return False
|
|
|
|
def _create_class(self, module_class_string, super_cls: type = None, **kwargs):
|
|
"""
|
|
Method to create a class from a fqn python string.
|
|
:param module_class_string: full name of the class to create an object of
|
|
:param super_cls: expected super class for validity, None if bypass
|
|
:param kwargs: parameters to pass
|
|
:return:
|
|
"""
|
|
module_name, class_name = module_class_string.rsplit(".", 1)
|
|
try:
|
|
module = importlib.import_module(module_name)
|
|
except Exception as ex:
|
|
LOG.error("Failed to load Plugin '{}' : '{}'".format(module_name, ex))
|
|
return
|
|
|
|
assert hasattr(module, class_name), "class {} is not in {}".format(
|
|
class_name,
|
|
module_name,
|
|
)
|
|
# click.echo('reading class {} from module {}'.format(
|
|
# class_name, module_name))
|
|
cls = getattr(module, class_name)
|
|
if super_cls is not None:
|
|
assert issubclass(cls, super_cls), "class {} should inherit from {}".format(
|
|
class_name,
|
|
super_cls.__name__,
|
|
)
|
|
# click.echo('initialising {} with params {}'.format(class_name, kwargs))
|
|
obj = cls(**kwargs)
|
|
return obj
|
|
|
|
def _load_plugin(self, plugin_name):
|
|
"""
|
|
Given a python fully qualified class path.name,
|
|
Try importing the path, then creating the object,
|
|
then registering it as a aprsd Command Plugin
|
|
"""
|
|
plugin_obj = None
|
|
try:
|
|
plugin_obj = self._create_class(
|
|
plugin_name,
|
|
APRSDPluginBase,
|
|
config=self.config,
|
|
)
|
|
if plugin_obj:
|
|
LOG.info(
|
|
"Registering Command plugin '{}'({}) '{}'".format(
|
|
plugin_name,
|
|
plugin_obj.version,
|
|
plugin_obj.command_regex,
|
|
),
|
|
)
|
|
self._pluggy_pm.register(plugin_obj)
|
|
except Exception as ex:
|
|
LOG.exception("Couldn't load plugin '{}'".format(plugin_name), ex)
|
|
|
|
def setup_plugins(self):
|
|
"""Create the plugin manager and register plugins."""
|
|
|
|
LOG.info("Loading Core APRSD Command Plugins")
|
|
enabled_plugins = self.config["aprsd"].get("enabled_plugins", None)
|
|
self._pluggy_pm = pluggy.PluginManager("aprsd")
|
|
self._pluggy_pm.add_hookspecs(APRSDCommandSpec)
|
|
if enabled_plugins:
|
|
for p_name in enabled_plugins:
|
|
self._load_plugin(p_name)
|
|
else:
|
|
# Enabled plugins isn't set, so we default to loading all of
|
|
# the core plugins.
|
|
for p_name in CORE_PLUGINS:
|
|
self._load_plugin(p_name)
|
|
|
|
plugin_dir = self.config["aprsd"].get("plugin_dir", None)
|
|
if plugin_dir:
|
|
LOG.info("Trying to load custom plugins from '{}'".format(plugin_dir))
|
|
plugins_list = self.load_plugins_from_path(plugin_dir)
|
|
if plugins_list:
|
|
LOG.info("Discovered {} modules to load".format(len(plugins_list)))
|
|
for o in plugins_list:
|
|
plugin_obj = None
|
|
# not setting enabled plugins means load all?
|
|
plugin_obj = o["obj"]
|
|
|
|
if plugin_obj:
|
|
LOG.info(
|
|
"Registering Command plugin '{}'({}) '{}'".format(
|
|
o["name"],
|
|
o["obj"].version,
|
|
o["obj"].command_regex,
|
|
),
|
|
)
|
|
self._pluggy_pm.register(o["obj"])
|
|
|
|
else:
|
|
LOG.info("Skipping Custom Plugins directory.")
|
|
LOG.info("Completed Plugin Loading.")
|
|
|
|
def run(self, *args, **kwargs):
|
|
"""Execute all the pluguns run method."""
|
|
return self._pluggy_pm.hook.run(*args, **kwargs)
|
|
|
|
def register(self, obj):
|
|
"""Register the plugin."""
|
|
self._pluggy_pm.register(obj)
|
|
|
|
def get_plugins(self):
|
|
return self._pluggy_pm.get_plugins()
|
|
|