Skip to content

openstb.simulator.cli.plugin

Functions:

Name Description
list_plugins

List registered plugins.

plugin

Find registered plugins.

search

Search for a plugin.

list_plugins

list_plugins(plugin_type, output)

List registered plugins.

This lists the registered simulation plugins of the given type. The type can be the full plugin entry point (e.g., openstb.simulator.distortion) or for convencience just the final name (e.g., distortion). If no type is specified all registered plugins are listed.

Source code in openstb/simulator/cli/plugin.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@plugin.command("list")
@click.argument("plugin-type", default=None, metavar="TYPE")
@click.option(
    "--output",
    "-o",
    type=click.File(mode="w"),
    default="-",
    show_default=False,
    help=(
        "Where to write the list of plugins. By default the list is written to the "
        "terminal."
    ),
)
def list_plugins(plugin_type: str | None, output: TextIOWrapper):
    """List registered plugins.

    This lists the registered simulation plugins of the given type. The type can be the
    full plugin entry point (e.g., openstb.simulator.distortion) or for convencience
    just the final name (e.g., distortion). If no type is specified all registered
    plugins are listed.

    """
    if plugin_type is None:
        for group in sorted(importlib.metadata.entry_points().groups):
            if not group.startswith("openstb.simulator"):
                continue
            _output_group(group, output)
    else:
        if "." not in plugin_type:
            plugin_type = f"openstb.simulator.{plugin_type}"
        _output_group(plugin_type, output)

plugin

plugin()

Find registered plugins.

Source code in openstb/simulator/cli/plugin.py
13
14
15
16
@click.group
def plugin():
    """Find registered plugins."""
    pass

search

search(keyword, plugin_type, name_only, match_any, case, output)

Search for a plugin.

This searches all plugins (or optionally, plugins of a certain type) for one or more keywords. By default, both the names and the help strings of the plugin are searched. Each result shows the name, source and type of the plugin along with the first line of its help string. The keywords are highlighted in the name and help string.

Note that if a keyword only matches in the main body of the help string, the result may appear to be missing this keyword since only the first line is output.

Source code in openstb/simulator/cli/plugin.py
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
@plugin.command
@click.argument("keyword", nargs=-1, required=True)
@click.option(
    "--type",
    "-t",
    "plugin_type",
    multiple=True,
    help=(
        "Restrict results to this type of plugin, e.g., openstb.simulator.distortion "
        "(or the shorter distortion for convenience). Can be given multiple times."
    ),
)
@click.option(
    "--name-only",
    "-n",
    is_flag=True,
    help="Only search the registered names of the plugins.",
)
@click.option(
    "--any",
    "match_any",
    is_flag=True,
    help=(
        "List plugins which match any of the keywords. The default behaviour is to "
        "only list those which match all keywords."
    ),
)
@click.option(
    "--case",
    "-c",
    is_flag=True,
    help="Make the search case-sensitive. By default it is case-insensitive.",
)
@click.option(
    "--output",
    "-o",
    type=click.File(mode="w"),
    default="-",
    show_default=False,
    help=(
        "Where to write the list of plugins. By default the list is written to the "
        "terminal."
    ),
)
def search(
    keyword: tuple[str],
    plugin_type: tuple[str],
    name_only: bool,
    match_any: bool,
    case: bool,
    output: TextIOWrapper,
):
    """Search for a plugin.

    This searches all plugins (or optionally, plugins of a certain type) for one or more
    keywords. By default, both the names and the help strings of the plugin are
    searched. Each result shows the name, source and type of the plugin along with the
    first line of its help string. The keywords are highlighted in the name and help
    string.

    Note that if a keyword only matches in the main body of the help string, the result
    may appear to be missing this keyword since only the first line is output.

    """

    # Find the entry points belonging to the specified groups.
    eps: list[tuple[importlib.metadata.EntryPoint, abc.Plugin]]
    if len(plugin_type) == 0:
        eps = [
            (ep, ep.load())
            for ep in importlib.metadata.entry_points()
            if ep.group.startswith("openstb.simulator.")
        ]
    else:
        type_set = {
            t if t.startswith("openstb.simulator.") else f"openstb.simulator.{t}"
            for t in plugin_type
        }
        eps = [
            (ep, ep.load())
            for ep in importlib.metadata.entry_points()
            if ep.group in type_set
        ]

    # Pre-process the keywords.
    if case:
        keywords = set(keyword)
    else:
        keywords = {k.lower() for k in keyword}

    def matches_keywords(ep: tuple[importlib.metadata.EntryPoint, abc.Plugin]) -> bool:
        """See if an entry point matches the keywords.

        Parameters
        ----------
        ep
            The entry point specification and implementing class.

        Returns
        -------
        bool
            True if it matches the keywords, False otherwise.

        """
        # Get the strings we are searching and normalise the case if required.
        name = ep[0].name
        doc = getattr(ep[1], "__doc__", "") or ""
        if not case:
            name = name.lower()
            doc = doc.lower()

        # Matching any keyword: return as soon as one matches.
        if match_any:
            for k in keywords:
                if k in name or (not name_only and k in doc):
                    return True
            return False

        # Matching all keywords: return as soon as one fails.
        else:
            for k in keywords:
                if k not in name and (name_only or k not in doc):
                    return False
            return True

    # Filter out plugins which don't match the keywords.
    eps = [ep for ep in eps if matches_keywords(ep)]

    def repl(match):
        return click.style(match.group(0), bold=True)

    # Define a regular expression to highlight the keywords in the result.
    if case:
        kw_re = re.compile(f"({'|'.join(keywords)})")
    else:
        kw_re = re.compile(f"({'|'.join(keywords)})", flags=re.IGNORECASE)

    # And now we can output the results.
    for ep, cls in eps:
        # Name, source and type of the plugin.
        name = kw_re.sub(repl, ep.name)
        if ep.dist is not None:
            if ep.dist.name == "openstb-simulator":
                src = "built-in"
            else:
                src = f"provided by {ep.dist.name}"
        else:
            src = "unknown plugin source"

        click.echo(f"{name} ({src})", file=output)
        click.secho(f"    Plugin type: {ep.group}", italic=True, file=output)

        # First line of the docstring if given.
        doc = getattr(cls, "__doc__", "") or ""
        if doc:
            doc = doc.splitlines()[0]
            click.echo(f"    {kw_re.sub(repl, doc)}", file=output)
        else:
            click.echo("    No description given.", file=output)
        click.echo(file=output)