diff --git a/poetry/console/application.py b/poetry/console/application.py
index 268aa7cb9ce..3285e681c87 100644
--- a/poetry/console/application.py
+++ b/poetry/console/application.py
@@ -72,6 +72,7 @@ def _load() -> Type[Command]:
"env use",
# Plugin commands
"plugin add",
+ "plugin show",
# Self commands
"self update",
]
diff --git a/poetry/console/commands/plugin/show.py b/poetry/console/commands/plugin/show.py
new file mode 100644
index 00000000000..ced1a0fd282
--- /dev/null
+++ b/poetry/console/commands/plugin/show.py
@@ -0,0 +1,95 @@
+from collections import defaultdict
+from typing import TYPE_CHECKING
+from typing import DefaultDict
+from typing import Dict
+from typing import List
+from typing import Union
+
+from poetry.console.commands.command import Command
+
+
+if TYPE_CHECKING:
+ from poetry.core.packages.package import Package
+
+
+class PluginShowCommand(Command):
+
+ name = "plugin show"
+
+ description = "Shows information about the currently installed plugins."
+
+ def handle(self) -> int:
+ from poetry.plugins.application_plugin import ApplicationPlugin
+ from poetry.plugins.plugin_manager import PluginManager
+ from poetry.repositories.installed_repository import InstalledRepository
+ from poetry.utils.env import EnvManager
+ from poetry.utils.helpers import canonicalize_name
+
+ plugins: DefaultDict[str, Dict[str, Union["Package", List[str]]]] = defaultdict(
+ lambda: {
+ "package": None,
+ "plugins": [],
+ "application_plugins": [],
+ }
+ )
+
+ entry_points = (
+ PluginManager("application.plugin").get_plugin_entry_points()
+ + PluginManager("plugin").get_plugin_entry_points()
+ )
+
+ system_env = EnvManager.get_system_env()
+ installed_repository = InstalledRepository.load(
+ system_env, with_dependencies=True
+ )
+
+ packages_by_name = {pkg.name: pkg for pkg in installed_repository.packages}
+
+ for entry_point in entry_points:
+ plugin = entry_point.load()
+ category = "plugins"
+ if issubclass(plugin, ApplicationPlugin):
+ category = "application_plugins"
+
+ package = packages_by_name[canonicalize_name(entry_point.name)]
+ plugins[package.pretty_name]["package"] = package
+ plugins[package.pretty_name][category].append(entry_point)
+
+ for name, info in plugins.items():
+ package = info["package"]
+ self.line("")
+ self.line(
+ " • {} ({}){}".format(
+ name,
+ package.version,
+ " " + package.description if package.description else "",
+ )
+ )
+ provide_line = " "
+ if info["plugins"]:
+ provide_line += " {} plugin{}".format(
+ len(info["plugins"]), "s" if len(info["plugins"]) > 1 else ""
+ )
+
+ if info["application_plugins"]:
+ if info["plugins"]:
+ provide_line += " and"
+
+ provide_line += " {} application plugin{}".format(
+ len(info["application_plugins"]),
+ "s" if len(info["application_plugins"]) > 1 else "",
+ )
+
+ self.line(provide_line)
+
+ if package.requires:
+ self.line("")
+ self.line(" Dependencies")
+ for dependency in package.requires:
+ self.line(
+ " - {} ({})".format(
+ dependency.pretty_name, dependency.pretty_constraint
+ )
+ )
+
+ return 0
diff --git a/poetry/plugins/plugin_manager.py b/poetry/plugins/plugin_manager.py
index b1c43921f80..6f9e8f49ba2 100644
--- a/poetry/plugins/plugin_manager.py
+++ b/poetry/plugins/plugin_manager.py
@@ -1,5 +1,7 @@
import logging
+from typing import List
+
import entrypoints
from .application_plugin import ApplicationPlugin
@@ -23,11 +25,14 @@ def load_plugins(self): # type: () -> None
if self._disable_plugins:
return
- plugin_entrypoints = entrypoints.get_group_all("poetry.{}".format(self._type))
+ plugin_entrypoints = self.get_plugin_entry_points()
for entrypoint in plugin_entrypoints:
self._load_plugin_entrypoint(entrypoint)
+ def get_plugin_entry_points(self) -> List[entrypoints.EntryPoint]:
+ return entrypoints.get_group_all("poetry.{}".format(self._type))
+
def add_plugin(self, plugin): # type: (Plugin) -> None
if not isinstance(plugin, (Plugin, ApplicationPlugin)):
raise ValueError(
diff --git a/tests/console/commands/plugin/test_show.py b/tests/console/commands/plugin/test_show.py
new file mode 100644
index 00000000000..80c990376da
--- /dev/null
+++ b/tests/console/commands/plugin/test_show.py
@@ -0,0 +1,172 @@
+import pytest
+
+from entrypoints import EntryPoint as _EntryPoint
+
+from poetry.__version__ import __version__
+from poetry.core.packages.package import Package
+from poetry.factory import Factory
+from poetry.plugins.application_plugin import ApplicationPlugin
+from poetry.plugins.plugin import Plugin
+from poetry.repositories.installed_repository import InstalledRepository
+from poetry.repositories.pool import Pool
+from poetry.utils.env import EnvManager
+
+
+class EntryPoint(_EntryPoint):
+ def load(self):
+ if "ApplicationPlugin" in self.object_name:
+ return ApplicationPlugin
+
+ return Plugin
+
+
+@pytest.fixture()
+def tester(command_tester_factory):
+ return command_tester_factory("plugin show")
+
+
+@pytest.fixture()
+def installed():
+ repository = InstalledRepository()
+
+ repository.add_package(Package("poetry", __version__))
+
+ return repository
+
+
+def configure_sources_factory(repo):
+ def _configure_sources(poetry, sources, config, io):
+ pool = Pool()
+ pool.add_repository(repo)
+ poetry.set_pool(pool)
+
+ return _configure_sources
+
+
+@pytest.fixture(autouse=True)
+def setup_mocks(mocker, env, repo, installed):
+ mocker.patch.object(EnvManager, "get_system_env", return_value=env)
+ mocker.patch.object(InstalledRepository, "load", return_value=installed)
+ mocker.patch.object(
+ Factory, "configure_sources", side_effect=configure_sources_factory(repo)
+ )
+
+
+def test_show_displays_installed_plugins(app, tester, installed, mocker):
+ mocker.patch(
+ "entrypoints.get_group_all",
+ side_effect=[
+ [
+ EntryPoint(
+ "poetry-plugin",
+ "poetry_plugin.plugins:ApplicationPlugin",
+ "FirstApplicationPlugin",
+ )
+ ],
+ [
+ EntryPoint(
+ "poetry-plugin",
+ "poetry_plugin.plugins:Plugin",
+ "FirstPlugin",
+ )
+ ],
+ ],
+ )
+
+ installed.add_package(Package("poetry-plugin", "1.2.3"))
+
+ tester.execute("")
+
+ expected = """
+ • poetry-plugin (1.2.3)
+ 1 plugin and 1 application plugin
+"""
+
+ assert tester.io.fetch_output() == expected
+
+
+def test_show_displays_installed_plugins_with_multiple_plugins(
+ app, tester, installed, mocker
+):
+ mocker.patch(
+ "entrypoints.get_group_all",
+ side_effect=[
+ [
+ EntryPoint(
+ "poetry-plugin",
+ "poetry_plugin.plugins:ApplicationPlugin",
+ "FirstApplicationPlugin",
+ ),
+ EntryPoint(
+ "poetry-plugin",
+ "poetry_plugin.plugins:ApplicationPlugin",
+ "SecondApplicationPlugin",
+ ),
+ ],
+ [
+ EntryPoint(
+ "poetry-plugin",
+ "poetry_plugin.plugins:Plugin",
+ "FirstPlugin",
+ ),
+ EntryPoint(
+ "poetry-plugin",
+ "poetry_plugin.plugins:Plugin",
+ "SecondPlugin",
+ ),
+ ],
+ ],
+ )
+
+ installed.add_package(Package("poetry-plugin", "1.2.3"))
+
+ tester.execute("")
+
+ expected = """
+ • poetry-plugin (1.2.3)
+ 2 plugins and 2 application plugins
+"""
+
+ assert tester.io.fetch_output() == expected
+
+
+def test_show_displays_installed_plugins_with_dependencies(
+ app, tester, installed, mocker
+):
+ mocker.patch(
+ "entrypoints.get_group_all",
+ side_effect=[
+ [
+ EntryPoint(
+ "poetry-plugin",
+ "poetry_plugin.plugins:ApplicationPlugin",
+ "FirstApplicationPlugin",
+ )
+ ],
+ [
+ EntryPoint(
+ "poetry-plugin",
+ "poetry_plugin.plugins:Plugin",
+ "FirstPlugin",
+ )
+ ],
+ ],
+ )
+
+ plugin = Package("poetry-plugin", "1.2.3")
+ plugin.add_dependency(Factory.create_dependency("foo", ">=1.2.3"))
+ plugin.add_dependency(Factory.create_dependency("bar", "<4.5.6"))
+ installed.add_package(plugin)
+
+ tester.execute("")
+
+ expected = """
+ • poetry-plugin (1.2.3)
+ 1 plugin and 1 application plugin
+
+ Dependencies
+ - foo (>=1.2.3)
+ - bar (<4.5.6)
+"""
+
+ assert tester.io.fetch_output() == expected