Differences From Artifact [5fc579b1c8]:

To Artifact [9584d270b3]:


99
100
101
102
103
104

105
106
107
108
109
99
100
101
102
103

104
105
106
107
108
109




-
+





 <li> Main function <a href="#pluginconf.plugin_meta">plugin_meta()</a> unpacks meta fields
    into dictionaries.
 <li> Other utility code is about module listing, relative to
   <a href="#pluginconf.plugin_base">plugin_base</a> anchors.
 <li> <a href="https://pypi.org/project/pluginconf/">//pypi.org/project/pluginconf/</a>
 <li> <a href="https://fossil.include-once.org/pluginspec/">//fossil.include-once.org/pluginspec/</a>
<li><a href="https://fossil.include-once.org/pluginspec/">//fossil.include-once.org/pluginspec/</a>
</td></tr></table>
"""


import sys
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
252
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




-
-
+
+
-
-
+
-
-
+
-
-
+
-
-
+
-
-
-
-
+









-
+
-









-
-
+
+
-
-
+
-
-
-
-
+









-
+





    Fetches file content from install path or from within PYZ
    archive. This is just an alias and convenience wrapper for
    pkgutil.get_data().
    Utilizes the data_root as top-level reference.

    Parameters
    ----------
    | Parameters  | | |
    |-------------|---------|----------------------------|
    filename :  str
        filename in pyz or bundle
    | filename    | str     | filename in pyz or bundle  |
    decode : bool
        text file decoding utf-8
    | decode      | bool    | text file decoding utf-8   |
    gzip : bool
        automatic gzdecode
    | gzip        | bool    | automatic gzdecode         |
    file_root : list
        alternative base module (application or pyz root)
    | file_root   | list    | alternative base module (application or pyz root) |

    Returns
    -------
    str : file contents
    | **Returns** |  str    | file contents |
    """
    try:
        data = pkgutil.get_data(file_root or data_root, filename)
        if gzip:
            data = gzip_decode(data)
        if decode:
            return data.decode("utf-8", errors='ignore')
        return str(data)
    except: #(FileNotFoundError, IOError, OSError, ImportError, gzip.BadGzipFile):
        log.error("get_data() didn't find '%s' in '%s'", filename, file_root)
        log.warning("get_data() didn't find '%s' in '%s'", filename, file_root)
        pass


# Plugin name lookup
# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
def module_list(extra_paths=None):
    """
    Search through ./plugins/ (and other configured plugin_base
    names → paths) and get module basenames.

    Parameters
    ----------
    | Parameter   | | |
    |-------------|---------|---------------------------------|
    extra_paths : list
        in addition to plugin_base list
    | extra_paths | list    | in addition to plugin_base list |

    Returns
    -------
    list : names of found plugins
    | **Returns** | list    | names of found plugins          |
    """

    # Convert plugin_base package names into paths for iter_modules
    paths = []
    for module_or_path in plugin_base:
        if sys.modules.get(module_or_path):
            try:
                paths += sys.modules[module_or_path].__path__
            except AttributeError:
                paths += os.path.dirname(os.path.realname(
                paths += os.path.dirname(os.path.realpath(
                    sys.modules[module_or_path]
                ))
        elif os.path.exists(module_or_path):
            paths.append(module_or_path)

261
262
263
264
265
266
267
268



269
270
271
272
273
274
275
276
277
278
279
280
281
282
283


284
285

286
287

288
289

290
291

292
293

294
295

296
297
298
299

300
301

302
303

304
305
306
307
308
249
250
251
252
253



254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269


270
271


272


273


274


275


276


277




278
279

280
281

282
283
284
285
286
287




-
-
-
+
+
+













-
-
+
+
-
-
+
-
-
+
-
-
+
-
-
+
-
-
+
-
-
+
-
-
-
-
+

-
+

-
+





    """
    This is a trivial wrapper to assemble a complete dictionary
    of available/installed plugins. It associates each plugin name
    with a its meta{} fields.

    Returns
    -------
    dict : names to meta data dict
    | Parameters  | | |
    |-------------|---------|---------------------------------|
    | **Returns** | dict    | names to meta data dict         |
    """
    return {
        name: plugin_meta(module=name) for name in module_list()
    }


# Plugin meta data extraction
# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
@renamed_arguments({"fn": "filename"})
def plugin_meta(filename=None, src=None, module=None, frame=1, **kwargs):
    """
    Extract plugin meta data block from specified source.

    Parameters
    ----------
    | Parameters  | | |
    |-------------|---------|-------------------------------------------------|
    filename : str
        Read literal files, or .pyz contents.
    | filename    | str     | Read literal files, or .pyz contents.           |
    src : str
        From already uncovered script code.
    | src         | str     | From already uncovered script code.             |
    module : str
        Lookup per pkgutil, from plugin_base or top-level modules.
    | module      | str     | Lookup per pkgutil, relative to plugin_base     |
    frame : int
        Extract comment header of caller (default).
    | frame       | int     | Extract comment header of caller (default).     |
    extra_base : list
        Additional search directories.
    | extra_base  | list    | Additional search directories.                  |
    max_length : list
        Maximum size to read from files (6K default).
    | max_length  | list    | Maximum size to read from files (6K default).   |

    Returns
    -------
    dict : Extracted comment fields, with config: preparsed
    | **Returns** | dict    | Extracted comment fields, with config: preparsed|

    The result dictionary has fields accessible as e.g. `pmd["title"]`
    The result dictionary (`PluginMeta`) has fields accessible as e.g. `pmd["title"]`
    or `pmd.version`. The documentation block after all fields: is called
    ["doc"]`. And `pmd.config` already parsed as a list of dictionaries.
    `["doc"]`. And `pmd.config` already parsed as a list (`OptionList`) of dictionaries.
    """

    # Try via pkgutil first,
    # find any plugins.* modules, or main packages
    if module:
350
351
352
353
354
355
356


357
358

359
360

361
362


363
364
365
366
367
329
330
331
332
333


334
335


336


337


338
339
340
341
342
343
344




-
-
+
+
-
-
+
-
-
+
-
-
+
+





    """
    Finds the first comment block. Splits key:value header
    fields from comment. Turns everything into an dict, with
    some stub fields if absent. Dashes substituted for underscores.

    Parameters
    ----------
    | Parameters  | | |
    |-------------|---------|---------------------------------|
    src : str
        from existing source code
    | src         | str     | from existing source code       |
    filename : str
        set filename attribute
    | filename    | str     | set filename attribute          |
    literls : bool
        just split comment from doc
    | literls     | bool    | just split comment from doc     |
    | **Returns** | dict    | fields                          |
    """

    # Defaults
    meta = {
        "id": os.path.splitext(os.path.basename(filename or ""))[0],
380
381
382
383
384
385

386
387
388
389
390
357
358
359
360
361

362
363
364
365
366
367




-
+





    # Extract coherent comment block
    src = src.replace("\r", "")
    if not literal:
        src = rx.comment.search(src)
        if not src:
            log.warn("Couldn't read source meta information: %s", filename)
            log.warning("Couldn't read source meta information: %s", filename)
            return meta
        src = src.group(0)
        src = rx.hash.sub("", src).strip()

    # Split comment block
441
442
443
444
445
446
447


448
449

450
451
452
453

454
455
456
457
458
418
419
420
421
422


423
424


425




426
427
428
429
430
431




-
-
+
+
-
-
+
-
-
-
-
+





    Stubs out name, value, type, description if absent.
    # config:
       { name: 'var1', type: text, value: "default, ..." }
       { name=option2, type=boolean, $value=1, etc. }

    Parameters
    ----------
    | Parameters  | | |
    |-------------|---------|--------------------------------------|
    src : str
        unprocessed config: field
    | src         | str     | unprocessed config: field            |

    Returns
    -------
    list : of option dictionaries
    | **Returns** | list    | of option dictionaries               |
    """

    config = []
    for entry in rx.config.findall(src):
        entry = entry[0] or entry[1]
582
583
584
585
586
587
588


589
590

591
592

593
594

595
596


597
598
599
600
601
602
603

604
605
606
607
608
555
556
557
558
559


560
561


562


563


564


565
566
567
568
569
570
571
572

573
574
575
576
577
578




-
-
+
+
-
-
+
-
-
+
-
-
+
-
-
+
+






-
+





    """
    Utility function which collect defaults from plugin meta data to
    a config store. Which in the case of streamtuner2 is really just a
    dictionary `conf{}` and a plugin list in `conf.plugins{}`.

    Parameters
    ----------
    | Parameters  | | |
    |-------------|---------|--------------------------------------|
    conf_options : dict : input/output
        storage for amassed options
    |conf_options | dict 🔁 | storage for amassed options          |
    conf_plugins : dict : input/output
        enable status based on plugin state/priority:
    |conf_plugins | dict 🔁 | enable status based on plugin state/priority: |
    meta : dict
        input plugin meta data (invoke once per plugin)
    |meta         | dict    | input plugin meta data (invoke once per plugin)|
    module : str
        basename of meta: blocks plugin file
    |module       | str     | basename of meta: blocks plugin file |
    | **Returns** | None    | -                                    |
    """

    # Option defaults, if not yet defined
    for opt in meta.get("config", []):
        if "name" not in opt or "value" not in opt:
            continue
        _value = opt.get("value", "")
        _value = opt.get("value") or ""
        _name = opt.get("name")
        _type = opt.get("type")
        if _name in conf_options:
            continue
        # typemap