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