tretool 0.2.1__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.
- tretool/__init__.py +27 -0
- tretool/config.py +262 -0
- tretool/encoding.py +92 -0
- tretool/jsonlib.py +299 -0
- tretool/markfunc.py +152 -0
- tretool/mathlib.py +620 -0
- tretool/memorizeTools.py +24 -0
- tretool/path.py +1139 -0
- tretool/platformlib.py +332 -0
- tretool/plugin/plu.py +0 -0
- tretool/plugin.py +348 -0
- tretool/timelib.py +518 -0
- tretool/transform/__init__.py +1 -0
- tretool/transform/pdf.py +396 -0
- tretool/writeLog.py +69 -0
- tretool-0.2.1.dist-info/METADATA +28 -0
- tretool-0.2.1.dist-info/RECORD +20 -0
- tretool-0.2.1.dist-info/WHEEL +5 -0
- tretool-0.2.1.dist-info/licenses/LICENSE +19 -0
- tretool-0.2.1.dist-info/top_level.txt +1 -0
tretool/__init__.py
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
"""
|
2
|
+
# tretool
|
3
|
+
|
4
|
+
## tretool - Python多功能工具库
|
5
|
+
|
6
|
+
[](https://www.python.org/)
|
7
|
+
|
8
|
+
**tretool** 是一个集成常用功能的Python工具库。
|
9
|
+
"""
|
10
|
+
|
11
|
+
from . import config
|
12
|
+
|
13
|
+
from . import encoding
|
14
|
+
|
15
|
+
from . import jsonlib
|
16
|
+
|
17
|
+
from . import markfunc
|
18
|
+
from . import memorizeTools
|
19
|
+
|
20
|
+
from . import path
|
21
|
+
from . import platformlib
|
22
|
+
from . import plugin
|
23
|
+
|
24
|
+
from . import timelib
|
25
|
+
from . import transform
|
26
|
+
|
27
|
+
from . import writeLog
|
tretool/config.py
ADDED
@@ -0,0 +1,262 @@
|
|
1
|
+
"""
|
2
|
+
### 配置管理库,提供安全的键值对存储和访问机制
|
3
|
+
|
4
|
+
特性:
|
5
|
+
- 类型安全的设置和获取
|
6
|
+
- 默认值支持
|
7
|
+
- 批量操作支持
|
8
|
+
- 配置项存在性检查
|
9
|
+
- 防止意外覆盖
|
10
|
+
- 配置文件持久化
|
11
|
+
- 配置变更回调
|
12
|
+
"""
|
13
|
+
|
14
|
+
import json
|
15
|
+
import os
|
16
|
+
from typing import Any, Callable, Dict, Optional
|
17
|
+
|
18
|
+
class Config:
|
19
|
+
"""
|
20
|
+
配置管理类,提供安全的键值对存储和访问机制
|
21
|
+
|
22
|
+
特性:
|
23
|
+
- 类型安全的设置和获取
|
24
|
+
- 默认值支持
|
25
|
+
- 批量操作支持
|
26
|
+
- 配置项存在性检查
|
27
|
+
- 防止意外覆盖
|
28
|
+
- 配置变更通知
|
29
|
+
|
30
|
+
使用示例:
|
31
|
+
```
|
32
|
+
config = Config({"theme": "dark"})
|
33
|
+
config.set_config("font_size", 14)
|
34
|
+
theme = config.get_config("theme", default="light")
|
35
|
+
|
36
|
+
# 添加配置变更监听
|
37
|
+
def on_config_change(key, old_value, new_value):
|
38
|
+
print(f"配置变更: {key} 从 {old_value} 改为 {new_value}")
|
39
|
+
|
40
|
+
config.add_change_listener(on_config_change)
|
41
|
+
|
42
|
+
# 保存和加载配置
|
43
|
+
config.save_to_file("settings.json")
|
44
|
+
new_config = Config.load_from_file("settings.json")
|
45
|
+
```
|
46
|
+
"""
|
47
|
+
|
48
|
+
def __init__(self, initial_config: Optional[Dict[str, Any]] = None):
|
49
|
+
"""
|
50
|
+
初始化配置存储
|
51
|
+
|
52
|
+
参数:
|
53
|
+
initial_config: 初始配置字典 (可选)
|
54
|
+
"""
|
55
|
+
self.config_dict = initial_config.copy() if initial_config else {}
|
56
|
+
self._lock = False # 防止意外修改的锁
|
57
|
+
self._change_listeners = [] # 配置变更监听器列表
|
58
|
+
|
59
|
+
def __str__(self) -> str:
|
60
|
+
"""返回配置的可读字符串表示"""
|
61
|
+
return json.dumps(self.config_dict, indent=2, ensure_ascii=False)
|
62
|
+
|
63
|
+
def __repr__(self) -> str:
|
64
|
+
"""返回配置的正式表示"""
|
65
|
+
return f"Config({self.config_dict})"
|
66
|
+
|
67
|
+
def __contains__(self, item: str) -> bool:
|
68
|
+
"""检查配置项是否存在"""
|
69
|
+
return item in self.config_dict
|
70
|
+
|
71
|
+
def __len__(self) -> int:
|
72
|
+
"""返回配置项的数量"""
|
73
|
+
return len(self.config_dict)
|
74
|
+
|
75
|
+
def add_change_listener(self, listener: Callable[[str, Any, Any], None]):
|
76
|
+
"""
|
77
|
+
添加配置变更监听器
|
78
|
+
|
79
|
+
参数:
|
80
|
+
listener: 回调函数,格式为 func(key, old_value, new_value)
|
81
|
+
"""
|
82
|
+
if listener not in self._change_listeners:
|
83
|
+
self._change_listeners.append(listener)
|
84
|
+
|
85
|
+
def remove_change_listener(self, listener: Callable[[str, Any, Any], None]):
|
86
|
+
"""移除配置变更监听器"""
|
87
|
+
if listener in self._change_listeners:
|
88
|
+
self._change_listeners.remove(listener)
|
89
|
+
|
90
|
+
def _notify_change(self, key: str, old_value: Any, new_value: Any):
|
91
|
+
"""通知所有监听器配置变更"""
|
92
|
+
for listener in self._change_listeners:
|
93
|
+
try:
|
94
|
+
listener(key, old_value, new_value)
|
95
|
+
except Exception as e:
|
96
|
+
print(f"配置变更通知错误: {e}")
|
97
|
+
|
98
|
+
def get_config(self, item: str, default: Any = None) -> Any:
|
99
|
+
"""
|
100
|
+
安全获取配置项
|
101
|
+
|
102
|
+
参数:
|
103
|
+
item: 配置键名
|
104
|
+
default: 当键不存在时返回的默认值
|
105
|
+
|
106
|
+
返回:
|
107
|
+
配置值或默认值
|
108
|
+
"""
|
109
|
+
return self.config_dict.get(item, default)
|
110
|
+
|
111
|
+
def set_config(self, item: str, value: Any) -> bool:
|
112
|
+
"""
|
113
|
+
设置配置项
|
114
|
+
|
115
|
+
参数:
|
116
|
+
item: 配置键名
|
117
|
+
value: 配置值
|
118
|
+
|
119
|
+
返回:
|
120
|
+
True 设置成功, False 设置失败
|
121
|
+
"""
|
122
|
+
if self._lock:
|
123
|
+
print(f"警告: 配置系统已锁定,无法修改 '{item}'")
|
124
|
+
return False
|
125
|
+
|
126
|
+
old_value = self.config_dict.get(item)
|
127
|
+
self.config_dict[item] = value
|
128
|
+
|
129
|
+
# 通知变更
|
130
|
+
self._notify_change(item, old_value, value)
|
131
|
+
return True
|
132
|
+
|
133
|
+
def delete_config(self, item: str) -> bool:
|
134
|
+
"""
|
135
|
+
删除配置项
|
136
|
+
|
137
|
+
参数:
|
138
|
+
item: 要删除的配置键名
|
139
|
+
|
140
|
+
返回:
|
141
|
+
True 删除成功, False 键不存在
|
142
|
+
"""
|
143
|
+
if item in self.config_dict:
|
144
|
+
old_value = self.config_dict[item]
|
145
|
+
del self.config_dict[item]
|
146
|
+
|
147
|
+
# 通知变更 (值为 None 表示删除)
|
148
|
+
self._notify_change(item, old_value, None)
|
149
|
+
return True
|
150
|
+
return False
|
151
|
+
|
152
|
+
def save_to_file(self, filename: str) -> bool:
|
153
|
+
"""
|
154
|
+
保存配置到文件
|
155
|
+
|
156
|
+
参数:
|
157
|
+
filename: 文件名
|
158
|
+
|
159
|
+
返回:
|
160
|
+
True 保存成功, False 保存失败
|
161
|
+
"""
|
162
|
+
try:
|
163
|
+
with open(filename, 'w', encoding='utf-8') as f:
|
164
|
+
json.dump(self.get_all_configs(), f, indent=2, ensure_ascii=False)
|
165
|
+
return True
|
166
|
+
except (IOError, TypeError) as e:
|
167
|
+
print(f"保存配置失败: {e}")
|
168
|
+
return False
|
169
|
+
|
170
|
+
@classmethod
|
171
|
+
def load_from_file(cls, filename: str) -> Optional['Config']:
|
172
|
+
"""
|
173
|
+
从文件加载配置
|
174
|
+
|
175
|
+
参数:
|
176
|
+
filename: 文件名
|
177
|
+
|
178
|
+
返回:
|
179
|
+
加载成功的 Config 实例,失败返回 None
|
180
|
+
"""
|
181
|
+
if not os.path.exists(filename):
|
182
|
+
print(f"配置文件不存在: {filename}")
|
183
|
+
return None
|
184
|
+
|
185
|
+
try:
|
186
|
+
with open(filename, 'r', encoding='utf-8') as f:
|
187
|
+
config_data = json.load(f)
|
188
|
+
return cls(config_data)
|
189
|
+
except (IOError, json.JSONDecodeError) as e:
|
190
|
+
print(f"加载配置失败: {e}")
|
191
|
+
return None
|
192
|
+
|
193
|
+
def has_config(self, item: str) -> bool:
|
194
|
+
"""检查配置项是否存在"""
|
195
|
+
return item in self.config_dict
|
196
|
+
|
197
|
+
def lock_config(self):
|
198
|
+
"""锁定配置防止修改"""
|
199
|
+
self._lock = True
|
200
|
+
|
201
|
+
def unlock_config(self):
|
202
|
+
"""解锁配置允许修改"""
|
203
|
+
self._lock = False
|
204
|
+
|
205
|
+
def is_locked(self) -> bool:
|
206
|
+
"""检查配置是否已锁定"""
|
207
|
+
return self._lock
|
208
|
+
|
209
|
+
def bulk_update(self, update_dict: Dict[str, Any]) -> bool:
|
210
|
+
"""
|
211
|
+
批量更新配置
|
212
|
+
|
213
|
+
参数:
|
214
|
+
update_dict: 包含多个键值对的字典
|
215
|
+
|
216
|
+
返回:
|
217
|
+
True 更新成功, False 更新失败
|
218
|
+
"""
|
219
|
+
if self._lock:
|
220
|
+
print("警告: 配置系统已锁定,批量更新被拒绝")
|
221
|
+
return False
|
222
|
+
|
223
|
+
# 记录变更
|
224
|
+
changes = {}
|
225
|
+
for key, value in update_dict.items():
|
226
|
+
old_value = self.config_dict.get(key)
|
227
|
+
self.config_dict[key] = value
|
228
|
+
changes[key] = (old_value, value)
|
229
|
+
|
230
|
+
# 批量通知变更
|
231
|
+
for key, (old_value, new_value) in changes.items():
|
232
|
+
self._notify_change(key, old_value, new_value)
|
233
|
+
|
234
|
+
return True
|
235
|
+
|
236
|
+
def get_all_configs(self) -> Dict[str, Any]:
|
237
|
+
"""获取所有配置的副本"""
|
238
|
+
return self.config_dict.copy()
|
239
|
+
|
240
|
+
def reset_config(self, new_config: Optional[Dict[str, Any]] = None) -> None:
|
241
|
+
"""
|
242
|
+
重置所有配置
|
243
|
+
|
244
|
+
参数:
|
245
|
+
new_config: 新的配置字典 (可选,默认清空)
|
246
|
+
"""
|
247
|
+
if self._lock:
|
248
|
+
print("警告: 配置系统已锁定,无法重置")
|
249
|
+
return
|
250
|
+
|
251
|
+
# 记录所有变更(删除)
|
252
|
+
for key in list(self.config_dict.keys()):
|
253
|
+
old_value = self.config_dict[key]
|
254
|
+
self._notify_change(key, old_value, None)
|
255
|
+
|
256
|
+
# 重置配置
|
257
|
+
self.config_dict = new_config.copy() if new_config else {}
|
258
|
+
|
259
|
+
# 通知所有新配置项
|
260
|
+
for key, value in self.config_dict.items():
|
261
|
+
self._notify_change(key, None, value)
|
262
|
+
|
tretool/encoding.py
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
import chardet
|
2
|
+
|
3
|
+
from typing import Union, BinaryIO
|
4
|
+
|
5
|
+
def detect_encoding(
|
6
|
+
input_data: Union[bytes, str, BinaryIO],
|
7
|
+
sample_size: int = 1024,
|
8
|
+
fallback_encoding: str = 'utf-8'
|
9
|
+
) -> str:
|
10
|
+
"""
|
11
|
+
自动检测文本数据的字符编码
|
12
|
+
|
13
|
+
参数:
|
14
|
+
input_data: 可以是以下类型之一:
|
15
|
+
- bytes: 原始字节数据
|
16
|
+
- str: 字符串(将尝试重新编码检测)
|
17
|
+
- BinaryIO: 文件对象(将读取前sample_size字节)
|
18
|
+
sample_size: 从文件/大数据中采样的字节数(默认1024)
|
19
|
+
fallback_encoding: 无法检测时使用的回退编码(默认'utf-8')
|
20
|
+
|
21
|
+
返回:
|
22
|
+
检测到的编码名称字符串
|
23
|
+
|
24
|
+
示例:
|
25
|
+
# 检测字节数据编码
|
26
|
+
detect_encoding(b'\xc3\xa9chantillon')
|
27
|
+
|
28
|
+
# 检测文件编码
|
29
|
+
with open('file.txt', 'rb') as f:
|
30
|
+
encoding = detect_encoding(f)
|
31
|
+
"""
|
32
|
+
raw_data = _get_sample_data(input_data, sample_size)
|
33
|
+
|
34
|
+
if not raw_data:
|
35
|
+
return fallback_encoding
|
36
|
+
|
37
|
+
try:
|
38
|
+
# 使用chardet进行编码检测
|
39
|
+
result = chardet.detect(raw_data)
|
40
|
+
confidence = result['confidence']
|
41
|
+
encoding = result['encoding'].lower()
|
42
|
+
|
43
|
+
# 验证检测结果
|
44
|
+
if confidence > 0.9:
|
45
|
+
return encoding
|
46
|
+
if confidence > 0.7 and validate_encoding(raw_data, encoding):
|
47
|
+
return encoding
|
48
|
+
|
49
|
+
# 尝试常见编码验证
|
50
|
+
for enc in ['utf-8', 'latin-1', 'gbk', 'gb2312', 'big5']:
|
51
|
+
if validate_encoding(raw_data, enc):
|
52
|
+
return enc
|
53
|
+
|
54
|
+
except Exception:
|
55
|
+
pass
|
56
|
+
|
57
|
+
return fallback_encoding
|
58
|
+
|
59
|
+
|
60
|
+
def _get_sample_data(
|
61
|
+
input_data: Union[bytes, str, BinaryIO],
|
62
|
+
sample_size: int
|
63
|
+
) -> bytes:
|
64
|
+
"""获取用于检测的样本数据"""
|
65
|
+
if isinstance(input_data, bytes):
|
66
|
+
return input_data[:sample_size]
|
67
|
+
|
68
|
+
if isinstance(input_data, str):
|
69
|
+
try:
|
70
|
+
return input_data.encode('latin-1', errors='ignore')[:sample_size]
|
71
|
+
except:
|
72
|
+
return b''
|
73
|
+
|
74
|
+
if hasattr(input_data, 'read'):
|
75
|
+
try:
|
76
|
+
pos = input_data.tell()
|
77
|
+
data = input_data.read(sample_size)
|
78
|
+
input_data.seek(pos) # 重置文件指针
|
79
|
+
return data if isinstance(data, bytes) else b''
|
80
|
+
except:
|
81
|
+
return b''
|
82
|
+
|
83
|
+
return b''
|
84
|
+
|
85
|
+
|
86
|
+
def validate_encoding(data: bytes, encoding: str) -> bool:
|
87
|
+
"""验证编码是否有效"""
|
88
|
+
try:
|
89
|
+
data.decode(encoding, errors='strict')
|
90
|
+
return True
|
91
|
+
except:
|
92
|
+
return False
|
tretool/jsonlib.py
ADDED
@@ -0,0 +1,299 @@
|
|
1
|
+
import chardet
|
2
|
+
from typing import Any, Union, Dict, List, BinaryIO
|
3
|
+
|
4
|
+
class JSONEncodeError(Exception):
|
5
|
+
"""JSON 编码异常"""
|
6
|
+
pass
|
7
|
+
|
8
|
+
class JSONDecodeError(Exception):
|
9
|
+
"""JSON 解码异常"""
|
10
|
+
pass
|
11
|
+
|
12
|
+
def dump_to_str(obj: Any, indent: Union[int, None] = None) -> str:
|
13
|
+
"""
|
14
|
+
将 Python 对象转换为 JSON 字符串
|
15
|
+
|
16
|
+
参数:
|
17
|
+
obj: 要序列化的 Python 对象
|
18
|
+
indent: 缩进空格数(None 表示紧凑格式)
|
19
|
+
|
20
|
+
返回:
|
21
|
+
JSON 格式字符串
|
22
|
+
|
23
|
+
异常:
|
24
|
+
JSONEncodeError: 当遇到不可序列化的对象时
|
25
|
+
"""
|
26
|
+
if indent is not None and not isinstance(indent, int):
|
27
|
+
raise TypeError("indent must be int or None")
|
28
|
+
|
29
|
+
return _Encoder(indent).encode(obj)
|
30
|
+
|
31
|
+
def load_from_str(json_str: str) -> Any:
|
32
|
+
"""
|
33
|
+
将 JSON 字符串解析为 Python 对象
|
34
|
+
|
35
|
+
参数:
|
36
|
+
json_str: JSON 格式字符串
|
37
|
+
|
38
|
+
返回:
|
39
|
+
对应的 Python 对象
|
40
|
+
|
41
|
+
异常:
|
42
|
+
JSONDecodeError: 当 JSON 格式无效时
|
43
|
+
"""
|
44
|
+
parser = _Parser(json_str)
|
45
|
+
return parser.parse()
|
46
|
+
|
47
|
+
class _Encoder:
|
48
|
+
"""JSON 编码器实现"""
|
49
|
+
|
50
|
+
def __init__(self, indent: Union[int, None] = None):
|
51
|
+
self.indent = indent
|
52
|
+
self._current_indent = 0
|
53
|
+
|
54
|
+
def encode(self, obj: Any) -> str:
|
55
|
+
if obj is None:
|
56
|
+
return "null"
|
57
|
+
elif isinstance(obj, bool):
|
58
|
+
return "true" if obj else "false"
|
59
|
+
elif isinstance(obj, (int, float)):
|
60
|
+
return self._encode_number(obj)
|
61
|
+
elif isinstance(obj, str):
|
62
|
+
return self._encode_string(obj)
|
63
|
+
elif isinstance(obj, (list, tuple)):
|
64
|
+
return self._encode_array(obj)
|
65
|
+
elif isinstance(obj, dict):
|
66
|
+
return self._encode_object(obj)
|
67
|
+
else:
|
68
|
+
raise JSONEncodeError(f"Object of type {type(obj)} is not JSON serializable")
|
69
|
+
|
70
|
+
def _encode_number(self, num: Union[int, float]) -> str:
|
71
|
+
if isinstance(num, int):
|
72
|
+
return str(num)
|
73
|
+
elif num.is_integer():
|
74
|
+
return str(int(num))
|
75
|
+
else:
|
76
|
+
return str(num)
|
77
|
+
|
78
|
+
def _encode_string(self, s: str) -> str:
|
79
|
+
escape_map = {
|
80
|
+
'\"': '\\"',
|
81
|
+
'\\': '\\\\',
|
82
|
+
'\b': '\\b',
|
83
|
+
'\f': '\\f',
|
84
|
+
'\n': '\\n',
|
85
|
+
'\r': '\\r',
|
86
|
+
'\t': '\\t',
|
87
|
+
}
|
88
|
+
|
89
|
+
result = []
|
90
|
+
for char in s:
|
91
|
+
if char in escape_map:
|
92
|
+
result.append(escape_map[char])
|
93
|
+
elif ord(char) < 0x20:
|
94
|
+
result.append(f"\\u{ord(char):04x}")
|
95
|
+
else:
|
96
|
+
result.append(char)
|
97
|
+
|
98
|
+
return f'"{"".join(result)}"'
|
99
|
+
|
100
|
+
def _encode_array(self, array: List[Any]) -> str:
|
101
|
+
if not array:
|
102
|
+
return "[]"
|
103
|
+
|
104
|
+
if self.indent is None:
|
105
|
+
items = [self.encode(item) for item in array]
|
106
|
+
return f"[{','.join(items)}]"
|
107
|
+
else:
|
108
|
+
self._current_indent += self.indent
|
109
|
+
indent_str = "\n" + " " * self._current_indent
|
110
|
+
items = [f"{indent_str}{self.encode(item)}" for item in array]
|
111
|
+
self._current_indent -= self.indent
|
112
|
+
return f"[{','.join(items)}\n{' ' * self._current_indent}]"
|
113
|
+
|
114
|
+
def _encode_object(self, obj: Dict[str, Any]) -> str:
|
115
|
+
if not obj:
|
116
|
+
return "{}"
|
117
|
+
|
118
|
+
if self.indent is None:
|
119
|
+
items = [f"{self.encode(k)}:{self.encode(v)}" for k, v in obj.items()]
|
120
|
+
return f"{{{','.join(items)}}}"
|
121
|
+
else:
|
122
|
+
self._current_indent += self.indent
|
123
|
+
indent_str = "\n" + " " * self._current_indent
|
124
|
+
items = []
|
125
|
+
for k, v in obj.items():
|
126
|
+
key_str = self.encode(k)
|
127
|
+
value_str = self.encode(v)
|
128
|
+
items.append(f"{indent_str}{key_str}: {value_str}")
|
129
|
+
self._current_indent -= self.indent
|
130
|
+
return f"{{{','.join(items)}\n{' ' * self._current_indent}}}"
|
131
|
+
|
132
|
+
class _Parser:
|
133
|
+
"""JSON 解析器实现"""
|
134
|
+
|
135
|
+
def __init__(self, json_str: str):
|
136
|
+
self.json_str = json_str.strip()
|
137
|
+
self.idx = 0
|
138
|
+
self.len = len(json_str)
|
139
|
+
|
140
|
+
def parse(self) -> Any:
|
141
|
+
char = self._peek()
|
142
|
+
|
143
|
+
if char == '{':
|
144
|
+
return self._parse_object()
|
145
|
+
elif char == '[':
|
146
|
+
return self._parse_array()
|
147
|
+
elif char == '"':
|
148
|
+
return self._parse_string()
|
149
|
+
elif char == 'n' and self._peek_next(4) == 'null':
|
150
|
+
self.idx += 4
|
151
|
+
return None
|
152
|
+
elif char == 't' and self._peek_next(4) == 'true':
|
153
|
+
self.idx += 4
|
154
|
+
return True
|
155
|
+
elif char == 'f' and self._peek_next(5) == 'false':
|
156
|
+
self.idx += 5
|
157
|
+
return False
|
158
|
+
elif char == '-' or char.isdigit():
|
159
|
+
return self._parse_number()
|
160
|
+
else:
|
161
|
+
raise JSONDecodeError(f"Unexpected character at position {self.idx}: {char}")
|
162
|
+
|
163
|
+
def _parse_object(self) -> Dict[str, Any]:
|
164
|
+
obj = {}
|
165
|
+
self._consume('{')
|
166
|
+
|
167
|
+
while self._peek() != '}':
|
168
|
+
# 解析键
|
169
|
+
key = self._parse_string()
|
170
|
+
self._consume(':')
|
171
|
+
|
172
|
+
# 解析值
|
173
|
+
value = self.parse()
|
174
|
+
obj[key] = value
|
175
|
+
|
176
|
+
# 处理逗号或结束
|
177
|
+
if self._peek() == ',':
|
178
|
+
self._consume(',')
|
179
|
+
elif self._peek() != '}':
|
180
|
+
raise JSONDecodeError("Expected ',' or '}' after object pair")
|
181
|
+
|
182
|
+
self._consume('}')
|
183
|
+
return obj
|
184
|
+
|
185
|
+
def _parse_array(self) -> List[Any]:
|
186
|
+
arr = []
|
187
|
+
self._consume('[')
|
188
|
+
|
189
|
+
while self._peek() != ']':
|
190
|
+
# 解析元素
|
191
|
+
arr.append(self.parse())
|
192
|
+
|
193
|
+
# 处理逗号或结束
|
194
|
+
if self._peek() == ',':
|
195
|
+
self._consume(',')
|
196
|
+
elif self._peek() != ']':
|
197
|
+
raise JSONDecodeError("Expected ',' or ']' after array element")
|
198
|
+
|
199
|
+
self._consume(']')
|
200
|
+
return arr
|
201
|
+
|
202
|
+
def _parse_string(self) -> str:
|
203
|
+
self._consume('"')
|
204
|
+
chars = []
|
205
|
+
|
206
|
+
while self._peek() != '"':
|
207
|
+
char = self._peek()
|
208
|
+
|
209
|
+
if char == '\\':
|
210
|
+
self._consume('\\')
|
211
|
+
esc_char = self._peek()
|
212
|
+
if esc_char == 'u':
|
213
|
+
# Unicode 转义
|
214
|
+
self._consume('u')
|
215
|
+
hex_str = self.json_str[self.idx:self.idx+4]
|
216
|
+
if len(hex_str) != 4:
|
217
|
+
raise JSONDecodeError("Invalid Unicode escape sequence")
|
218
|
+
self.idx += 4
|
219
|
+
chars.append(chr(int(hex_str, 16)))
|
220
|
+
else:
|
221
|
+
# 简单转义字符
|
222
|
+
escape_map = {
|
223
|
+
'"': '"',
|
224
|
+
'\\': '\\',
|
225
|
+
'/': '/',
|
226
|
+
'b': '\b',
|
227
|
+
'f': '\f',
|
228
|
+
'n': '\n',
|
229
|
+
'r': '\r',
|
230
|
+
't': '\t',
|
231
|
+
}
|
232
|
+
chars.append(escape_map.get(esc_char, esc_char))
|
233
|
+
self._consume(esc_char)
|
234
|
+
else:
|
235
|
+
chars.append(char)
|
236
|
+
self._consume(char)
|
237
|
+
|
238
|
+
self._consume('"')
|
239
|
+
return ''.join(chars)
|
240
|
+
|
241
|
+
def _parse_number(self) -> Union[int, float]:
|
242
|
+
start_idx = self.idx
|
243
|
+
is_float = False
|
244
|
+
|
245
|
+
# 处理符号
|
246
|
+
if self._peek() == '-':
|
247
|
+
self._consume('-')
|
248
|
+
|
249
|
+
# 整数部分
|
250
|
+
while self._peek().isdigit():
|
251
|
+
self._consume()
|
252
|
+
|
253
|
+
# 小数部分
|
254
|
+
if self._peek() == '.':
|
255
|
+
is_float = True
|
256
|
+
self._consume('.')
|
257
|
+
while self._peek().isdigit():
|
258
|
+
self._consume()
|
259
|
+
|
260
|
+
# 指数部分
|
261
|
+
if self._peek().lower() == 'e':
|
262
|
+
is_float = True
|
263
|
+
self._consume()
|
264
|
+
if self._peek() in ('+', '-'):
|
265
|
+
self._consume()
|
266
|
+
while self._peek().isdigit():
|
267
|
+
self._consume()
|
268
|
+
|
269
|
+
num_str = self.json_str[start_idx:self.idx]
|
270
|
+
try:
|
271
|
+
return float(num_str) if is_float else int(num_str)
|
272
|
+
except ValueError:
|
273
|
+
raise JSONDecodeError(f"Invalid number literal: {num_str}")
|
274
|
+
|
275
|
+
def _peek(self) -> str:
|
276
|
+
if self.idx >= self.len:
|
277
|
+
raise JSONDecodeError("Unexpected end of JSON input")
|
278
|
+
return self.json_str[self.idx]
|
279
|
+
|
280
|
+
def _peek_next(self, n: int) -> str:
|
281
|
+
if self.idx + n > self.len:
|
282
|
+
raise JSONDecodeError("Unexpected end of JSON input")
|
283
|
+
return self.json_str[self.idx:self.idx+n]
|
284
|
+
|
285
|
+
def _consume(self, expected: str = None):
|
286
|
+
if expected is not None and self._peek() != expected:
|
287
|
+
raise JSONDecodeError(f"Expected '{expected}' at position {self.idx}")
|
288
|
+
self.idx += 1
|
289
|
+
|
290
|
+
# 简化版接口
|
291
|
+
def dump_to_file(obj: Any, filepath: str, indent: Union[int, None] = 4, encoding:str='utf-8'):
|
292
|
+
"""将 Python 对象序列化为 JSON 格式并写入文件"""
|
293
|
+
with open(filepath, 'w', encoding=encoding) as file:
|
294
|
+
file.write(dump_to_str(obj, indent))
|
295
|
+
|
296
|
+
def load_from_file(filepath:str, encoding:str='utf-8'):
|
297
|
+
"""从文件读取 JSON 数据并解析为 Python 对象"""
|
298
|
+
with open(filepath, 'r', encoding=encoding) as file:
|
299
|
+
return load_from_str(file.read())
|