mwxlib 1.0.0__py3-none-any.whl → 1.7.13__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 +133 -57
- mwx/controls.py +441 -375
- mwx/framework.py +531 -513
- mwx/graphman.py +683 -677
- mwx/images.py +16 -0
- mwx/matplot2.py +225 -216
- mwx/matplot2g.py +735 -652
- mwx/matplot2lg.py +192 -183
- mwx/mgplt.py +20 -22
- mwx/nutshell.py +1063 -939
- mwx/plugins/ffmpeg_view.py +74 -75
- mwx/plugins/fft_view.py +13 -15
- mwx/plugins/frame_listview.py +55 -52
- mwx/plugins/line_profile.py +1 -1
- mwx/py/filling.py +9 -8
- mwx/testsuite.py +38 -0
- mwx/utilus.py +273 -210
- mwx/wxmon.py +39 -38
- mwx/wxpdb.py +81 -83
- mwx/wxwil.py +18 -18
- mwx/wxwit.py +32 -35
- {mwxlib-1.0.0.dist-info → mwxlib-1.7.13.dist-info}/METADATA +12 -5
- mwxlib-1.7.13.dist-info/RECORD +28 -0
- {mwxlib-1.0.0.dist-info → mwxlib-1.7.13.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.7.13.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,7 +75,7 @@ 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
|
|
|
@@ -80,7 +85,7 @@ def _Not(p):
|
|
|
80
85
|
p = instance(p)
|
|
81
86
|
def _pred(v):
|
|
82
87
|
return not p(v)
|
|
83
|
-
_pred.__name__ =
|
|
88
|
+
_pred.__name__ = "not {}".format(p.__name__)
|
|
84
89
|
return _pred
|
|
85
90
|
|
|
86
91
|
|
|
@@ -92,7 +97,7 @@ def _And(p, q):
|
|
|
92
97
|
q = instance(q)
|
|
93
98
|
def _pred(v):
|
|
94
99
|
return p(v) and q(v)
|
|
95
|
-
_pred.__name__ =
|
|
100
|
+
_pred.__name__ = "{} and {}".format(p.__name__, q.__name__)
|
|
96
101
|
return _pred
|
|
97
102
|
|
|
98
103
|
|
|
@@ -104,7 +109,7 @@ def _Or(p, q):
|
|
|
104
109
|
q = instance(q)
|
|
105
110
|
def _pred(v):
|
|
106
111
|
return p(v) or q(v)
|
|
107
|
-
_pred.__name__ =
|
|
112
|
+
_pred.__name__ = "{} or {}".format(p.__name__, q.__name__)
|
|
108
113
|
return _pred
|
|
109
114
|
|
|
110
115
|
|
|
@@ -138,9 +143,6 @@ 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
147
|
pred = predicate(pred, locals)
|
|
146
148
|
|
|
@@ -163,7 +165,7 @@ def apropos(obj, rexpr='', ignorecase=True, alias=None, pred=None, locals=None):
|
|
|
163
165
|
except re.error as e:
|
|
164
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
|
-
if pp:
|
|
279
|
-
pp.indent = 1
|
|
280
|
-
pp.width = 80 # default 80
|
|
281
|
-
pp.depth = None
|
|
282
|
-
if sys.version_info >= (3,6):
|
|
283
|
-
pp.compact = False
|
|
284
|
-
if sys.version_info >= (3,8):
|
|
285
|
-
pp.sort_dicts = True
|
|
286
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
|
|
287
304
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
305
|
+
|
|
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,24 +817,24 @@ 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:
|
|
@@ -852,7 +847,7 @@ class FSM(dict):
|
|
|
852
847
|
warn(f"- FSM cannot append new transaction ({state!r} : {event!r}).\n"
|
|
853
848
|
f" The transaction must be a list, not a tuple.")
|
|
854
849
|
return action
|
|
855
|
-
|
|
850
|
+
|
|
856
851
|
def unbind(self, event, action=None, state=None):
|
|
857
852
|
"""Remove a transaction from the context.
|
|
858
853
|
|
|
@@ -887,6 +882,41 @@ class FSM(dict):
|
|
|
887
882
|
f" The transaction must be a list, not a tuple")
|
|
888
883
|
return False
|
|
889
884
|
|
|
885
|
+
def binds(self, event, action=None, state=None, state2=None):
|
|
886
|
+
"""Append a one-time transaction to the context.
|
|
887
|
+
|
|
888
|
+
Like `bind`, but unbinds itself after being called once.
|
|
889
|
+
"""
|
|
890
|
+
if action is None:
|
|
891
|
+
return lambda f: self.binds(event, f, state, state2)
|
|
892
|
+
|
|
893
|
+
@wraps(action)
|
|
894
|
+
def _act(*v, **kw):
|
|
895
|
+
try:
|
|
896
|
+
return action(*v, **kw)
|
|
897
|
+
finally:
|
|
898
|
+
self.unbind(event, _act, state)
|
|
899
|
+
return self.bind(event, _act, state, state2)
|
|
900
|
+
|
|
901
|
+
def define(self, event, action=None, /, *args, **kwargs):
|
|
902
|
+
"""Define event action.
|
|
903
|
+
|
|
904
|
+
Note:
|
|
905
|
+
The funcall kwargs `doc` and `alias` are reserved as kw-only-args.
|
|
906
|
+
"""
|
|
907
|
+
state = self.default_state
|
|
908
|
+
if action is None:
|
|
909
|
+
self[state].pop(event, None) # cf. undefine
|
|
910
|
+
return lambda f: self.define(event, f, *args, **kwargs)
|
|
911
|
+
|
|
912
|
+
f = funcall(action, *args, **kwargs)
|
|
913
|
+
self.update({state: {event: [state, f]}})
|
|
914
|
+
return action
|
|
915
|
+
|
|
916
|
+
def undefine(self, event):
|
|
917
|
+
"""Delete event context."""
|
|
918
|
+
self.define(event, None)
|
|
919
|
+
|
|
890
920
|
|
|
891
921
|
class TreeList:
|
|
892
922
|
"""Interface class for tree list control.
|
|
@@ -902,41 +932,41 @@ class TreeList:
|
|
|
902
932
|
"""
|
|
903
933
|
def __init__(self, ls=None):
|
|
904
934
|
self.__items = ls or []
|
|
905
|
-
|
|
935
|
+
|
|
906
936
|
def __call__(self, k):
|
|
907
937
|
return TreeList(self[k])
|
|
908
|
-
|
|
938
|
+
|
|
909
939
|
def __len__(self):
|
|
910
940
|
return len(self.__items)
|
|
911
|
-
|
|
941
|
+
|
|
912
942
|
def __contains__(self, k):
|
|
913
943
|
return self._getf(self.__items, k)
|
|
914
|
-
|
|
944
|
+
|
|
915
945
|
def __iter__(self):
|
|
916
946
|
return self.__items.__iter__()
|
|
917
|
-
|
|
947
|
+
|
|
918
948
|
def __getitem__(self, k):
|
|
919
949
|
if isinstance(k, str):
|
|
920
950
|
return self._getf(self.__items, k)
|
|
921
951
|
return self.__items.__getitem__(k)
|
|
922
|
-
|
|
952
|
+
|
|
923
953
|
def __setitem__(self, k, v):
|
|
924
954
|
if isinstance(k, str):
|
|
925
955
|
return self._setf(self.__items, k, v)
|
|
926
956
|
return self.__items.__setitem__(k, v)
|
|
927
|
-
|
|
957
|
+
|
|
928
958
|
def __delitem__(self, k):
|
|
929
959
|
if isinstance(k, str):
|
|
930
960
|
return self._delf(self.__items, k)
|
|
931
961
|
return self.__items.__delitem__(k)
|
|
932
|
-
|
|
962
|
+
|
|
933
963
|
def _find_item(self, ls, key):
|
|
934
964
|
for x in ls:
|
|
935
965
|
if isinstance(x, (tuple, list)) and x and x[0] == key:
|
|
936
966
|
if len(x) < 2:
|
|
937
967
|
raise ValueError(f"No value for {key=!r}")
|
|
938
968
|
return x
|
|
939
|
-
|
|
969
|
+
|
|
940
970
|
def _getf(self, ls, key):
|
|
941
971
|
if '/' in key:
|
|
942
972
|
a, b = key.split('/', 1)
|
|
@@ -947,7 +977,7 @@ class TreeList:
|
|
|
947
977
|
li = self._find_item(ls, key)
|
|
948
978
|
if li is not None:
|
|
949
979
|
return li[-1]
|
|
950
|
-
|
|
980
|
+
|
|
951
981
|
def _setf(self, ls, key, value):
|
|
952
982
|
if '/' in key:
|
|
953
983
|
a, b = key.split('/', 1)
|
|
@@ -955,19 +985,19 @@ class TreeList:
|
|
|
955
985
|
if la is not None:
|
|
956
986
|
return self._setf(la, b, value)
|
|
957
987
|
p, key = key.rsplit('/', 1)
|
|
958
|
-
return self._setf(ls, p, [[key, value]])
|
|
988
|
+
return self._setf(ls, p, [[key, value]]) # ls[p].append([key, value])
|
|
959
989
|
try:
|
|
960
990
|
li = self._find_item(ls, key)
|
|
961
991
|
if li is not None:
|
|
962
992
|
try:
|
|
963
|
-
li[-1] = value
|
|
993
|
+
li[-1] = value # assign value to item (ls must be a list)
|
|
964
994
|
except TypeError:
|
|
965
|
-
li[-1][:] = value
|
|
995
|
+
li[-1][:] = value # assign value to items:list
|
|
966
996
|
else:
|
|
967
|
-
ls.append([key, value])
|
|
997
|
+
ls.append([key, value]) # append to items:list
|
|
968
998
|
except (ValueError, TypeError, AttributeError) as e:
|
|
969
999
|
warn(f"- TreeList {e!r}: {key=!r}")
|
|
970
|
-
|
|
1000
|
+
|
|
971
1001
|
def _delf(self, ls, key):
|
|
972
1002
|
if '/' in key:
|
|
973
1003
|
p, key = key.rsplit('/', 1)
|
|
@@ -976,26 +1006,44 @@ class TreeList:
|
|
|
976
1006
|
|
|
977
1007
|
|
|
978
1008
|
def get_fullargspec(f):
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
1009
|
+
"""Get the names and default values of a callable object's parameters.
|
|
1010
|
+
If the object is a built-in function, it tries to get argument
|
|
1011
|
+
information from the docstring. If it fails, it returns None.
|
|
1012
|
+
|
|
1013
|
+
Returns:
|
|
1014
|
+
args: a list of the parameter names.
|
|
1015
|
+
varargs: the name of the * parameter or None.
|
|
1016
|
+
varkwargs: the name of the ** parameter or None.
|
|
1017
|
+
defaults: a dict mapping names from args to defaults.
|
|
1018
|
+
kwonlyargs: a list of keyword-only parameter names.
|
|
1019
|
+
kwonlydefaults: a dict mapping names from kwonlyargs to defaults.
|
|
1020
|
+
|
|
1021
|
+
Note:
|
|
1022
|
+
`self` parameter is not reported for bound methods.
|
|
1023
|
+
|
|
1024
|
+
cf. inspect.getfullargspec
|
|
1025
|
+
"""
|
|
1026
|
+
argv = [] # <before /> 0:POSITIONAL_ONLY
|
|
1027
|
+
# <before *> 1:POSITIONAL_OR_KEYWORD
|
|
1028
|
+
varargs = None # <*args> 2:VAR_POSITIONAL
|
|
1029
|
+
varkwargs = None # <**kwargs> 4:VAR_KEYWORD
|
|
1030
|
+
defaults = {} #
|
|
1031
|
+
kwonlyargs = [] # <after *> 3:KEYWORD_ONLY
|
|
1032
|
+
kwonlydefaults = {} #
|
|
985
1033
|
try:
|
|
986
1034
|
sig = inspect.signature(f)
|
|
987
1035
|
for k, v in sig.parameters.items():
|
|
988
|
-
if v.kind
|
|
1036
|
+
if v.kind in (v.POSITIONAL_ONLY, v.POSITIONAL_OR_KEYWORD):
|
|
989
1037
|
argv.append(k)
|
|
990
1038
|
if v.default != v.empty:
|
|
991
1039
|
defaults[k] = v.default
|
|
992
|
-
elif v.kind ==
|
|
1040
|
+
elif v.kind == v.VAR_POSITIONAL:
|
|
993
1041
|
varargs = k
|
|
994
|
-
elif v.kind ==
|
|
1042
|
+
elif v.kind == v.KEYWORD_ONLY:
|
|
995
1043
|
kwonlyargs.append(k)
|
|
996
1044
|
if v.default != v.empty:
|
|
997
1045
|
kwonlydefaults[k] = v.default
|
|
998
|
-
elif v.kind ==
|
|
1046
|
+
elif v.kind == v.VAR_KEYWORD:
|
|
999
1047
|
varkwargs = k
|
|
1000
1048
|
except ValueError:
|
|
1001
1049
|
## Builtin functions don't have an argspec that we can get.
|
|
@@ -1006,25 +1054,39 @@ def get_fullargspec(f):
|
|
|
1006
1054
|
##
|
|
1007
1055
|
## ...(details)...
|
|
1008
1056
|
## ```
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1057
|
+
doc = inspect.getdoc(f)
|
|
1058
|
+
for word in split_parts(doc or ''): # Search pattern for `func(argspec)`.
|
|
1059
|
+
if word.startswith('('):
|
|
1060
|
+
argspec = word[1:-1]
|
|
1061
|
+
break
|
|
1062
|
+
else:
|
|
1063
|
+
return None # no argument spec information
|
|
1064
|
+
if argspec:
|
|
1065
|
+
argparts = ['']
|
|
1066
|
+
for part in split_parts(argspec): # Separate argument parts with commas.
|
|
1067
|
+
if not part.strip():
|
|
1068
|
+
continue
|
|
1069
|
+
if part != ',':
|
|
1070
|
+
argparts[-1] += part
|
|
1071
|
+
else:
|
|
1072
|
+
argparts.append('')
|
|
1073
|
+
for v in argparts:
|
|
1074
|
+
m = re.match(r"(\w+):?", v) # argv + kwonlyargs
|
|
1017
1075
|
if m:
|
|
1018
1076
|
argv.append(m.group(1))
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1077
|
+
m = re.match(r"(\w+)(?::\w+)?=(.+)", v) # defaults + kwonlydefaults
|
|
1078
|
+
if m:
|
|
1079
|
+
defaults.update([m.groups()])
|
|
1080
|
+
elif v.startswith('**'): # <**kwargs>
|
|
1081
|
+
varkwargs = v[2:]
|
|
1082
|
+
elif v.startswith('*'): # <*args>
|
|
1083
|
+
varargs = v[1:]
|
|
1022
1084
|
return (argv, varargs, varkwargs,
|
|
1023
1085
|
defaults, kwonlyargs, kwonlydefaults)
|
|
1024
1086
|
|
|
1025
1087
|
|
|
1026
1088
|
def funcall(f, *args, doc=None, alias=None, **kwargs):
|
|
1027
|
-
"""Decorator of event handler
|
|
1089
|
+
"""Decorator of event handler.
|
|
1028
1090
|
|
|
1029
1091
|
Check if the event argument can be omitted and if any other
|
|
1030
1092
|
required arguments are specified in args and kwargs.
|
|
@@ -1035,10 +1097,10 @@ def funcall(f, *args, doc=None, alias=None, **kwargs):
|
|
|
1035
1097
|
>>> Act1 = lambda *v,**kw: f(*(v+args), **(kwargs|kw))
|
|
1036
1098
|
>>> Act2 = lambda *v,**kw: f(*args, **(kwargs|kw))
|
|
1037
1099
|
|
|
1038
|
-
|
|
1100
|
+
`Act1` is returned (accepts event arguments) if event arguments
|
|
1039
1101
|
cannot be omitted or if there are any remaining arguments
|
|
1040
1102
|
that must be explicitly specified.
|
|
1041
|
-
Otherwise,
|
|
1103
|
+
Otherwise, `Act2` is returned (ignores event arguments).
|
|
1042
1104
|
"""
|
|
1043
1105
|
assert callable(f)
|
|
1044
1106
|
assert isinstance(doc, (str, type(None)))
|
|
@@ -1047,18 +1109,19 @@ def funcall(f, *args, doc=None, alias=None, **kwargs):
|
|
|
1047
1109
|
@wraps(f)
|
|
1048
1110
|
def _Act(*v, **kw):
|
|
1049
1111
|
kwargs.update(kw)
|
|
1050
|
-
return f(*v, *args, **kwargs)
|
|
1112
|
+
return f(*v, *args, **kwargs) # function with event args
|
|
1051
1113
|
|
|
1052
1114
|
@wraps(f)
|
|
1053
1115
|
def _Act2(*v, **kw):
|
|
1054
1116
|
kwargs.update(kw)
|
|
1055
|
-
return f(*args, **kwargs)
|
|
1117
|
+
return f(*args, **kwargs) # function with no explicit args
|
|
1056
1118
|
|
|
1057
1119
|
action = _Act
|
|
1058
1120
|
try:
|
|
1059
1121
|
(argv, varargs, varkwargs, defaults,
|
|
1060
1122
|
kwonlyargs, kwonlydefaults) = get_fullargspec(f)
|
|
1061
1123
|
except Exception:
|
|
1124
|
+
warn(f"Failed to get the signature of {f}.")
|
|
1062
1125
|
return f
|
|
1063
1126
|
if not varargs:
|
|
1064
1127
|
N = len(argv)
|