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/__init__.py +55 -0
- evm/__main__.py +9 -0
- evm/_completion.py +253 -0
- evm/_crypto.py +177 -0
- evm/_groups.py +127 -0
- evm/_history.py +141 -0
- evm/_io.py +421 -0
- evm/_json.py +52 -0
- evm/_schema.py +265 -0
- evm/_typing.py +40 -0
- evm/cli.py +961 -0
- evm/exceptions.py +150 -0
- evm/formatters.py +285 -0
- evm/manager.py +644 -0
- evm_cli-2.4.0.dist-info/METADATA +748 -0
- evm_cli-2.4.0.dist-info/RECORD +20 -0
- evm_cli-2.4.0.dist-info/WHEEL +5 -0
- evm_cli-2.4.0.dist-info/entry_points.txt +2 -0
- evm_cli-2.4.0.dist-info/licenses/LICENSE +21 -0
- evm_cli-2.4.0.dist-info/top_level.txt +1 -0
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
|
+
]
|