Overview
Comment:use table markup for api docs
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: cd360e316ff48b02a1776025bbf630c32b52d2da5a7380477c3bc1146476307b
User & Date: mario on 2022-10-30 15:03:17
Other Links: manifest | tags
Context
2022-10-30
15:54
update template (RTD-like) check-in: feb199bddf user: mario tags: trunk
15:03
use table markup for api docs check-in: cd360e316f user: mario tags: trunk
09:13
doc updates, minor test changes (reset/tearDown) check-in: d8c4b46a36 user: mario tags: trunk
Changes

Modified html/bind.html from [f4a4553477] to [6d1581117d].

67
68
69
70
71
72
73



74
75
76
77
78
79

























80
81
82


83
84

85
86
87
88
89
90
91



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
67
68
69
70
71
72
73
74
75
76






77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
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







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






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





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





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






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







<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="pluginconf.bind.base"><code class="name flex">
<span>def <span class="ident">base</span></span>(<span>module, path=None)</span>
</code></dt>
<dd>
<div class="desc"><p>Register module as package/plugin_base. Or expand its search path 🛠 .</p>
<table>
<thead>
<tr>
<h2 id="parameters">Parameters</h2>
<dl>
<dt><strong><code>module</code></strong> :&ensp;<code>module/str</code></dt>
<dd>The package basename to later load plugins from (must be a package,
like <code>plugins/__init__.py</code>, or be tied to a path= or zip). Ideally
this module was already imported in main. But parameter may be a string.</dd>
<th>Parameters</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>module</td>
<td>module/str</td>
<td>Package basename to later load plugins from</td>
</tr>
<tr>
<td>path</td>
<td>str</td>
<td>Bind directory or pyz/zip bundle to plugin_base.</td>
</tr>
<tr>
<td><strong>Returns</strong></td>
<td>None</td>
<td>-</td>
</tr>
</tbody>
</table>
<p>Module should be a package, as in a directory and init <code>plugins/__init__.py</code>.
Ideally this module was already imported in main. But parameter may be a string.</p>
<dt><strong><code>path</code></strong> :&ensp;<code>str</code></dt>
<dd>Add a directory or pyz/zip bundle to registered plugin_base. Could
be invoked multiple times =./contrib/, =/usr/share/app/extenstions/,
<p>This could be invoked multiple times for the package name to append further
path= arguments (=./contrib/, =/usr/share/app/extenstions/, or a .pyz). Which
=~/.config/app/userplug/ (same as declaring the <code>__path__</code> in the
base <code>package/__init__.py</code>.)</dd>
is functionally identical to delcaring <code>__path__</code> in the <code>package/__init__.py</code>.</p></div>
</dl></div>
</dd>
<dt id="pluginconf.bind.defaults"><code class="name flex">
<span>def <span class="ident">defaults</span></span>(<span>conf)</span>
</code></dt>
<dd>
<div class="desc"><p>Traverse installed plugins and expand config dict with presets 🧩 🧾</p>
<table>
<thead>
<tr>
<h2 id="parameters">Parameters</h2>
<dl>
<dt><strong><code>conf</code></strong> :&ensp;<code>dict 🔁</code></dt>
<dd>Expands the top-level config dict with preset values from any plugins.</dd>
</dl></div>
<th>Parameters</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>conf</td>
<td>dict 🔁</td>
<td>Expands the conf dict with preset values from any plugins.</td>
</tr>
<tr>
<td><strong>Returns</strong></td>
<td>None</td>
<td>-</td>
</tr>
</tbody>
</table></div>
</dd>
<dt id="pluginconf.bind.find"><code class="name flex">
<span>def <span class="ident">find</span></span>(<span>**kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>Find plugins by e.g. type= or category= 🧩</p>
<h2 id="parameters">Parameters</h2>
<dl>
<dt><strong><code>type</code></strong> :&ensp;<code>str</code></dt>
<dd>Usually you'd search on a designated plugin categorization, like type=
<div class="desc"><p>Find plugins by any combination of e.g. type= or category= 🧩</p>
<table>
<thead>
<tr>
<th>Parameters</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>type</td>
<td>str</td>
<td>Search by type: in plugins</td>
</tr>
<tr>
<td>api</td>
<td>str</td>
<td>Matching api: designator</td>
</tr>
<tr>
<td>category</td>
and api=, or slot=, or class= or whatever is most consistent. Multiple
attributes can be filtered on. (Version/title probably not useful here.)</dd>
</dl>
<h2 id="returns">Returns</h2>
<dl>
<dt><strong><code>dict</code></strong> :&ensp;<code>basename → PluginMeta dict</code></dt>
<dd>&nbsp;</dd>
</dl></div>
<td>str</td>
<td>Or a menu/category or other attributes</td>
</tr>
<tr>
<td><strong>Returns</strong></td>
<td>dict</td>
<td>basename → <code>PluginMeta</code> dict of matches</td>
</tr>
</tbody>
</table></div>
</dd>
<dt id="pluginconf.bind.load"><code class="name flex">
<span>def <span class="ident">load</span></span>(<span>name)</span>
</code></dt>
<dd>
<div class="desc"><p>Import individual plugin from any of the base paths 🚐</p>
<h2 id="parameters">Parameters</h2>
<dl>
<dt><strong><code>name</code></strong> :&ensp;<code>str</code></dt>
<dd>Plugin name in any of the registered plugin_base´s. (The whole
<div class="desc"><p>Import individual plugin from any of the base paths 🚐
(The whole namespace is assumed to be flat, and identifiers to be unique.)</p>
<table>
<thead>
<tr>
<th>Parameters</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>name</td>
<td>str</td>
<td>Plugin name in any of the registered plugin_base´s.</td>
namespace is assumed to be flat, and identifiers to be unique.)</dd>
</dl></div>
</tr>
<tr>
<td><strong>Returns</strong></td>
<td>module</td>
<td>Imported module</td>
</tr>
</tbody>
</table></div>
</dd>
<dt id="pluginconf.bind.load_enabled"><code class="name flex">
<span>def <span class="ident">load_enabled</span></span>(<span>conf)</span>
</code></dt>
<dd>
<div class="desc"><p>Import modules that are enabled in conf[plugins]={name:True,…} 🧾 🚐</p>
<table>
<thead>
<tr>
<h2 id="parameters">Parameters</h2>
<dl>
<dt><strong><code>conf</code></strong> :&ensp;<code>dict</code></dt>
<dd>Simple options-value dictionary, but with one conf["plugins"] = {} subdict,
<th>Parameters</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>conf</td>
<td>dict</td>
<td>Should contain conf["plugins"] activation states</td>
which holds plugin activation states. The config dict doesn't have to match
the available plugin options (defaults can be added), but should focus on
essential presets. Differentiation only might become relevant for storage.</dd>
</dl></div>
</tr>
<tr>
<td><strong>Returns</strong></td>
<td>generator</td>
<td>Of loaded modules</td>
</tr>
</tbody>
</table></div>
</dd>
</dl>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="pluginconf.bind.isolated"><code class="flex name class">

Modified html/index.html from [9345b17570] to [0bfaf66345].

28
29
30
31
32
33
34
35

36
37
38
39
40
41
42
28
29
30
31
32
33
34

35
36
37
38
39
40
41
42







-
+







</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>
<li><a href="https://fossil.include-once.org/pluginspec/">//fossil.include-once.org/pluginspec/</a>
</td></tr></table>
</section>
<section>
<h2 class="section-title" id="header-submodules">Sub-modules</h2>
<dl>
<dt><code class="name"><a title="pluginconf.bind" href="bind.html">pluginconf.bind</a></code></dt>
<dd>
89
90
91
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
89
90
91
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
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







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








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









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







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






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

-
+







<dt id="pluginconf.add_plugin_defaults"><code class="name flex">
<span>def <span class="ident">add_plugin_defaults</span></span>(<span>conf_options, conf_plugins, meta, module='')</span>
</code></dt>
<dd>
<div class="desc"><p>Utility function which collect defaults from plugin meta data to
a config store. Which in the case of streamtuner2 is really just a
dictionary <code>conf{}</code> and a plugin list in <code>conf.plugins{}</code>.</p>
<table>
<thead>
<tr>
<h2 id="parameters">Parameters</h2>
<dl>
<dt><strong><code>conf_options</code></strong> :&ensp;<code>dict : input/output</code></dt>
<dd>storage for amassed options</dd>
<dt><strong><code>conf_plugins</code></strong> :&ensp;<code>dict : input/output</code></dt>
<dd>enable status based on plugin state/priority:</dd>
<dt><strong><code>meta</code></strong> :&ensp;<code>dict</code></dt>
<dd>input plugin meta data (invoke once per plugin)</dd>
<dt><strong><code>module</code></strong> :&ensp;<code>str</code></dt>
<dd>basename of meta: blocks plugin file</dd>
</dl></div>
<th>Parameters</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>conf_options</td>
<td>dict 🔁</td>
<td>storage for amassed options</td>
</tr>
<tr>
<td>conf_plugins</td>
<td>dict 🔁</td>
<td>enable status based on plugin state/priority:</td>
</tr>
<tr>
<td>meta</td>
<td>dict</td>
<td>input plugin meta data (invoke once per plugin)</td>
</tr>
<tr>
<td>module</td>
<td>str</td>
<td>basename of meta: blocks plugin file</td>
</tr>
<tr>
<td><strong>Returns</strong></td>
<td>None</td>
<td>-</td>
</tr>
</tbody>
</table></div>
</dd>
<dt id="pluginconf.all_plugin_meta"><code class="name flex">
<span>def <span class="ident">all_plugin_meta</span></span>(<span>)</span>
</code></dt>
<dd>
<div class="desc"><p>This is a trivial wrapper to assemble a complete dictionary
of available/installed plugins. It associates each plugin name
with a its meta{} fields.</p>
<table>
<thead>
<tr>
<th>Parameters</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<h2 id="returns">Returns</h2>
<dl>
<dt><strong><code>dict</code></strong> :&ensp;<code>names to meta data dict</code></dt>
<dd>&nbsp;</dd>
</dl></div>
<td><strong>Returns</strong></td>
<td>dict</td>
<td>names to meta data dict</td>
</tr>
</tbody>
</table></div>
</dd>
<dt id="pluginconf.get_data"><code class="name flex">
<span>def <span class="ident">get_data</span></span>(<span>filename, decode=False, gzip=False, file_root=None)</span>
</code></dt>
<dd>
<div class="desc"><p>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.</p>
<table>
<thead>
<tr>
<h2 id="parameters">Parameters</h2>
<dl>
<dt><strong><code>filename</code></strong> :&ensp;<code> str</code></dt>
<dd>filename in pyz or bundle</dd>
<dt><strong><code>decode</code></strong> :&ensp;<code>bool</code></dt>
<dd>text file decoding utf-8</dd>
<dt><strong><code>gzip</code></strong> :&ensp;<code>bool</code></dt>
<dd>automatic gzdecode</dd>
<dt><strong><code>file_root</code></strong> :&ensp;<code>list</code></dt>
<dd>alternative base module (application or pyz root)</dd>
</dl>
<h2 id="returns">Returns</h2>
<dl>
<dt><strong><code>str</code></strong> :&ensp;<code>file contents</code></dt>
<dd>&nbsp;</dd>
</dl></div>
<th>Parameters</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>filename</td>
<td>str</td>
<td>filename in pyz or bundle</td>
</tr>
<tr>
<td>decode</td>
<td>bool</td>
<td>text file decoding utf-8</td>
</tr>
<tr>
<td>gzip</td>
<td>bool</td>
<td>automatic gzdecode</td>
</tr>
<tr>
<td>file_root</td>
<td>list</td>
<td>alternative base module (application or pyz root)</td>
</tr>
<tr>
<td><strong>Returns</strong></td>
<td>str</td>
<td>file contents</td>
</tr>
</tbody>
</table></div>
</dd>
<dt id="pluginconf.module_list"><code class="name flex">
<span>def <span class="ident">module_list</span></span>(<span>extra_paths=None)</span>
</code></dt>
<dd>
<div class="desc"><p>Search through ./plugins/ (and other configured plugin_base
names → paths) and get module basenames.</p>
<table>
<thead>
<tr>
<h2 id="parameters">Parameters</h2>
<dl>
<dt><strong><code>extra_paths</code></strong> :&ensp;<code>list</code></dt>
<dd>in addition to plugin_base list</dd>
</dl>
<h2 id="returns">Returns</h2>
<dl>
<dt><strong><code>list</code></strong> :&ensp;<code>names</code> of <code>found plugins</code></dt>
<dd>&nbsp;</dd>
</dl></div>
<th>Parameter</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>extra_paths</td>
<td>list</td>
<td>in addition to plugin_base list</td>
</tr>
<tr>
<td><strong>Returns</strong></td>
<td>list</td>
<td>names of found plugins</td>
</tr>
</tbody>
</table></div>
</dd>
<dt id="pluginconf.plugin_meta"><code class="name flex">
<span>def <span class="ident">plugin_meta</span></span>(<span>filename=None, src=None, module=None, frame=1, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>Extract plugin meta data block from specified source.</p>
<table>
<thead>
<tr>
<h2 id="parameters">Parameters</h2>
<dl>
<dt><strong><code>filename</code></strong> :&ensp;<code>str</code></dt>
<dd>Read literal files, or .pyz contents.</dd>
<dt><strong><code>src</code></strong> :&ensp;<code>str</code></dt>
<dd>From already uncovered script code.</dd>
<dt><strong><code>module</code></strong> :&ensp;<code>str</code></dt>
<dd>Lookup per pkgutil, from plugin_base or top-level modules.</dd>
<dt><strong><code>frame</code></strong> :&ensp;<code>int</code></dt>
<dd>Extract comment header of caller (default).</dd>
<dt><strong><code>extra_base</code></strong> :&ensp;<code>list</code></dt>
<dd>Additional search directories.</dd>
<dt><strong><code>max_length</code></strong> :&ensp;<code>list</code></dt>
<dd>Maximum size to read from files (6K default).</dd>
</dl>
<h2 id="returns">Returns</h2>
<dl>
<dt><strong><code>dict</code></strong> :&ensp;<code>Extracted comment fields, with config: preparsed</code></dt>
<dd>&nbsp;</dd>
</dl>
<p>The result dictionary has fields accessible as e.g. <code>pmd["title"]</code>
<th>Parameters</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>filename</td>
<td>str</td>
<td>Read literal files, or .pyz contents.</td>
</tr>
<tr>
<td>src</td>
<td>str</td>
<td>From already uncovered script code.</td>
</tr>
<tr>
<td>module</td>
<td>str</td>
<td>Lookup per pkgutil, relative to plugin_base</td>
</tr>
<tr>
<td>frame</td>
<td>int</td>
<td>Extract comment header of caller (default).</td>
</tr>
<tr>
<td>extra_base</td>
<td>list</td>
<td>Additional search directories.</td>
</tr>
<tr>
<td>max_length</td>
<td>list</td>
<td>Maximum size to read from files (6K default).</td>
</tr>
<tr>
<td><strong>Returns</strong></td>
<td>dict</td>
<td>Extracted comment fields, with config: preparsed</td>
</tr>
</tbody>
</table>
<p>The result dictionary (<code><a title="pluginconf.PluginMeta" href="#pluginconf.PluginMeta">PluginMeta</a></code>) has fields accessible as e.g. <code>pmd["title"]</code>
or <code>pmd.version</code>. The documentation block after all fields: is called
["doc"]<code>. And </code>pmd.config` already parsed as a list of dictionaries.</p></div>
<code>["doc"]</code>. And <code>pmd.config</code> already parsed as a list (<code><a title="pluginconf.OptionList" href="#pluginconf.OptionList">OptionList</a></code>) of dictionaries.</p></div>
</dd>
</dl>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="pluginconf.OptionList"><code class="flex name class">

Modified pluginconf/__init__.py from [5fc579b1c8] to [9584d270b3].

97
98
99
100
101
102
103
104

105
106
107
108
109
110
111
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>
<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
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
    ----------
    | 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)

    # 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
    | 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:
        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
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
    ----------
    | 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],
        "fn": filename,
        "api": "python",
378
379
380
381
382
383
384
385

386
387
388
389
390
391
392
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.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
    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
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
    ----------
    | 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]
        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
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
    ----------
    | 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
        if _type == "bool":
            val = _value.lower() in ("1", "true", "yes", "on")

Modified pluginconf/bind.py from [8927125c8f] to [ce733ed382].

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
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







+

-
-
+
+
-
-
-
+
+


















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



















-
+

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


















-
-
+
+
-
-
-
+
-
-
+













-
-
+
+
-
-
+
+







#-- reset pluginconf if .bind is used
pluginconf.plugin_base = []


def load(name):
    """
    Import individual plugin from any of the base paths 🚐
    (The whole namespace is assumed to be flat, and identifiers to be unique.)

    Parameters
    ----------
    | Parameters  | | |
    |-------------|--------|-------------------------------|
    name : str
        Plugin name in any of the registered plugin_base´s. (The whole
        namespace is assumed to be flat, and identifiers to be unique.)
    | name        | str    | Plugin name in any of the registered plugin_base´s. |
    | **Returns** | module | Imported module               |
    """

    for package in pluginconf.plugin_base:
        if package in ("", "."):
            continue
        module_name = package + "." + name
        if module_name in sys.modules:
            return sys.modules[module_name]
        try:
            return importlib.import_module(module_name)
        except ImportError:
            pass


def base(module, path=None):
    """
    Register module as package/plugin_base. Or expand its search path 🛠 .

    Parameters
    ----------
    | Parameters  | | |
    |-------------|------------|-------------------------------|
    module : module/str
        The package basename to later load plugins from (must be a package,
        like `plugins/__init__.py`, or be tied to a path= or zip). Ideally
        this module was already imported in main. But parameter may be a string.
    path : str
    | module      | module/str | Package basename to later load plugins from |
    | path        | str        | Bind directory or pyz/zip bundle to plugin_base. |
    | **Returns** | None   | -                             |

    Module should be a package, as in a directory and init `plugins/__init__.py`.
    Ideally this module was already imported in main. But parameter may be a string.
    
        Add a directory or pyz/zip bundle to registered plugin_base. Could
        be invoked multiple times =./contrib/, =/usr/share/app/extenstions/,
    This could be invoked multiple times for the package name to append further
    path= arguments (=./contrib/, =/usr/share/app/extenstions/, or a .pyz). Which
        =~/.config/app/userplug/ (same as declaring the `__path__` in the
        base `package/__init__.py`.)
    is functionally identical to delcaring `__path__` in the `package/__init__.py`.
    """

    # if supplied as string, instead of active module
    if isinstance(module, str):
        module = sys.modules.get(module) or __import__(module)

    # add to search list
    if module.__name__ not in pluginconf.plugin_base:
        pluginconf.plugin_base.append(module.__name__)

    # enjoin dir or pyz
    if not hasattr(module, "__path__"):
        module.__path__ = [_dirname(module.__file__)]
    if path:
        module.__path__.append(_dirname(path))


def find(**kwargs):
    """
    Find plugins by e.g. type= or category= 🧩
    Find plugins by any combination of e.g. type= or category= 🧩

    Parameters
    ----------
    type : str
        Usually you'd search on a designated plugin categorization, like type=
    | Parameters  | | |
    |-------------|-----------|-------------------------------------------|
    | type        | str       | Search by type: in plugins                |
    | api         | str       | Matching api: designator                  |
        and api=, or slot=, or class= or whatever is most consistent. Multiple
        attributes can be filtered on. (Version/title probably not useful here.)

    | category    | str       | Or a menu/category or other attributes    |
    Returns
    ----------
    dict : basename → PluginMeta dict
    | **Returns** | dict      | basename → `PluginMeta` dict of matches   |
    """

    def has_all(pmd):
        for key, dep in kwargs.items():
            if not pmd.get(key) == dep:
                break
        else:
            return True

    return pluginconf.PluginMeta({
        name: pmd for name, pmd in pluginconf.all_plugin_meta().items() if has_all(pmd)
    })


def load_enabled(conf):
    """
    Import modules that are enabled in conf[plugins]={name:True,…} 🧾 🚐

    Parameters
    ----------
    | Parameters  | | |
    |-------------|-----------|-------------------------------------------|
    conf : dict
        Simple options-value dictionary, but with one conf["plugins"] = {} subdict,
        which holds plugin activation states. The config dict doesn't have to match
    | conf        | dict      | Should contain conf["plugins"] activation states |
        the available plugin options (defaults can be added), but should focus on
        essential presets. Differentiation only might become relevant for storage.
    | **Returns** | generator | Of loaded modules                         |
    """
    for name, state in conf.get("plugins", conf).items():
        if not state:
            continue
        if name.startswith("_"):
            continue
        yield load(name)


def defaults(conf):
    """
    Traverse installed plugins and expand config dict with presets 🧩 🧾

    Parameters
    ----------
    | Parameters  | | |
    |-------------|-----------|-------------------------------------------|
    conf : dict 🔁
        Expands the top-level config dict with preset values from any plugins.
    | conf        | dict 🔁   | Expands the conf dict with preset values from any plugins. |
    | **Returns** | None      | -                                         |
    """
    for name, pmd in pluginconf.all_plugin_meta().items():
        pluginconf.add_plugin_defaults(conf, conf["plugins"], pmd, name)


# pylint: disable=invalid-name
class isolated():

Modified test/bind.py from [bdb0475b8c] to [02e2635fa3].

10
11
12
13
14
15
16
17

18
19
20
21
22
23
24
10
11
12
13
14
15
16

17
18
19
20
21
22
23
24







-
+







import logging
logging.basicConfig(level=logging.DEBUG)
import pluginconf
print(pluginconf.plugin_base)
import pluginconf.bind


def init(reset):
def init():
#    pluginconf.plugin_base = []
    import test.plugins
    pluginconf.bind.base(test.plugins)
    assert "test.plugins" in pluginconf.plugin_base
    assert test.plugins.__path__ != []

def test_first():