summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--dotbot/__init__.py1
-rw-r--r--dotbot/cli.py27
-rw-r--r--dotbot/dispatcher.py4
-rw-r--r--dotbot/executor/__init__.py4
-rw-r--r--dotbot/plugin.py (renamed from dotbot/executor/executor.py)8
-rw-r--r--dotbot/util/module.py29
-rw-r--r--plugins/clean.py (renamed from dotbot/executor/cleaner.py)7
-rw-r--r--plugins/link.py (renamed from dotbot/executor/linker.py)7
-rw-r--r--plugins/shell.py (renamed from dotbot/executor/commandrunner.py)7
9 files changed, 69 insertions, 25 deletions
diff --git a/dotbot/__init__.py b/dotbot/__init__.py
index 401da57..1d03464 100644
--- a/dotbot/__init__.py
+++ b/dotbot/__init__.py
@@ -1 +1,2 @@
from .cli import main
+from .plugin import Plugin
diff --git a/dotbot/cli.py b/dotbot/cli.py
index dc90909..d77ab42 100644
--- a/dotbot/cli.py
+++ b/dotbot/cli.py
@@ -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')
diff --git a/dotbot/dispatcher.py b/dotbot/dispatcher.py
index c3c7fe8..79231a0 100644
--- a/dotbot/dispatcher.py
+++ b/dotbot/dispatcher.py
@@ -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
diff --git a/dotbot/executor/__init__.py b/dotbot/executor/__init__.py
deleted file mode 100644
index d87ca4b..0000000
--- a/dotbot/executor/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-from .executor import Executor
-from .linker import Linker
-from .cleaner import Cleaner
-from .commandrunner import CommandRunner
diff --git a/dotbot/executor/executor.py b/dotbot/plugin.py
index e6862c0..a79639e 100644
--- a/dotbot/executor/executor.py
+++ b/dotbot/plugin.py
@@ -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
diff --git a/dotbot/util/module.py b/dotbot/util/module.py
new file mode 100644
index 0000000..af6b0ed
--- /dev/null
+++ b/dotbot/util/module.py
@@ -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)
diff --git a/dotbot/executor/cleaner.py b/plugins/clean.py
index 504c0de..22ec450 100644
--- a/dotbot/executor/cleaner.py
+++ b/plugins/clean.py
@@ -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):
diff --git a/dotbot/executor/linker.py b/plugins/link.py
index 5c12ea0..429158d 100644
--- a/dotbot/executor/linker.py
+++ b/plugins/link.py
@@ -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):
diff --git a/dotbot/executor/commandrunner.py b/plugins/shell.py
index 45d40f5..a2d9c1a 100644
--- a/dotbot/executor/commandrunner.py
+++ b/plugins/shell.py
@@ -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)