diff --git a/dotbot/cli.py b/dotbot/cli.py index 3a14d2d..84a1377 100644 --- a/dotbot/cli.py +++ b/dotbot/cli.py @@ -7,7 +7,7 @@ from argparse import ArgumentParser, RawTextHelpFormatter import dotbot from .config import ConfigReader, ReadingError -from .dispatcher import Dispatcher, DispatchError +from .dispatcher import Dispatcher, DispatchError, _all_plugins from .messenger import Level, Messenger from .plugins import Clean, Create, Link, Shell from .util import module @@ -151,6 +151,7 @@ def main(): # default to directory of config file base_directory = os.path.dirname(os.path.abspath(options.config_file)) os.chdir(base_directory) + _all_plugins[:] = plugins # for backwards compatibility, see dispatcher.py dispatcher = Dispatcher( base_directory, only=options.only, diff --git a/dotbot/dispatcher.py b/dotbot/dispatcher.py index 6660d88..5ed7ad5 100644 --- a/dotbot/dispatcher.py +++ b/dotbot/dispatcher.py @@ -5,6 +5,14 @@ from .context import Context from .messenger import Messenger from .plugin import Plugin +# Before b5499c7dc5b300462f3ce1c2a3d9b7a76233b39b, Dispatcher auto-loaded all +# plugins, but after that change, plugins are passed in explicitly (and loaded +# in cli.py). There are some plugins that rely on the old Dispatcher behavior, +# so this is a workaround for implementing similar functionality: when +# Dispatcher is constructed without an explicit list of plugins, _all_plugins is +# used instead. +_all_plugins = [] # filled in by cli.py + class Dispatcher: def __init__( @@ -16,9 +24,12 @@ class Dispatcher: options=Namespace(), plugins=None, ): + # if the caller wants no plugins, the caller needs to explicitly pass in + # plugins=[] self._log = Messenger() self._setup_context(base_directory, options, plugins) - plugins = plugins or [] + if plugins is None: + plugins = _all_plugins self._plugins = [plugin(self._context) for plugin in plugins] self._only = only self._skip = skip diff --git a/tests/dotbot_plugin_dispatcher_no_plugins.py b/tests/dotbot_plugin_dispatcher_no_plugins.py new file mode 100644 index 0000000..734169a --- /dev/null +++ b/tests/dotbot_plugin_dispatcher_no_plugins.py @@ -0,0 +1,18 @@ +# https://github.com/anishathalye/dotbot/issues/339, https://github.com/anishathalye/dotbot/pull/332 +# if plugins instantiate a Dispatcher without explicitly passing in plugins, +# the Dispatcher should have access to all plugins (matching context.plugins()) + +import dotbot +from dotbot.dispatcher import Dispatcher + + +class Dispatch(dotbot.Plugin): + def can_handle(self, directive): + return directive == "dispatch" + + def handle(self, directive, data): + dispatcher = Dispatcher( + base_directory=self._context.base_directory(), + options=self._context.options(), + ) + return dispatcher.dispatch(data) diff --git a/tests/test_cli.py b/tests/test_cli.py index a9fc7ae..136533b 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -199,3 +199,17 @@ def test_plugin_context_plugin(capfd, home, dotfiles, run_dotbot): stdout = capfd.readouterr().out.splitlines() assert any(line.startswith("apple") for line in stdout) + + +def test_plugin_dispatcher_no_plugins(capfd, home, dotfiles, run_dotbot): + """Verify that plugins instantiating Dispatcher without plugins work.""" + + plugin_file = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "dotbot_plugin_dispatcher_no_plugins.py" + ) + shutil.copy(plugin_file, os.path.join(dotfiles.directory, "plugin.py")) + dotfiles.write_config([{"dispatch": [{"shell": [{"command": "echo apple", "stdout": True}]}]}]) + run_dotbot("--plugin", os.path.join(dotfiles.directory, "plugin.py")) + + stdout = capfd.readouterr().out.splitlines() + assert any(line.startswith("apple") for line in stdout)