evolver-tools 1.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.
- evolver_tools/__init__.py +2 -0
- evolver_tools/__main__.py +3 -0
- evolver_tools/cli.py +89 -0
- evolver_tools/vendor/b64/__init__.py +2 -0
- evolver_tools/vendor/b64/b64.py +176 -0
- evolver_tools/vendor/cal_tool/__init__.py +1 -0
- evolver_tools/vendor/cal_tool/cli.py +234 -0
- evolver_tools/vendor/chart_cli/__init__.py +444 -0
- evolver_tools/vendor/chart_cli/__main__.py +3 -0
- evolver_tools/vendor/colors/__init__.py +5 -0
- evolver_tools/vendor/colors/__main__.py +97 -0
- evolver_tools/vendor/csv_stats/__init__.py +5 -0
- evolver_tools/vendor/csv_stats/__main__.py +4 -0
- evolver_tools/vendor/csv_stats/analyzer.py +258 -0
- evolver_tools/vendor/csv_stats/cli.py +45 -0
- evolver_tools/vendor/dirsize/__init__.py +183 -0
- evolver_tools/vendor/envcheck/__init__.py +426 -0
- evolver_tools/vendor/ff/__init__.py +427 -0
- evolver_tools/vendor/ff/__main__.py +3 -0
- evolver_tools/vendor/find_dups/__init__.py +7 -0
- evolver_tools/vendor/find_dups/cli.py +392 -0
- evolver_tools/vendor/hashsum/__init__.py +211 -0
- evolver_tools/vendor/hashsum/__main__.py +5 -0
- evolver_tools/vendor/http_live/__init__.py +265 -0
- evolver_tools/vendor/http_live/__main__.py +2 -0
- evolver_tools/vendor/ipinfo/__init__.py +3 -0
- evolver_tools/vendor/ipinfo/__main__.py +30 -0
- evolver_tools/vendor/jq_lite/__init__.py +257 -0
- evolver_tools/vendor/jq_lite/__main__.py +5 -0
- evolver_tools/vendor/json2csv/__init__.py +3 -0
- evolver_tools/vendor/json2csv/__main__.py +82 -0
- evolver_tools/vendor/jsonql/__init__.py +326 -0
- evolver_tools/vendor/jsonql/__main__.py +5 -0
- evolver_tools/vendor/license_cli/__init__.py +1 -0
- evolver_tools/vendor/license_cli/__main__.py +4 -0
- evolver_tools/vendor/license_cli/cli.py +289 -0
- evolver_tools/vendor/markdown_check/__init__.py +211 -0
- evolver_tools/vendor/nb/__init__.py +319 -0
- evolver_tools/vendor/nb/__main__.py +3 -0
- evolver_tools/vendor/passgen/__init__.py +224 -0
- evolver_tools/vendor/portcheck/__init__.py +2 -0
- evolver_tools/vendor/portcheck/__main__.py +66 -0
- evolver_tools/vendor/project_doctor/__init__.py +412 -0
- evolver_tools/vendor/project_doctor/__main__.py +3 -0
- evolver_tools/vendor/ren/__init__.py +283 -0
- evolver_tools/vendor/ren/__main__.py +3 -0
- evolver_tools/vendor/siege_lite/__init__.py +250 -0
- evolver_tools/vendor/siege_lite/__main__.py +3 -0
- evolver_tools/vendor/smellfinder/__init__.py +376 -0
- evolver_tools/vendor/smellfinder/__main__.py +3 -0
- evolver_tools/vendor/sqlite_cli/__init__.py +326 -0
- evolver_tools/vendor/sqlite_cli/__main__.py +5 -0
- evolver_tools/vendor/sysmon/__init__.py +299 -0
- evolver_tools/vendor/sysmon/__main__.py +3 -0
- evolver_tools/vendor/timer/__init__.py +127 -0
- evolver_tools/vendor/treedir/__init__.py +2 -0
- evolver_tools/vendor/treedir/__main__.py +128 -0
- evolver_tools/vendor/urlparse_tool/__init__.py +3 -0
- evolver_tools/vendor/urlparse_tool/cli.py +212 -0
- evolver_tools/vendor/web_summary/__init__.py +341 -0
- evolver_tools/vendor/web_summary/__main__.py +3 -0
- evolver_tools/vendor/wordcount/__init__.py +2 -0
- evolver_tools/vendor/wordcount/__main__.py +101 -0
- evolver_tools-1.4.0.dist-info/METADATA +107 -0
- evolver_tools-1.4.0.dist-info/RECORD +69 -0
- evolver_tools-1.4.0.dist-info/WHEEL +5 -0
- evolver_tools-1.4.0.dist-info/entry_points.txt +34 -0
- evolver_tools-1.4.0.dist-info/licenses/LICENSE +21 -0
- evolver_tools-1.4.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
envcheck — 环境变量验证器
|
|
4
|
+
|
|
5
|
+
验证环境变量存在、格式、类型,检查默认值,显示缺失项。
|
|
6
|
+
|
|
7
|
+
Features:
|
|
8
|
+
- 检查必需变量是否存在
|
|
9
|
+
- 验证变量格式(email, url, port, path, regex模式)
|
|
10
|
+
- 转换为指定类型(int, float, bool, list)
|
|
11
|
+
- 检查默认值是否被覆盖
|
|
12
|
+
- 显示所有缺失的必需变量
|
|
13
|
+
- 0依赖,纯Python标准库
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
envcheck # 扫描所有env文件并检查
|
|
17
|
+
envcheck path/to/.env # 检查指定env文件
|
|
18
|
+
envcheck --require DB_HOST DB_PORT # 指定必需变量
|
|
19
|
+
envcheck --template .env.example # 对比模板
|
|
20
|
+
envcheck --json # JSON输出(用于CI)
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import os
|
|
24
|
+
import re
|
|
25
|
+
import sys
|
|
26
|
+
import json
|
|
27
|
+
import argparse
|
|
28
|
+
|
|
29
|
+
# ─── 格式验证器 ─────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
VALIDATORS = {
|
|
32
|
+
"email": re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$"),
|
|
33
|
+
"url": re.compile(r"^https?://[^\s]+$"),
|
|
34
|
+
"port": re.compile(r"^\d+$"),
|
|
35
|
+
"integer": re.compile(r"^-?\d+$"),
|
|
36
|
+
"float": re.compile(r"^-?\d+(\.\d+)?$"),
|
|
37
|
+
"boolean": re.compile(r"^(true|false|yes|no|1|0)$", re.I),
|
|
38
|
+
"path": re.compile(r"^[/~.]"),
|
|
39
|
+
"hex_color": re.compile(r"^#[0-9a-fA-F]{3,8}$"),
|
|
40
|
+
"uuid": re.compile(
|
|
41
|
+
r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
|
|
42
|
+
),
|
|
43
|
+
"semver": re.compile(r"^\d+\.\d+\.\d+"),
|
|
44
|
+
"datetime_iso": re.compile(
|
|
45
|
+
r"^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}"
|
|
46
|
+
),
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
FORMAT_HELP = {
|
|
50
|
+
"email": "格式应为 user@domain.com",
|
|
51
|
+
"url": "格式应为 https://...",
|
|
52
|
+
"port": "应为数字 (1-65535)",
|
|
53
|
+
"integer": "应为整数",
|
|
54
|
+
"float": "应为数字(可含小数)",
|
|
55
|
+
"boolean": "应为 true/false/yes/no/1/0",
|
|
56
|
+
"path": "应为路径 (以 / ~ 或 . 开头)",
|
|
57
|
+
"hex_color": "应为 #RGB/#RRGGBB/#RRGGBBAA",
|
|
58
|
+
"uuid": "应为 UUID 格式",
|
|
59
|
+
"semver": "应为语义版本号 (x.y.z)",
|
|
60
|
+
"datetime_iso": "应为 ISO 日期时间",
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def validate_format(value, fmt):
|
|
65
|
+
"""验证值格式"""
|
|
66
|
+
pattern = VALIDATORS.get(fmt)
|
|
67
|
+
if not pattern:
|
|
68
|
+
return True, "未知格式检查"
|
|
69
|
+
if pattern.match(str(value).strip()):
|
|
70
|
+
return True, None
|
|
71
|
+
return False, FORMAT_HELP.get(fmt, f"格式不匹配: {fmt}")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def cast_type(value, target_type):
|
|
75
|
+
"""类型转换"""
|
|
76
|
+
try:
|
|
77
|
+
if target_type == "int":
|
|
78
|
+
return int(value), True, None
|
|
79
|
+
elif target_type == "float":
|
|
80
|
+
return float(value), True, None
|
|
81
|
+
elif target_type == "bool":
|
|
82
|
+
return value.lower() in ("true", "yes", "1"), True, None
|
|
83
|
+
elif target_type == "list":
|
|
84
|
+
import shlex
|
|
85
|
+
parts = shlex.split(value)
|
|
86
|
+
return parts, True, None
|
|
87
|
+
else:
|
|
88
|
+
return value, True, None
|
|
89
|
+
except (ValueError, TypeError) as e:
|
|
90
|
+
return value, False, str(e)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# ─── .env 文件解析 ────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
def find_env_files(start_dir=None):
|
|
96
|
+
"""递归查找 .env 文件"""
|
|
97
|
+
if start_dir is None:
|
|
98
|
+
start_dir = os.getcwd()
|
|
99
|
+
results = []
|
|
100
|
+
for root, dirs, files in os.walk(start_dir):
|
|
101
|
+
# 跳过隐藏目录和 node_modules, .git, __pycache__
|
|
102
|
+
dirs[:] = [d for d in dirs
|
|
103
|
+
if not d.startswith('.') and
|
|
104
|
+
d not in ('node_modules', '__pycache__', 'venv', '.venv')]
|
|
105
|
+
for f in files:
|
|
106
|
+
if f == '.env' or f.endswith('.env') or f == '.env.example':
|
|
107
|
+
results.append(os.path.join(root, f))
|
|
108
|
+
return results
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def parse_env_file(filepath):
|
|
112
|
+
"""解析 .env 文件"""
|
|
113
|
+
variables = {}
|
|
114
|
+
errors = []
|
|
115
|
+
try:
|
|
116
|
+
with open(filepath, "r") as f:
|
|
117
|
+
for i, line in enumerate(f, 1):
|
|
118
|
+
line = line.strip()
|
|
119
|
+
if not line or line.startswith("#"):
|
|
120
|
+
continue
|
|
121
|
+
if "=" not in line:
|
|
122
|
+
errors.append((i, line[:60], "格式错误: 缺少 ="))
|
|
123
|
+
continue
|
|
124
|
+
key, _, val = line.partition("=")
|
|
125
|
+
key = key.strip()
|
|
126
|
+
val = val.strip()
|
|
127
|
+
# 去掉引号
|
|
128
|
+
if len(val) >= 2 and val[0] == val[-1] and val[0] in ('"', "'"):
|
|
129
|
+
val = val[1:-1]
|
|
130
|
+
variables[key] = val
|
|
131
|
+
return variables, errors
|
|
132
|
+
except FileNotFoundError:
|
|
133
|
+
return None, [(0, "", "文件未找到")]
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# ─── 报告输出 ──────────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
def color(text, code):
|
|
139
|
+
if sys.stdout.isatty():
|
|
140
|
+
return f"\033[{code}m{text}\033[0m"
|
|
141
|
+
return text
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def green(text):
|
|
145
|
+
return color(text, "92")
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def red(text):
|
|
149
|
+
return color(text, "91")
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def yellow(text):
|
|
153
|
+
return color(text, "93")
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def blue(text):
|
|
157
|
+
return color(text, "94")
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def print_report(result, json_output=False):
|
|
161
|
+
"""打印检查报告"""
|
|
162
|
+
if json_output:
|
|
163
|
+
print(json.dumps(result, indent=2, default=str))
|
|
164
|
+
return
|
|
165
|
+
|
|
166
|
+
total = result.get("total", 0)
|
|
167
|
+
present = result.get("present", 0)
|
|
168
|
+
missing = result.get("missing", 0)
|
|
169
|
+
format_issues = result.get("format_issues", 0)
|
|
170
|
+
type_issues = result.get("type_issues", 0)
|
|
171
|
+
|
|
172
|
+
print()
|
|
173
|
+
print(f" {blue('envcheck')} — 环境变量检查报告")
|
|
174
|
+
print(f" {'='*40}")
|
|
175
|
+
print(f" 来源: {result.get('source', '未知')}")
|
|
176
|
+
print()
|
|
177
|
+
|
|
178
|
+
if result.get("errors"):
|
|
179
|
+
for err in result["errors"]:
|
|
180
|
+
print(f" {red('⚠')} 解析错误 (行 {err[0]}): {err[1]}")
|
|
181
|
+
print()
|
|
182
|
+
|
|
183
|
+
if missing > 0:
|
|
184
|
+
print(f" {red('✗')} 缺失变量 ({missing}):")
|
|
185
|
+
for v in result.get("missing_vars", []):
|
|
186
|
+
print(f" • {v}")
|
|
187
|
+
print()
|
|
188
|
+
|
|
189
|
+
if format_issues > 0:
|
|
190
|
+
print(f" {yellow('⚠')} 格式问题 ({format_issues}):")
|
|
191
|
+
for v in result.get("format_issues_list", []):
|
|
192
|
+
print(f" • {v['var']} = {v['value'][:40]} — {v['hint']}")
|
|
193
|
+
print()
|
|
194
|
+
|
|
195
|
+
if type_issues > 0:
|
|
196
|
+
print(f" {yellow('⚠')} 类型转换问题 ({type_issues}):")
|
|
197
|
+
for v in result.get("type_issues_list", []):
|
|
198
|
+
print(f" • {v['var']} = {v['value'][:40]} — {v['hint']}")
|
|
199
|
+
print()
|
|
200
|
+
|
|
201
|
+
print(f" {green('✓')} 已定义: {present}/{total}")
|
|
202
|
+
|
|
203
|
+
if result.get("has_defaults"):
|
|
204
|
+
overridden = result.get("overridden_defaults", 0)
|
|
205
|
+
print(f" {blue('ℹ')} 覆盖默认值: {overridden}")
|
|
206
|
+
|
|
207
|
+
# 总体评分
|
|
208
|
+
score = result.get("score", 100)
|
|
209
|
+
if score >= 90:
|
|
210
|
+
c = green
|
|
211
|
+
elif score >= 70:
|
|
212
|
+
c = yellow
|
|
213
|
+
else:
|
|
214
|
+
c = red
|
|
215
|
+
print(f" {c('★')} 健康度: {c(str(score))}/100")
|
|
216
|
+
print()
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def check(args):
|
|
220
|
+
"""主检查逻辑"""
|
|
221
|
+
# 确定检查范围
|
|
222
|
+
if args.file:
|
|
223
|
+
files = [args.file]
|
|
224
|
+
else:
|
|
225
|
+
files = find_env_files()
|
|
226
|
+
|
|
227
|
+
if not files:
|
|
228
|
+
result = {
|
|
229
|
+
"status": "error",
|
|
230
|
+
"message": "未找到 .env 文件",
|
|
231
|
+
"total": 0, "present": 0, "missing": 0,
|
|
232
|
+
"format_issues": 0, "type_issues": 0,
|
|
233
|
+
"score": 0,
|
|
234
|
+
"source": "无文件",
|
|
235
|
+
}
|
|
236
|
+
print_report(result, args.json)
|
|
237
|
+
return 1
|
|
238
|
+
|
|
239
|
+
# 处理每个文件
|
|
240
|
+
all_vars = {}
|
|
241
|
+
all_errors = []
|
|
242
|
+
source_file = files[0]
|
|
243
|
+
|
|
244
|
+
for f in files:
|
|
245
|
+
vars_dict, errs = parse_env_file(f)
|
|
246
|
+
if vars_dict is not None:
|
|
247
|
+
all_vars.update(vars_dict)
|
|
248
|
+
all_errors.extend([(f, e[0], e[1]) for e in errs])
|
|
249
|
+
if source_file == files[0] and vars_dict is not None:
|
|
250
|
+
source_file = f
|
|
251
|
+
|
|
252
|
+
# 必需变量检查
|
|
253
|
+
required_vars = list(args.require) if args.require else []
|
|
254
|
+
if args.template:
|
|
255
|
+
tmpl_vars, _ = parse_env_file(args.template)
|
|
256
|
+
if tmpl_vars is not None:
|
|
257
|
+
required_vars = list(tmpl_vars.keys())
|
|
258
|
+
|
|
259
|
+
# 如果没有指定必需变量,把所有变量视为可选
|
|
260
|
+
if not required_vars:
|
|
261
|
+
required_vars = list(all_vars.keys())
|
|
262
|
+
|
|
263
|
+
missing_vars = [v for v in required_vars if v not in all_vars]
|
|
264
|
+
|
|
265
|
+
# 格式验证
|
|
266
|
+
format_issues_list = []
|
|
267
|
+
type_issues_list = []
|
|
268
|
+
format_count = 0
|
|
269
|
+
type_count = 0
|
|
270
|
+
|
|
271
|
+
for var_name in all_vars:
|
|
272
|
+
val = all_vars[var_name]
|
|
273
|
+
|
|
274
|
+
# 按变量名推断格式
|
|
275
|
+
hints = _infer_format_checks(var_name, val)
|
|
276
|
+
for fmt in hints:
|
|
277
|
+
ok, hint = validate_format(val, fmt)
|
|
278
|
+
if not ok:
|
|
279
|
+
format_issues_list.append({
|
|
280
|
+
"var": var_name, "value": val,
|
|
281
|
+
"check": fmt, "hint": hint
|
|
282
|
+
})
|
|
283
|
+
format_count += 1
|
|
284
|
+
|
|
285
|
+
# 类型转换检查
|
|
286
|
+
type_hints = _infer_type_checks(var_name, val)
|
|
287
|
+
for t in type_hints:
|
|
288
|
+
_, ok, hint = cast_type(val, t)
|
|
289
|
+
if not ok:
|
|
290
|
+
type_issues_list.append({
|
|
291
|
+
"var": var_name, "value": val,
|
|
292
|
+
"target": t, "hint": hint
|
|
293
|
+
})
|
|
294
|
+
type_count += 1
|
|
295
|
+
|
|
296
|
+
# 默认值覆盖
|
|
297
|
+
overridden = 0
|
|
298
|
+
has_defaults = False
|
|
299
|
+
if args.template:
|
|
300
|
+
has_defaults = True
|
|
301
|
+
tmpl_vars, _ = parse_env_file(args.template)
|
|
302
|
+
if tmpl_vars:
|
|
303
|
+
for k in tmpl_vars:
|
|
304
|
+
if k in all_vars and all_vars[k] != tmpl_vars[k]:
|
|
305
|
+
overridden += 1
|
|
306
|
+
|
|
307
|
+
# 评分
|
|
308
|
+
checks = len(required_vars) + format_count + type_count
|
|
309
|
+
passed = len(required_vars) - len(missing_vars) + (format_count + type_count) * 0
|
|
310
|
+
score = max(0, int((passed / max(checks, 1)) * 100 - len(missing_vars) * 10))
|
|
311
|
+
|
|
312
|
+
result = {
|
|
313
|
+
"status": "warning" if (missing_vars or format_issues_list)
|
|
314
|
+
else "ok",
|
|
315
|
+
"source": str(source_file),
|
|
316
|
+
"total": len(required_vars) if required_vars else len(all_vars),
|
|
317
|
+
"present": len(all_vars),
|
|
318
|
+
"missing": len(missing_vars),
|
|
319
|
+
"missing_vars": missing_vars,
|
|
320
|
+
"format_issues": format_count,
|
|
321
|
+
"format_issues_list": format_issues_list,
|
|
322
|
+
"type_issues": type_count,
|
|
323
|
+
"type_issues_list": type_issues_list,
|
|
324
|
+
"has_defaults": has_defaults,
|
|
325
|
+
"overridden_defaults": overridden,
|
|
326
|
+
"score": score,
|
|
327
|
+
"errors": all_errors,
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
print_report(result, args.json)
|
|
331
|
+
return 0 if result["status"] == "ok" else 1
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _infer_format_checks(var_name, value):
|
|
335
|
+
"""根据变量名推断应该检查什么格式"""
|
|
336
|
+
name_upper = var_name.upper()
|
|
337
|
+
checks = []
|
|
338
|
+
|
|
339
|
+
# 端口
|
|
340
|
+
if ("PORT" in name_upper or "PORT_" in name_upper or
|
|
341
|
+
name_upper.endswith("_PORT")):
|
|
342
|
+
checks.append("port")
|
|
343
|
+
|
|
344
|
+
# URL
|
|
345
|
+
if ("URL" in name_upper or "URI" in name_upper or
|
|
346
|
+
"ENDPOINT" in name_upper or "HOST" in name_upper):
|
|
347
|
+
checks.append("url")
|
|
348
|
+
|
|
349
|
+
# Email
|
|
350
|
+
if "EMAIL" in name_upper or "MAIL" in name_upper:
|
|
351
|
+
checks.append("email")
|
|
352
|
+
|
|
353
|
+
# 路径
|
|
354
|
+
if ("PATH" in name_upper or "DIR" in name_upper or
|
|
355
|
+
"FILE" in name_upper or "HOME" in name_upper):
|
|
356
|
+
checks.append("path")
|
|
357
|
+
|
|
358
|
+
# 颜色
|
|
359
|
+
if "COLOR" in name_upper or "COLOUR" in name_upper:
|
|
360
|
+
checks.append("hex_color")
|
|
361
|
+
|
|
362
|
+
# UUID
|
|
363
|
+
if "UUID" in name_upper or "GUID" in name_upper:
|
|
364
|
+
checks.append("uuid")
|
|
365
|
+
|
|
366
|
+
# 版本号
|
|
367
|
+
if "VERSION" in name_upper:
|
|
368
|
+
checks.append("semver")
|
|
369
|
+
|
|
370
|
+
return checks
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def _infer_type_checks(var_name, value):
|
|
374
|
+
"""根据变量名推断类型转换"""
|
|
375
|
+
name_upper = var_name.upper()
|
|
376
|
+
checks = []
|
|
377
|
+
|
|
378
|
+
if ("COUNT" in name_upper or "LIMIT" in name_upper or
|
|
379
|
+
"SIZE" in name_upper or "MAX" in name_upper or
|
|
380
|
+
"MIN" in name_upper or "TTL" in name_upper or
|
|
381
|
+
"TIMEOUT" in name_upper or "PORT" in name_upper):
|
|
382
|
+
checks.append("int")
|
|
383
|
+
|
|
384
|
+
if ("ENABLED" in name_upper or "DEBUG" in name_upper or
|
|
385
|
+
"DISABLE" in name_upper or "DRY_RUN" in name_upper or
|
|
386
|
+
"VERBOSE" in name_upper):
|
|
387
|
+
checks.append("bool")
|
|
388
|
+
|
|
389
|
+
if ("LIST" in name_upper or "ARGS" in name_upper or
|
|
390
|
+
"OPTIONS" in name_upper or "ITEMS" in name_upper):
|
|
391
|
+
checks.append("list")
|
|
392
|
+
|
|
393
|
+
return checks
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def main():
|
|
397
|
+
parser = argparse.ArgumentParser(
|
|
398
|
+
description="环境变量验证器 — 检查 .env 文件中的变量",
|
|
399
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
400
|
+
epilog="""
|
|
401
|
+
示例:
|
|
402
|
+
envcheck 扫描当前目录的 .env
|
|
403
|
+
envcheck .env.production 指定文件
|
|
404
|
+
envcheck --require DB_HOST DB_PORT 指定必需变量
|
|
405
|
+
envcheck --template .env.example 对比模板
|
|
406
|
+
envcheck --json JSON 输出
|
|
407
|
+
""",
|
|
408
|
+
)
|
|
409
|
+
parser.add_argument("file", nargs="?", help="指定 .env 文件路径")
|
|
410
|
+
parser.add_argument("--require", "-r", nargs="+", help="必需的变量名列表")
|
|
411
|
+
parser.add_argument("--template", "-t", help="参考模板文件")
|
|
412
|
+
parser.add_argument("--json", "-j", action="store_true", help="JSON 格式输出")
|
|
413
|
+
args = parser.parse_args()
|
|
414
|
+
|
|
415
|
+
try:
|
|
416
|
+
sys.exit(check(args))
|
|
417
|
+
except KeyboardInterrupt:
|
|
418
|
+
print()
|
|
419
|
+
sys.exit(130)
|
|
420
|
+
except Exception as e:
|
|
421
|
+
print(f"错误: {e}", file=sys.stderr)
|
|
422
|
+
sys.exit(1)
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
if __name__ == "__main__":
|
|
426
|
+
main()
|