︙ | | | ︙ | |
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
|
</td>
<td>
<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>
</td></tr></table>
"""
import sys
import os
import os.path
|
|
|
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
|
</td>
<td>
<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>
</td></tr></table>
"""
import sys
import os
import os.path
|
︙ | | | ︙ | |
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
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
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
|
def get_data(filename, decode=False, gzip=False, file_root=None):
"""
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
----------
filename : str
filename in pyz or bundle
decode : bool
text file decoding utf-8
gzip : bool
automatic gzdecode
file_root : list
alternative base module (application or pyz root)
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)
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
----------
extra_paths : list
in addition to plugin_base list
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(
sys.modules[module_or_path]
))
elif os.path.exists(module_or_path):
paths.append(module_or_path)
# Should list plugins within zips as well as local paths
dirs = pkgutil.iter_modules(paths + (extra_paths or []))
return [name for loader, name, ispkg in dirs]
# Plugin => meta dict
# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
def all_plugin_meta():
"""
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
"""
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
----------
filename : str
Read literal files, or .pyz contents.
src : str
From already uncovered script code.
module : str
Lookup per pkgutil, from plugin_base or top-level modules.
frame : int
Extract comment header of caller (default).
extra_base : list
Additional search directories.
max_length : list
Maximum size to read from files (6K default).
Returns
-------
dict : Extracted comment fields, with config: preparsed
The result dictionary 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.
"""
# Try via pkgutil first,
# find any plugins.* modules, or main packages
if module:
filename = module
for base in plugin_base + kwargs.get("extra_base", []):
|
|
|
<
|
<
|
<
|
<
|
<
<
<
|
|
<
|
|
<
|
<
<
<
|
|
|
|
|
|
|
<
|
<
|
<
|
<
|
<
|
<
|
<
<
<
|
|
|
|
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
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
288
289
|
def get_data(filename, decode=False, gzip=False, file_root=None):
"""
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 | | |
|-------------|---------|----------------------------|
| filename | str | filename in pyz or bundle |
| decode | bool | text file decoding utf-8 |
| gzip | bool | automatic gzdecode |
| file_root | list | alternative base module (application or pyz root) |
| **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.warning("get_data() didn't find '%s' in '%s'", filename, file_root)
# Plugin name lookup
# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
def module_list(extra_paths=None):
"""
Search through ./plugins/ (and other configured plugin_base
names → paths) and get module basenames.
| Parameter | | |
|-------------|---------|---------------------------------|
| extra_paths | list | in addition to plugin_base list |
| **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.realpath(
sys.modules[module_or_path]
))
elif os.path.exists(module_or_path):
paths.append(module_or_path)
# Should list plugins within zips as well as local paths
dirs = pkgutil.iter_modules(paths + (extra_paths or []))
return [name for loader, name, ispkg in dirs]
# Plugin => meta dict
# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
def all_plugin_meta():
"""
This is a trivial wrapper to assemble a complete dictionary
of available/installed plugins. It associates each plugin name
with a its meta{} fields.
| 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 | | |
|-------------|---------|-------------------------------------------------|
| filename | str | Read literal files, or .pyz contents. |
| src | str | From already uncovered script code. |
| module | str | Lookup per pkgutil, relative to plugin_base |
| frame | int | Extract comment header of caller (default). |
| extra_base | list | Additional search directories. |
| max_length | list | Maximum size to read from files (6K default). |
| **Returns** | dict | Extracted comment fields, with config: preparsed|
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 (`OptionList`) of dictionaries.
"""
# Try via pkgutil first,
# find any plugins.* modules, or main packages
if module:
filename = module
for base in plugin_base + kwargs.get("extra_base", []):
|
︙ | | | ︙ | |
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
|
@renamed_arguments({"fn": "filename"})
def plugin_meta_extract(src="", filename=None, literal=False):
"""
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
----------
src : str
from existing source code
filename : str
set filename attribute
literls : bool
just split comment from doc
"""
# Defaults
meta = {
"id": os.path.splitext(os.path.basename(filename or ""))[0],
"fn": filename,
"api": "python",
|
|
|
<
|
<
|
<
|
>
|
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
|
@renamed_arguments({"fn": "filename"})
def plugin_meta_extract(src="", filename=None, literal=False):
"""
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 | | |
|-------------|---------|---------------------------------|
| src | str | from existing source code |
| filename | str | set filename attribute |
| literls | bool | just split comment from doc |
| **Returns** | dict | fields |
"""
# Defaults
meta = {
"id": os.path.splitext(os.path.basename(filename or ""))[0],
"fn": filename,
"api": "python",
|
︙ | | | ︙ | |
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
|
}
# 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)
return meta
src = src.group(0)
src = rx.hash.sub("", src).strip()
# Split comment block
if src.find("\n\n") > 0:
src, meta["doc"] = src.split("\n\n", 1)
|
|
|
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
|
}
# Extract coherent comment block
src = src.replace("\r", "")
if not literal:
src = rx.comment.search(src)
if not src:
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
if src.find("\n\n") > 0:
src, meta["doc"] = src.split("\n\n", 1)
|
︙ | | | ︙ | |
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
|
Creates an array from JSON/YAML option lists.
Stubs out name, value, type, description if absent.
# config:
{ name: 'var1', type: text, value: "default, ..." }
{ name=option2, type=boolean, $value=1, etc. }
Parameters
----------
src : str
unprocessed config: field
Returns
-------
list : of option dictionaries
"""
config = []
for entry in rx.config.findall(src):
entry = entry[0] or entry[1]
opt = {
"type": None,
|
|
|
<
|
<
<
<
|
|
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
|
Creates an array from JSON/YAML option lists.
Stubs out name, value, type, description if absent.
# config:
{ name: 'var1', type: text, value: "default, ..." }
{ name=option2, type=boolean, $value=1, etc. }
| Parameters | | |
|-------------|---------|--------------------------------------|
| src | str | unprocessed config: field |
| **Returns** | list | of option dictionaries |
"""
config = []
for entry in rx.config.findall(src):
entry = entry[0] or entry[1]
opt = {
"type": None,
|
︙ | | | ︙ | |
580
581
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
609
610
|
# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
def add_plugin_defaults(conf_options, conf_plugins, meta, module=""):
"""
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
----------
conf_options : dict : input/output
storage for amassed options
conf_plugins : dict : input/output
enable status based on plugin state/priority:
meta : dict
input plugin meta data (invoke once per plugin)
module : str
basename of meta: blocks plugin file
"""
# 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", "")
_name = opt.get("name")
_type = opt.get("type")
if _name in conf_options:
continue
# typemap
if _type == "bool":
val = _value.lower() in ("1", "true", "yes", "on")
|
|
|
<
|
<
|
<
|
<
|
>
|
|
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
|
# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
def add_plugin_defaults(conf_options, conf_plugins, meta, module=""):
"""
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 | | |
|-------------|---------|--------------------------------------|
|conf_options | dict 🔁 | storage for amassed options |
|conf_plugins | dict 🔁 | enable status based on plugin state/priority: |
|meta | dict | input plugin meta data (invoke once per plugin)|
|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") or ""
_name = opt.get("name")
_type = opt.get("type")
if _name in conf_options:
continue
# typemap
if _type == "bool":
val = _value.lower() in ("1", "true", "yes", "on")
|
︙ | | | ︙ | |