Separate module importing from plugin identification
This change allows the test framework to reliably specify which plugins to load and use within the same process. Previously, plugins were loaded by importing files and then accessing the Plugin class' list of subclasses. Now, it's possible to run dotbot multiple times without plugins accruing across runs with different configurations and CLI arguments. In addition, this fixes some circular imports that were previously avoided because plugins were imported in a function.
This commit is contained in:
parent
a8dd89f48f
commit
b5499c7dc5
7 changed files with 38 additions and 25 deletions
|
@ -1,4 +1,4 @@
|
|||
import os, glob
|
||||
import glob
|
||||
import sys
|
||||
|
||||
from argparse import ArgumentParser, RawTextHelpFormatter
|
||||
|
@ -6,6 +6,7 @@ from .config import ConfigReader, ReadingError
|
|||
from .dispatcher import Dispatcher, DispatchError
|
||||
from .messenger import Messenger
|
||||
from .messenger import Level
|
||||
from .plugins import Clean, Create, Link, Shell
|
||||
from .util import module
|
||||
|
||||
import dotbot
|
||||
|
@ -118,9 +119,10 @@ def main():
|
|||
else:
|
||||
log.use_color(sys.stdout.isatty())
|
||||
|
||||
plugins = []
|
||||
plugin_directories = list(options.plugin_dirs)
|
||||
if not options.disable_built_in_plugins:
|
||||
from .plugins import Clean, Create, Link, Shell
|
||||
plugins.extend([Clean, Create, Link, Shell])
|
||||
plugin_paths = []
|
||||
for directory in plugin_directories:
|
||||
for plugin_path in glob.glob(os.path.join(directory, "*.py")):
|
||||
|
@ -129,7 +131,7 @@ def main():
|
|||
plugin_paths.append(plugin_path)
|
||||
for plugin_path in plugin_paths:
|
||||
abspath = os.path.abspath(plugin_path)
|
||||
module.load(abspath)
|
||||
plugins.extend(module.load(abspath))
|
||||
if not options.config_file:
|
||||
log.error("No configuration file specified")
|
||||
exit(1)
|
||||
|
@ -151,6 +153,7 @@ def main():
|
|||
skip=options.skip,
|
||||
exit_on_failure=options.exit_on_failure,
|
||||
options=options,
|
||||
plugins=plugins,
|
||||
)
|
||||
success = dispatcher.dispatch(tasks)
|
||||
if success:
|
||||
|
|
|
@ -7,11 +7,12 @@ from .context import Context
|
|||
|
||||
class Dispatcher(object):
|
||||
def __init__(
|
||||
self, base_directory, only=None, skip=None, exit_on_failure=False, options=Namespace()
|
||||
self, base_directory, only=None, skip=None, exit_on_failure=False, options=Namespace(), plugins=None,
|
||||
):
|
||||
self._log = Messenger()
|
||||
self._setup_context(base_directory, options)
|
||||
self._load_plugins()
|
||||
plugins = plugins or []
|
||||
self._plugins = [plugin(self._context) for plugin in plugins]
|
||||
self._only = only
|
||||
self._skip = skip
|
||||
self._exit = exit_on_failure
|
||||
|
@ -65,9 +66,6 @@ class Dispatcher(object):
|
|||
return False
|
||||
return success
|
||||
|
||||
def _load_plugins(self):
|
||||
self._plugins = [plugin(self._context) for plugin in Plugin.__subclasses__()]
|
||||
|
||||
|
||||
class DispatchError(Exception):
|
||||
pass
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
import dotbot
|
||||
from ..plugin import Plugin
|
||||
|
||||
|
||||
class Clean(dotbot.Plugin):
|
||||
class Clean(Plugin):
|
||||
"""
|
||||
Cleans broken symbolic links.
|
||||
"""
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import os
|
||||
import dotbot
|
||||
|
||||
from ..plugin import Plugin
|
||||
|
||||
|
||||
class Create(dotbot.Plugin):
|
||||
class Create(Plugin):
|
||||
"""
|
||||
Create empty paths.
|
||||
"""
|
||||
|
|
|
@ -2,11 +2,12 @@ import os
|
|||
import sys
|
||||
import glob
|
||||
import shutil
|
||||
import dotbot
|
||||
import dotbot.util
|
||||
|
||||
from ..plugin import Plugin
|
||||
from ..util import shell_command
|
||||
|
||||
|
||||
class Link(dotbot.Plugin):
|
||||
class Link(Plugin):
|
||||
"""
|
||||
Symbolically links dotfiles.
|
||||
"""
|
||||
|
@ -139,7 +140,7 @@ class Link(dotbot.Plugin):
|
|||
return success
|
||||
|
||||
def _test_success(self, command):
|
||||
ret = dotbot.util.shell_command(command, cwd=self._context.base_directory())
|
||||
ret = shell_command(command, cwd=self._context.base_directory())
|
||||
if ret != 0:
|
||||
self._log.debug("Test '%s' returned false" % command)
|
||||
return ret == 0
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
import os
|
||||
import subprocess
|
||||
import dotbot
|
||||
import dotbot.util
|
||||
from ..plugin import Plugin
|
||||
from ..util import shell_command
|
||||
|
||||
|
||||
class Shell(dotbot.Plugin):
|
||||
class Shell(Plugin):
|
||||
"""
|
||||
Run arbitrary shell commands.
|
||||
"""
|
||||
|
@ -50,7 +48,7 @@ class Shell(dotbot.Plugin):
|
|||
self._log.lowinfo("%s [%s]" % (msg, cmd))
|
||||
stdout = options.get("stdout", stdout)
|
||||
stderr = options.get("stderr", stderr)
|
||||
ret = dotbot.util.shell_command(
|
||||
ret = shell_command(
|
||||
cmd,
|
||||
cwd=self._context.base_directory(),
|
||||
enable_stdin=stdin,
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import sys, os.path
|
||||
import os
|
||||
import sys
|
||||
|
||||
from dotbot.plugin import Plugin
|
||||
|
||||
# We keep references to loaded modules so they don't get garbage collected.
|
||||
loaded_modules = []
|
||||
|
@ -7,8 +10,17 @@ 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)
|
||||
loaded_module = load_module(module_name, path)
|
||||
plugins = []
|
||||
for name in dir(loaded_module):
|
||||
possible_plugin = getattr(loaded_module, name)
|
||||
try:
|
||||
if issubclass(possible_plugin, Plugin) and possible_plugin is not Plugin:
|
||||
plugins.append(possible_plugin)
|
||||
except TypeError:
|
||||
pass
|
||||
loaded_modules.append(loaded_module)
|
||||
return plugins
|
||||
|
||||
|
||||
if sys.version_info >= (3, 5):
|
||||
|
|
Loading…
Reference in a new issue