Added a plugin loader
This commit is contained in:
parent
1afc8bfbbf
commit
e8014b531f
9 changed files with 69 additions and 25 deletions
|
@ -1 +1,2 @@
|
||||||
from .cli import main
|
from .cli import main
|
||||||
|
from .plugin import Plugin
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
|
import os, glob
|
||||||
|
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from .config import ConfigReader, ReadingError
|
from .config import ConfigReader, ReadingError
|
||||||
from .dispatcher import Dispatcher, DispatchError
|
from .dispatcher import Dispatcher, DispatchError
|
||||||
from .messenger import Messenger
|
from .messenger import Messenger
|
||||||
from .messenger import Level
|
from .messenger import Level
|
||||||
|
from .util import module
|
||||||
|
|
||||||
def add_options(parser):
|
def add_options(parser):
|
||||||
parser.add_argument('-Q', '--super-quiet', dest='super_quiet', action='store_true',
|
parser.add_argument('-Q', '--super-quiet', dest='super_quiet', action='store_true',
|
||||||
|
@ -17,6 +20,12 @@ def add_options(parser):
|
||||||
parser.add_argument('-c', '--config-file', nargs=1, dest='config_file',
|
parser.add_argument('-c', '--config-file', nargs=1, dest='config_file',
|
||||||
help='run commands given in CONFIGFILE', metavar='CONFIGFILE',
|
help='run commands given in CONFIGFILE', metavar='CONFIGFILE',
|
||||||
required=True)
|
required=True)
|
||||||
|
parser.add_argument('-p', '--plugin', action='append', dest='plugins', default=[],
|
||||||
|
help='load PLUGIN as a plugin', metavar='PLUGIN')
|
||||||
|
parser.add_argument('--disable-built-in-plugins', dest='disable_built_in_plugins',
|
||||||
|
action='store_true', help='disable built-in plugins')
|
||||||
|
parser.add_argument('--plugin-dir', action='append', dest='plugin_dirs', default=[],
|
||||||
|
metavar='PLUGIN_DIR', help='load all plugins in PLUGIN_DIR')
|
||||||
|
|
||||||
def read_config(config_file):
|
def read_config(config_file):
|
||||||
reader = ConfigReader(config_file)
|
reader = ConfigReader(config_file)
|
||||||
|
@ -28,12 +37,24 @@ def main():
|
||||||
parser = ArgumentParser()
|
parser = ArgumentParser()
|
||||||
add_options(parser)
|
add_options(parser)
|
||||||
options = parser.parse_args()
|
options = parser.parse_args()
|
||||||
if (options.super_quiet):
|
if options.super_quiet:
|
||||||
log.set_level(Level.WARNING)
|
log.set_level(Level.WARNING)
|
||||||
if (options.quiet):
|
if options.quiet:
|
||||||
log.set_level(Level.INFO)
|
log.set_level(Level.INFO)
|
||||||
if (options.verbose):
|
if options.verbose:
|
||||||
log.set_level(Level.DEBUG)
|
log.set_level(Level.DEBUG)
|
||||||
|
plugin_directories = list(options.plugin_dirs)
|
||||||
|
if not options.disable_built_in_plugins:
|
||||||
|
plugin_directories.append(os.path.join(os.path.dirname(__file__), '..', 'plugins'))
|
||||||
|
plugin_paths = []
|
||||||
|
for directory in plugin_directories:
|
||||||
|
for plugin_path in glob.glob(os.path.join(directory, '*.py')):
|
||||||
|
plugin_paths.append(plugin_path)
|
||||||
|
for plugin_path in options.plugins:
|
||||||
|
plugin_paths.append(plugin_path)
|
||||||
|
for plugin_path in plugin_paths:
|
||||||
|
abspath = os.path.abspath(plugin_path)
|
||||||
|
module.load(plugin_path)
|
||||||
tasks = read_config(options.config_file[0])
|
tasks = read_config(options.config_file[0])
|
||||||
if not isinstance(tasks, list):
|
if not isinstance(tasks, list):
|
||||||
raise ReadingError('Configuration file must be a list of tasks')
|
raise ReadingError('Configuration file must be a list of tasks')
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import os
|
import os
|
||||||
from .executor import Executor
|
from .plugin import Plugin
|
||||||
from .messenger import Messenger
|
from .messenger import Messenger
|
||||||
|
|
||||||
class Dispatcher(object):
|
class Dispatcher(object):
|
||||||
|
@ -37,7 +37,7 @@ class Dispatcher(object):
|
||||||
|
|
||||||
def _load_plugins(self):
|
def _load_plugins(self):
|
||||||
self._plugins = [plugin(self._base_directory)
|
self._plugins = [plugin(self._base_directory)
|
||||||
for plugin in Executor.__subclasses__()]
|
for plugin in Plugin.__subclasses__()]
|
||||||
|
|
||||||
class DispatchError(Exception):
|
class DispatchError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
from .executor import Executor
|
|
||||||
from .linker import Linker
|
|
||||||
from .cleaner import Cleaner
|
|
||||||
from .commandrunner import CommandRunner
|
|
|
@ -1,6 +1,6 @@
|
||||||
from ..messenger import Messenger
|
from .messenger import Messenger
|
||||||
|
|
||||||
class Executor(object):
|
class Plugin(object):
|
||||||
'''
|
'''
|
||||||
Abstract base class for commands that process directives.
|
Abstract base class for commands that process directives.
|
||||||
'''
|
'''
|
||||||
|
@ -11,7 +11,7 @@ class Executor(object):
|
||||||
|
|
||||||
def can_handle(self, directive):
|
def can_handle(self, directive):
|
||||||
'''
|
'''
|
||||||
Returns true if the Executor can handle the directive.
|
Returns true if the Plugin can handle the directive.
|
||||||
'''
|
'''
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@ -19,6 +19,6 @@ class Executor(object):
|
||||||
'''
|
'''
|
||||||
Executes the directive.
|
Executes the directive.
|
||||||
|
|
||||||
Returns true if the Executor successfully handled the directive.
|
Returns true if the Plugin successfully handled the directive.
|
||||||
'''
|
'''
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
29
dotbot/util/module.py
Normal file
29
dotbot/util/module.py
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import sys, os.path
|
||||||
|
|
||||||
|
# We keep references to loaded modules so they don't get garbage collected.
|
||||||
|
loaded_modules = []
|
||||||
|
|
||||||
|
def load(path):
|
||||||
|
basename = os.path.basename(path)
|
||||||
|
module_name, extension = os.path.splitext(basename)
|
||||||
|
plugin = load_module(module_name, path)
|
||||||
|
loaded_modules.append(plugin)
|
||||||
|
|
||||||
|
if sys.version_info >= (3, 5):
|
||||||
|
import importlib.util, os.path
|
||||||
|
|
||||||
|
def load_module(module_name, path):
|
||||||
|
spec = importlib.util.spec_from_file_location(module_name, path)
|
||||||
|
module = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(module)
|
||||||
|
return module
|
||||||
|
elif sys.version_info >= (3, 3):
|
||||||
|
from importlib.machinery import SourceFileLoader
|
||||||
|
|
||||||
|
def load_module(module_name, path):
|
||||||
|
return SourceFileLoader(module_name, path).load_module()
|
||||||
|
else:
|
||||||
|
import imp
|
||||||
|
|
||||||
|
def load_module(module_name, path):
|
||||||
|
return imp.load_source(module_name, path)
|
|
@ -1,7 +1,6 @@
|
||||||
import os
|
import os, dotbot
|
||||||
from . import Executor
|
|
||||||
|
|
||||||
class Cleaner(Executor):
|
class Clean(dotbot.Plugin):
|
||||||
'''
|
'''
|
||||||
Cleans broken symbolic links.
|
Cleans broken symbolic links.
|
||||||
'''
|
'''
|
||||||
|
@ -13,7 +12,7 @@ class Cleaner(Executor):
|
||||||
|
|
||||||
def handle(self, directive, data):
|
def handle(self, directive, data):
|
||||||
if directive != self._directive:
|
if directive != self._directive:
|
||||||
raise ValueError('Cleaner cannot handle directive %s' % directive)
|
raise ValueError('Clean cannot handle directive %s' % directive)
|
||||||
return self._process_clean(data)
|
return self._process_clean(data)
|
||||||
|
|
||||||
def _process_clean(self, targets):
|
def _process_clean(self, targets):
|
|
@ -1,7 +1,6 @@
|
||||||
import os, shutil
|
import os, shutil, dotbot
|
||||||
from . import Executor
|
|
||||||
|
|
||||||
class Linker(Executor):
|
class Link(dotbot.Plugin):
|
||||||
'''
|
'''
|
||||||
Symbolically links dotfiles.
|
Symbolically links dotfiles.
|
||||||
'''
|
'''
|
||||||
|
@ -13,7 +12,7 @@ class Linker(Executor):
|
||||||
|
|
||||||
def handle(self, directive, data):
|
def handle(self, directive, data):
|
||||||
if directive != self._directive:
|
if directive != self._directive:
|
||||||
raise ValueError('Linker cannot handle directive %s' % directive)
|
raise ValueError('Link cannot handle directive %s' % directive)
|
||||||
return self._process_links(data)
|
return self._process_links(data)
|
||||||
|
|
||||||
def _process_links(self, links):
|
def _process_links(self, links):
|
|
@ -1,7 +1,6 @@
|
||||||
import os, subprocess
|
import os, subprocess, dotbot
|
||||||
from . import Executor
|
|
||||||
|
|
||||||
class CommandRunner(Executor):
|
class Shell(dotbot.Plugin):
|
||||||
'''
|
'''
|
||||||
Run arbitrary shell commands.
|
Run arbitrary shell commands.
|
||||||
'''
|
'''
|
||||||
|
@ -13,7 +12,7 @@ class CommandRunner(Executor):
|
||||||
|
|
||||||
def handle(self, directive, data):
|
def handle(self, directive, data):
|
||||||
if directive != self._directive:
|
if directive != self._directive:
|
||||||
raise ValueError('CommandRunner cannot handle directive %s' %
|
raise ValueError('Shell cannot handle directive %s' %
|
||||||
directive)
|
directive)
|
||||||
return self._process_commands(data)
|
return self._process_commands(data)
|
||||||
|
|
Loading…
Reference in a new issue