evm-cli 2.4.0__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.
evm/exceptions.py ADDED
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ EVM 自定义异常体系
4
+
5
+ 所有 EVM 错误都继承自 EVMError,便于调用者统一捕获。
6
+ """
7
+
8
+
9
+ class EVMError(Exception):
10
+ """EVM 所有错误的基类"""
11
+ pass
12
+
13
+
14
+ class KeyNotFoundError(EVMError):
15
+ """请求的环境变量不存在"""
16
+ def __init__(self, key: str):
17
+ self.key = key
18
+ super().__init__(f"Environment variable '{key}' not found")
19
+
20
+
21
+ class KeyAlreadyExistsError(EVMError):
22
+ """目标变量名已存在(rename/copy 冲突)"""
23
+ def __init__(self, key: str):
24
+ self.key = key
25
+ super().__init__(f"Environment variable '{key}' already exists")
26
+
27
+
28
+ class StorageError(EVMError):
29
+ """存储文件读写失败"""
30
+ pass
31
+
32
+
33
+ class CorruptedStorageError(StorageError):
34
+ """存储文件损坏(JSON 解析失败)"""
35
+ pass
36
+
37
+
38
+ class StoragePermissionError(StorageError):
39
+ """存储文件权限不足"""
40
+ pass
41
+
42
+
43
+ class LockTimeoutError(StorageError):
44
+ """获取文件锁超时"""
45
+ def __init__(self, path: str, timeout: float):
46
+ self.path = path
47
+ self.timeout = timeout
48
+ super().__init__(
49
+ f"Failed to acquire file lock on '{path}' within {timeout}s. "
50
+ f"Another process may be writing."
51
+ )
52
+
53
+
54
+ class ExportError(EVMError):
55
+ """导出失败"""
56
+ pass
57
+
58
+
59
+ class ImportFailedError(EVMError):
60
+ """导入失败"""
61
+ def __init__(self, message: str, file_path: str = None):
62
+ self.file_path = file_path
63
+ super().__init__(message)
64
+
65
+
66
+ class CommandNotFoundError(EVMError):
67
+ """exec 命令找不到可执行文件"""
68
+ def __init__(self, command: str):
69
+ self.command = command
70
+ super().__init__(f"Command not found: {command}")
71
+
72
+
73
+ class GroupNotFoundError(EVMError):
74
+ """请求的分组不存在"""
75
+ def __init__(self, group: str):
76
+ self.group = group
77
+ super().__init__(f"Group '{group}' not found or has no variables")
78
+
79
+
80
+ class GroupOperationError(EVMError):
81
+ """分组操作错误(如删除 default 组)"""
82
+ pass
83
+
84
+
85
+ class BackupError(EVMError):
86
+ """备份/恢复失败"""
87
+ pass
88
+
89
+
90
+ class EditorError(EVMError):
91
+ """编辑器相关错误"""
92
+ pass
93
+
94
+
95
+ class DecryptionError(EVMError):
96
+ """解密失败"""
97
+ pass
98
+
99
+
100
+ class ValidationError(EVMError):
101
+ """变量值校验失败"""
102
+ def __init__(self, key: str, value: str, expected_format: str):
103
+ self.key = key
104
+ self.value = value
105
+ self.expected_format = expected_format
106
+ super().__init__(
107
+ f"Variable '{key}' value does not match format '{expected_format}'"
108
+ )
109
+
110
+
111
+ class SchemaError(EVMError):
112
+ """Schema 相关错误"""
113
+ def __init__(self, message: str, key: str = None):
114
+ self.key = key
115
+ super().__init__(message)
116
+
117
+
118
+ class OperationCancelledError(EVMError):
119
+ """用户取消操作"""
120
+ pass
121
+
122
+
123
+ # 向后兼容别名(将在未来版本移除)
124
+ PermissionError_ = StoragePermissionError
125
+ ImportError_ = ImportFailedError
126
+
127
+
128
+ __all__ = [
129
+ 'EVMError',
130
+ 'KeyNotFoundError',
131
+ 'KeyAlreadyExistsError',
132
+ 'StorageError',
133
+ 'CorruptedStorageError',
134
+ 'StoragePermissionError',
135
+ 'LockTimeoutError',
136
+ 'ExportError',
137
+ 'ImportFailedError',
138
+ 'CommandNotFoundError',
139
+ 'GroupNotFoundError',
140
+ 'GroupOperationError',
141
+ 'BackupError',
142
+ 'EditorError',
143
+ 'DecryptionError',
144
+ 'ValidationError',
145
+ 'SchemaError',
146
+ 'OperationCancelledError',
147
+ # 向后兼容
148
+ 'PermissionError_',
149
+ 'ImportError_',
150
+ ]
evm/formatters.py ADDED
@@ -0,0 +1,285 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ EVM 输出格式化
4
+
5
+ 将业务数据转换为人类可读的终端输出。
6
+ 所有 print() 调用集中在此模块。
7
+ """
8
+
9
+ import shutil
10
+ from typing import Optional
11
+
12
+ # 默认终端宽度(无法检测时的回退值)
13
+ _DEFAULT_WIDTH = 80
14
+
15
+
16
+ def _term_width() -> int:
17
+ """获取当前终端宽度,非终端环境返回默认值"""
18
+ return shutil.get_terminal_size((_DEFAULT_WIDTH, 24)).columns
19
+
20
+
21
+ def print_vars_table(
22
+ vars_dict: dict[str, str],
23
+ title: str = "Environment Variables",
24
+ show_total: bool = True,
25
+ ) -> None:
26
+ """以表格形式打印变量字典"""
27
+ if not vars_dict:
28
+ print("No environment variables set")
29
+ return
30
+
31
+ max_key_len = max(len(k) for k in vars_dict.keys())
32
+ width = max(max_key_len + 50, min(_term_width(), 120))
33
+
34
+ print(f"\n{title}:")
35
+ print("-" * width)
36
+ for key, value in sorted(vars_dict.items()):
37
+ print(f"{key:<{max_key_len}} = {value}")
38
+ print("-" * width)
39
+ if show_total:
40
+ print(f"Total: {len(vars_dict)} variables")
41
+
42
+
43
+ def print_vars_by_group(vars_dict: dict[str, str]) -> None:
44
+ """按分组打印变量"""
45
+ groups: dict[str, dict[str, str]] = {}
46
+ for key, value in vars_dict.items():
47
+ if ':' in key:
48
+ group, var_name = key.split(':', 1)
49
+ else:
50
+ group = 'default'
51
+ var_name = key
52
+ groups.setdefault(group, {})[var_name] = value
53
+
54
+ if not groups:
55
+ print("No environment variables to display")
56
+ return
57
+
58
+ width = min(_term_width(), 120)
59
+ print("\nEnvironment Variables (by group):")
60
+ print("=" * width)
61
+ total_vars = 0
62
+ for group_name in sorted(groups.keys()):
63
+ print(f"\n[{group_name}]")
64
+ print("-" * width)
65
+ group_vars = groups[group_name]
66
+ max_key_len = max(len(k) for k in group_vars.keys()) if group_vars else 0
67
+ for key, value in sorted(group_vars.items()):
68
+ print(f"{key:<{max_key_len}} = {value}")
69
+ print("-" * width)
70
+ print(f"{len(group_vars)} variables")
71
+ total_vars += len(group_vars)
72
+ print("\n+" * width)
73
+ print(f"Total: {len(groups)} groups, {total_vars} variables")
74
+
75
+
76
+ def print_search_results(
77
+ results: dict[str, str],
78
+ pattern: str,
79
+ search_value: bool = False,
80
+ ) -> None:
81
+ """打印搜索结果"""
82
+ if not results:
83
+ search_text = "key and value" if search_value else "key"
84
+ print(f"No environment variables match '{pattern}' in {search_text}")
85
+ return
86
+
87
+ print_vars_table(
88
+ results,
89
+ title=f"Search results for '{pattern}'",
90
+ )
91
+
92
+
93
+ def print_groups(groups: dict[str, int]) -> None:
94
+ """打印分组列表"""
95
+ if not groups:
96
+ print("No groups found. All variables are in the default namespace.")
97
+ return
98
+
99
+ width = min(_term_width(), 80)
100
+ print("\nAvailable Groups:")
101
+ print("-" * width)
102
+ for group in sorted(groups):
103
+ print(f"{group:<30} ({groups[group]} variables)")
104
+ print("-" * width)
105
+ print(f"Total: {len(groups)} groups")
106
+
107
+
108
+ def print_info(info: dict) -> None:
109
+ """打印工具信息"""
110
+ print("EVM (Environment Variable Manager)")
111
+ print(f"Version: {info['version']}")
112
+ print(f"Author: {info['author']}")
113
+ print(f"License: {info['license']}")
114
+ print(f"Python: {info['python']}")
115
+ print(f"Platform: {info['platform']}")
116
+ print(f"Storage: {info['storage_path']}")
117
+ print(f"Storage exists: {info['storage_exists']}")
118
+ print(f"Total variables: {info['total_variables']}")
119
+ print(f"Total groups: {info['total_groups']}")
120
+ print(f"Secret variables: {info['secret_variables']}")
121
+ if info.get('groups'):
122
+ print("Groups:")
123
+ for g, c in sorted(info['groups'].items()):
124
+ print(f" {g}: {c} variables")
125
+ print(f"\nRepository: {info['repository']}")
126
+
127
+
128
+ def print_diff(diff_result: dict) -> None:
129
+ """打印 diff 结果"""
130
+ added = diff_result.get('added', {})
131
+ removed = diff_result.get('removed', {})
132
+ changed = diff_result.get('changed', {})
133
+ timestamp = diff_result.get('backup_timestamp', '')
134
+
135
+ if timestamp:
136
+ print(f"Comparing with backup (timestamp: {timestamp})\n")
137
+
138
+ if not added and not removed and not changed:
139
+ print("No differences found.")
140
+ return
141
+
142
+ width = min(_term_width(), 80)
143
+
144
+ if added:
145
+ print(f"Added ({len(added)}):")
146
+ print("-" * width)
147
+ for key, value in sorted(added.items()):
148
+ print(f" + {key} = {value}")
149
+ print()
150
+
151
+ if removed:
152
+ print(f"Removed ({len(removed)}):")
153
+ print("-" * width)
154
+ for key, value in sorted(removed.items()):
155
+ print(f" - {key} = {value}")
156
+ print()
157
+
158
+ if changed:
159
+ print(f"Changed ({len(changed)}):")
160
+ print("-" * width)
161
+ for key, values in sorted(changed.items()):
162
+ print(f" ~ {key}")
163
+ print(f" backup: {values['backup']}")
164
+ print(f" current: {values['current']}")
165
+ print()
166
+
167
+ total = len(added) + len(removed) + len(changed)
168
+ print(f"Total: {total} differences "
169
+ f"(+{len(added)} added, -{len(removed)} removed, ~{len(changed)} changed)")
170
+
171
+
172
+ def print_load_memory_result(
173
+ loaded_count: int,
174
+ add_evm_prefix: bool,
175
+ filter_prefix: Optional[str],
176
+ ) -> None:
177
+ """打印 loadmemory 结果"""
178
+ if loaded_count > 0:
179
+ print(f"Loaded {loaded_count} environment variables to memory")
180
+ if add_evm_prefix:
181
+ print("Prefix 'EVM:' added to all variable names")
182
+ if filter_prefix:
183
+ print(f"Filter: keys starting with '{filter_prefix}'")
184
+ else:
185
+ print("No environment variables to load")
186
+ if filter_prefix:
187
+ print(f"No variables found with prefix '{filter_prefix}'")
188
+
189
+
190
+ def print_history(entries: list[dict]) -> None:
191
+ """打印操作历史"""
192
+ if not entries:
193
+ print("No history entries found.")
194
+ return
195
+
196
+ width = min(_term_width(), 80)
197
+ print(f"\nOperation History (latest {len(entries)} entries):")
198
+ print("-" * width)
199
+ for entry in entries:
200
+ ts = entry.get('timestamp', '')[:19]
201
+ op = entry.get('operation', '')
202
+ key = entry.get('key', '')
203
+ details = entry.get('details', '')
204
+ status = entry.get('status', '')
205
+ status_mark = '✓' if status == 'success' else '✗'
206
+ line = f" {ts} {status_mark} {op:<12}"
207
+ if key:
208
+ line += f" {key}"
209
+ if details:
210
+ line += f" ({details})"
211
+ print(line)
212
+ print("-" * width)
213
+
214
+
215
+ def print_validate_result(key: str, result: dict) -> None:
216
+ """打印单个变量的校验结果"""
217
+ if result['valid']:
218
+ print(f" ✓ {key}: valid")
219
+ else:
220
+ print(f" ✗ {key}: INVALID")
221
+ for err in result.get('errors', []):
222
+ print(f" error: {err}")
223
+ for warn in result.get('warnings', []):
224
+ print(f" warning: {warn}")
225
+
226
+
227
+ def print_validate_all(results: dict[str, dict]) -> None:
228
+ """打印所有变量的校验结果"""
229
+ if not results:
230
+ print("No schema definitions found.")
231
+ return
232
+
233
+ valid_count = sum(1 for r in results.values() if r['valid'])
234
+ total = len(results)
235
+
236
+ width = min(_term_width(), 80)
237
+ print(f"\nSchema Validation ({valid_count}/{total} valid):")
238
+ print("-" * width)
239
+ for key in sorted(results):
240
+ print_validate_result(key, results[key])
241
+ print("-" * width)
242
+ if valid_count == total:
243
+ print("All variables passed validation.")
244
+ else:
245
+ print(f"{total - valid_count} variable(s) failed validation.")
246
+
247
+
248
+ def print_schema(schema: dict) -> None:
249
+ """打印 schema 定义"""
250
+ if not schema:
251
+ print("No schema definitions found.")
252
+ return
253
+
254
+ width = min(_term_width(), 80)
255
+ print("\nSchema Definitions:")
256
+ print("-" * width)
257
+ for key in sorted(schema):
258
+ entry = schema[key]
259
+ parts = []
260
+ if 'format' in entry:
261
+ parts.append(f"format={entry['format']}")
262
+ if 'required' in entry:
263
+ parts.append(f"required={entry['required']}")
264
+ if 'pattern' in entry:
265
+ parts.append(f"pattern={entry['pattern']}")
266
+ if 'description' in entry:
267
+ parts.append(f"desc={entry['description']}")
268
+ print(f" {key:<30} {', '.join(parts)}")
269
+ print("-" * width)
270
+ print(f"Total: {len(schema)} definitions")
271
+
272
+
273
+ __all__ = [
274
+ 'print_vars_table',
275
+ 'print_vars_by_group',
276
+ 'print_search_results',
277
+ 'print_groups',
278
+ 'print_info',
279
+ 'print_diff',
280
+ 'print_load_memory_result',
281
+ 'print_history',
282
+ 'print_validate_result',
283
+ 'print_validate_all',
284
+ 'print_schema',
285
+ ]