uiml 0.2.4__tar.gz → 0.3.0.dev2__tar.gz
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.
- {uiml-0.2.4/src/uiml.egg-info → uiml-0.3.0.dev2}/PKG-INFO +1 -1
- {uiml-0.2.4 → uiml-0.3.0.dev2}/pyproject.toml +1 -1
- {uiml-0.2.4 → uiml-0.3.0.dev2}/setup.py +2 -2
- {uiml-0.2.4 → uiml-0.3.0.dev2}/src/uiml/__init__.py +147 -32
- {uiml-0.2.4 → uiml-0.3.0.dev2/src/uiml.egg-info}/PKG-INFO +1 -1
- {uiml-0.2.4 → uiml-0.3.0.dev2}/src/uiml.egg-info/SOURCES.txt +2 -0
- uiml-0.3.0.dev2/tests/test_error.py +87 -0
- uiml-0.3.0.dev2/tests/test_fallback.py +60 -0
- {uiml-0.2.4 → uiml-0.3.0.dev2}/LICENSE +0 -0
- {uiml-0.2.4 → uiml-0.3.0.dev2}/README.md +0 -0
- {uiml-0.2.4 → uiml-0.3.0.dev2}/setup.cfg +0 -0
- {uiml-0.2.4 → uiml-0.3.0.dev2}/src/uiml/uilib.py +0 -0
- {uiml-0.2.4 → uiml-0.3.0.dev2}/src/uiml.egg-info/dependency_links.txt +0 -0
- {uiml-0.2.4 → uiml-0.3.0.dev2}/src/uiml.egg-info/requires.txt +0 -0
- {uiml-0.2.4 → uiml-0.3.0.dev2}/src/uiml.egg-info/top_level.txt +0 -0
- {uiml-0.2.4 → uiml-0.3.0.dev2}/tests/test_symbol.py +0 -0
|
@@ -2,10 +2,10 @@ from setuptools import setup, find_packages
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name = "uiml",
|
|
5
|
-
version = "0.
|
|
5
|
+
version = "0.3.0.dev2",
|
|
6
6
|
package_dir={"": "src"},
|
|
7
7
|
packages=find_packages(where="src"),
|
|
8
|
-
install_requires = ["PySide6>=6.10.0"],
|
|
8
|
+
install_requires = ["PySide6>=6.10.0", "deprecated>=1.3"],
|
|
9
9
|
python_requires = ">=3.10",
|
|
10
10
|
author = "xystudio",
|
|
11
11
|
author_email = "173288240@qq.com",
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import Any, Dict, Optional, Tuple
|
|
3
|
+
from typing import Any, Dict, Optional, Tuple, Callable
|
|
4
|
+
from deprecated import deprecated
|
|
5
|
+
from warnings import warn
|
|
4
6
|
|
|
5
7
|
try:
|
|
6
8
|
from uiml.uilib import *
|
|
@@ -12,8 +14,77 @@ except ImportError as e:
|
|
|
12
14
|
|
|
13
15
|
import inspect
|
|
14
16
|
import re
|
|
17
|
+
from enum import Enum
|
|
15
18
|
|
|
16
19
|
_UNICODE = re.compile(r'&#[xX]([0-9a-fA-F]+);',)
|
|
20
|
+
func = list[Callable]
|
|
21
|
+
|
|
22
|
+
namespace_api_enabled = False
|
|
23
|
+
|
|
24
|
+
class ParserEnum(Enum):
|
|
25
|
+
replacer = 1 # 参数替换器
|
|
26
|
+
widget_parser = 2 # 组件解析器
|
|
27
|
+
layout_parser = 3 # 布局解析器
|
|
28
|
+
|
|
29
|
+
removed_widget_key = 5 # 自动删除的组件项目列表
|
|
30
|
+
removed_layout_key = 6 # 自动删除的布局项目列表
|
|
31
|
+
|
|
32
|
+
uiml_class = 7 # 对layout渲染的扩展
|
|
33
|
+
|
|
34
|
+
class ParamEnum(Enum):
|
|
35
|
+
reverse_namespace = 1 # 翻转命名空间顺序
|
|
36
|
+
value_convert = 2 # 允许变量转化
|
|
37
|
+
debug_mode = 3 # 调试模式
|
|
38
|
+
namespace_parser = 4 # 命名空间的解析器
|
|
39
|
+
|
|
40
|
+
class ParserChain:
|
|
41
|
+
def __init__(self, funcs: Optional[func] = None):
|
|
42
|
+
self._funcs = [] if funcs is None else funcs
|
|
43
|
+
|
|
44
|
+
if funcs is not None:
|
|
45
|
+
self.funcs = funcs # 初始校验
|
|
46
|
+
|
|
47
|
+
def add(self, func:func):
|
|
48
|
+
'''添加一个函数 / 装饰器'''
|
|
49
|
+
self.funcs.append(func)
|
|
50
|
+
self.check(self.funcs)
|
|
51
|
+
return func
|
|
52
|
+
|
|
53
|
+
def remove(self, func:func):
|
|
54
|
+
'''删除一个函数'''
|
|
55
|
+
self.funcs.remove(func)
|
|
56
|
+
|
|
57
|
+
def set(self, funcs: func):
|
|
58
|
+
self.funcs = funcs
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def funcs(self):
|
|
62
|
+
return self._funcs
|
|
63
|
+
|
|
64
|
+
@funcs.setter
|
|
65
|
+
def funcs(self, v):
|
|
66
|
+
self.check(v)
|
|
67
|
+
|
|
68
|
+
def check(self, v):
|
|
69
|
+
if isinstance(v, list):
|
|
70
|
+
for func in v:
|
|
71
|
+
if not isinstance(func, Callable):
|
|
72
|
+
raise TypeError('Function list item must be function')
|
|
73
|
+
self._funcs = v
|
|
74
|
+
else:
|
|
75
|
+
raise TypeError('Function list must be list')
|
|
76
|
+
|
|
77
|
+
def run(self, *args, **kwargs):
|
|
78
|
+
'''按照列表的反向顺序执行'''
|
|
79
|
+
for func in reversed(self.funcs):
|
|
80
|
+
result = func(*args, **kwargs)
|
|
81
|
+
|
|
82
|
+
if result is not None:
|
|
83
|
+
return result
|
|
84
|
+
|
|
85
|
+
replacer_chain = ParserChain()
|
|
86
|
+
widget_parser_chain = ParserChain()
|
|
87
|
+
layout_parser_chain = ParserChain()
|
|
17
88
|
|
|
18
89
|
skip_level=1
|
|
19
90
|
|
|
@@ -357,6 +428,23 @@ def set_style(widget, class_name: str):
|
|
|
357
428
|
used_widget_key = ['name', 'type', 'args', 'kwargs', 'init_steps', 'signals', 'style']
|
|
358
429
|
used_layout_key = ['name', 'direction', 'content', 'stretch']
|
|
359
430
|
|
|
431
|
+
def enable_namespace_api():
|
|
432
|
+
global namespace_api_enabled
|
|
433
|
+
|
|
434
|
+
namespace_api_enabled = True
|
|
435
|
+
|
|
436
|
+
set_namespace(
|
|
437
|
+
default_replacer, default_layout_parser, default_widget_parser,
|
|
438
|
+
namespace_parser_func=load_namespace, reverse=False,
|
|
439
|
+
enable_value_convert=False, is_debug=False
|
|
440
|
+
) # 设置默认解析函数
|
|
441
|
+
|
|
442
|
+
warn(
|
|
443
|
+
'Namespace API is deprecated, please use Parser API (add_parser) instead. Now Parser API is disabled.',
|
|
444
|
+
category=DeprecationWarning
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
@deprecated(reason="Namespace API is deprecated, please use Parser API (add_parser) instead.")
|
|
360
448
|
def set_namespace(value_replace_func=None, layout_parser_func=None, widget_parser_func=None,
|
|
361
449
|
additional_used_widget_key=None, additional_used_layout_key=None,
|
|
362
450
|
namespace_parser_func=None, reverse=None, enable_value_convert=None,
|
|
@@ -365,6 +453,8 @@ def set_namespace(value_replace_func=None, layout_parser_func=None, widget_parse
|
|
|
365
453
|
'''
|
|
366
454
|
设置解析函数/变量时使用的操作。
|
|
367
455
|
'''
|
|
456
|
+
assert namespace_api_enabled, "Please add 'uiml.enable_namespace_api()' at the beginning of the code to enable the Namespace API"
|
|
457
|
+
|
|
368
458
|
global replacer, layout_parser, widget_parser, used_widget_key, used_layout_key, namespace_parser, default_reverse, value_convert, debug
|
|
369
459
|
if value_replace_func is not None:replacer = value_replace_func
|
|
370
460
|
if layout_parser_func is not None:layout_parser = layout_parser_func
|
|
@@ -375,7 +465,8 @@ def set_namespace(value_replace_func=None, layout_parser_func=None, widget_parse
|
|
|
375
465
|
if reverse is not None:default_reverse = reverse
|
|
376
466
|
if enable_value_convert is not None:value_convert = enable_value_convert
|
|
377
467
|
if is_debug is not None:debug = is_debug
|
|
378
|
-
|
|
468
|
+
|
|
469
|
+
@replacer_chain.add
|
|
379
470
|
def default_replacer(value: str):
|
|
380
471
|
'''默认替换函数,返回原值,可以自定义添加函数,如替换变量、函数调用等。'''
|
|
381
472
|
unicode = re.sub(
|
|
@@ -385,6 +476,7 @@ def default_replacer(value: str):
|
|
|
385
476
|
)
|
|
386
477
|
return unicode
|
|
387
478
|
|
|
479
|
+
@layout_parser_chain.add
|
|
388
480
|
def default_layout_parser(ui_data: Dict[str, Any], namespace: Dict[str, Any]=None, additional=None):
|
|
389
481
|
'''默认布局解析函数,进行递归解析返回'''
|
|
390
482
|
additional_value = {} if additional is None else additional # 默认添加额外的空字典
|
|
@@ -402,7 +494,10 @@ def default_layout_parser(ui_data: Dict[str, Any], namespace: Dict[str, Any]=Non
|
|
|
402
494
|
|
|
403
495
|
def reg_replacer(value):
|
|
404
496
|
if isinstance(value, str):
|
|
405
|
-
|
|
497
|
+
if namespace_api_enabled:
|
|
498
|
+
return replacer(value) # 使用 Namespace API
|
|
499
|
+
else:
|
|
500
|
+
return replacer_chain.run(value) # 使用 Parser API
|
|
406
501
|
elif isinstance(value, dict):
|
|
407
502
|
return default_reg_replacer_dict(value)
|
|
408
503
|
elif isinstance(value, list) or isinstance(value, tuple) or isinstance(value, set):
|
|
@@ -428,6 +523,7 @@ def _pop_dict(dict_data, key):
|
|
|
428
523
|
dict_copy.pop(k, None)
|
|
429
524
|
return dict_copy
|
|
430
525
|
|
|
526
|
+
@widget_parser_chain.add
|
|
431
527
|
def default_widget_parser(widget_data: Dict[str, Any], namespace: Dict[str, Any]=None, additional=None):
|
|
432
528
|
'''默认控件解析函数,进行解析返回'''
|
|
433
529
|
additional_value = {} if additional is None else additional # 默认添加额外的空字典
|
|
@@ -471,12 +567,19 @@ def default_widget_parser(widget_data: Dict[str, Any], namespace: Dict[str, Any]
|
|
|
471
567
|
|
|
472
568
|
def compile_ui(ui_data, namespace=None, additional=None):
|
|
473
569
|
additional_value = {} if additional is None else additional # 默认添加额外的空字典
|
|
474
|
-
if namespace is None:
|
|
570
|
+
if namespace is None:
|
|
571
|
+
namespace = namespace_parser() # 获取命名空间
|
|
475
572
|
|
|
476
573
|
if ui_data.get('direction'): # 这是layout类型
|
|
477
|
-
|
|
574
|
+
if namespace_api_enabled: # namespace API 语法
|
|
575
|
+
return layout_parser(ui_data, namespace, additional=additional_value)
|
|
576
|
+
else:
|
|
577
|
+
return layout_parser_chain.run(ui_data, namespace, additional=additional)
|
|
478
578
|
else: # 这是widget类型
|
|
479
|
-
|
|
579
|
+
if namespace_api_enabled: # namespace API 语法
|
|
580
|
+
return widget_parser(ui_data, namespace, additional=additional_value)
|
|
581
|
+
else:
|
|
582
|
+
return widget_parser_chain.run(ui_data, namespace, additional=additional)
|
|
480
583
|
|
|
481
584
|
def compile_ui_file(file_path: str, namespace=None, additional=None):
|
|
482
585
|
'''
|
|
@@ -488,7 +591,12 @@ def compile_ui_file(file_path: str, namespace=None, additional=None):
|
|
|
488
591
|
'''
|
|
489
592
|
additional_value = {} if additional is None else additional # 默认添加额外的空字典
|
|
490
593
|
return compile_ui(xml_parse_file(file_path, namespace, additional=additional_value), namespace, additional=additional_value) # 解析 XML 字符串并编译
|
|
491
|
-
|
|
594
|
+
|
|
595
|
+
default_reverse = False
|
|
596
|
+
debug = False
|
|
597
|
+
value_convert = True
|
|
598
|
+
namespace_parser = load_namespace
|
|
599
|
+
|
|
492
600
|
class UIMLLayout:
|
|
493
601
|
def __init__(self, list):
|
|
494
602
|
self.list = list
|
|
@@ -529,20 +637,7 @@ class UIMLLayout:
|
|
|
529
637
|
|
|
530
638
|
# 最后一段
|
|
531
639
|
if i == len(parts) - 1:
|
|
532
|
-
|
|
533
|
-
# 普通布局:返回 content 列表
|
|
534
|
-
content = current.get('content')
|
|
535
|
-
if content is None:
|
|
536
|
-
raise ValueError(f"Layout '{part}' has no content")
|
|
537
|
-
if not isinstance(content, list):
|
|
538
|
-
raise TypeError(f"Layout '{part}' content is not a list")
|
|
539
|
-
return content
|
|
540
|
-
else:
|
|
541
|
-
# 控件:返回 content 属性
|
|
542
|
-
content = current.get('content')
|
|
543
|
-
if content is None:
|
|
544
|
-
raise ValueError(f"Widget '{part}' has no content")
|
|
545
|
-
return content
|
|
640
|
+
return self.loader(current, part)
|
|
546
641
|
|
|
547
642
|
# 不是最后一段,当前节点必须是布局
|
|
548
643
|
if 'direction' not in current:
|
|
@@ -552,10 +647,7 @@ class UIMLLayout:
|
|
|
552
647
|
found = None
|
|
553
648
|
|
|
554
649
|
# 普通布局从 content 中查找
|
|
555
|
-
|
|
556
|
-
if child.get('name') == next_name:
|
|
557
|
-
found = child
|
|
558
|
-
break
|
|
650
|
+
found = self.subelement_finder(current, next_name)
|
|
559
651
|
|
|
560
652
|
if found is None:
|
|
561
653
|
raise KeyError(f"Child '{next_name}' not found in layout '{part}'")
|
|
@@ -564,6 +656,35 @@ class UIMLLayout:
|
|
|
564
656
|
# 正常流程不会执行到这里
|
|
565
657
|
return None
|
|
566
658
|
|
|
659
|
+
def loader(self, current, part):
|
|
660
|
+
if 'direction' in current: # layout 类型
|
|
661
|
+
return self.layout_loader(current, part)
|
|
662
|
+
else:
|
|
663
|
+
return self.widget_loader(current, part)
|
|
664
|
+
|
|
665
|
+
def layout_loader(self, current, part):
|
|
666
|
+
# 普通布局:返回 content 列表
|
|
667
|
+
content = current.get('content')
|
|
668
|
+
if content is None:
|
|
669
|
+
raise ValueError(f"Layout '{part}' has no content")
|
|
670
|
+
if not isinstance(content, list):
|
|
671
|
+
raise TypeError(f"Layout '{part}' content is not a list")
|
|
672
|
+
return content
|
|
673
|
+
|
|
674
|
+
def widget_loader(self, current, part):
|
|
675
|
+
# 控件:返回 content 属性
|
|
676
|
+
content = current.get('content')
|
|
677
|
+
if content is None:
|
|
678
|
+
raise ValueError(f"Widget '{part}' has no content")
|
|
679
|
+
return content
|
|
680
|
+
|
|
681
|
+
def subelement_finder(self, current, next):
|
|
682
|
+
# 普通布局从 content 中查找
|
|
683
|
+
for child in current.get('content', []):
|
|
684
|
+
if child.get('name') == next:
|
|
685
|
+
found = child
|
|
686
|
+
return found
|
|
687
|
+
|
|
567
688
|
def show(self):
|
|
568
689
|
'''显示布局,返回布局对象和类型字符串'''
|
|
569
690
|
return self.draw_layout()[0]
|
|
@@ -623,7 +744,7 @@ class UIMLLayout:
|
|
|
623
744
|
'''
|
|
624
745
|
return (widget[0], ), {}
|
|
625
746
|
|
|
626
|
-
def return_layout(layout):
|
|
747
|
+
def return_layout(self, layout):
|
|
627
748
|
'''
|
|
628
749
|
返回布局对象和类型字符串,可以在不复制draw_layout()函数的情况下,直接在原布局上添加加载时代码
|
|
629
750
|
参数:
|
|
@@ -646,9 +767,3 @@ class UIMLException(Exception):
|
|
|
646
767
|
def direction_error(direction):
|
|
647
768
|
'''错误方案:在widget类型中找不到direction属性时,抛出错误。可以自定义添加函数,如替换变量、函数调用等。'''
|
|
648
769
|
raise UIMLException(f'Cannot load direction "{direction}" in class uiml::layout')
|
|
649
|
-
|
|
650
|
-
set_namespace(
|
|
651
|
-
default_replacer, default_layout_parser, default_widget_parser,
|
|
652
|
-
namespace_parser_func=load_namespace, reverse=False,
|
|
653
|
-
enable_value_convert=False, is_debug=False
|
|
654
|
-
) # 设置默认解析函数
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from typing import Optional, Callable
|
|
2
|
+
|
|
3
|
+
func = list[Callable]
|
|
4
|
+
|
|
5
|
+
class ParserChain:
|
|
6
|
+
def __init__(self, funcs: Optional[func] = None):
|
|
7
|
+
self._funcs = [] if funcs is None else funcs
|
|
8
|
+
|
|
9
|
+
if funcs is not None:
|
|
10
|
+
self.funcs = funcs # 初始校验
|
|
11
|
+
|
|
12
|
+
def add(self, func:func):
|
|
13
|
+
'''添加一个函数 / 装饰器'''
|
|
14
|
+
self.funcs.append(func)
|
|
15
|
+
self.check(func)
|
|
16
|
+
return func
|
|
17
|
+
|
|
18
|
+
def remove(self, func:func):
|
|
19
|
+
'''删除一个函数'''
|
|
20
|
+
self.funcs.remove(func)
|
|
21
|
+
|
|
22
|
+
def set(self, funcs: func):
|
|
23
|
+
self.funcs = funcs
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def funcs(self):
|
|
27
|
+
return self._funcs
|
|
28
|
+
|
|
29
|
+
@funcs.setter
|
|
30
|
+
def funcs(self, v):
|
|
31
|
+
self.check(v)
|
|
32
|
+
|
|
33
|
+
def check(self, v):
|
|
34
|
+
if isinstance(v, list):
|
|
35
|
+
for func in v:
|
|
36
|
+
if not isinstance(func, Callable):
|
|
37
|
+
raise TypeError('Function list item must be function')
|
|
38
|
+
self._funcs = v
|
|
39
|
+
else:
|
|
40
|
+
raise TypeError('Function list must be list')
|
|
41
|
+
|
|
42
|
+
def run(self, *args, **kwargs):
|
|
43
|
+
'''按照列表的反向顺序执行'''
|
|
44
|
+
for func in reversed(self.funcs):
|
|
45
|
+
result = func(*args, **kwargs)
|
|
46
|
+
|
|
47
|
+
if result is not None:
|
|
48
|
+
return result
|
|
49
|
+
|
|
50
|
+
def test_main():
|
|
51
|
+
try:
|
|
52
|
+
ParserChain(123) # 初始化:不是列表
|
|
53
|
+
|
|
54
|
+
assert False
|
|
55
|
+
except TypeError:
|
|
56
|
+
print('Success!')
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
ParserChain([123]) # 初始化:列表项不是函数
|
|
60
|
+
|
|
61
|
+
assert False
|
|
62
|
+
except TypeError:
|
|
63
|
+
print('Success!')
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
v = ParserChain([]) # 初始化:列表项不是函数
|
|
67
|
+
v.set(123) # 设置非法值
|
|
68
|
+
|
|
69
|
+
assert False
|
|
70
|
+
except TypeError:
|
|
71
|
+
print('Success!')
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
v = ParserChain([]) # 初始化:列表项不是函数
|
|
75
|
+
v.add(123) # 添加非法值
|
|
76
|
+
|
|
77
|
+
assert False
|
|
78
|
+
except TypeError:
|
|
79
|
+
print('Success!')
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
v = ParserChain([]) # 初始化:列表项不是函数
|
|
83
|
+
v.funcs = 123 # 连接非法值
|
|
84
|
+
|
|
85
|
+
assert False
|
|
86
|
+
except TypeError:
|
|
87
|
+
print('Success!')
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from typing import Callable, Optional
|
|
2
|
+
|
|
3
|
+
func = list[Callable]
|
|
4
|
+
|
|
5
|
+
class ParserChain:
|
|
6
|
+
def __init__(self, funcs: Optional[func] = None):
|
|
7
|
+
self.funcs: func = [] if funcs is None else funcs
|
|
8
|
+
|
|
9
|
+
def add(self, func:func):
|
|
10
|
+
'''添加一个函数 / 装饰器'''
|
|
11
|
+
self.funcs.append(func)
|
|
12
|
+
return func
|
|
13
|
+
|
|
14
|
+
def remove(self, func:func):
|
|
15
|
+
'''删除一个函数'''
|
|
16
|
+
self.funcs.remove(func)
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def get_funcs(self):
|
|
20
|
+
return self.funcs
|
|
21
|
+
|
|
22
|
+
def run(self, *args, **kwargs):
|
|
23
|
+
'''按照列表的反向顺序执行'''
|
|
24
|
+
for func in reversed(self.funcs):
|
|
25
|
+
result = func(*args, **kwargs)
|
|
26
|
+
|
|
27
|
+
if result is not None:
|
|
28
|
+
return result
|
|
29
|
+
|
|
30
|
+
def test_main():
|
|
31
|
+
# ---------------- 示例 ----------------
|
|
32
|
+
chain = ParserChain()
|
|
33
|
+
|
|
34
|
+
@chain.add
|
|
35
|
+
def low(v): # 最先添加 → 最后执行
|
|
36
|
+
return f"low({v})"
|
|
37
|
+
|
|
38
|
+
@chain.add
|
|
39
|
+
def mid(v): # 第二个添加
|
|
40
|
+
return None if v < 10 else f"mid({v})"
|
|
41
|
+
|
|
42
|
+
@chain.add
|
|
43
|
+
def high(v): # 最后添加 → 最先执行
|
|
44
|
+
return None if v < 100 else f"high({v})"
|
|
45
|
+
|
|
46
|
+
@chain.add
|
|
47
|
+
def no_add(v):
|
|
48
|
+
raise ValueError('This is Added and runed!')
|
|
49
|
+
|
|
50
|
+
chain.remove(no_add)
|
|
51
|
+
|
|
52
|
+
assert chain.get_funcs == [low, mid, high]
|
|
53
|
+
assert chain.run(5) == "low(5)" # high→None, mid→None, low→"low(5)"
|
|
54
|
+
assert chain.run(50) == "mid(50)" # high→None, mid→"mid(50)"
|
|
55
|
+
assert chain.run(500) == "high(500)" # "high(500)"
|
|
56
|
+
|
|
57
|
+
# 用列表初始化也支持
|
|
58
|
+
c2 = ParserChain([lambda v: None, lambda v: f"got {v}"])
|
|
59
|
+
assert c2.run(1) == "got 1" # "got 1"
|
|
60
|
+
assert ParserChain().run(1) is None # None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|