Overview
Comment:doc additions, add params for setup and gui, fix flit sample
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 93aac201515248d5ceb9bdf723aff53c8088094340759afa9fddd920f10227f6
User & Date: mario on 2022-10-31 12:56:46
Other Links: manifest | tags
Context
2022-10-31
18:56
add pacakge disovery, and additional comment styles (different languages) check-in: f03780244f user: mario tags: trunk
12:56
doc additions, add params for setup and gui, fix flit sample check-in: 93aac20151 user: mario tags: trunk
06:12
updated template to mimick RTD theme (barely) check-in: e12fd2f3f3 user: mario tags: trunk
Changes

Modified html/depends.html from [ee1c309587] to [8dad0b793d].

46
47
48
49
50
51
52



53


54




55
56








57
58


59

60
61


62



63


64




65
66
67

68

69


70
71
72
73
74
75
76
77
helper might want to auto-tick them on, etc. This example is just
meant for probing downloadable plugins.</p>
<p>The .valid() helper only asserts the api: string, or skips existing
modules, and if they're more recent.
While .depends() compares minimum versions against existing modules.</p>
<p>In practice there's little need for full-blown dependency resolving
for application-level modules.</p>



<h2 id="attributes">Attributes</h2>


<dl>




<dt><strong><code>api</code></strong> :&ensp;<code>list</code></dt>
<dd>allowed api: identifiers for .valid() stream checks</dd>








<dt><strong><code>log</code></strong> :&ensp;<code>logging</code></dt>
<dd>warning handler</dd>


<dt><strong><code>have</code></strong> :&ensp;<code>dict</code></dt>

<dd>accumulated list of existing/virtual plugins</dd>
</dl>


<p>Prepare list of known plugins and versions in self.have={}</p>



<h2 id="parameters">Parameters</h2>


<dl>




<dt><strong><code>add</code></strong> :&ensp;<code>dict</code></dt>
<dd>name to pmd list of existing/core/virtual plugins (can define
versions or own dependencies)</dd>

<dt><strong><code>core</code></strong> :&ensp;<code>list</code></dt>

<dd>name list of virtual plugins</dd>


</dl></div>
<h3>Class variables</h3>
<dl>
<dt id="pluginconf.depends.Check.api"><code class="name">var <span class="ident">api</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="pluginconf.depends.Check.log"><code class="name">var <span class="ident">log</span></code></dt>







>
>
>
|
>
>
|
>
>
>
>
|
|
>
>
>
>
>
>
>
>
|
|
>
>
|
>
|
|
>
>

>
>
>
|
>
>
|
>
>
>
>
|
|
|
>
|
>
|
>
>
|







46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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
helper might want to auto-tick them on, etc. This example is just
meant for probing downloadable plugins.</p>
<p>The .valid() helper only asserts the api: string, or skips existing
modules, and if they're more recent.
While .depends() compares minimum versions against existing modules.</p>
<p>In practice there's little need for full-blown dependency resolving
for application-level modules.</p>
<table>
<thead>
<tr>
<th>Attributes</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>api</td>
<td>list</td>
<td>allowed api: identifiers for .valid() stream checks</td>
</tr>
<tr>
<td>system_deps</td>
<td>bool</td>
<td>check <code>bin:app</code> or <code>python:package</code> dependencies</td>
</tr>
<tr>
<td>log</td>
<td>logging</td>
<td>warning handler</td>
</tr>
<tr>
<td>have</td>
<td>dict</td>
<td>accumulated list of existing/virtual plugins</td>
</tr>
</tbody>
</table>
<p>Prepare list of known plugins and versions in self.have={}</p>
<table>
<thead>
<tr>
<th>Parameters</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>add</td>
<td>dict</td>
<td>namepmd of existing/core plugins (incl ver or deps)</td>
</tr>
<tr>
<td>core</td>
<td>list</td>
<td>name list of virtual plugins</td>
</tr>
</tbody>
</table></div>
<h3>Class variables</h3>
<dl>
<dt id="pluginconf.depends.Check.api"><code class="name">var <span class="ident">api</span></code></dt>
<dd>
<div class="desc"></div>
</dd>
<dt id="pluginconf.depends.Check.log"><code class="name">var <span class="ident">log</span></code></dt>
97
98
99
100
101
102
103
104





















105
106
107
108
109
110
111
<dd>
<div class="desc"><p>Single comparison</p></div>
</dd>
<dt id="pluginconf.depends.Check.depends"><code class="name flex">
<span>def <span class="ident">depends</span></span>(<span>self, plugin)</span>
</code></dt>
<dd>
<div class="desc"><p>Verify depends: and breaks: against existing plugins/modules</p></div>





















</dd>
<dt id="pluginconf.depends.Check.module_test"><code class="name flex">
<span>def <span class="ident">module_test</span></span>(<span>self, urn, name)</span>
</code></dt>
<dd>
<div class="desc"><p>Probes "bin:name" or "python:name" dependency URNs</p></div>
</dd>







|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







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
<dd>
<div class="desc"><p>Single comparison</p></div>
</dd>
<dt id="pluginconf.depends.Check.depends"><code class="name flex">
<span>def <span class="ident">depends</span></span>(<span>self, plugin)</span>
</code></dt>
<dd>
<div class="desc"><p>Verify depends: and breaks: against existing plugins/modules</p>
<table>
<thead>
<tr>
<th>Parameters</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>plugin</td>
<td>dict</td>
<td>plugin meta properties of (new?) plugin</td>
</tr>
<tr>
<td><strong>Returns</strong></td>
<td>bool</td>
<td>matches up with existing .have{} installation</td>
</tr>
</tbody>
</table></div>
</dd>
<dt id="pluginconf.depends.Check.module_test"><code class="name flex">
<span>def <span class="ident">module_test</span></span>(<span>self, urn, name)</span>
</code></dt>
<dd>
<div class="desc"><p>Probes "bin:name" or "python:name" dependency URNs</p></div>
</dd>
125
126
127
128
129
130
131
132





















133
134
135
136
137
138
139
</dd>
<dt id="pluginconf.depends.Check.valid"><code class="name flex">
<span>def <span class="ident">valid</span></span>(<span>self, new_plugin)</span>
</code></dt>
<dd>
<div class="desc"><p>Plugin pre-screening from online repository stream.
Fields are $name, $file, $dist, api, id, depends, etc
Exclude installed or for newer-version presence.</p></div>





















</dd>
</dl>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">







|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







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
</dd>
<dt id="pluginconf.depends.Check.valid"><code class="name flex">
<span>def <span class="ident">valid</span></span>(<span>self, new_plugin)</span>
</code></dt>
<dd>
<div class="desc"><p>Plugin pre-screening from online repository stream.
Fields are $name, $file, $dist, api, id, depends, etc
Exclude installed or for newer-version presence.</p>
<table>
<thead>
<tr>
<th>Parameters</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>new_plugin</td>
<td>dict</td>
<td>online properties of available plugin</td>
</tr>
<tr>
<td><strong>Returns</strong></td>
<td>bool</td>
<td>is updatatable</td>
</tr>
</tbody>
</table></div>
</dd>
</dl>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">

Modified html/flit.html from [560905addb] to [f4ec8aad3f].

26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<p>monkeypatches flit to use pluginconf sources for packaging with a
<code>pyproject.toml</code> like:</p>
<table>
<tr><th>pyproject.toml</th>
<th>foobar/__init__.py</th></tr>
<tr><td><code><pre>
[build-system]
requires = ["pluginconf", "flit]
build-backend = "pluginconf.flit"

[project]
name = "foobar"
dynamic = ["*"]
</pre></code></td>
<td><code><pre>







|







26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<p>monkeypatches flit to use pluginconf sources for packaging with a
<code>pyproject.toml</code> like:</p>
<table>
<tr><th>pyproject.toml</th>
<th>foobar/__init__.py</th></tr>
<tr><td><code><pre>
[build-system]
requires = ["pluginconf", "flit"]
build-backend = "pluginconf.flit"

[project]
name = "foobar"
dynamic = ["*"]
</pre></code></td>
<td><code><pre>

Modified html/gui.html from [bd7e7636e7] to [8c9d3bdc20].

44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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
<dd>
<div class="desc"><p>checkbox for plugin name</p></div>
</dd>
<dt id="pluginconf.gui.plugin_layout"><code class="name flex">
<span>def <span class="ident">plugin_layout</span></span>(<span>pmd_list, config, plugin_states, opt_label=False)</span>
</code></dt>
<dd>
<div class="desc"><p>craft list of widgets for each read plugin</p></div>
</dd>
<dt id="pluginconf.gui.read_options"><code class="name flex">
<span>def <span class="ident">read_options</span></span>(<span>files)</span>
</code></dt>
<dd>
<div class="desc"><p>read files, return dict of {id:pmd} for all plugins</p></div>
</dd>
<dt id="pluginconf.gui.window"><code class="name flex">
<span>def <span class="ident">window</span></span>(<span>config, plugin_states, files=['*/*.py'], **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>Reads *.py files and crafts a settings dialog from meta data.</p>
<p>Where <code>plugin_states{}</code> is usually an entry in <code>config{}</code> itself. Depending on plugin
and option names, it might even be a flat/shared namespace for both. Per default you'd
set <code>files=["plugins/*.py", __file__]</code> to be read. But with <code>files=[]</code> it's possible to
provide a <code>plugins=pluginconf.get_plugin_meta()</code> or prepared plugin/options dict instead.</p>



<h2 id="parameters">Parameters</h2>


<dl>




<dt><strong><code>config</code></strong> :&ensp;<code>dict 🔁</code></dt>
<dd>Config settings, updated after dialog completion</dd>


<dt><strong><code>plugin_states</code></strong> :&ensp;<code>dict 🔁</code></dt>

<dd>Plugin activation states, also input/output</dd>


<dt><strong><code>files</code></strong> :&ensp;<code>list</code></dt>

<dd>Glob list of *.py files to extract meta definitions from</dd>


<dt><strong><code>plugins</code></strong> :&ensp;<code>dict</code></dt>

<dd>Alternatively to files=[] list, a preparsed list of pluginmeta+config dicts can be injected</dd>


<dt><strong><code>opt_label</code></strong> :&ensp;<code>bool</code></dt>

<dd>Show config name= as label</dd>


<dt><strong><code>theme</code></strong> :&ensp;<code>str</code></dt>

<dd>Set PSG window theme.</dd>


<dt><strong><code>**kwargs</code></strong> :&ensp;<code>dict</code></dt>

<dd>Other options are passed on to PySimpleGUI</dd>
</dl>

<h2 id="returns">Returns</h2>
<dl>
<dt><strong><code>True</code></strong> :&ensp;<code>if changed config{} values are to be saved (the dict will be updated in any case)</code></dt>
<dd>&nbsp;</dd>

</dl></div>
</dd>
<dt id="pluginconf.gui.wrap"><code class="name flex">
<span>def <span class="ident">wrap</span></span>(<span>text, width=50)</span>
</code></dt>
<dd>
<div class="desc"><p>textwrap for <code>description</code> and <code>help</code> option fields</p></div>
</dd>







|
















>
>
>
|
>
>
|
>
>
>
>
|
|
>
>
|
>
|
>
>
|
>
|
>
>
|
>
|
>
>
|
>
|
>
>
|
>
|
>
>
|
>
|
|
>
|
|
|
|
>
|







44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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
<dd>
<div class="desc"><p>checkbox for plugin name</p></div>
</dd>
<dt id="pluginconf.gui.plugin_layout"><code class="name flex">
<span>def <span class="ident">plugin_layout</span></span>(<span>pmd_list, config, plugin_states, opt_label=False)</span>
</code></dt>
<dd>
<div class="desc"><p>craft list of widgets: *( <code><a title="pluginconf.gui.plugin_entry" href="#pluginconf.gui.plugin_entry">plugin_entry()</a></code>, *<code><a title="pluginconf.gui.option_entry" href="#pluginconf.gui.option_entry">option_entry()</a></code> )</p></div>
</dd>
<dt id="pluginconf.gui.read_options"><code class="name flex">
<span>def <span class="ident">read_options</span></span>(<span>files)</span>
</code></dt>
<dd>
<div class="desc"><p>read files, return dict of {id:pmd} for all plugins</p></div>
</dd>
<dt id="pluginconf.gui.window"><code class="name flex">
<span>def <span class="ident">window</span></span>(<span>config, plugin_states, files=['*/*.py'], **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>Reads *.py files and crafts a settings dialog from meta data.</p>
<p>Where <code>plugin_states{}</code> is usually an entry in <code>config{}</code> itself. Depending on plugin
and option names, it might even be a flat/shared namespace for both. Per default you'd
set <code>files=["plugins/*.py", __file__]</code> to be read. But with <code>files=[]</code> it's possible to
provide a <code>plugins=pluginconf.get_plugin_meta()</code> or prepared plugin/options dict instead.</p>
<table>
<thead>
<tr>
<th>Params</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>config</td>
<td>dict 🔁</td>
<td>Config settings, updated after dialog completion</td>
</tr>
<tr>
<td>plugin_states</td>
<td>dict 🔁</td>
<td>Plugin activation states, also input/output</td>
</tr>
<tr>
<td>files</td>
<td>list</td>
<td>Glob list of *.py files to extract meta definitions from</td>
</tr>
<tr>
<td>plugins</td>
<td>dict</td>
<td>Alternatively to files=[] list, a preparsed list of pluginmeta+config dicts can be injected</td>
</tr>
<tr>
<td>opt_label</td>
<td>bool</td>
<td>Show config name= as label (instead of description)</td>
</tr>
<tr>
<td>theme</td>
<td>str</td>
<td>Set PSG window theme.</td>
</tr>
<tr>
<td>**kwargs</td>
<td>dict</td>
<td>Other options are passed on to PySimpleGUI</td>
</tr>
<tr>
<td><strong>Returns</strong></td>
<td>True</td>
<td>if updated config{} values should be [Saved]</td>
</tr>
</tbody>
</table></div>
</dd>
<dt id="pluginconf.gui.wrap"><code class="name flex">
<span>def <span class="ident">wrap</span></span>(<span>text, width=50)</span>
</code></dt>
<dd>
<div class="desc"><p>textwrap for <code>description</code> and <code>help</code> option fields</p></div>
</dd>

Modified html/index.html from [62873924d5] to [bd45e395b2].

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
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<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>
<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>







|
|
<












|




|







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
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<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 to collect defaults from plugin meta data to
a config dict/store.</p>

<table>
<thead>
<tr>
<th>Parameters</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>conf_options</td>
<td>dict 🔁</td>
<td>storage for amassed #config: options</td>
</tr>
<tr>
<td>conf_plugins</td>
<td>dict 🔁</td>
<td>activation status derived from state/priority:</td>
</tr>
<tr>
<td>meta</td>
<td>dict</td>
<td>input plugin meta data (invoke once per plugin)</td>
</tr>
<tr>
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
<th></th>
</tr>
</thead>
<tbody>
<tr>
<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>







|







144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Returns</strong></td>
<td>dict</td>
<td>names to <code><a title="pluginconf.PluginMeta" href="#pluginconf.PluginMeta">PluginMeta</a></code> 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>

Modified html/setup.html from [6a9e752056] to [e6c6068172].

45
46
47
48
49
50
51



52


53



54

55


56

57


58

59
60


61
62
63
64
65
66
67
</dd>
<dt id="pluginconf.setup.setup"><code class="name flex">
<span>def <span class="ident">setup</span></span>(<span>filename=None, debug=False, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>Wrapper around <code>setuptools.setup()</code> which adds some defaults
and plugin meta data import, with some shortcut parameters:</p>



<h2 id="parameters">Parameters</h2>


<dl>



<dt><strong><code>filename</code></strong> :&ensp;<code>str</code></dt>

<dd>main file "pkg/main.py" (else deduced from primary package name)</dd>


<dt><strong><code>debug</code></strong> :&ensp;<code>bool</code></dt>

<dd>display collected options prior setuptools.setup() invocation</dd>


<dt><strong><code>long_description</code></strong> :&ensp;<code>str</code></dt>

<dd>e.g. "README.md", else comment block used</dd>
</dl>


<p>Other setup() params work as usual, and are passed trough. Notably
entry_points= or data_files= can be used, even if they get augmented.</p></div>
</dd>
</dl>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>







>
>
>
|
>
>
|
>
>
>
|
>
|
>
>
|
>
|
>
>
|
>
|
|
>
>







45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
</dd>
<dt id="pluginconf.setup.setup"><code class="name flex">
<span>def <span class="ident">setup</span></span>(<span>filename=None, debug=False, **kwargs)</span>
</code></dt>
<dd>
<div class="desc"><p>Wrapper around <code>setuptools.setup()</code> which adds some defaults
and plugin meta data import, with some shortcut parameters:</p>
<table>
<thead>
<tr>
<th>Parameters</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>filename</td>
<td>str</td>
<td>main file "pkg/main.py" (else deduced from primary package name)</td>
</tr>
<tr>
<td>debug</td>
<td>bool</td>
<td>display collected options prior setuptools.setup() invocation</td>
</tr>
<tr>
<td>long_description</td>
<td>str</td>
<td>e.g. "README.md", else the comment block gets used</td>
</tr>
</tbody>
</table>
<p>Other setup() params work as usual, and are passed trough. Notably
entry_points= or data_files= can be used, even if they get augmented.</p></div>
</dd>
</dl>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>

Modified pluginconf/__init__.py from [66c11c0c8c] to [5e7e9e9afe].

9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# classifiers: documentation
# depends: python >= 2.7
# suggests: python:flit, python:PySimpleGUI
# license: PD
# priority: core
# api-docs: https://fossil.include-once.org/pluginspec/doc/trunk/html/index.html
# docs: https://fossil.include-once.org/pluginspec/
# url: http://fossil.include-once.org/streamtuner2/wiki/plugin+meta+data
# config: -
# format: off
# permissive: 0.75
# pylint: disable=invalid-name
# console-scripts: flit-pluginconf=pluginconf.flit:main
#
# Provides plugin lookup and meta data extraction utility functions.







|







9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# classifiers: documentation
# depends: python >= 2.7
# suggests: python:flit, python:PySimpleGUI
# license: PD
# priority: core
# api-docs: https://fossil.include-once.org/pluginspec/doc/trunk/html/index.html
# docs: https://fossil.include-once.org/pluginspec/
# url: https://fossil.include-once.org/pluginspec/wiki/pluginconf
# config: -
# format: off
# permissive: 0.75
# pylint: disable=invalid-name
# console-scripts: flit-pluginconf=pluginconf.flit:main
#
# Provides plugin lookup and meta data extraction utility functions.
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
    """
    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







|







250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
    """
    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 `PluginMeta` dict      |
    """
    return {
        name: plugin_meta(module=name) for name in module_list()
    }


# Plugin meta data extraction
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
    return {k: w for k, w in kwargs.items() if w is not None}


# Add plugin defaults to conf.* store
# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
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", []):







|
|
<



|
|







555
556
557
558
559
560
561
562
563

564
565
566
567
568
569
570
571
572
573
574
575
    return {k: w for k, w in kwargs.items() if w is not None}


# Add plugin defaults to conf.* store
# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
def add_plugin_defaults(conf_options, conf_plugins, meta, module=""):
    """
    Utility function to collect defaults from plugin meta data to
    a config dict/store.


    | Parameters  | | |
    |-------------|---------|--------------------------------------|
    | conf_options| dict 🔁 | storage for amassed #config: options |
    | conf_plugins| dict 🔁 | activation status derived from 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", []):

Modified pluginconf/bind.py from [ce733ed382] to [fcff8bd0e5].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# encoding: utf-8
# api: pluginconf
##type: loader
# title: plugin loader
# description: implements a trivial unified namespace importer
# version: 0.1
# state: alpha
# priority: optional
#
# Most basic plugin management/loader. It unifies meta data retrieval
# and instantiation. It still doesn't dictate a plugin API or config
# storage (using json in examples). And can be a simple setup:
#
# Create an empty plugins/__init__.py to use as package and for
# plugin discovery.
#
# Designate it as such:
#
#      import pluginconf.bind
#      pluginconf.bind.base("plugins")
#
# Set up a default conf={} in your application, with some presets,
# or updating it from a stored config file:
#
#      conf = {
#          "first_run": 1,











|






|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# encoding: utf-8
# api: pluginconf
##type: loader
# title: plugin loader
# description: implements a trivial unified namespace importer
# version: 0.1
# state: alpha
# priority: optional
#
# Most basic plugin management/loader. It unifies meta data retrieval
# and instantiation. It still doesn't dictate a plugin API or config
# storage (using json in examples). And can be a simpler setup:
#
# Create an empty plugins/__init__.py to use as package and for
# plugin discovery.
#
# Designate it as such:
#
#      import pluginconf.bind  # (first import resets .plugin_base)
#      pluginconf.bind.base("plugins")
#
# Set up a default conf={} in your application, with some presets,
# or updating it from a stored config file:
#
#      conf = {
#          "first_run": 1,
37
38
39
40
41
42
43
44
45
46


47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64













65
66
67
68
69
70
71
#          pluginconf.bind.defaults(conf)
#
# Instantiate plugin modules based on load conf[plugins] state:
#
#      for module in pluginconf.bind.load_enabled(conf):
#          module.register(main_window)
#
# Using a simple init function often suffices, if plugins don't
# register themselves. Alternatively use a class name aligned with
# the plugin basename to disover it, or dir(), or similar such.


#
# If you want users to update settings or plugin states, use the
# .window module:
#
#      pluginconf.gui.window(conf, conf["plugins"], files=["plugins/*.py"])
#      json.dump(conf, open("app.json", "w"))
#
# Alternatively there's the load() for known plugin names, or find()
# to uncover them based on descriptors. Or isolated() to instantiate
# from a different set.
#
# Notably the whole setup makes most sense if you have user-supplied
# plugin and some repository to fetch/update new ones from. (Out of
# scope here, but a zip/pyz download and extract might suffice). If
# so, entangle the alternative directory or pyz to be scanned:
#
#      pluginconf.bind.base("plugins", dir="~/.config/app/usrplugs/")
#      pluginconf.bind.defaults(conf)  # update













#
# With PySimpleGUI, `conf` could be a psg.UserSettings("app.json") for
# automatic settings+update storage, btw.
#

#-- bit briefer for API docs --
"""







|


>
>







|



|
|
|
|



>
>
>
>
>
>
>
>
>
>
>
>
>







37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#          pluginconf.bind.defaults(conf)
#
# Instantiate plugin modules based on load conf[plugins] state:
#
#      for module in pluginconf.bind.load_enabled(conf):
#          module.register(main_window)
#
# Electing a simple init function often suffices, if plugins don't
# register themselves. Alternatively use a class name aligned with
# the plugin basename to disover it, or dir(), or similar such.
# (This is what "pluginconf imposes no API" means: you still have
# to decide on a convention.)
#
# If you want users to update settings or plugin states, use the
# .window module:
#
#      pluginconf.gui.window(conf, conf["plugins"], files=["plugins/*.py"])
#      json.dump(conf, open("app.json", "w"))
#
# Alternatively there's load() for well-known plugin names, or find()
# to uncover them based on descriptors. Or isolated() to instantiate
# from a different set.
#
# Notably the whole effort makes most sense if you have user-supplied
# plugins and some repository to fetch/update new ones from. (Optional
# meta descriptions are quite suitable to signify beta or experimental
# extensions). If so, entangle the alternative directory to be scanned:
#
#      pluginconf.bind.base("plugins", dir="~/.config/app/usrplugs/")
#      pluginconf.bind.defaults(conf)  # update
#
# A simpler user plugin mechanism might just download a zip:
#
#      usr_pyz = f"{os.environ['HOME']}/.config/app/community.pyz"
#      with open(usr_pyz, "w") as write:
#          write.write(
#              requests.get("http://example.org/usr-plugins.zip").content
#          )
#
# And register that as pyz instead (on startup):
#
#      if os.path.exists(usr_pyz):
#          pluginconf.bind.base("plugins", dir=usr_pyz)
#
# With PySimpleGUI, `conf` could be a psg.UserSettings("app.json") for
# automatic settings+update storage, btw.
#

#-- bit briefer for API docs --
"""
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
        mod.init()

### Find by type

    for name, pmd in pluginconf.bind.find(type="effect").items():
        mod = pluginconf.bind.load(name)
        if pmd.category == "menu":
            main_win.add_menu(mod.menu) 

Note that this uses meta data extraction, so should best be confined
for app setup/initialization, not used recurringly. The config state
usage is the preferred method. (Only requires property loading
once, for installation or with `pluginconf.gui.window()` usage.)

----
Method interactions: 🚐 = import, 🧩 = meta, 🧾 = config, 🛠  = setup
"""

import os
import sys
import importlib

import pluginconf

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


def load(name):







|













>







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
        mod.init()

### Find by type

    for name, pmd in pluginconf.bind.find(type="effect").items():
        mod = pluginconf.bind.load(name)
        if pmd.category == "menu":
            main_win.add_menu(mod.menu)

Note that this uses meta data extraction, so should best be confined
for app setup/initialization, not used recurringly. The config state
usage is the preferred method. (Only requires property loading
once, for installation or with `pluginconf.gui.window()` usage.)

----
Method interactions: 🚐 = import, 🧩 = meta, 🧾 = config, 🛠  = setup
"""

import os
import sys
import importlib
import functools
import pluginconf

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


def load(name):
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
    |-------------|------------|-------------------------------|
    | 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.
    
    This could be invoked multiple times for the package name to append further
    path= arguments (=./contrib/, =/usr/share/app/extenstions/, or a .pyz). Which
    is functionally identical to delcaring `__path__` in the `package/__init__.py`.
    """

    # if supplied as string, instead of active module
    if isinstance(module, str):







|







164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
    |-------------|------------|-------------------------------|
    | 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.

    This could be invoked multiple times for the package name to append further
    path= arguments (=./contrib/, =/usr/share/app/extenstions/, or a .pyz). Which
    is functionally identical to delcaring `__path__` in the `package/__init__.py`.
    """

    # if supplied as string, instead of active module
    if isinstance(module, str):
276
277
278
279
280
281
282
283













284

285
    @staticmethod
    def defaults():
        """ *return* defaults for isolated plugin structure 🧩 🧾 """
        conf = {"plugins": {}}
        defaults(conf)
        return conf















def _dirname(file):

    return os.path.dirname(os.path.realpath(file))








>
>
>
>
>
>
>
>
>
>
>
>
>

>

292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
    @staticmethod
    def defaults():
        """ *return* defaults for isolated plugin structure 🧩 🧾 """
        conf = {"plugins": {}}
        defaults(conf)
        return conf


def _enable_cache(state=True):
    """
    Reduce plugin_meta() lookup costs, suitable for repeat find() calls
    """
    if hasattr(pluginconf.plugin_meta, "__wrapped__"):
        if state:
            return
        pluginconf.plugin_meta = pluginconf.plugin_meta.__wrapped__
    elif state:
        decorator = functools.lru_cache(maxsize=None)
        pluginconf.plugin_meta = decorator(pluginconf.plugin_meta)


def _dirname(file):
    """ absolute dirname for file """
    return os.path.dirname(os.path.realpath(file))

Modified pluginconf/depends.py from [f7f101f5bf] to [b7808bf08a].

56
57
58
59
60
61
62
63
64
65
66
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
    The .valid() helper only asserts the api: string, or skips existing
    modules, and if they're more recent.
    While .depends() compares minimum versions against existing modules.

    In practice there's little need for full-blown dependency resolving
    for application-level modules.

    Attributes
    ----------
    api : list
        allowed api: identifiers for .valid() stream checks
    log : logging

        warning handler
    have : dict
        accumulated list of existing/virtual plugins
    """

    # supported APIs
    api = ["python", "streamtuner2"]

    # debugging
    log = logging.getLogger("pluginconf.dependency")

    # ignore bin:… or python:… package in depends
    system_deps = False

    def __init__(self, add=None, core=["st2", "uikit", "config", "action"]):
        """
        Prepare list of known plugins and versions in self.have={}

        Parameters
        ----------
        add : dict
            name to pmd list of existing/core/virtual plugins (can define
            versions or own dependencies)
        core : list
            name list of virtual plugins
        """
        self.have = {
            "python": {"version": sys.version}
        }

        # inject virtual modules
        for name, meta in (add or {}).items():







|
|
<
|
<
>
|
<
|















|
|
<
|
<
<
|







56
57
58
59
60
61
62
63
64

65

66
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
    The .valid() helper only asserts the api: string, or skips existing
    modules, and if they're more recent.
    While .depends() compares minimum versions against existing modules.

    In practice there's little need for full-blown dependency resolving
    for application-level modules.

    | Attributes | | |
    |------------|---------|-----------------------------------------------------|

    | api        | list    | allowed api: identifiers for .valid() stream checks |

    | system_deps| bool    | check `bin:app` or `python:package` dependencies    |
    | log        | logging | warning handler                                     |

    | have       | dict    | accumulated list of existing/virtual plugins        |
    """

    # supported APIs
    api = ["python", "streamtuner2"]

    # debugging
    log = logging.getLogger("pluginconf.dependency")

    # ignore bin:… or python:… package in depends
    system_deps = False

    def __init__(self, add=None, core=["st2", "uikit", "config", "action"]):
        """
        Prepare list of known plugins and versions in self.have={}

        | Parameters | | |
        |------------|---------|------------------------------------------------------|

        | add        | dict    | namepmd of existing/core plugins (incl ver or deps) |


        | core       | list    | name list of virtual plugins                         |
        """
        self.have = {
            "python": {"version": sys.version}
        }

        # inject virtual modules
        for name, meta in (add or {}).items():
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
                    self.have[alias] = self.have[name]

    def valid(self, new_plugin):
        """
        Plugin pre-screening from online repository stream.
        Fields are $name, $file, $dist, api, id, depends, etc
        Exclude installed or for newer-version presence.





        """
        if not "$name" in new_plugin:
            self.log.warning(".valid() checks online plugin lists, requires $name")
        name = new_plugin.get("$name", "__invalid")
        have_ver = self.have.get(name, {}).get("version", "0")
        if name.find("__") == 0:
            self.log.debug("wrong/no id")
        elif new_plugin.get("api") not in self.api:
            self.log.debug("not in allowed APIs")
        elif {new_plugin.get("status"), new_plugin.get("priority")} & {"obsolete", "broken"}:
            self.log.debug("wrong status (obsolete/broken)")
        elif have_ver >= new_plugin.get("version", "0.0"):
            self.log.debug("newer version already installed")
        else:
            return True
        return False

    def depends(self, plugin):

        """ Verify depends: and breaks: against existing plugins/modules """






        result = True
        if plugin.get("depends"):
            result &= self.and_or(self.split(plugin["depends"]), self.have)
        if plugin.get("breaks"):
            result &= self.neither(self.split(plugin["breaks"]), self.have)
        self.log.debug("plugin '%s' matching requirements: %i", plugin["id"], result)
        return result







>
>
>
>
>


















>
|
>
>
>
>
>
>







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
                    self.have[alias] = self.have[name]

    def valid(self, new_plugin):
        """
        Plugin pre-screening from online repository stream.
        Fields are $name, $file, $dist, api, id, depends, etc
        Exclude installed or for newer-version presence.

        | Parameters  | | |
        |-------------|---------|------------------------------------------------------|
        | new_plugin  | dict    | online properties of available plugin                |
        | **Returns** | bool    | is updatatable                                       |
        """
        if not "$name" in new_plugin:
            self.log.warning(".valid() checks online plugin lists, requires $name")
        name = new_plugin.get("$name", "__invalid")
        have_ver = self.have.get(name, {}).get("version", "0")
        if name.find("__") == 0:
            self.log.debug("wrong/no id")
        elif new_plugin.get("api") not in self.api:
            self.log.debug("not in allowed APIs")
        elif {new_plugin.get("status"), new_plugin.get("priority")} & {"obsolete", "broken"}:
            self.log.debug("wrong status (obsolete/broken)")
        elif have_ver >= new_plugin.get("version", "0.0"):
            self.log.debug("newer version already installed")
        else:
            return True
        return False

    def depends(self, plugin):
        """
        Verify depends: and breaks: against existing plugins/modules

        | Parameters  | | |
        |-------------|---------|------------------------------------------------------|
        | plugin      | dict    | plugin meta properties of (new?) plugin              |
        | **Returns** | bool    | matches up with existing .have{} installation        |
        """
        result = True
        if plugin.get("depends"):
            result &= self.and_or(self.split(plugin["depends"]), self.have)
        if plugin.get("breaks"):
            result &= self.neither(self.split(plugin["breaks"]), self.have)
        self.log.debug("plugin '%s' matching requirements: %i", plugin["id"], result)
        return result

Modified pluginconf/flit.py from [4725663a44] to [7e6be741f0].

33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
`pyproject.toml` like:

<table>
<tr><th>pyproject.toml</th>
    <th>foobar/__init__.py</th></tr>
<tr><td><code><pre>
[build-system]
requires = ["pluginconf", "flit]
build-backend = "pluginconf.flit"

[project]
name = "foobar"
dynamic = ["*"]
</pre></code></td>
<td><code><pre>







|







33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
`pyproject.toml` like:

<table>
<tr><th>pyproject.toml</th>
    <th>foobar/__init__.py</th></tr>
<tr><td><code><pre>
[build-system]
requires = ["pluginconf", "flit"]
build-backend = "pluginconf.flit"

[project]
name = "foobar"
dynamic = ["*"]
</pre></code></td>
<td><code><pre>

Modified pluginconf/gui.py from [fd35fa0517] to [579f93bf8f].

48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
    Reads *.py files and crafts a settings dialog from meta data.

    Where `plugin_states{}` is usually an entry in `config{}` itself. Depending on plugin
    and option names, it might even be a flat/shared namespace for both. Per default you'd
    set `files=["plugins/*.py", __file__]` to be read. But with `files=[]` it's possible to
    provide a `plugins=pluginconf.get_plugin_meta()` or prepared plugin/options dict instead.

    Parameters
    ----------
    config : dict 🔁
        Config settings, updated after dialog completion
    plugin_states : dict 🔁
        Plugin activation states, also input/output
    files : list
        Glob list of *.py files to extract meta definitions from
    plugins : dict
        Alternatively to files=[] list, a preparsed list of pluginmeta+config dicts can be injected
    opt_label : bool
        Show config name= as label
    theme : str
        Set PSG window theme.
    **kwargs : dict
        Other options are passed on to PySimpleGUI

    Returns
    -------
    True : if changed config{} values are to be saved (the dict will be updated in any case)
    """
    plugins = kwargs.get("plugins", {})
    opt_label = kwargs.get("opt_label", False)
    theme = kwargs.get("theme", "DefaultNoMoreNagging")
    if theme:
        if "theme" in kwargs:
            del kwargs["theme"]







|
|
<
|
<
|
<
|
<
|
<
|
<
|
<
|
|
<
<
<







48
49
50
51
52
53
54
55
56

57

58

59

60

61

62

63
64



65
66
67
68
69
70
71
    Reads *.py files and crafts a settings dialog from meta data.

    Where `plugin_states{}` is usually an entry in `config{}` itself. Depending on plugin
    and option names, it might even be a flat/shared namespace for both. Per default you'd
    set `files=["plugins/*.py", __file__]` to be read. But with `files=[]` it's possible to
    provide a `plugins=pluginconf.get_plugin_meta()` or prepared plugin/options dict instead.

    | Params        | | |
    |---------------|---------|---------------------------------------------------------|

    | config        | dict 🔁 | Config settings, updated after dialog completion        |

    | plugin_states | dict 🔁 | Plugin activation states, also input/output             |

    | files         | list    | Glob list of *.py files to extract meta definitions from|

    | plugins       | dict    | Alternatively to files=[] list, a preparsed list of pluginmeta+config dicts can be injected |

    | opt_label     | bool    | Show config name= as label (instead of description)     |

    | theme         | str     | Set PSG window theme.                                   |

    | **kwargs      | dict    | Other options are passed on to PySimpleGUI              |
    | **Returns**   | True    | if updated config{} values should be [Saved]            |



    """
    plugins = kwargs.get("plugins", {})
    opt_label = kwargs.get("opt_label", False)
    theme = kwargs.get("theme", "DefaultNoMoreNagging")
    if theme:
        if "theme" in kwargs:
            del kwargs["theme"]
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
                if plugins.get(key):
                    plugin_states[key] = val
        return True
    #print(config, plugin_states)


def plugin_layout(pmd_list, config, plugin_states, opt_label=False):
    """ craft list of widgets for each read plugin """
    layout = []
    for plg in pmd_list:
        #print(plg.get("id"))
        layout = layout + plugin_entry(plg, plugin_states)
        for opt in plg["config"]:
            if opt.get("name"):
                if opt_label:







|







100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
                if plugins.get(key):
                    plugin_states[key] = val
        return True
    #print(config, plugin_states)


def plugin_layout(pmd_list, config, plugin_states, opt_label=False):
    """ craft list of widgets: \*( `plugin_entry`, \*`option_entry` )"""
    layout = []
    for plg in pmd_list:
        #print(plg.get("id"))
        layout = layout + plugin_entry(plg, plugin_states)
        for opt in plg["config"]:
            if opt.get("name"):
                if opt_label:

Modified pluginconf/setup.py from [a5d9b67dec] to [69618d8a4a].

199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220

@pluginconf.renamed_arguments({"fn": "filename"})
def setup(filename=None, debug=False, **kwargs):
    """
    Wrapper around `setuptools.setup()` which adds some defaults
    and plugin meta data import, with some shortcut parameters:

    Parameters
    ----------
    filename : str
        main file "pkg/main.py" (else deduced from primary package name)
    debug : bool
        display collected options prior setuptools.setup() invocation
    long_description : str
        e.g. "README.md", else comment block used

    Other setup() params work as usual, and are passed trough. Notably
    entry_points= or data_files= can be used, even if they get augmented.
    """

    # optionalized here (avoid dependency in flit import)
    # p-y-l-i-n-t: disable=import-outside-toplevel







|
|
<
|
<
|
<
|







199
200
201
202
203
204
205
206
207

208

209

210
211
212
213
214
215
216
217

@pluginconf.renamed_arguments({"fn": "filename"})
def setup(filename=None, debug=False, **kwargs):
    """
    Wrapper around `setuptools.setup()` which adds some defaults
    and plugin meta data import, with some shortcut parameters:

    | Parameters  | | |
    |-------------|--------|------------------------------------------------|

    | filename    | str    | main file "pkg/main.py" (else deduced from primary package name) |

    | debug       | bool   | display collected options prior setuptools.setup() invocation |

    |long_description| str | e.g. "README.md", else the comment block gets used |

    Other setup() params work as usual, and are passed trough. Notably
    entry_points= or data_files= can be used, even if they get augmented.
    """

    # optionalized here (avoid dependency in flit import)
    # p-y-l-i-n-t: disable=import-outside-toplevel