mwxlib 1.0.0__py3-none-any.whl → 1.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mwx/__init__.py +6 -4
- mwx/bookshelf.py +144 -68
- mwx/controls.py +444 -378
- mwx/framework.py +567 -546
- mwx/graphman.py +745 -726
- mwx/images.py +16 -0
- mwx/matplot2.py +244 -235
- mwx/matplot2g.py +812 -751
- mwx/matplot2lg.py +218 -209
- mwx/mgplt.py +20 -22
- mwx/nutshell.py +1119 -1015
- mwx/plugins/ffmpeg_view.py +96 -90
- mwx/plugins/fft_view.py +13 -15
- mwx/plugins/frame_listview.py +68 -75
- mwx/plugins/line_profile.py +1 -1
- mwx/py/filling.py +13 -14
- mwx/testsuite.py +38 -0
- mwx/utilus.py +284 -219
- mwx/wxmon.py +43 -44
- mwx/wxpdb.py +83 -84
- mwx/wxwil.py +19 -18
- mwx/wxwit.py +38 -45
- {mwxlib-1.0.0.dist-info → mwxlib-1.8.0.dist-info}/METADATA +12 -5
- mwxlib-1.8.0.dist-info/RECORD +28 -0
- {mwxlib-1.0.0.dist-info → mwxlib-1.8.0.dist-info}/WHEEL +1 -1
- mwxlib-1.0.0.dist-info/LICENSE +0 -21
- mwxlib-1.0.0.dist-info/RECORD +0 -28
- {mwxlib-1.0.0.dist-info → mwxlib-1.8.0.dist-info}/top_level.txt +0 -0
mwx/utilus.py
CHANGED
|
@@ -16,8 +16,7 @@ import fnmatch
|
|
|
16
16
|
import pkgutil
|
|
17
17
|
import pydoc
|
|
18
18
|
import inspect
|
|
19
|
-
from inspect import
|
|
20
|
-
isfunction, isgenerator, isframe, iscode, istraceback)
|
|
19
|
+
from inspect import isclass, ismodule, ismethod, isbuiltin, isfunction
|
|
21
20
|
from pprint import pprint
|
|
22
21
|
|
|
23
22
|
|
|
@@ -37,20 +36,26 @@ def ignore(*category):
|
|
|
37
36
|
yield
|
|
38
37
|
|
|
39
38
|
|
|
40
|
-
def warn(message, category=None):
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
frame
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
39
|
+
def warn(message, category=None, stacklevel=None):
|
|
40
|
+
if stacklevel is None:
|
|
41
|
+
frame = inspect.currentframe().f_back # previous call stack frame
|
|
42
|
+
skip = [frame.f_code.co_filename]
|
|
43
|
+
stacklevel = 1
|
|
44
|
+
while frame.f_code.co_filename in skip:
|
|
45
|
+
frame = frame.f_back
|
|
46
|
+
if not frame:
|
|
47
|
+
break
|
|
48
|
+
stacklevel += 1
|
|
49
49
|
return warnings.warn(message, category, stacklevel+1)
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
def atom(v):
|
|
53
|
-
|
|
53
|
+
## Not a class, method, function, module, or any type (class, int, str, etc.).
|
|
54
|
+
if (isclass(v) or ismethod(v) or isfunction(v) or isbuiltin(v)
|
|
55
|
+
or ismodule(v) or isinstance(v, type)):
|
|
56
|
+
return False
|
|
57
|
+
## Include the case where __name__ is manually defined for a class instance.
|
|
58
|
+
return not hasattr(v, '__name__') or hasattr(v, '__class__')
|
|
54
59
|
|
|
55
60
|
|
|
56
61
|
def isobject(v):
|
|
@@ -62,7 +67,7 @@ def instance(*types):
|
|
|
62
67
|
## return lambda v: isinstance(v, types)
|
|
63
68
|
def _pred(v):
|
|
64
69
|
return isinstance(v, types)
|
|
65
|
-
_pred.__name__ =
|
|
70
|
+
_pred.__name__ = "instance<{}>".format(','.join(p.__name__ for p in types))
|
|
66
71
|
return _pred
|
|
67
72
|
|
|
68
73
|
|
|
@@ -70,45 +75,45 @@ def subclass(*types):
|
|
|
70
75
|
## return lambda v: issubclass(v, types)
|
|
71
76
|
def _pred(v):
|
|
72
77
|
return issubclass(v, types)
|
|
73
|
-
_pred.__name__ =
|
|
78
|
+
_pred.__name__ = "subclass<{}>".format(','.join(p.__name__ for p in types))
|
|
74
79
|
return _pred
|
|
75
80
|
|
|
76
81
|
|
|
77
82
|
def _Not(p):
|
|
78
83
|
## return lambda v: not p(v)
|
|
79
|
-
if isinstance(p, type):
|
|
80
|
-
p = instance(p)
|
|
81
84
|
def _pred(v):
|
|
82
85
|
return not p(v)
|
|
83
|
-
|
|
86
|
+
if isinstance(p, type):
|
|
87
|
+
p = instance(p)
|
|
88
|
+
_pred.__name__ = "not {}".format(p.__name__)
|
|
84
89
|
return _pred
|
|
85
90
|
|
|
86
91
|
|
|
87
92
|
def _And(p, q):
|
|
88
93
|
## return lambda v: p(v) and q(v)
|
|
94
|
+
def _pred(v):
|
|
95
|
+
return p(v) and q(v)
|
|
89
96
|
if isinstance(p, type):
|
|
90
97
|
p = instance(p)
|
|
91
98
|
if isinstance(q, type):
|
|
92
99
|
q = instance(q)
|
|
93
|
-
|
|
94
|
-
return p(v) and q(v)
|
|
95
|
-
_pred.__name__ = str("{} and {}".format(p.__name__, q.__name__))
|
|
100
|
+
_pred.__name__ = "{} and {}".format(p.__name__, q.__name__)
|
|
96
101
|
return _pred
|
|
97
102
|
|
|
98
103
|
|
|
99
104
|
def _Or(p, q):
|
|
100
105
|
## return lambda v: p(v) or q(v)
|
|
106
|
+
def _pred(v):
|
|
107
|
+
return p(v) or q(v)
|
|
101
108
|
if isinstance(p, type):
|
|
102
109
|
p = instance(p)
|
|
103
110
|
if isinstance(q, type):
|
|
104
111
|
q = instance(q)
|
|
105
|
-
|
|
106
|
-
return p(v) or q(v)
|
|
107
|
-
_pred.__name__ = str("{} or {}".format(p.__name__, q.__name__))
|
|
112
|
+
_pred.__name__ = "{} or {}".format(p.__name__, q.__name__)
|
|
108
113
|
return _pred
|
|
109
114
|
|
|
110
115
|
|
|
111
|
-
def
|
|
116
|
+
def _Predicate(text, locals):
|
|
112
117
|
tokens = [x for x in split_words(text.strip()) if not x.isspace()]
|
|
113
118
|
j = 0
|
|
114
119
|
while j < len(tokens):
|
|
@@ -138,11 +143,8 @@ def apropos(obj, rexpr='', ignorecase=True, alias=None, pred=None, locals=None):
|
|
|
138
143
|
"""
|
|
139
144
|
name = alias or typename(obj)
|
|
140
145
|
|
|
141
|
-
rexpr = (rexpr.replace('\\a','[a-z0-9]') #\a: identifier chars (custom rule)
|
|
142
|
-
.replace('\\A','[A-Z0-9]')) #\A:
|
|
143
|
-
|
|
144
146
|
if isinstance(pred, str):
|
|
145
|
-
pred =
|
|
147
|
+
pred = _Predicate(pred, locals)
|
|
146
148
|
|
|
147
149
|
if isinstance(pred, type):
|
|
148
150
|
pred = instance(pred)
|
|
@@ -161,9 +163,9 @@ def apropos(obj, rexpr='', ignorecase=True, alias=None, pred=None, locals=None):
|
|
|
161
163
|
try:
|
|
162
164
|
p = re.compile(rexpr, re.I if ignorecase else 0)
|
|
163
165
|
except re.error as e:
|
|
164
|
-
print("- re:miss compilation
|
|
166
|
+
print("- re:miss compilation;", e)
|
|
165
167
|
else:
|
|
166
|
-
keys = sorted(filter(p.search, dir(obj)), key=lambda s:s.upper())
|
|
168
|
+
keys = sorted(filter(p.search, dir(obj)), key=lambda s: s.upper())
|
|
167
169
|
n = 0
|
|
168
170
|
for key in keys:
|
|
169
171
|
try:
|
|
@@ -176,7 +178,7 @@ def apropos(obj, rexpr='', ignorecase=True, alias=None, pred=None, locals=None):
|
|
|
176
178
|
except Exception as e:
|
|
177
179
|
word = f"#<{e!r}>"
|
|
178
180
|
if len(word) > 80:
|
|
179
|
-
word = word[:80] + '...'
|
|
181
|
+
word = word[:80] + '...' # truncate words +3 ellipsis
|
|
180
182
|
print(" {}.{:<36s} {}".format(name, key, word))
|
|
181
183
|
if pred:
|
|
182
184
|
print("found {} of {} words with :{}".format(n, len(keys), pred.__name__))
|
|
@@ -187,22 +189,26 @@ def apropos(obj, rexpr='', ignorecase=True, alias=None, pred=None, locals=None):
|
|
|
187
189
|
def typename(obj, docp=False, qualp=True):
|
|
188
190
|
"""Formatted object type name.
|
|
189
191
|
"""
|
|
190
|
-
if
|
|
192
|
+
if not atom(obj): # module, class, method, function, etc.
|
|
191
193
|
if qualp:
|
|
192
194
|
name = getattr(obj, '__qualname__', obj.__name__)
|
|
193
195
|
else:
|
|
194
196
|
name = obj.__name__
|
|
195
|
-
elif hasattr(obj, '
|
|
197
|
+
elif hasattr(obj, '__class__'): # class instance -> module.class
|
|
196
198
|
name = obj.__class__.__name__
|
|
197
199
|
else:
|
|
198
|
-
return pydoc.describe(obj)
|
|
200
|
+
return pydoc.describe(obj) # atom -> short description
|
|
199
201
|
|
|
200
202
|
modname = getattr(obj, '__module__', None)
|
|
201
|
-
if modname
|
|
202
|
-
|
|
203
|
+
if modname:
|
|
204
|
+
if qualp:
|
|
205
|
+
name = modname + '.' + name
|
|
206
|
+
else:
|
|
207
|
+
if not modname.startswith(("__main__", "mwx")):
|
|
208
|
+
name = modname + '..' + name
|
|
203
209
|
|
|
204
210
|
if docp and callable(obj) and obj.__doc__:
|
|
205
|
-
name += "<{!r}>".format(obj.__doc__.splitlines()[0])
|
|
211
|
+
name += "<{!r}>".format(obj.__doc__.splitlines()[0]) # concat the first doc line
|
|
206
212
|
return name
|
|
207
213
|
|
|
208
214
|
|
|
@@ -227,8 +233,8 @@ def where(obj):
|
|
|
227
233
|
name = obj.tb_frame.f_code.co_name
|
|
228
234
|
return "{}:{}:{}".format(filename, lineno, name)
|
|
229
235
|
|
|
230
|
-
|
|
231
|
-
|
|
236
|
+
# if inspect.isbuiltin(obj):
|
|
237
|
+
# return None
|
|
232
238
|
|
|
233
239
|
def _where(obj):
|
|
234
240
|
obj = inspect.unwrap(obj)
|
|
@@ -241,18 +247,18 @@ def where(obj):
|
|
|
241
247
|
|
|
242
248
|
try:
|
|
243
249
|
try:
|
|
244
|
-
return _where(obj)
|
|
250
|
+
return _where(obj) # module, class, method, function, frame, or code
|
|
245
251
|
except TypeError:
|
|
246
|
-
return _where(obj.__class__)
|
|
252
|
+
return _where(obj.__class__) # otherwise, class of the object
|
|
247
253
|
except Exception:
|
|
248
254
|
pass
|
|
249
255
|
## The source code cannot be retrieved.
|
|
250
256
|
## Try to get filename where the object is defined.
|
|
251
257
|
try:
|
|
252
258
|
try:
|
|
253
|
-
return inspect.getfile(obj)
|
|
259
|
+
return inspect.getfile(obj) # compiled file?
|
|
254
260
|
except TypeError:
|
|
255
|
-
return inspect.getfile(obj.__class__)
|
|
261
|
+
return inspect.getfile(obj.__class__) # or a special class?
|
|
256
262
|
except Exception:
|
|
257
263
|
pass
|
|
258
264
|
|
|
@@ -275,34 +281,50 @@ def mro(obj):
|
|
|
275
281
|
def pp(obj):
|
|
276
282
|
pprint(obj, **pp.__dict__)
|
|
277
283
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
284
|
+
|
|
285
|
+
pp.indent = 1
|
|
286
|
+
pp.width = 80 # default 80
|
|
287
|
+
pp.depth = None
|
|
288
|
+
pp.compact = False
|
|
289
|
+
pp.sort_dicts = False
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
## --------------------------------
|
|
293
|
+
## Shell internal helper functions.
|
|
294
|
+
## --------------------------------
|
|
295
|
+
|
|
296
|
+
def fix_fnchars(filename, substr='_'):
|
|
297
|
+
"""Replace invalid filename characters with substr."""
|
|
298
|
+
if os.name == 'nt':
|
|
299
|
+
## Replace Windows-invalid chars [:*?"<>|] with substr.
|
|
300
|
+
## Do not replace \\ or / to preserve folder structure.
|
|
301
|
+
return re.sub(r'[:*?"<>|]', substr, filename)
|
|
302
|
+
else:
|
|
303
|
+
return filename
|
|
286
304
|
|
|
287
305
|
|
|
288
|
-
def
|
|
289
|
-
"""
|
|
290
|
-
If reverse is True,
|
|
306
|
+
def split_words(text, reverse=False):
|
|
307
|
+
"""Generates words (python phrase) extracted from text.
|
|
308
|
+
If reverse is True, process from tail to head.
|
|
291
309
|
"""
|
|
292
310
|
tokens = list(split_tokens(text))
|
|
293
311
|
if reverse:
|
|
294
312
|
tokens = tokens[::-1]
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
313
|
+
while tokens:
|
|
314
|
+
words = []
|
|
315
|
+
while 1:
|
|
316
|
+
word = _extract_words_from_tokens(tokens, reverse)
|
|
317
|
+
if not word:
|
|
318
|
+
break
|
|
319
|
+
words += word
|
|
320
|
+
if words:
|
|
321
|
+
yield ''.join(reversed(words) if reverse else words)
|
|
322
|
+
if tokens:
|
|
323
|
+
yield tokens.pop(0) # sep-token
|
|
302
324
|
|
|
303
325
|
|
|
304
|
-
def
|
|
305
|
-
"""Generates words extracted from text.
|
|
326
|
+
def split_parts(text, reverse=False):
|
|
327
|
+
"""Generates portions (words and parens) extracted from text.
|
|
306
328
|
If reverse is True, process from tail to head.
|
|
307
329
|
"""
|
|
308
330
|
tokens = list(split_tokens(text))
|
|
@@ -313,7 +335,7 @@ def split_words(text, reverse=False):
|
|
|
313
335
|
if words:
|
|
314
336
|
yield ''.join(reversed(words) if reverse else words)
|
|
315
337
|
else:
|
|
316
|
-
yield tokens.pop(0)
|
|
338
|
+
yield tokens.pop(0) # sep-token
|
|
317
339
|
|
|
318
340
|
|
|
319
341
|
def split_tokens(text, comment=True):
|
|
@@ -326,17 +348,18 @@ def split_tokens(text, comment=True):
|
|
|
326
348
|
j, k = 1, 0
|
|
327
349
|
for type, string, start, end, line in tokens:
|
|
328
350
|
l, m = start
|
|
329
|
-
if type in (
|
|
351
|
+
if type in (tokenize.INDENT, tokenize.DEDENT) or not string:
|
|
352
|
+
## Empty strings such as NEWLINE and ENDMARKER are also skipped.
|
|
330
353
|
continue
|
|
331
|
-
if type ==
|
|
332
|
-
token = next(tokens)
|
|
333
|
-
string = token.string
|
|
354
|
+
if type == tokenize.COMMENT and not comment:
|
|
355
|
+
token = next(tokens) # eats a trailing token
|
|
356
|
+
string = token.string # cr/lf or ''
|
|
334
357
|
if m == 0:
|
|
335
358
|
continue # line starting with a comment
|
|
336
359
|
if l > j and m > 0:
|
|
337
360
|
yield ' ' * m # indent spaces
|
|
338
361
|
elif m > k:
|
|
339
|
-
yield ' ' * (m-k)
|
|
362
|
+
yield ' ' * (m-k) # white spaces
|
|
340
363
|
j, k = end
|
|
341
364
|
yield string
|
|
342
365
|
except tokenize.TokenError:
|
|
@@ -346,10 +369,6 @@ def split_tokens(text, comment=True):
|
|
|
346
369
|
def _extract_words_from_tokens(tokens, reverse=False):
|
|
347
370
|
"""Extracts pythonic expressions from tokens.
|
|
348
371
|
|
|
349
|
-
Extraction continues until the parenthesis is closed
|
|
350
|
-
and the following token starts with a char in sep, where
|
|
351
|
-
the sep includes `@, ops, delims, and whitespaces, etc.
|
|
352
|
-
|
|
353
372
|
Returns:
|
|
354
373
|
A token list extracted including the parenthesis.
|
|
355
374
|
If reverse is True, the order of the tokens will be reversed.
|
|
@@ -361,53 +380,28 @@ def _extract_words_from_tokens(tokens, reverse=False):
|
|
|
361
380
|
stack = []
|
|
362
381
|
words = []
|
|
363
382
|
for j, c in enumerate(tokens):
|
|
383
|
+
if not c:
|
|
384
|
+
continue
|
|
364
385
|
if c in p:
|
|
365
386
|
stack.append(c)
|
|
366
387
|
elif c in q:
|
|
367
|
-
if not stack:
|
|
388
|
+
if not stack: # error("open-paren")
|
|
368
389
|
break
|
|
369
|
-
if c != q[p.index(stack.pop())]:
|
|
390
|
+
if c != q[p.index(stack.pop())]: # error("mismatch-paren")
|
|
370
391
|
break
|
|
371
|
-
elif not stack and c[0] in sep:
|
|
392
|
+
elif not stack and c[0] in sep: # ok; starts with a char in sep
|
|
372
393
|
break
|
|
373
394
|
words.append(c)
|
|
374
|
-
|
|
395
|
+
if not stack: # ok
|
|
396
|
+
j += 1 # to remove current token
|
|
397
|
+
break
|
|
398
|
+
else:
|
|
399
|
+
# if stack: error("unclosed-paren")
|
|
375
400
|
j = None
|
|
376
|
-
del tokens[:j]
|
|
401
|
+
del tokens[:j] # remove extracted tokens (except the last one)
|
|
377
402
|
return words
|
|
378
403
|
|
|
379
404
|
|
|
380
|
-
def _extract_paren_from_tokens(tokens, reverse=False):
|
|
381
|
-
"""Extracts parenthesis from tokens.
|
|
382
|
-
|
|
383
|
-
The first token must be a parenthesis.
|
|
384
|
-
Returns:
|
|
385
|
-
A token list extracted including the parenthesis,
|
|
386
|
-
or an empty list if the parenthesis is not closed.
|
|
387
|
-
If reverse is True, the order of the tokens will be reversed.
|
|
388
|
-
"""
|
|
389
|
-
p, q = "({[", ")}]"
|
|
390
|
-
if reverse:
|
|
391
|
-
p, q = q, p
|
|
392
|
-
stack = []
|
|
393
|
-
words = []
|
|
394
|
-
for j, c in enumerate(tokens):
|
|
395
|
-
if c in p:
|
|
396
|
-
stack.append(c)
|
|
397
|
-
elif c in q:
|
|
398
|
-
if not stack: # error("open-paren")
|
|
399
|
-
break
|
|
400
|
-
if c != q[p.index(stack.pop())]: # error("mismatch-paren")
|
|
401
|
-
break
|
|
402
|
-
elif j == 0:
|
|
403
|
-
break # first char is not paren
|
|
404
|
-
words.append(c)
|
|
405
|
-
if not stack: # ok
|
|
406
|
-
del tokens[:j+1] # remove extracted tokens
|
|
407
|
-
return words
|
|
408
|
-
return [] # error("unclosed-paren")
|
|
409
|
-
|
|
410
|
-
|
|
411
405
|
def walk_packages_no_import(path=None, prefix=''):
|
|
412
406
|
"""Yields module info recursively for all submodules on path.
|
|
413
407
|
If path is None, yields all top-level modules on sys.path.
|
|
@@ -443,9 +437,9 @@ def find_modules(force=False, verbose=True):
|
|
|
443
437
|
|
|
444
438
|
if not force and os.path.exists(fn):
|
|
445
439
|
with open(fn, 'r') as o:
|
|
446
|
-
lm = eval(o.read())
|
|
440
|
+
lm = eval(o.read()) # read and evaluate module list
|
|
447
441
|
|
|
448
|
-
## Check additional packages and modules
|
|
442
|
+
## Check additional packages and modules.
|
|
449
443
|
verbose = False
|
|
450
444
|
for info in walk_packages_no_import(['.']):
|
|
451
445
|
_callback('.', info.name)
|
|
@@ -464,7 +458,7 @@ def find_modules(force=False, verbose=True):
|
|
|
464
458
|
|
|
465
459
|
lm.sort(key=str.upper)
|
|
466
460
|
with open(fn, 'w') as o:
|
|
467
|
-
pprint(lm, stream=o)
|
|
461
|
+
pprint(lm, stream=o) # write module list
|
|
468
462
|
print("The results were written in {!r}.".format(fn))
|
|
469
463
|
return lm
|
|
470
464
|
|
|
@@ -480,19 +474,19 @@ def get_rootpath(fn):
|
|
|
480
474
|
|
|
481
475
|
|
|
482
476
|
## --------------------------------
|
|
483
|
-
## Finite State Machine
|
|
477
|
+
## Finite State Machine.
|
|
484
478
|
## --------------------------------
|
|
485
479
|
|
|
486
480
|
class SSM(dict):
|
|
487
|
-
"""Single State Machine/Context of FSM
|
|
481
|
+
"""Single State Machine/Context of FSM.
|
|
488
482
|
"""
|
|
489
483
|
def __call__(self, event, *args, **kwargs):
|
|
490
484
|
for act in self[event]:
|
|
491
485
|
act(*args, **kwargs)
|
|
492
|
-
|
|
486
|
+
|
|
493
487
|
def __repr__(self):
|
|
494
488
|
return "<{} object at 0x{:X}>".format(self.__class__.__name__, id(self))
|
|
495
|
-
|
|
489
|
+
|
|
496
490
|
def __str__(self):
|
|
497
491
|
def _lstr(v):
|
|
498
492
|
def _name(a):
|
|
@@ -501,7 +495,7 @@ class SSM(dict):
|
|
|
501
495
|
return repr(a)
|
|
502
496
|
return ', '.join(_name(a) for a in v)
|
|
503
497
|
return '\n'.join("{:>32} : {}".format(str(k), _lstr(v)) for k, v in self.items())
|
|
504
|
-
|
|
498
|
+
|
|
505
499
|
def bind(self, event, action=None):
|
|
506
500
|
"""Append a transaction to the context."""
|
|
507
501
|
assert callable(action) or action is None
|
|
@@ -513,7 +507,7 @@ class SSM(dict):
|
|
|
513
507
|
if action not in transaction:
|
|
514
508
|
transaction.append(action)
|
|
515
509
|
return action
|
|
516
|
-
|
|
510
|
+
|
|
517
511
|
def unbind(self, event, action=None):
|
|
518
512
|
"""Remove a transaction from the context."""
|
|
519
513
|
assert callable(action) or action is None
|
|
@@ -530,7 +524,7 @@ class SSM(dict):
|
|
|
530
524
|
|
|
531
525
|
|
|
532
526
|
class FSM(dict):
|
|
533
|
-
"""Finite State Machine
|
|
527
|
+
"""Finite State Machine.
|
|
534
528
|
|
|
535
529
|
Args:
|
|
536
530
|
contexts: map of context <DNA>
|
|
@@ -554,27 +548,28 @@ class FSM(dict):
|
|
|
554
548
|
[8] +++ (max verbose level) to put all args and kwargs.
|
|
555
549
|
|
|
556
550
|
Note:
|
|
557
|
-
|
|
558
|
-
If there is only one state, that state
|
|
551
|
+
default=None is given as an argument to ``__init__``.
|
|
552
|
+
If there is only one state, that state is used as the default.
|
|
559
553
|
|
|
560
554
|
Note:
|
|
561
555
|
There is no enter/exit event handler.
|
|
562
556
|
"""
|
|
563
557
|
debug = 0
|
|
564
|
-
|
|
565
|
-
default_state = None
|
|
558
|
+
|
|
559
|
+
default_state = None # Used for define/undefine methods.
|
|
560
|
+
|
|
566
561
|
current_state = property(lambda self: self.__state)
|
|
567
562
|
previous_state = property(lambda self: self.__prev_state)
|
|
568
|
-
|
|
563
|
+
|
|
569
564
|
current_event = property(lambda self: self.__event)
|
|
570
565
|
previous_event = property(lambda self: self.__prev_event)
|
|
571
|
-
|
|
566
|
+
|
|
572
567
|
@current_state.setter
|
|
573
568
|
def current_state(self, state):
|
|
574
569
|
self.__state = state
|
|
575
570
|
self.__event = '*forced*'
|
|
576
571
|
self.__debcall__(self.__event)
|
|
577
|
-
|
|
572
|
+
|
|
578
573
|
def clear(self, state):
|
|
579
574
|
"""Reset current and previous states."""
|
|
580
575
|
self.__state = state
|
|
@@ -582,28 +577,28 @@ class FSM(dict):
|
|
|
582
577
|
self.__event = None
|
|
583
578
|
self.__prev_event = None
|
|
584
579
|
self.__matched_pattern = None
|
|
585
|
-
|
|
580
|
+
|
|
586
581
|
def __init__(self, contexts=None, default=None):
|
|
587
|
-
dict.__init__(self)
|
|
588
|
-
dict.clear(self)
|
|
582
|
+
dict.__init__(self) # update dict, however, it does not clear
|
|
583
|
+
dict.clear(self) # if and when __init__ is called, all contents are cleared
|
|
589
584
|
if contexts is None:
|
|
590
585
|
contexts = {}
|
|
591
|
-
if default is None:
|
|
586
|
+
if default is None: # if no default given, reset the first state as the default
|
|
592
587
|
if self.default_state is None:
|
|
593
588
|
default = next((k for k in contexts if k is not None), None)
|
|
594
589
|
self.default_state = default
|
|
595
|
-
self.clear(default)
|
|
590
|
+
self.clear(default) # the first clear creates object localvars
|
|
596
591
|
self.update(contexts)
|
|
597
|
-
|
|
592
|
+
|
|
598
593
|
def __missing__(self, key):
|
|
599
594
|
raise Exception("FSM logic-error: undefined state {!r}".format(key))
|
|
600
|
-
|
|
595
|
+
|
|
601
596
|
def __repr__(self):
|
|
602
597
|
return "<{} object at 0x{:X}>".format(self.__class__.__name__, id(self))
|
|
603
|
-
|
|
598
|
+
|
|
604
599
|
def __str__(self):
|
|
605
600
|
return '\n'.join("[ {!r} ]\n{!s}".format(k, v) for k, v in self.items())
|
|
606
|
-
|
|
601
|
+
|
|
607
602
|
def __call__(self, event, *args, **kwargs):
|
|
608
603
|
"""Handle the event.
|
|
609
604
|
|
|
@@ -617,8 +612,8 @@ class FSM(dict):
|
|
|
617
612
|
- process the event (no actions) -> []
|
|
618
613
|
- no event:transaction -> None
|
|
619
614
|
"""
|
|
620
|
-
recept = False
|
|
621
|
-
retvals = []
|
|
615
|
+
recept = False # Is transaction performed?
|
|
616
|
+
retvals = [] # retvals of actions
|
|
622
617
|
self.__event = event
|
|
623
618
|
if None in self:
|
|
624
619
|
org = self.__state
|
|
@@ -626,17 +621,17 @@ class FSM(dict):
|
|
|
626
621
|
try:
|
|
627
622
|
self.__state = None
|
|
628
623
|
self.__prev_state = None
|
|
629
|
-
ret = self.call(event, *args, **kwargs)
|
|
624
|
+
ret = self.call(event, *args, **kwargs) # None process
|
|
630
625
|
if ret is not None:
|
|
631
626
|
recept = True
|
|
632
627
|
retvals += ret
|
|
633
628
|
finally:
|
|
634
|
-
if self.__state is None:
|
|
629
|
+
if self.__state is None: # restore original
|
|
635
630
|
self.__state = org
|
|
636
631
|
self.__prev_state = prev
|
|
637
632
|
|
|
638
633
|
if self.__state is not None:
|
|
639
|
-
ret = self.call(event, *args, **kwargs)
|
|
634
|
+
ret = self.call(event, *args, **kwargs) # normal process
|
|
640
635
|
if ret is not None:
|
|
641
636
|
recept = True
|
|
642
637
|
retvals += ret
|
|
@@ -646,7 +641,7 @@ class FSM(dict):
|
|
|
646
641
|
self.__prev_state = self.__state
|
|
647
642
|
if recept:
|
|
648
643
|
return retvals
|
|
649
|
-
|
|
644
|
+
|
|
650
645
|
def fork(self, event, *args, **kwargs):
|
|
651
646
|
"""Invoke the event handlers (internal use only).
|
|
652
647
|
|
|
@@ -657,7 +652,7 @@ class FSM(dict):
|
|
|
657
652
|
ret = self.call(event, *args, **kwargs)
|
|
658
653
|
self.__prev_event = self.__event
|
|
659
654
|
return ret
|
|
660
|
-
|
|
655
|
+
|
|
661
656
|
def call(self, event, *args, **kwargs):
|
|
662
657
|
"""Invoke the event handlers (internal use only).
|
|
663
658
|
|
|
@@ -674,16 +669,16 @@ class FSM(dict):
|
|
|
674
669
|
context = self[self.__state]
|
|
675
670
|
if event in context:
|
|
676
671
|
transaction = context[event]
|
|
677
|
-
self.__prev_state = self.__state
|
|
678
|
-
self.__state = transaction[0]
|
|
679
|
-
self.__debcall__(event, *args, **kwargs)
|
|
672
|
+
self.__prev_state = self.__state # save previous state
|
|
673
|
+
self.__state = transaction[0] # the state transits here
|
|
674
|
+
self.__debcall__(event, *args, **kwargs) # check after transition
|
|
680
675
|
retvals = []
|
|
681
676
|
for act in transaction[1:]:
|
|
682
677
|
## Save the event before each action (for nested call).
|
|
683
678
|
if self.__matched_pattern is None:
|
|
684
679
|
self.__event = event
|
|
685
680
|
try:
|
|
686
|
-
ret = act(*args, **kwargs)
|
|
681
|
+
ret = act(*args, **kwargs) # call actions after transition
|
|
687
682
|
retvals.append(ret)
|
|
688
683
|
except BdbQuit:
|
|
689
684
|
pass
|
|
@@ -699,15 +694,15 @@ class FSM(dict):
|
|
|
699
694
|
self.__matched_pattern = None
|
|
700
695
|
return retvals
|
|
701
696
|
|
|
702
|
-
if isinstance(event, str):
|
|
697
|
+
if isinstance(event, str): # matching test using fnmatch
|
|
703
698
|
for pat in context:
|
|
704
699
|
if fnmatch.fnmatchcase(event, pat):
|
|
705
700
|
self.__matched_pattern = pat
|
|
706
|
-
return self.call(pat, *args, **kwargs)
|
|
701
|
+
return self.call(pat, *args, **kwargs) # recursive call
|
|
707
702
|
|
|
708
|
-
self.__debcall__(event, *args, **kwargs)
|
|
709
|
-
return None
|
|
710
|
-
|
|
703
|
+
self.__debcall__(event, *args, **kwargs) # check when no transition
|
|
704
|
+
return None # no event, no action
|
|
705
|
+
|
|
711
706
|
def __debcall__(self, pattern, *args, **kwargs):
|
|
712
707
|
v = self.debug
|
|
713
708
|
if v and self.__state is not None:
|
|
@@ -722,7 +717,7 @@ class FSM(dict):
|
|
|
722
717
|
a = '' if not actions else ('=> ' + actions),
|
|
723
718
|
c = '*' if self.__prev_state != self.__state else ' '))
|
|
724
719
|
|
|
725
|
-
elif v > 3:
|
|
720
|
+
elif v > 3: # state is None
|
|
726
721
|
transaction = self[None].get(pattern) or []
|
|
727
722
|
actions = ', '.join(typename(a, qualp=0) for a in transaction[1:])
|
|
728
723
|
if actions or v > 4:
|
|
@@ -730,13 +725,13 @@ class FSM(dict):
|
|
|
730
725
|
self.__event,
|
|
731
726
|
a = '' if not actions else ('=> ' + actions)))
|
|
732
727
|
|
|
733
|
-
if v > 7:
|
|
728
|
+
if v > 7: # max verbose level puts all args
|
|
734
729
|
self.log("\t:", args, kwargs)
|
|
735
|
-
|
|
730
|
+
|
|
736
731
|
@staticmethod
|
|
737
732
|
def log(*args):
|
|
738
733
|
print(*args, file=sys.__stdout__)
|
|
739
|
-
|
|
734
|
+
|
|
740
735
|
@staticmethod
|
|
741
736
|
def dump(*args):
|
|
742
737
|
fn = get_rootpath("deb-dump.log")
|
|
@@ -744,7 +739,7 @@ class FSM(dict):
|
|
|
744
739
|
print(time.strftime('!!! %Y/%m/%d %H:%M:%S'), file=o)
|
|
745
740
|
print(*args, traceback.format_exc(), sep='\n', file=o)
|
|
746
741
|
print(*args, traceback.format_exc(), sep='\n', file=sys.__stderr__)
|
|
747
|
-
|
|
742
|
+
|
|
748
743
|
@staticmethod
|
|
749
744
|
def duplicate(context):
|
|
750
745
|
"""Duplicate the transaction:list in the context.
|
|
@@ -752,63 +747,63 @@ class FSM(dict):
|
|
|
752
747
|
This method is used for the contexts given to :append and :update
|
|
753
748
|
so that the original transaction (if they are lists) is not removed.
|
|
754
749
|
"""
|
|
755
|
-
return {event:transaction[:] for event, transaction in context.items()}
|
|
756
|
-
|
|
750
|
+
return {event: transaction[:] for event, transaction in context.items()}
|
|
751
|
+
|
|
757
752
|
def validate(self, state):
|
|
758
753
|
"""Sort and move to end items with key which includes ``*?[]``."""
|
|
759
754
|
context = self[state]
|
|
760
755
|
ast = []
|
|
761
756
|
bra = []
|
|
762
|
-
for event in list(context):
|
|
757
|
+
for event in list(context): # context mutates during iteration
|
|
763
758
|
if re.search(r"\[.+\]", event):
|
|
764
|
-
bra.append((event, context.pop(event)))
|
|
759
|
+
bra.append((event, context.pop(event))) # event key has '[]'
|
|
765
760
|
elif '*' in event or '?' in event:
|
|
766
|
-
ast.append((event, context.pop(event)))
|
|
761
|
+
ast.append((event, context.pop(event))) # event key has '*?'
|
|
767
762
|
|
|
768
|
-
temp = sorted(context.items())
|
|
763
|
+
temp = sorted(context.items()) # normal event key
|
|
769
764
|
context.clear()
|
|
770
765
|
context.update(temp)
|
|
771
766
|
context.update(sorted(bra, reverse=1))
|
|
772
|
-
context.update(sorted(ast, reverse=1, key=lambda v:len(v[0])))
|
|
773
|
-
|
|
767
|
+
context.update(sorted(ast, reverse=1, key=lambda v: len(v[0])))
|
|
768
|
+
|
|
774
769
|
def update(self, contexts):
|
|
775
770
|
"""Update each context or Add new contexts."""
|
|
776
771
|
for k, v in contexts.items():
|
|
777
772
|
if k in self:
|
|
778
773
|
self[k].update(self.duplicate(v))
|
|
779
774
|
else:
|
|
780
|
-
self[k] = SSM(self.duplicate(v))
|
|
775
|
+
self[k] = SSM(self.duplicate(v)) # new context
|
|
781
776
|
self.validate(k)
|
|
782
|
-
|
|
777
|
+
|
|
783
778
|
def append(self, contexts):
|
|
784
779
|
"""Append new contexts."""
|
|
785
780
|
for k, v in contexts.items():
|
|
786
781
|
if k in self:
|
|
787
782
|
for event, transaction in v.items():
|
|
788
783
|
if event not in self[k]:
|
|
789
|
-
self[k][event] = transaction[:]
|
|
784
|
+
self[k][event] = transaction[:] # copy the event:transaction
|
|
790
785
|
continue
|
|
791
786
|
for act in transaction[1:]:
|
|
792
787
|
self.bind(event, act, k, transaction[0])
|
|
793
788
|
else:
|
|
794
|
-
self[k] = SSM(self.duplicate(v))
|
|
789
|
+
self[k] = SSM(self.duplicate(v)) # new context
|
|
795
790
|
self.validate(k)
|
|
796
|
-
|
|
791
|
+
|
|
797
792
|
def remove(self, contexts):
|
|
798
793
|
"""Remove old contexts."""
|
|
799
794
|
for k, v in contexts.items():
|
|
800
795
|
if k in self:
|
|
801
796
|
for event, transaction in v.items():
|
|
802
797
|
if self[k].get(event) == transaction:
|
|
803
|
-
self[k].pop(event)
|
|
798
|
+
self[k].pop(event) # remove the event:transaction
|
|
804
799
|
continue
|
|
805
800
|
for act in transaction[1:]:
|
|
806
801
|
self.unbind(event, act, k)
|
|
807
|
-
##
|
|
808
|
-
for k, v in list(self.items()):
|
|
802
|
+
## Cleanup.
|
|
803
|
+
for k, v in list(self.items()): # self mutates during iteration
|
|
809
804
|
if not v:
|
|
810
805
|
del self[k]
|
|
811
|
-
|
|
806
|
+
|
|
812
807
|
def bind(self, event, action=None, state=None, state2=None):
|
|
813
808
|
"""Append a transaction to the context.
|
|
814
809
|
|
|
@@ -822,37 +817,39 @@ class FSM(dict):
|
|
|
822
817
|
|
|
823
818
|
if state not in self:
|
|
824
819
|
warn(f"- FSM [{state!r}] context newly created.")
|
|
825
|
-
self[state] = SSM()
|
|
820
|
+
self[state] = SSM() # new context
|
|
826
821
|
|
|
827
|
-
context = self[state]
|
|
828
822
|
if state2 is None:
|
|
829
823
|
state2 = state
|
|
830
824
|
|
|
825
|
+
context = self[state]
|
|
831
826
|
if event in context:
|
|
832
827
|
if state2 != context[event][0]:
|
|
833
828
|
warn(f"- FSM transaction may conflict ({event!r} : {state!r} --> {state2!r}).\n"
|
|
834
829
|
f" The state {state2!r} is different from the original state.")
|
|
835
830
|
pass
|
|
836
|
-
context[event][0] = state2
|
|
831
|
+
context[event][0] = state2 # update transition
|
|
837
832
|
else:
|
|
838
833
|
if state2 not in self:
|
|
839
834
|
warn(f"- FSM transaction may contradict ({event!r} : {state!r} --> {state2!r}).\n"
|
|
840
835
|
f" The state {state2!r} is not found in the contexts.")
|
|
841
836
|
pass
|
|
842
|
-
context[event] = [state2]
|
|
837
|
+
context[event] = [state2] # new event:transaction
|
|
843
838
|
|
|
844
839
|
transaction = context[event]
|
|
845
840
|
if action is None:
|
|
846
841
|
return lambda f: self.bind(event, f, state, state2)
|
|
847
842
|
|
|
848
|
-
if action
|
|
843
|
+
if action in transaction:
|
|
844
|
+
warn(f"- FSM duplicate transaction ({state!r} : {event!r}).")
|
|
845
|
+
else:
|
|
849
846
|
try:
|
|
850
847
|
transaction.append(action)
|
|
851
848
|
except AttributeError:
|
|
852
849
|
warn(f"- FSM cannot append new transaction ({state!r} : {event!r}).\n"
|
|
853
850
|
f" The transaction must be a list, not a tuple.")
|
|
854
851
|
return action
|
|
855
|
-
|
|
852
|
+
|
|
856
853
|
def unbind(self, event, action=None, state=None):
|
|
857
854
|
"""Remove a transaction from the context.
|
|
858
855
|
|
|
@@ -887,6 +884,41 @@ class FSM(dict):
|
|
|
887
884
|
f" The transaction must be a list, not a tuple")
|
|
888
885
|
return False
|
|
889
886
|
|
|
887
|
+
def binds(self, event, action=None, state=None, state2=None):
|
|
888
|
+
"""Append a one-time transaction to the context.
|
|
889
|
+
|
|
890
|
+
Like `bind`, but unbinds itself after being called once.
|
|
891
|
+
"""
|
|
892
|
+
if action is None:
|
|
893
|
+
return lambda f: self.binds(event, f, state, state2)
|
|
894
|
+
|
|
895
|
+
@wraps(action)
|
|
896
|
+
def _act(*v, **kw):
|
|
897
|
+
try:
|
|
898
|
+
return action(*v, **kw)
|
|
899
|
+
finally:
|
|
900
|
+
self.unbind(event, _act, state)
|
|
901
|
+
return self.bind(event, _act, state, state2)
|
|
902
|
+
|
|
903
|
+
def define(self, event, action=None, /, *args, **kwargs):
|
|
904
|
+
"""Define event action.
|
|
905
|
+
|
|
906
|
+
Note:
|
|
907
|
+
The funcall kwargs `doc` and `alias` are reserved as kw-only-args.
|
|
908
|
+
"""
|
|
909
|
+
state = self.default_state
|
|
910
|
+
if action is None:
|
|
911
|
+
self[state].pop(event, None) # cf. undefine
|
|
912
|
+
return lambda f: self.define(event, f, *args, **kwargs)
|
|
913
|
+
|
|
914
|
+
f = funcall(action, *args, **kwargs)
|
|
915
|
+
self.update({state: {event: [state, f]}})
|
|
916
|
+
return action
|
|
917
|
+
|
|
918
|
+
def undefine(self, event):
|
|
919
|
+
"""Delete event context."""
|
|
920
|
+
self.define(event, None)
|
|
921
|
+
|
|
890
922
|
|
|
891
923
|
class TreeList:
|
|
892
924
|
"""Interface class for tree list control.
|
|
@@ -902,41 +934,41 @@ class TreeList:
|
|
|
902
934
|
"""
|
|
903
935
|
def __init__(self, ls=None):
|
|
904
936
|
self.__items = ls or []
|
|
905
|
-
|
|
937
|
+
|
|
906
938
|
def __call__(self, k):
|
|
907
939
|
return TreeList(self[k])
|
|
908
|
-
|
|
940
|
+
|
|
909
941
|
def __len__(self):
|
|
910
942
|
return len(self.__items)
|
|
911
|
-
|
|
943
|
+
|
|
912
944
|
def __contains__(self, k):
|
|
913
945
|
return self._getf(self.__items, k)
|
|
914
|
-
|
|
946
|
+
|
|
915
947
|
def __iter__(self):
|
|
916
948
|
return self.__items.__iter__()
|
|
917
|
-
|
|
949
|
+
|
|
918
950
|
def __getitem__(self, k):
|
|
919
951
|
if isinstance(k, str):
|
|
920
952
|
return self._getf(self.__items, k)
|
|
921
953
|
return self.__items.__getitem__(k)
|
|
922
|
-
|
|
954
|
+
|
|
923
955
|
def __setitem__(self, k, v):
|
|
924
956
|
if isinstance(k, str):
|
|
925
957
|
return self._setf(self.__items, k, v)
|
|
926
958
|
return self.__items.__setitem__(k, v)
|
|
927
|
-
|
|
959
|
+
|
|
928
960
|
def __delitem__(self, k):
|
|
929
961
|
if isinstance(k, str):
|
|
930
962
|
return self._delf(self.__items, k)
|
|
931
963
|
return self.__items.__delitem__(k)
|
|
932
|
-
|
|
964
|
+
|
|
933
965
|
def _find_item(self, ls, key):
|
|
934
966
|
for x in ls:
|
|
935
967
|
if isinstance(x, (tuple, list)) and x and x[0] == key:
|
|
936
968
|
if len(x) < 2:
|
|
937
969
|
raise ValueError(f"No value for {key=!r}")
|
|
938
970
|
return x
|
|
939
|
-
|
|
971
|
+
|
|
940
972
|
def _getf(self, ls, key):
|
|
941
973
|
if '/' in key:
|
|
942
974
|
a, b = key.split('/', 1)
|
|
@@ -947,7 +979,7 @@ class TreeList:
|
|
|
947
979
|
li = self._find_item(ls, key)
|
|
948
980
|
if li is not None:
|
|
949
981
|
return li[-1]
|
|
950
|
-
|
|
982
|
+
|
|
951
983
|
def _setf(self, ls, key, value):
|
|
952
984
|
if '/' in key:
|
|
953
985
|
a, b = key.split('/', 1)
|
|
@@ -955,19 +987,19 @@ class TreeList:
|
|
|
955
987
|
if la is not None:
|
|
956
988
|
return self._setf(la, b, value)
|
|
957
989
|
p, key = key.rsplit('/', 1)
|
|
958
|
-
return self._setf(ls, p, [[key, value]])
|
|
990
|
+
return self._setf(ls, p, [[key, value]]) # ls[p].append([key, value])
|
|
959
991
|
try:
|
|
960
992
|
li = self._find_item(ls, key)
|
|
961
993
|
if li is not None:
|
|
962
994
|
try:
|
|
963
|
-
li[-1] = value
|
|
995
|
+
li[-1] = value # assign value to item (ls must be a list)
|
|
964
996
|
except TypeError:
|
|
965
|
-
li[-1][:] = value
|
|
997
|
+
li[-1][:] = value # assign value to items:list
|
|
966
998
|
else:
|
|
967
|
-
ls.append([key, value])
|
|
999
|
+
ls.append([key, value]) # append to items:list
|
|
968
1000
|
except (ValueError, TypeError, AttributeError) as e:
|
|
969
1001
|
warn(f"- TreeList {e!r}: {key=!r}")
|
|
970
|
-
|
|
1002
|
+
|
|
971
1003
|
def _delf(self, ls, key):
|
|
972
1004
|
if '/' in key:
|
|
973
1005
|
p, key = key.rsplit('/', 1)
|
|
@@ -976,26 +1008,44 @@ class TreeList:
|
|
|
976
1008
|
|
|
977
1009
|
|
|
978
1010
|
def get_fullargspec(f):
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1011
|
+
"""Get the names and default values of a callable object's parameters.
|
|
1012
|
+
If the object is a built-in function, it tries to get argument
|
|
1013
|
+
information from the docstring. If it fails, it returns None.
|
|
1014
|
+
|
|
1015
|
+
Returns:
|
|
1016
|
+
args: a list of the parameter names.
|
|
1017
|
+
varargs: the name of the * parameter or None.
|
|
1018
|
+
varkwargs: the name of the ** parameter or None.
|
|
1019
|
+
defaults: a dict mapping names from args to defaults.
|
|
1020
|
+
kwonlyargs: a list of keyword-only parameter names.
|
|
1021
|
+
kwonlydefaults: a dict mapping names from kwonlyargs to defaults.
|
|
1022
|
+
|
|
1023
|
+
Note:
|
|
1024
|
+
`self` parameter is not reported for bound methods.
|
|
1025
|
+
|
|
1026
|
+
cf. inspect.getfullargspec
|
|
1027
|
+
"""
|
|
1028
|
+
argv = [] # <before /> 0:POSITIONAL_ONLY
|
|
1029
|
+
# <before *> 1:POSITIONAL_OR_KEYWORD
|
|
1030
|
+
varargs = None # <*args> 2:VAR_POSITIONAL
|
|
1031
|
+
varkwargs = None # <**kwargs> 4:VAR_KEYWORD
|
|
1032
|
+
defaults = {} #
|
|
1033
|
+
kwonlyargs = [] # <after *> 3:KEYWORD_ONLY
|
|
984
1034
|
kwonlydefaults = {}
|
|
985
1035
|
try:
|
|
986
1036
|
sig = inspect.signature(f)
|
|
987
1037
|
for k, v in sig.parameters.items():
|
|
988
|
-
if v.kind
|
|
1038
|
+
if v.kind in (v.POSITIONAL_ONLY, v.POSITIONAL_OR_KEYWORD):
|
|
989
1039
|
argv.append(k)
|
|
990
1040
|
if v.default != v.empty:
|
|
991
1041
|
defaults[k] = v.default
|
|
992
|
-
elif v.kind ==
|
|
1042
|
+
elif v.kind == v.VAR_POSITIONAL:
|
|
993
1043
|
varargs = k
|
|
994
|
-
elif v.kind ==
|
|
1044
|
+
elif v.kind == v.KEYWORD_ONLY:
|
|
995
1045
|
kwonlyargs.append(k)
|
|
996
1046
|
if v.default != v.empty:
|
|
997
1047
|
kwonlydefaults[k] = v.default
|
|
998
|
-
elif v.kind ==
|
|
1048
|
+
elif v.kind == v.VAR_KEYWORD:
|
|
999
1049
|
varkwargs = k
|
|
1000
1050
|
except ValueError:
|
|
1001
1051
|
## Builtin functions don't have an argspec that we can get.
|
|
@@ -1006,25 +1056,39 @@ def get_fullargspec(f):
|
|
|
1006
1056
|
##
|
|
1007
1057
|
## ...(details)...
|
|
1008
1058
|
## ```
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1059
|
+
doc = inspect.getdoc(f)
|
|
1060
|
+
for word in split_parts(doc or ''): # Search pattern for `func(argspec)`.
|
|
1061
|
+
if word.startswith('('):
|
|
1062
|
+
argspec = word[1:-1]
|
|
1063
|
+
break
|
|
1064
|
+
else:
|
|
1065
|
+
return None # no argument spec information
|
|
1066
|
+
if argspec:
|
|
1067
|
+
argparts = ['']
|
|
1068
|
+
for part in split_parts(argspec): # Separate argument parts with commas.
|
|
1069
|
+
if not part.strip():
|
|
1070
|
+
continue
|
|
1071
|
+
if part != ',':
|
|
1072
|
+
argparts[-1] += part
|
|
1073
|
+
else:
|
|
1074
|
+
argparts.append('')
|
|
1075
|
+
for v in argparts:
|
|
1076
|
+
m = re.match(r"(\w+):?", v) # argv + kwonlyargs
|
|
1017
1077
|
if m:
|
|
1018
1078
|
argv.append(m.group(1))
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1079
|
+
m = re.match(r"(\w+)(?::\w+)?=(.+)", v) # defaults + kwonlydefaults
|
|
1080
|
+
if m:
|
|
1081
|
+
defaults.update([m.groups()])
|
|
1082
|
+
elif v.startswith('**'): # <**kwargs>
|
|
1083
|
+
varkwargs = v[2:]
|
|
1084
|
+
elif v.startswith('*'): # <*args>
|
|
1085
|
+
varargs = v[1:]
|
|
1022
1086
|
return (argv, varargs, varkwargs,
|
|
1023
1087
|
defaults, kwonlyargs, kwonlydefaults)
|
|
1024
1088
|
|
|
1025
1089
|
|
|
1026
1090
|
def funcall(f, *args, doc=None, alias=None, **kwargs):
|
|
1027
|
-
"""Decorator of event handler
|
|
1091
|
+
"""Decorator of event handler.
|
|
1028
1092
|
|
|
1029
1093
|
Check if the event argument can be omitted and if any other
|
|
1030
1094
|
required arguments are specified in args and kwargs.
|
|
@@ -1035,10 +1099,10 @@ def funcall(f, *args, doc=None, alias=None, **kwargs):
|
|
|
1035
1099
|
>>> Act1 = lambda *v,**kw: f(*(v+args), **(kwargs|kw))
|
|
1036
1100
|
>>> Act2 = lambda *v,**kw: f(*args, **(kwargs|kw))
|
|
1037
1101
|
|
|
1038
|
-
|
|
1102
|
+
`Act1` is returned (accepts event arguments) if event arguments
|
|
1039
1103
|
cannot be omitted or if there are any remaining arguments
|
|
1040
1104
|
that must be explicitly specified.
|
|
1041
|
-
Otherwise,
|
|
1105
|
+
Otherwise, `Act2` is returned (ignores event arguments).
|
|
1042
1106
|
"""
|
|
1043
1107
|
assert callable(f)
|
|
1044
1108
|
assert isinstance(doc, (str, type(None)))
|
|
@@ -1047,18 +1111,19 @@ def funcall(f, *args, doc=None, alias=None, **kwargs):
|
|
|
1047
1111
|
@wraps(f)
|
|
1048
1112
|
def _Act(*v, **kw):
|
|
1049
1113
|
kwargs.update(kw)
|
|
1050
|
-
return f(*v, *args, **kwargs)
|
|
1114
|
+
return f(*v, *args, **kwargs) # function with event args
|
|
1051
1115
|
|
|
1052
1116
|
@wraps(f)
|
|
1053
1117
|
def _Act2(*v, **kw):
|
|
1054
1118
|
kwargs.update(kw)
|
|
1055
|
-
return f(*args, **kwargs)
|
|
1119
|
+
return f(*args, **kwargs) # function with no explicit args
|
|
1056
1120
|
|
|
1057
1121
|
action = _Act
|
|
1058
1122
|
try:
|
|
1059
1123
|
(argv, varargs, varkwargs, defaults,
|
|
1060
1124
|
kwonlyargs, kwonlydefaults) = get_fullargspec(f)
|
|
1061
1125
|
except Exception:
|
|
1126
|
+
warn(f"Failed to get the signature of {f}.")
|
|
1062
1127
|
return f
|
|
1063
1128
|
if not varargs:
|
|
1064
1129
|
N = len(argv)
|