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 ADDED
@@ -0,0 +1,27 @@
1
+ """
2
+ # tretool
3
+
4
+ ## tretool - Python多功能工具库
5
+
6
+ [![Python Version](https://img.shields.io/badge/python-3.8%2B-blue)](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())