aboutsummaryrefslogtreecommitdiff
path: root/src/ModuleManager.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/ModuleManager.py')
-rw-r--r--src/ModuleManager.py147
1 files changed, 147 insertions, 0 deletions
diff --git a/src/ModuleManager.py b/src/ModuleManager.py
new file mode 100644
index 00000000..2a3ae713
--- /dev/null
+++ b/src/ModuleManager.py
@@ -0,0 +1,147 @@
+import glob, imp, inspect, os, sys, uuid
+
+BITBOT_HOOKS_MAGIC = "__bitbot_hooks"
+
+class ModuleException(Exception):
+ pass
+class ModuleWarning(Exception):
+ pass
+
+class ModuleNotFoundException(ModuleException):
+ pass
+class ModuleNameCollisionException(ModuleException):
+ pass
+class ModuleLoadException(ModuleException):
+ pass
+class ModuleUnloadException(ModuleException):
+ pass
+
+class ModuleNotLoadedWarning(ModuleWarning):
+ pass
+
+class BaseModule(object):
+ def __init__(self, bot, events, exports):
+ pass
+
+class ModuleManager(object):
+ def __init__(self, bot, events, exports, directory):
+ self.bot = bot
+ self.events = events
+ self.exports = exports
+ self.directory = directory
+ self.modules = {}
+ self.waiting_requirement = {}
+ def list_modules(self):
+ return sorted(glob.glob(os.path.join(self.directory, "*.py")))
+
+ def _module_name(self, path):
+ return os.path.basename(path).rsplit(".py", 1)[0].lower()
+ def _module_path(self, name):
+ return os.path.join(self.directory, "%s.py" % name)
+
+ def _load_module(self, name):
+ path = self._module_path(name)
+
+ with open(path) as module_file:
+ while True:
+ line = module_file.readline().strip()
+ line_split = line.split(" ")
+ if line and line.startswith("#--"):
+ # this is a hashflag
+ if line == "#--ignore":
+ # nope, ignore this module.
+ raise ModuleNotLoadedWarning("module ignored")
+ elif line_split[0] == "#--require-config" and len(
+ line_split) > 1:
+ if not line_split[1].lower() in self.bot.config or not self.bot.config[
+ line_split[1].lower()]:
+ # nope, required config option not present.
+ raise ModuleNotLoadedWarning(
+ "required config not present")
+ elif line_split[0] == "#--require-module" and len(
+ line_split) > 1:
+ if not "bitbot_%s" % line_split[1].lower() in sys.modules:
+ if not line_split[1].lower() in self.waiting_requirement:
+ self.waiting_requirement[line_split[1].lower()] = set([])
+ self.waiting_requirement[line_split[1].lower()].add(path)
+ raise ModuleNotLoadedWarning(
+ "waiting for requirement")
+ else:
+ break
+ module = imp.load_source(name, path)
+
+ if not hasattr(module, "Module"):
+ raise ModuleLoadException("module '%s' doesn't have a "
+ "'Module' class.")
+ if not inspect.isclass(module.Module):
+ raise ModuleLoadException("module '%s' has a 'Module' attribute "
+ "but it is not a class.")
+
+ context = str(uuid.uuid4())
+ context_events = self.events.new_context(context)
+ context_exports = self.exports.new_context(context)
+ module_object = module.Module(self.bot, context_events,
+ context_exports)
+
+ if not hasattr(module_object, "_name"):
+ module_object._name = name.title()
+ for attribute_name in dir(module_object):
+ attribute = getattr(module_object, attribute_name)
+ if inspect.ismethod(attribute) and hasattr(attribute,
+ BITBOT_HOOKS_MAGIC):
+ hooks = getattr(attribute, BITBOT_HOOKS_MAGIC)
+ for hook in hooks:
+ context_events.on(hook["event"]).hook(attribute,
+ **hook["kwargs"])
+
+ module_object._context = context
+ module_object._import_name = name
+
+ assert not module_object._name in self.modules, (
+ "module name '%s' attempted to be used twice.")
+ return module_object
+
+ def load_module(self, name):
+ try:
+ module = self._load_module(name)
+ except ModuleWarning as warning:
+ self.bot.log.error("Module '%s' not loaded", [name])
+ raise
+ except Exception as e:
+ self.bot.log.error("Failed to load module \"%s\": %s",
+ [name, str(e)])
+ raise
+
+ self.modules[module._import_name] = module
+ if name in self.waiting_requirement:
+ for requirement_name in self.waiting_requirement:
+ self.load_module(requirement_name)
+ self.bot.log.info("Module '%s' loaded", [name])
+
+ def load_modules(self, whitelist=[], blacklist=[]):
+ for path in self.list_modules():
+ name = self._module_name(path)
+ if name in whitelist or (not whitelist and not name in blacklist):
+ try:
+ self.load_module(name)
+ except ModuleWarning:
+ pass
+
+ def unload_module(self, name):
+ if not name in self.modules:
+ raise ModuleNotFoundException()
+ module = self.modules[name]
+ del self.modules[name]
+
+ context = module._context
+ self.events.purge_context(context)
+ self.exports.purge_context(context)
+
+ del sys.modules[name]
+ references = sys.getrefcount(module)
+ del module
+ references -= 1 # 'del module' removes one reference
+ references -= 1 # one of the refs is from getrefcount
+
+ self.bot.log.info("Module '%s' unloaded (%d reference%s)",
+ [name, references, "" if references == 1 else "s"])