From 28eb803a8e6aa569c9c4fa04a93baf64fba733c5 Mon Sep 17 00:00:00 2001 From: thatmattlove Date: Mon, 4 Oct 2021 01:39:00 -0700 Subject: [PATCH] Add plugins CLI --- hyperglass/cli/main.py | 40 +++++++++++++++++++++++++++++++++ hyperglass/plugins/_base.py | 45 +++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/hyperglass/cli/main.py b/hyperglass/cli/main.py index c7cea19..6e89d2e 100644 --- a/hyperglass/cli/main.py +++ b/hyperglass/cli/main.py @@ -236,6 +236,46 @@ def _directives( echo._console.print(Columns(panels)) +@cli.command(name="plugins") +def _plugins( + search: t.Optional[str] = typer.Argument(None, help="Plugin ID or Name Search Pattern"), + _input: bool = typer.Option( + False, "--input", show_default=False, is_flag=True, help="Show Input Plugins" + ), + output: bool = typer.Option( + False, "--output", show_default=False, is_flag=True, help="Show Output Plugins" + ), +): + """Show all configured devices""" + # Third Party + from rich.columns import Columns + + # Project + from hyperglass.state import use_state + + to_fetch = ("input", "output") + if _input is True: + to_fetch = ("input",) + + elif output is True: + to_fetch = ("output",) + + state = use_state() + all_plugins = [plugin for _type in to_fetch for plugin in state.plugins(_type)] + + if search is not None: + pattern = re.compile(search, re.IGNORECASE) + matching = [plugin for plugin in all_plugins if pattern.match(plugin.name)] + if len(matching) == 0: + echo.error(f"No plugins matching {search!r}") + raise typer.Exit(1) + else: + echo._console.print(Columns(matching)) + raise typer.Exit(0) + + echo._console.print(Columns(all_plugins)) + + @cli.command(name="params") def _params( path: t.Optional[str] = typer.Argument( diff --git a/hyperglass/plugins/_base.py b/hyperglass/plugins/_base.py index 20ce4b6..676d582 100644 --- a/hyperglass/plugins/_base.py +++ b/hyperglass/plugins/_base.py @@ -8,6 +8,13 @@ from inspect import Signature # Third Party from pydantic import BaseModel, PrivateAttr +# Project +from hyperglass.log import log as _logger + +if t.TYPE_CHECKING: + # Third Party + from loguru import Logger + PluginType = t.Union[t.Literal["output"], t.Literal["input"]] SupportedMethod = t.TypeVar("SupportedMethod") @@ -16,9 +23,11 @@ class HyperglassPlugin(BaseModel, ABC): """Plugin to interact with device command output.""" __hyperglass_builtin__: bool = PrivateAttr(False) + _type: t.ClassVar[str] name: str common: bool = False ref: t.Optional[str] = None + log: t.ClassVar["Logger"] = _logger @property def _signature(self) -> Signature: @@ -55,6 +64,42 @@ class HyperglassPlugin(BaseModel, ABC): name = kwargs.pop("name", None) or self.__class__.__name__ super().__init__(name=name, **kwargs) + def __rich_console__(self, *_, **__): + """Create a rich representation of this plugin for the hyperglass CLI.""" + + # Third Party + from rich.text import Text + from rich.panel import Panel + from rich.table import Table + from rich.pretty import Pretty + + table = Table.grid(padding=(0, 1), expand=False) + table.add_column(justify="right") + + data = {"builtin": True if self.__hyperglass_builtin__ else False} + data.update( + { + attr: getattr(self, attr) + for attr in ("name", "common", "directives", "platforms") + if hasattr(self, attr) + } + ) + data = {k: data[k] for k in sorted(data.keys())} + for key, value in data.items(): + table.add_row( + Text.assemble((key, "inspect.attr"), (" =", "inspect.equals")), Pretty(value) + ) + + yield Panel( + table, + expand=False, + title=f"[bold magenta]{self.name}", + title_align="left", + subtitle=f"[bold cornflower_blue]{self._type.capitalize()} Plugin", + subtitle_align="right", + padding=(1, 3), + ) + class DirectivePlugin(BaseModel): """Plugin associated with directives.