mirror of
1
0
Fork 0

Add plugin loader

This commit is contained in:
Casey Rodarmor 2016-01-16 19:00:15 -08:00 committed by Anish Athalye
parent ba9e9cbe70
commit eeb4c284fb
9 changed files with 69 additions and 25 deletions

View File

@ -1 +1,2 @@
from .cli import main
from .plugin import Plugin

View File

@ -1,8 +1,11 @@
import os, glob
from argparse import ArgumentParser
from .config import ConfigReader, ReadingError
from .dispatcher import Dispatcher, DispatchError
from .messenger import Messenger
from .messenger import Level
from .util import module
def add_options(parser):
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',
help='run commands given in CONFIGFILE', metavar='CONFIGFILE',
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):
reader = ConfigReader(config_file)
@ -28,12 +37,24 @@ def main():
parser = ArgumentParser()
add_options(parser)
options = parser.parse_args()
if (options.super_quiet):
if options.super_quiet:
log.set_level(Level.WARNING)
if (options.quiet):
if options.quiet:
log.set_level(Level.INFO)
if (options.verbose):
if options.verbose:
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(abspath)
tasks = read_config(options.config_file[0])
if not isinstance(tasks, list):
raise ReadingError('Configuration file must be a list of tasks')

View File

@ -1,5 +1,5 @@
import os
from .executor import Executor
from .plugin import Plugin
from .messenger import Messenger
class Dispatcher(object):
@ -37,7 +37,7 @@ class Dispatcher(object):
def _load_plugins(self):
self._plugins = [plugin(self._base_directory)
for plugin in Executor.__subclasses__()]
for plugin in Plugin.__subclasses__()]
class DispatchError(Exception):
pass

View File

@ -1,4 +0,0 @@
from .executor import Executor
from .linker import Linker
from .cleaner import Cleaner
from .commandrunner import CommandRunner

View File

@ -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.
'''
@ -11,7 +11,7 @@ class Executor(object):
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
@ -19,6 +19,6 @@ class Executor(object):
'''
Executes the directive.
Returns true if the Executor successfully handled the directive.
Returns true if the Plugin successfully handled the directive.
'''
raise NotImplementedError

29
dotbot/util/module.py Normal file
View 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
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)

View File

@ -1,7 +1,6 @@
import os
from . import Executor
import os, dotbot
class Cleaner(Executor):
class Clean(dotbot.Plugin):
'''
Cleans broken symbolic links.
'''
@ -13,7 +12,7 @@ class Cleaner(Executor):
def handle(self, directive, data):
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)
def _process_clean(self, targets):

View File

@ -1,7 +1,6 @@
import os, shutil
from . import Executor
import os, shutil, dotbot
class Linker(Executor):
class Link(dotbot.Plugin):
'''
Symbolically links dotfiles.
'''
@ -13,7 +12,7 @@ class Linker(Executor):
def handle(self, directive, data):
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)
def _process_links(self, links):

View File

@ -1,7 +1,6 @@
import os, subprocess
from . import Executor
import os, subprocess, dotbot
class CommandRunner(Executor):
class Shell(dotbot.Plugin):
'''
Run arbitrary shell commands.
'''
@ -13,7 +12,7 @@ class CommandRunner(Executor):
def handle(self, directive, data):
if directive != self._directive:
raise ValueError('CommandRunner cannot handle directive %s' %
raise ValueError('Shell cannot handle directive %s' %
directive)
return self._process_commands(data)