Overview
Comment:pylint fixes, doc additions
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: a88ee411640c2facb954c66a637d202d3dd5958541043384908f69c69c7225c8
User & Date: mario on 2022-10-27 10:43:54
Other Links: manifest | tags
Context
2022-10-28
07:06
ad PluginMeta dict wrapper for briefer property access check-in: 9427b32486 user: mario tags: trunk
2022-10-27
10:43
pylint fixes, doc additions check-in: a88ee41164 user: mario tags: trunk
07:58
resurrect functions for doc check-in: f0163c8621 user: mario tags: trunk
Changes

Modified html/gui.html from [4c36b56296] to [95289d3b21].

28
29
30
31
32
33
34
35

36
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
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
28
29
30
31
32
33
34

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







-
+


-
+


-
+


-
+


-
+


-
+





-
+


-
+



+
+
+
+


-
+

-
+







+
+


+
+
+
+
+



-
+


-
+






-
-
+
+


-
+


-
-
+
+


-
+

-
-
+
+


-
+

-
-
+
+


-
+







</section>
<section>
</section>
<section>
<h2 class="section-title" id="header-functions">Functions</h2>
<dl>
<dt id="pluginconf.gui.option_entry"><code class="name flex">
<span>def <span class="ident">option_entry</span></span>(<span>o, config)</span>
<span>def <span class="ident">option_entry</span></span>(<span>opt, config)</span>
</code></dt>
<dd>
<div class="desc"></div>
<div class="desc"><p>widgets for single config option</p></div>
</dd>
<dt id="pluginconf.gui.plugin_entry"><code class="name flex">
<span>def <span class="ident">plugin_entry</span></span>(<span>e, plugin_states)</span>
<span>def <span class="ident">plugin_entry</span></span>(<span>pmd, plugin_states)</span>
</code></dt>
<dd>
<div class="desc"></div>
<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>ls, config, plugin_states, opt_label=False)</span>
<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"></div>
<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"></div>
<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'], plugins={}, opt_label=False, theme='DefaultNoMoreNagging', **kwargs)</span>
<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>
<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>
<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>s, w=50)</span>
<span>def <span class="ident">wrap</span></span>(<span>text, width=50)</span>
</code></dt>
<dd>
<div class="desc"></div>
<div class="desc"><p>textwrap for <code>description</code> and <code>help</code> option fields</p></div>
</dd>
</dl>
</section>
<section>
<h2 class="section-title" id="header-classes">Classes</h2>
<dl>
<dt id="pluginconf.gui.cast"><code class="flex name class">
<span>class <span class="ident">cast</span></span>
<dt id="pluginconf.gui.Cast"><code class="flex name class">
<span>class <span class="ident">Cast</span></span>
</code></dt>
<dd>
<div class="desc"></div>
<div class="desc"><p>map option types (from strings)</p></div>
<h3>Static methods</h3>
<dl>
<dt id="pluginconf.gui.cast.bool"><code class="name flex">
<span>def <span class="ident">bool</span></span>(<span>v)</span>
<dt id="pluginconf.gui.Cast.bool"><code class="name flex">
<span>def <span class="ident">bool</span></span>(<span>val)</span>
</code></dt>
<dd>
<div class="desc"></div>
<div class="desc"><p>map boolean literals</p></div>
</dd>
<dt id="pluginconf.gui.cast.fromtype"><code class="name flex">
<span>def <span class="ident">fromtype</span></span>(<span>v, opt)</span>
<dt id="pluginconf.gui.Cast.fromtype"><code class="name flex">
<span>def <span class="ident">fromtype</span></span>(<span>val, opt)</span>
</code></dt>
<dd>
<div class="desc"></div>
<div class="desc"><p>cast according to option type</p></div>
</dd>
<dt id="pluginconf.gui.cast.int"><code class="name flex">
<span>def <span class="ident">int</span></span>(<span>v)</span>
<dt id="pluginconf.gui.Cast.int"><code class="name flex">
<span>def <span class="ident">int</span></span>(<span>val)</span>
</code></dt>
<dd>
<div class="desc"></div>
<div class="desc"><p>verify integer</p></div>
</dd>
</dl>
</dd>
</dl>
</section>
</article>
<nav id="sidebar">
137
138
139
140
141
142
143
144

145
146
147
148



149
150
151
152
153
154
155
156
157
158
159
160
148
149
150
151
152
153
154

155
156



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171







-
+

-
-
-
+
+
+












<li><code><a title="pluginconf.gui.window" href="#pluginconf.gui.window">window</a></code></li>
<li><code><a title="pluginconf.gui.wrap" href="#pluginconf.gui.wrap">wrap</a></code></li>
</ul>
</li>
<li><h3><a href="#header-classes">Classes</a></h3>
<ul>
<li>
<h4><code><a title="pluginconf.gui.cast" href="#pluginconf.gui.cast">cast</a></code></h4>
<h4><code><a title="pluginconf.gui.Cast" href="#pluginconf.gui.Cast">Cast</a></code></h4>
<ul class="">
<li><code><a title="pluginconf.gui.cast.bool" href="#pluginconf.gui.cast.bool">bool</a></code></li>
<li><code><a title="pluginconf.gui.cast.fromtype" href="#pluginconf.gui.cast.fromtype">fromtype</a></code></li>
<li><code><a title="pluginconf.gui.cast.int" href="#pluginconf.gui.cast.int">int</a></code></li>
<li><code><a title="pluginconf.gui.Cast.bool" href="#pluginconf.gui.Cast.bool">bool</a></code></li>
<li><code><a title="pluginconf.gui.Cast.fromtype" href="#pluginconf.gui.Cast.fromtype">fromtype</a></code></li>
<li><code><a title="pluginconf.gui.Cast.int" href="#pluginconf.gui.Cast.int">int</a></code></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
</main>
<footer id="footer">
<p>Generated by <a href="https://pdoc3.github.io/pdoc" title="pdoc: Python API documentation generator"><cite>pdoc</cite> 0.10.0</a>.</p>
</footer>
</body>
</html>

Modified pluginconf/gui.py from [a9823722c2] to [96aaed6261].

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
27
28
29
30
31
32

33
34
35
36

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





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

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
27
28
29
30
31
32
33

34
35
36

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

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


-
+







+















+
+
+
+
+


-



-
+



-
+


-
+
+
+
+
+
+


-
+

-
+







+
+


+
+
+
+

-
+
+
+







-
+


-
+


-
+

-
+



-
-
+
+

-
-
-
+
+
+

-
-
-
-
-
+
+
+
+
+


-
-
-
+
+
+
+
-

-
+





-
+


-
-
+
+
+
-
-
+

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


-
-
+
+

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

-
-
-
-
+
+
+
+

-
-
-
-
+
+
+
+

-
-
-
+
+
+

-
-
-
-
+
+
+
+

-
+
-
-
-
-
-
+
+
+
+

-
-
-
-
+
+
+
+
+
+
+

-
+


-

-
-
-
-
+
+
+
+
+
+

-
-
-
+
+
+

-
-
+
+
+


+

-
-
+
+
+
+

-
+
+

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

+
-
+
-
-
+
# encoding: UTF-8
# api: python
# type: ui
##type: gui
# category: io
# title: Config GUI
# description: Display plugins + options in setup window
# version: 0.8
# depends: python:pysimplegui (>= 4.0)
# priority: optional
# config: -
# pylint: disable=line-too-long
#
# Creates a PySimpleGUI options list. Scans a given list of *.py files
# for meta data, then populates a config{} dict and (optionally) a state
# map for plugins themselves.
#
#    jsoncfg = {}
#    pluginconf.gui.window(jsoncfg, {}, ["plugins/*.py"])
#
# Very crude, and not as many widgets as the Gtk/St2 implementation.
# Supports type: str, bool, select, int, dict, text config: options.
#

""" PySimpleGUI window to populate config dict via plugin options """


#import os
import re
#import json
import glob
import textwrap
import PySimpleGUI as sg
import pluginconf
import glob, json, os, re, textwrap


# temporarily store collected plugin config: dicts
options = {}
OPTIONS = {}


#-- show configuation window
def window(config={}, plugin_states={}, files=["*/*.py"], plugins={}, opt_label=False, theme="DefaultNoMoreNagging", **kwargs):
def window(config, plugin_states, files=["*/*.py"], **kwargs):
    """
    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 : dict 🔁
        Config settings, updated after dialog completion
    plugin_states : dict
    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:
        sg.theme(theme)
    if files:
        plugins = read_options(files)
    layout = plugin_layout(plugins.values(), config, plugin_states, opt_label=opt_label)
    layout.append([sg.T(" ")])
    #print(repr(layout))
    

    # pack window
    layout = [
        [sg.Column(layout, expand_x=1, expand_y=0, size=(575,680), scrollable="vertically", element_justification='left')],
        [sg.Column(layout, expand_x=1, expand_y=0, size=(575, 680), scrollable="vertically", element_justification='left')],
        [sg.Column([[sg.Button("Cancel"), sg.Button("Save")]], element_justification='right')]
    ]
    if not "title" in kwargs:
    if "title" not in kwargs:
        kwargs["title"] = "Options"
    if not "font" in kwargs:
    if "font" not in kwargs:
        kwargs["font"] = "Sans 11"
    win = sg.Window(layout=layout, resizable=1, **kwargs)

    # wait for save/exit        
    event,data = win.read()
    # wait for save/exit
    event, data = win.read()
    win.close()
    if event=="Save":
        for k,v in data.items():
            if options.get(k):
    if event == "Save":
        for key, val in data.items():
            if OPTIONS.get(key):
                #@ToDo: handle array[key] names
                config[k] = cast.fromtype(data[k], options[k])
            elif type(k) is str and k.startswith('p:'):
                k = k.replace('p:', '')
                if plugins.get(k):
                    plugin_states[k] = v
                config[key] = Cast.fromtype(data[key], OPTIONS[key])
            elif isinstance(key, str) and key.startswith('p:'):
                key = key.replace('p:', '')
                if plugins.get(key):
                    plugin_states[key] = val
        return True
    #print(config, plugin_states)
    
    
# craft list of widgets for each read plugin


def plugin_layout(pmd_list, config, plugin_states, opt_label=False):
    """ craft list of widgets for each read plugin """
def plugin_layout(ls, config, plugin_states, opt_label=False):
    layout = []
    for plg in ls:
    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:
                    layout.append([sg.T(opt["name"], font=("Sans",11,"bold"), pad=((50,0),(7,0)))])
                    layout.append([sg.T(opt["name"], font=("Sans", 11, "bold"), pad=((50, 0), (7, 0)))])
                layout.append(option_entry(opt, config))
    return layout
    
# checkbox for plugin name

def plugin_entry(pmd, plugin_states):
    """ checkbox for plugin name """
def plugin_entry(e, plugin_states):
    id = e["id"]
    name = pmd["id"]
    return [
         [
             sg.Checkbox(
                  e.get("title", id), key='p:'+id, default=plugin_states.get(id, 0), tooltip=e.get("doc"), metadata="plugin",
                  font="bold", pad=(0,(8,0))
             ),
             sg.Text("({}/{})".format(e.get("type"), e.get("category")), text_color="#005", pad=(0,(8,0))),
             sg.Text(e.get("version"), text_color="#a72", pad=(0,(8,0)))
         ],
         [
             sg.Text(e.get("description", ""), tooltip=e.get("doc"), font=("sans", 10), pad=(26,(0,10)))
         ]
        [
            sg.Checkbox(
                pmd.get("title", name), key='p:'+name, default=plugin_states.get(name, 0),
                tooltip=pmd.get("doc"), metadata="plugin", font="bold", pad=(0, (8, 0))
            ),
            sg.Text("({}/{})".format(pmd.get("type"), pmd.get("category")), text_color="#005", pad=(0, (8, 0))),
            sg.Text(pmd.get("version"), text_color="#a72", pad=(0, (8, 0)))
        ],
        [
            sg.Text(pmd.get("description", ""), tooltip=pmd.get("doc"), font=("sans", 10), pad=(26, (0, 10)))
        ]
    ]

# widgets for single config option
def option_entry(o, config):
def option_entry(opt, config):
    """ widgets for single config option """
    #print(o)
    name = o.get("name", "")
    desc = wrap(o.get("description", name), 60)
    type = o.get("type", "str")
    help = o.get("help", None)
    if help:
    name = opt.get("name", "")
    desc = wrap(opt.get("description", name), 60)
    typedef = opt.get("type", "str")
    tooltip = wrap(opt.get("help", name), 60)
    OPTIONS[name] = opt
    val = config.get(name, opt.get("value", ""))

        help = wrap(help, 60)
    options[name] = o
    widget = []
    val = config.get(name, o.get("value", ""))
    if o.get("hidden"):
    if opt.get("hidden"):
        pass
    elif type == "str":
        return [
            sg.InputText(key=name, default_text=str(val), size=(20,1), pad=((50,0),3)),
            sg.Text(wrap(desc, 50), pad=(5,2), tooltip=help or name, justification='left', auto_size_text=1)
    elif typedef == "str":
        widget = [
            sg.InputText(key=name, default_text=str(val), size=(20, 1), pad=((50, 0), 3)),
            sg.Text(wrap(desc, 50), pad=(5, 2), tooltip=tooltip, justification='left', auto_size_text=1)
        ]
    elif type == "text":
        return [
            sg.Multiline(key=name, default_text=str(val), size=(45,4), pad=((40,0),3)),
            sg.Text(wrap(desc, 20), pad=(5,2), tooltip=help or name, justification='left', auto_size_text=1)
    elif typedef == "text":
        widget = [
            sg.Multiline(key=name, default_text=str(val), size=(45, 4), pad=((40, 0), 3)),
            sg.Text(wrap(desc, 20), pad=(5, 2), tooltip=tooltip, justification='left', auto_size_text=1)
        ]
    elif type == "bool":
        return [
            sg.Checkbox(wrap(desc, 70), key=name, default=cast.bool(val), tooltip=help or name, pad=((40,0),2), auto_size_text=1)
    elif typedef == "bool":
        widget = [
            sg.Checkbox(wrap(desc, 70), key=name, default=Cast.bool(val), tooltip=tooltip, pad=((40, 0), 2), auto_size_text=1)
        ]
    elif type == "int":
        return [
            sg.InputText(key=name, default_text=str(val), size=(6,1), pad=((50,0),3)),
            sg.Text(wrap(desc, 60), pad=(5,2), tooltip=help or name, auto_size_text=1)
    elif typedef == "int":
        widget = [
            sg.InputText(key=name, default_text=str(val), size=(6, 1), pad=((50, 0), 3)),
            sg.Text(wrap(desc, 60), pad=(5, 2), tooltip=tooltip, auto_size_text=1)
        ]
    elif type == "select":
    elif typedef == "select":
        #o["select"] = parse_select(o.get("select", ""))
        values = [v for v in o["select"].values()]
        return [
            sg.Combo(key=name, default_value=o["select"].get(val, val), values=values, size=(15,1), pad=((50,0),0), font="Sans 11"),
            sg.Text(wrap(desc, 47), pad=(5,2), tooltip=help or name, auto_size_text=1)
        values = opt["select"].values()
        widget = [
            sg.Combo(key=name, default_value=opt["select"].get(val, val), values=values, size=(15, 1), pad=((50, 0), 0), font="Sans 11"),
            sg.Text(wrap(desc, 47), pad=(5, 2), tooltip=tooltip, auto_size_text=1)
        ]
    elif type == "dict":  # or "table" rather ?
        return [
            sg.Table(values=config.get(name, ["", ""]), headings=o.get("columns", "Key,Value").split(","),
            num_rows=5, col_widths=30, def_col_width=30, auto_size_columns=False, max_col_width=150, key=name, tooltip=help or desc)
    elif typedef == "dict":  # or "table" rather ?
        widget = [
            sg.Table(
                values=config.get(name, ["", ""]), headings=opt.get("columns", "Key,Value").split(","),
                num_rows=5, col_widths=30, def_col_width=30, auto_size_columns=False, max_col_width=150,
                key=name, tooltip=wrap(opt.get("help", desc))
            )
        ]
    return []
    return widget


#-- read files, return dict of {id:pmd} for all plugins
def read_options(files):
    ls = [pluginconf.plugin_meta(fn=fn) for pattern in files for fn in glob.glob(pattern)]
    return dict(
        (meta["id"], meta) for meta in ls
    )
    """ read files, return dict of {id:pmd} for all plugins """
    return {
        meta["id"]: meta for meta in
        [pluginconf.plugin_meta(fn=fn) for pattern in files for fn in glob.glob(pattern)]
    }



#-- map option types (from strings)
class cast:
class Cast:
    """ map option types (from strings) """

    @staticmethod
    def bool(v):
        if v in ("1", 1, True, "true", "TRUE", "yes", "YES", "on", "ON"):
    def bool(val):
        """ map boolean literals """
        if val in ("1", 1, True, "true", "TRUE", "yes", "YES", "on", "ON"):
            return True
        return False

    @staticmethod
    def int(v):        
        return int(v) if re.match("-?\d+", v) else 0
    def int(val):
        """ verify integer """
        return int(val) if re.match(r"-?\d+", val) else 0

    @staticmethod
    def fromtype(v, opt):
    def fromtype(val, opt):
        """ cast according to option type """
        if not opt.get("type"):
            return str(v)
        elif opt["type"] == "int":
            return cast.int(v)
        elif opt["type"] == "bool":
            return cast.bool(v)
        elif opt["type"] == "select":
            inverse = dict((v,k) for k,v in opt["select"].items())
            return inverse.get(v, v)
        elif opt["type"] == "text":
            return str(v).rstrip()
        else:
            return v
            return str(val)
        if opt["type"] == "int":
            return Cast.int(val)
        if opt["type"] == "bool":
            return Cast.bool(val)
        if opt["type"] == "select":
            inverse = dict((val, key) for key, val in opt["select"].items())
            return inverse.get(val, val)
        if opt["type"] == "text":
            return str(val).rstrip()
        # else:
        return val

def wrap(text, width=50):
#-- textwrap for `description` and `help` option fields
    """ textwrap for `description` and `help` option fields """
def wrap(s, w=50):
    return "\n".join(textwrap.wrap(s, w)) if s else ""
    return "\n".join(textwrap.wrap(text, width)) if text else ""