uiml 0.2.3__tar.gz → 0.3.0.dev1__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.3/src/uiml.egg-info → uiml-0.3.0.dev1}/PKG-INFO +1 -1
- {uiml-0.2.3 → uiml-0.3.0.dev1}/pyproject.toml +1 -1
- {uiml-0.2.3 → uiml-0.3.0.dev1}/setup.py +2 -2
- {uiml-0.2.3 → uiml-0.3.0.dev1}/src/uiml/__init__.py +160 -38
- {uiml-0.2.3 → uiml-0.3.0.dev1/src/uiml.egg-info}/PKG-INFO +1 -1
- {uiml-0.2.3 → uiml-0.3.0.dev1}/src/uiml.egg-info/SOURCES.txt +2 -0
- uiml-0.3.0.dev1/tests/test_error.py +87 -0
- uiml-0.3.0.dev1/tests/test_fallback.py +60 -0
- {uiml-0.2.3 → uiml-0.3.0.dev1}/LICENSE +0 -0
- {uiml-0.2.3 → uiml-0.3.0.dev1}/README.md +0 -0
- {uiml-0.2.3 → uiml-0.3.0.dev1}/setup.cfg +0 -0
- {uiml-0.2.3 → uiml-0.3.0.dev1}/src/uiml/uilib.py +0 -0
- {uiml-0.2.3 → uiml-0.3.0.dev1}/src/uiml.egg-info/dependency_links.txt +0 -0
- {uiml-0.2.3 → uiml-0.3.0.dev1}/src/uiml.egg-info/requires.txt +0 -0
- {uiml-0.2.3 → uiml-0.3.0.dev1}/src/uiml.egg-info/top_level.txt +0 -0
- {uiml-0.2.3 → uiml-0.3.0.dev1}/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.dev1",
|
|
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,6 +14,77 @@ except ImportError as e:
|
|
|
12
14
|
|
|
13
15
|
import inspect
|
|
14
16
|
import re
|
|
17
|
+
from enum import Enum
|
|
18
|
+
|
|
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(func)
|
|
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()
|
|
15
88
|
|
|
16
89
|
skip_level=1
|
|
17
90
|
|
|
@@ -355,6 +428,23 @@ def set_style(widget, class_name: str):
|
|
|
355
428
|
used_widget_key = ['name', 'type', 'args', 'kwargs', 'init_steps', 'signals', 'style']
|
|
356
429
|
used_layout_key = ['name', 'direction', 'content', 'stretch']
|
|
357
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.")
|
|
358
448
|
def set_namespace(value_replace_func=None, layout_parser_func=None, widget_parser_func=None,
|
|
359
449
|
additional_used_widget_key=None, additional_used_layout_key=None,
|
|
360
450
|
namespace_parser_func=None, reverse=None, enable_value_convert=None,
|
|
@@ -363,6 +453,8 @@ def set_namespace(value_replace_func=None, layout_parser_func=None, widget_parse
|
|
|
363
453
|
'''
|
|
364
454
|
设置解析函数/变量时使用的操作。
|
|
365
455
|
'''
|
|
456
|
+
assert namespace_api_enabled, "Please add 'uiml.enable_namespace_api()' at the beginning of the code to enable the Namespace API"
|
|
457
|
+
|
|
366
458
|
global replacer, layout_parser, widget_parser, used_widget_key, used_layout_key, namespace_parser, default_reverse, value_convert, debug
|
|
367
459
|
if value_replace_func is not None:replacer = value_replace_func
|
|
368
460
|
if layout_parser_func is not None:layout_parser = layout_parser_func
|
|
@@ -373,11 +465,18 @@ def set_namespace(value_replace_func=None, layout_parser_func=None, widget_parse
|
|
|
373
465
|
if reverse is not None:default_reverse = reverse
|
|
374
466
|
if enable_value_convert is not None:value_convert = enable_value_convert
|
|
375
467
|
if is_debug is not None:debug = is_debug
|
|
376
|
-
|
|
468
|
+
|
|
469
|
+
@replacer_chain.add
|
|
377
470
|
def default_replacer(value: str):
|
|
378
471
|
'''默认替换函数,返回原值,可以自定义添加函数,如替换变量、函数调用等。'''
|
|
379
|
-
|
|
380
|
-
|
|
472
|
+
unicode = re.sub(
|
|
473
|
+
_UNICODE,
|
|
474
|
+
lambda m: chr(int(m.group(1), 16)),
|
|
475
|
+
value
|
|
476
|
+
)
|
|
477
|
+
return unicode
|
|
478
|
+
|
|
479
|
+
@layout_parser_chain.add
|
|
381
480
|
def default_layout_parser(ui_data: Dict[str, Any], namespace: Dict[str, Any]=None, additional=None):
|
|
382
481
|
'''默认布局解析函数,进行递归解析返回'''
|
|
383
482
|
additional_value = {} if additional is None else additional # 默认添加额外的空字典
|
|
@@ -395,7 +494,10 @@ def default_layout_parser(ui_data: Dict[str, Any], namespace: Dict[str, Any]=Non
|
|
|
395
494
|
|
|
396
495
|
def reg_replacer(value):
|
|
397
496
|
if isinstance(value, str):
|
|
398
|
-
|
|
497
|
+
if namespace_api_enabled:
|
|
498
|
+
return replacer(value) # 使用 Namespace API
|
|
499
|
+
else:
|
|
500
|
+
return replacer_chain.run(value) # 使用 Parser API
|
|
399
501
|
elif isinstance(value, dict):
|
|
400
502
|
return default_reg_replacer_dict(value)
|
|
401
503
|
elif isinstance(value, list) or isinstance(value, tuple) or isinstance(value, set):
|
|
@@ -421,6 +523,7 @@ def _pop_dict(dict_data, key):
|
|
|
421
523
|
dict_copy.pop(k, None)
|
|
422
524
|
return dict_copy
|
|
423
525
|
|
|
526
|
+
@widget_parser_chain.add
|
|
424
527
|
def default_widget_parser(widget_data: Dict[str, Any], namespace: Dict[str, Any]=None, additional=None):
|
|
425
528
|
'''默认控件解析函数,进行解析返回'''
|
|
426
529
|
additional_value = {} if additional is None else additional # 默认添加额外的空字典
|
|
@@ -464,12 +567,19 @@ def default_widget_parser(widget_data: Dict[str, Any], namespace: Dict[str, Any]
|
|
|
464
567
|
|
|
465
568
|
def compile_ui(ui_data, namespace=None, additional=None):
|
|
466
569
|
additional_value = {} if additional is None else additional # 默认添加额外的空字典
|
|
467
|
-
if namespace is None:
|
|
570
|
+
if namespace is None:
|
|
571
|
+
namespace = namespace_parser() # 获取命名空间
|
|
468
572
|
|
|
469
573
|
if ui_data.get('direction'): # 这是layout类型
|
|
470
|
-
|
|
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)
|
|
471
578
|
else: # 这是widget类型
|
|
472
|
-
|
|
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)
|
|
473
583
|
|
|
474
584
|
def compile_ui_file(file_path: str, namespace=None, additional=None):
|
|
475
585
|
'''
|
|
@@ -481,7 +591,12 @@ def compile_ui_file(file_path: str, namespace=None, additional=None):
|
|
|
481
591
|
'''
|
|
482
592
|
additional_value = {} if additional is None else additional # 默认添加额外的空字典
|
|
483
593
|
return compile_ui(xml_parse_file(file_path, namespace, additional=additional_value), namespace, additional=additional_value) # 解析 XML 字符串并编译
|
|
484
|
-
|
|
594
|
+
|
|
595
|
+
default_reverse = False
|
|
596
|
+
debug = False
|
|
597
|
+
value_convert = True
|
|
598
|
+
namespace_parser = load_namespace
|
|
599
|
+
|
|
485
600
|
class UIMLLayout:
|
|
486
601
|
def __init__(self, list):
|
|
487
602
|
self.list = list
|
|
@@ -522,20 +637,7 @@ class UIMLLayout:
|
|
|
522
637
|
|
|
523
638
|
# 最后一段
|
|
524
639
|
if i == len(parts) - 1:
|
|
525
|
-
|
|
526
|
-
# 普通布局:返回 content 列表
|
|
527
|
-
content = current.get('content')
|
|
528
|
-
if content is None:
|
|
529
|
-
raise ValueError(f"Layout '{part}' has no content")
|
|
530
|
-
if not isinstance(content, list):
|
|
531
|
-
raise TypeError(f"Layout '{part}' content is not a list")
|
|
532
|
-
return content
|
|
533
|
-
else:
|
|
534
|
-
# 控件:返回 content 属性
|
|
535
|
-
content = current.get('content')
|
|
536
|
-
if content is None:
|
|
537
|
-
raise ValueError(f"Widget '{part}' has no content")
|
|
538
|
-
return content
|
|
640
|
+
return self.loader(current, part)
|
|
539
641
|
|
|
540
642
|
# 不是最后一段,当前节点必须是布局
|
|
541
643
|
if 'direction' not in current:
|
|
@@ -545,10 +647,7 @@ class UIMLLayout:
|
|
|
545
647
|
found = None
|
|
546
648
|
|
|
547
649
|
# 普通布局从 content 中查找
|
|
548
|
-
|
|
549
|
-
if child.get('name') == next_name:
|
|
550
|
-
found = child
|
|
551
|
-
break
|
|
650
|
+
found = self.subelement_finder(current, next_name)
|
|
552
651
|
|
|
553
652
|
if found is None:
|
|
554
653
|
raise KeyError(f"Child '{next_name}' not found in layout '{part}'")
|
|
@@ -557,6 +656,35 @@ class UIMLLayout:
|
|
|
557
656
|
# 正常流程不会执行到这里
|
|
558
657
|
return None
|
|
559
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
|
+
|
|
560
688
|
def show(self):
|
|
561
689
|
'''显示布局,返回布局对象和类型字符串'''
|
|
562
690
|
return self.draw_layout()[0]
|
|
@@ -616,7 +744,7 @@ class UIMLLayout:
|
|
|
616
744
|
'''
|
|
617
745
|
return (widget[0], ), {}
|
|
618
746
|
|
|
619
|
-
def return_layout(layout):
|
|
747
|
+
def return_layout(self, layout):
|
|
620
748
|
'''
|
|
621
749
|
返回布局对象和类型字符串,可以在不复制draw_layout()函数的情况下,直接在原布局上添加加载时代码
|
|
622
750
|
参数:
|
|
@@ -628,20 +756,14 @@ class UIMLLayout:
|
|
|
628
756
|
|
|
629
757
|
def extend_layout(self, list_info):
|
|
630
758
|
'''扩展布局,可以在不复制draw_layout()函数的情况下,直接在原布局上添加加载时代码'''
|
|
631
|
-
|
|
759
|
+
UIMLException.direction_error(list_info['direction'].lower()) # 默认这里抛出错误,因为widget类型没有direction属性
|
|
632
760
|
|
|
633
761
|
def extend_widget(self, widget_info):
|
|
634
762
|
'''扩展控件,可以在不复制draw_layout()函数的情况下,直接在原控件上添加加载时代码'''
|
|
635
763
|
return widget_info['content'], 'widget'
|
|
636
764
|
|
|
637
|
-
class
|
|
765
|
+
class UIMLException(Exception):
|
|
638
766
|
@staticmethod
|
|
639
|
-
def direction_error():
|
|
767
|
+
def direction_error(direction):
|
|
640
768
|
'''错误方案:在widget类型中找不到direction属性时,抛出错误。可以自定义添加函数,如替换变量、函数调用等。'''
|
|
641
|
-
raise
|
|
642
|
-
|
|
643
|
-
set_namespace(
|
|
644
|
-
default_replacer, default_layout_parser, default_widget_parser,
|
|
645
|
-
namespace_parser_func=load_namespace, reverse=False,
|
|
646
|
-
enable_value_convert=False, is_debug=False
|
|
647
|
-
) # 设置默认解析函数
|
|
769
|
+
raise UIMLException(f'Cannot load direction "{direction}" in class uiml::layout')
|
|
@@ -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
|