devkit-tools 0.1.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.
- dev_toolkit/__init__.py +3 -0
- dev_toolkit/__main__.py +4 -0
- dev_toolkit/cli.py +19 -0
- dev_toolkit/convert/__init__.py +6 -0
- dev_toolkit/convert/cli.py +55 -0
- dev_toolkit/convert/core.py +40 -0
- dev_toolkit/convert/handlers.py +125 -0
- dev_toolkit/envcheck/__init__.py +5 -0
- dev_toolkit/envcheck/checks.py +73 -0
- dev_toolkit/envcheck/cli.py +27 -0
- dev_toolkit/envcheck/core.py +41 -0
- dev_toolkit/envcheck/reporters.py +49 -0
- devkit_tools-0.1.0.dist-info/METADATA +95 -0
- devkit_tools-0.1.0.dist-info/RECORD +17 -0
- devkit_tools-0.1.0.dist-info/WHEEL +4 -0
- devkit_tools-0.1.0.dist-info/entry_points.txt +2 -0
- devkit_tools-0.1.0.dist-info/licenses/LICENSE +21 -0
dev_toolkit/__init__.py
ADDED
dev_toolkit/__main__.py
ADDED
dev_toolkit/cli.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""devkit 主 CLI 组。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from . import __version__
|
|
8
|
+
from .convert import convert_cmd
|
|
9
|
+
from .envcheck import envcheck_cmd
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@click.group()
|
|
13
|
+
@click.version_option(__version__, prog_name="devkit")
|
|
14
|
+
def cli() -> None:
|
|
15
|
+
"""devkit — 面向开发者的命令行工具包。"""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
cli.add_command(convert_cmd)
|
|
19
|
+
cli.add_command(envcheck_cmd)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""convert 子命令的 Click 入口。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
from .core import convert
|
|
11
|
+
from .handlers import FORMATS, ConvertError
|
|
12
|
+
|
|
13
|
+
# JSON/YAML/TOML 整树加载,设输入大小上限(requirements.md NF-001)
|
|
14
|
+
MAX_INPUT_BYTES = 50 * 1024 * 1024
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@click.command("convert")
|
|
18
|
+
@click.argument("input", type=click.Path(dir_okay=False, path_type=Path), required=False)
|
|
19
|
+
@click.argument("output", type=click.Path(dir_okay=False, path_type=Path), required=False)
|
|
20
|
+
@click.option("--from", "from_fmt", type=click.Choice(FORMATS), help="输入格式(默认自动检测)")
|
|
21
|
+
@click.option("--to", "to_fmt", type=click.Choice(FORMATS), help="输出格式(默认按 output 扩展名)")
|
|
22
|
+
@click.option("--pretty", is_flag=True, help="美化输出")
|
|
23
|
+
def convert_cmd(input: Path | None, output: Path | None,
|
|
24
|
+
from_fmt: str | None, to_fmt: str | None, pretty: bool) -> None:
|
|
25
|
+
"""在 JSON / YAML / TOML / CSV 之间转换。
|
|
26
|
+
|
|
27
|
+
INPUT 省略时从 stdin 读取;OUTPUT 省略时写到 stdout。
|
|
28
|
+
"""
|
|
29
|
+
filename = None
|
|
30
|
+
if input is None:
|
|
31
|
+
text = sys.stdin.read()
|
|
32
|
+
else:
|
|
33
|
+
raw = input.read_bytes()
|
|
34
|
+
if len(raw) > MAX_INPUT_BYTES:
|
|
35
|
+
raise click.ClickException(
|
|
36
|
+
f"输入超过上限 {MAX_INPUT_BYTES} 字节;大文件转换暂不支持"
|
|
37
|
+
)
|
|
38
|
+
text = raw.decode("utf-8")
|
|
39
|
+
filename = input.name
|
|
40
|
+
|
|
41
|
+
if to_fmt is None and output is not None:
|
|
42
|
+
from .core import detect_format
|
|
43
|
+
to_fmt = detect_format("", output.name)
|
|
44
|
+
if to_fmt is None:
|
|
45
|
+
raise click.ClickException("无法确定输出格式,请用 --to 指定")
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
result = convert(text, to_fmt, from_fmt=from_fmt, filename=filename, pretty=pretty)
|
|
49
|
+
except ConvertError as exc:
|
|
50
|
+
raise click.ClickException(str(exc)) from exc
|
|
51
|
+
|
|
52
|
+
if output is None:
|
|
53
|
+
click.echo(result, nl=False)
|
|
54
|
+
else:
|
|
55
|
+
output.write_text(result, encoding="utf-8")
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""convert 统一转换接口:格式检测 + 读 → 写。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .handlers import FORMATS, ConvertError, dump, load
|
|
6
|
+
|
|
7
|
+
_EXT = {
|
|
8
|
+
".json": "json",
|
|
9
|
+
".yaml": "yaml",
|
|
10
|
+
".yml": "yaml",
|
|
11
|
+
".toml": "toml",
|
|
12
|
+
".csv": "csv",
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
# ponytail: 内容嗅探只覆盖无歧义的首字符;拿不准就让用户用 --from。
|
|
16
|
+
def detect_format(text: str, filename: str | None = None) -> str | None:
|
|
17
|
+
"""从扩展名优先、内容嗅探兜底地推断格式。无法确定返回 None。"""
|
|
18
|
+
if filename:
|
|
19
|
+
for ext, fmt in _EXT.items():
|
|
20
|
+
if filename.lower().endswith(ext):
|
|
21
|
+
return fmt
|
|
22
|
+
stripped = text.lstrip()
|
|
23
|
+
if not stripped:
|
|
24
|
+
return None
|
|
25
|
+
if stripped[0] in "{[":
|
|
26
|
+
return "json"
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def convert(text: str, to_fmt: str, from_fmt: str | None = None,
|
|
31
|
+
filename: str | None = None, pretty: bool = False) -> str:
|
|
32
|
+
if from_fmt is None:
|
|
33
|
+
from_fmt = detect_format(text, filename)
|
|
34
|
+
if from_fmt is None:
|
|
35
|
+
raise ConvertError(
|
|
36
|
+
"无法自动检测输入格式,请用 --from 指定"
|
|
37
|
+
f"(支持:{', '.join(FORMATS)})"
|
|
38
|
+
)
|
|
39
|
+
data = load(text, from_fmt)
|
|
40
|
+
return dump(data, to_fmt, pretty=pretty)
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""各数据格式的读写处理器。
|
|
2
|
+
|
|
3
|
+
读 → 统一的 Python dict/list/scalar 数据模型 → 写。
|
|
4
|
+
CSV 仅支持二维表(表头 + 行记录),见 requirements.md 2.2.1 转换边界。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import csv
|
|
10
|
+
import io
|
|
11
|
+
import json
|
|
12
|
+
import sys
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
import yaml
|
|
16
|
+
|
|
17
|
+
# TOML 读:tomllib(3.11+) / tomli(3.10 回退);写:tomli-w
|
|
18
|
+
if sys.version_info >= (3, 11):
|
|
19
|
+
import tomllib as _toml_read
|
|
20
|
+
else: # pragma: no cover - 仅 3.10 走这里
|
|
21
|
+
import tomli as _toml_read
|
|
22
|
+
|
|
23
|
+
import tomli_w
|
|
24
|
+
|
|
25
|
+
FORMATS = ("json", "yaml", "toml", "csv")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ConvertError(ValueError):
|
|
29
|
+
"""转换边界相关的可预期错误(格式不支持、CSV 结构不符等)。"""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# ---- 读:bytes/str → 数据模型 ----
|
|
33
|
+
|
|
34
|
+
def _load_json(text: str) -> Any:
|
|
35
|
+
return json.loads(text)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _load_yaml(text: str) -> Any:
|
|
39
|
+
return yaml.safe_load(text)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _load_toml(text: str) -> Any:
|
|
43
|
+
return _toml_read.loads(text)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _load_csv(text: str) -> list[dict[str, str]]:
|
|
47
|
+
return list(csv.DictReader(io.StringIO(text)))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
_LOADERS = {
|
|
51
|
+
"json": _load_json,
|
|
52
|
+
"yaml": _load_yaml,
|
|
53
|
+
"toml": _load_toml,
|
|
54
|
+
"csv": _load_csv,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# ---- 写:数据模型 → str ----
|
|
59
|
+
|
|
60
|
+
def _dump_json(data: Any, pretty: bool) -> str:
|
|
61
|
+
return json.dumps(data, ensure_ascii=False, indent=2 if pretty else None)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _dump_yaml(data: Any, pretty: bool) -> str:
|
|
65
|
+
# pretty 对 YAML 无额外含义;safe_dump 已是可读多行格式
|
|
66
|
+
return yaml.safe_dump(data, allow_unicode=True, sort_keys=False)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _dump_toml(data: Any, pretty: bool) -> str:
|
|
70
|
+
if not isinstance(data, dict):
|
|
71
|
+
raise ConvertError("TOML 顶层必须是表(dict),无法写入列表或标量")
|
|
72
|
+
return tomli_w.dumps(data)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _dump_csv(data: Any, pretty: bool) -> str:
|
|
76
|
+
rows = _as_table(data)
|
|
77
|
+
fieldnames: list[str] = []
|
|
78
|
+
for row in rows:
|
|
79
|
+
for key in row:
|
|
80
|
+
if key not in fieldnames:
|
|
81
|
+
fieldnames.append(key)
|
|
82
|
+
buf = io.StringIO()
|
|
83
|
+
writer = csv.DictWriter(buf, fieldnames=fieldnames)
|
|
84
|
+
writer.writeheader()
|
|
85
|
+
writer.writerows(rows)
|
|
86
|
+
return buf.getvalue()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _as_table(data: Any) -> list[dict[str, Any]]:
|
|
90
|
+
"""把数据模型规约为 CSV 二维表(list[dict[扁平标量]]),否则显式失败。"""
|
|
91
|
+
if isinstance(data, dict):
|
|
92
|
+
data = [data]
|
|
93
|
+
if not isinstance(data, list):
|
|
94
|
+
raise ConvertError("CSV 仅支持二维表:需要对象列表或单个对象")
|
|
95
|
+
rows: list[dict[str, Any]] = []
|
|
96
|
+
for item in data:
|
|
97
|
+
if not isinstance(item, dict):
|
|
98
|
+
raise ConvertError("CSV 行必须是对象(dict)")
|
|
99
|
+
for key, value in item.items():
|
|
100
|
+
if isinstance(value, (dict, list)):
|
|
101
|
+
raise ConvertError(
|
|
102
|
+
f"CSV 不支持嵌套字段 {key!r};请先显式展开为扁平字段"
|
|
103
|
+
)
|
|
104
|
+
rows.append(item)
|
|
105
|
+
return rows
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
_DUMPERS = {
|
|
109
|
+
"json": _dump_json,
|
|
110
|
+
"yaml": _dump_yaml,
|
|
111
|
+
"toml": _dump_toml,
|
|
112
|
+
"csv": _dump_csv,
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def load(text: str, fmt: str) -> Any:
|
|
117
|
+
if fmt not in _LOADERS:
|
|
118
|
+
raise ConvertError(f"不支持的输入格式:{fmt}(支持:{', '.join(FORMATS)})")
|
|
119
|
+
return _LOADERS[fmt](text)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def dump(data: Any, fmt: str, pretty: bool = False) -> str:
|
|
123
|
+
if fmt not in _DUMPERS:
|
|
124
|
+
raise ConvertError(f"不支持的输出格式:{fmt}(支持:{', '.join(FORMATS)})")
|
|
125
|
+
return _DUMPERS[fmt](data, pretty)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""单个工具检查的实现:定位可执行文件、取版本、比对最低版本。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
|
|
10
|
+
from packaging.version import InvalidVersion, Version
|
|
11
|
+
|
|
12
|
+
_VERSION_RE = re.compile(r"(\d+\.\d+(?:\.\d+)?)")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class CheckSpec:
|
|
17
|
+
"""一条检查规格(来自 YAML 配置)。"""
|
|
18
|
+
|
|
19
|
+
name: str
|
|
20
|
+
command: str
|
|
21
|
+
version_arg: str = "--version"
|
|
22
|
+
min_version: str | None = None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class CheckResult:
|
|
27
|
+
name: str
|
|
28
|
+
found: bool
|
|
29
|
+
path: str | None
|
|
30
|
+
version: str | None
|
|
31
|
+
ok: bool
|
|
32
|
+
detail: str
|
|
33
|
+
|
|
34
|
+
def suggestion(self) -> str | None:
|
|
35
|
+
if not self.found:
|
|
36
|
+
return f"未找到 {self.name},请安装并确保其在 PATH 中"
|
|
37
|
+
if not self.ok:
|
|
38
|
+
return f"{self.name} 版本 {self.version} 低于要求,请升级"
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _query_version(command: str, version_arg: str) -> str | None:
|
|
43
|
+
try:
|
|
44
|
+
proc = subprocess.run(
|
|
45
|
+
[command, version_arg],
|
|
46
|
+
capture_output=True, text=True, timeout=10,
|
|
47
|
+
)
|
|
48
|
+
except (OSError, subprocess.SubprocessError):
|
|
49
|
+
return None
|
|
50
|
+
output = (proc.stdout or "") + (proc.stderr or "")
|
|
51
|
+
match = _VERSION_RE.search(output)
|
|
52
|
+
return match.group(1) if match else None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def run_check(spec: CheckSpec) -> CheckResult:
|
|
56
|
+
path = shutil.which(spec.command)
|
|
57
|
+
if path is None:
|
|
58
|
+
return CheckResult(spec.name, False, None, None, False, "未安装或不在 PATH")
|
|
59
|
+
|
|
60
|
+
version = _query_version(spec.command, spec.version_arg)
|
|
61
|
+
if spec.min_version is None:
|
|
62
|
+
return CheckResult(spec.name, True, path, version, True, "已安装")
|
|
63
|
+
|
|
64
|
+
if version is None:
|
|
65
|
+
return CheckResult(spec.name, True, path, None, False, "无法解析版本")
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
ok = Version(version) >= Version(spec.min_version)
|
|
69
|
+
except InvalidVersion:
|
|
70
|
+
return CheckResult(spec.name, True, path, version, False, "版本号格式异常")
|
|
71
|
+
|
|
72
|
+
detail = "满足要求" if ok else f"需要 >= {spec.min_version}"
|
|
73
|
+
return CheckResult(spec.name, True, path, version, ok, detail)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""envcheck 子命令的 Click 入口。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
from .core import load_specs, run_all
|
|
11
|
+
from .reporters import render
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@click.command("envcheck")
|
|
15
|
+
@click.option("--config", type=click.Path(exists=True, dir_okay=False, path_type=Path),
|
|
16
|
+
help="YAML 检查项配置(默认使用预置检查项)")
|
|
17
|
+
@click.option("--format", "fmt", type=click.Choice(["table", "json"]), default="table",
|
|
18
|
+
show_default=True, help="报告格式(html 属 v0.2+)")
|
|
19
|
+
@click.option("--suggest-fix", is_flag=True, help="输出修复建议(不修改系统)")
|
|
20
|
+
def envcheck_cmd(config: Path | None, fmt: str, suggest_fix: bool) -> None:
|
|
21
|
+
"""诊断开发环境:检查必备工具的安装与版本。"""
|
|
22
|
+
specs = load_specs(config)
|
|
23
|
+
results = run_all(specs)
|
|
24
|
+
click.echo(render(results, fmt, suggest_fix))
|
|
25
|
+
# 任一检查未通过 → 非零退出码(requirements.md NF-003)
|
|
26
|
+
if any(not r.ok for r in results):
|
|
27
|
+
sys.exit(1)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""envcheck 引擎:加载配置 → 并行跑检查 → 汇总结果。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import yaml
|
|
9
|
+
|
|
10
|
+
from .checks import CheckResult, CheckSpec, run_check
|
|
11
|
+
|
|
12
|
+
# 无配置文件时的预置检查项
|
|
13
|
+
DEFAULT_CHECKS = [
|
|
14
|
+
CheckSpec("Python", "python", "--version", "3.10"),
|
|
15
|
+
CheckSpec("Git", "git", "--version"),
|
|
16
|
+
CheckSpec("Node.js", "node", "--version"),
|
|
17
|
+
CheckSpec("Docker", "docker", "--version"),
|
|
18
|
+
CheckSpec("Go", "go", "version"),
|
|
19
|
+
CheckSpec("Rust", "rustc", "--version"),
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def load_specs(config: Path | None) -> list[CheckSpec]:
|
|
24
|
+
if config is None:
|
|
25
|
+
return DEFAULT_CHECKS
|
|
26
|
+
data = yaml.safe_load(config.read_text(encoding="utf-8")) or {}
|
|
27
|
+
specs = []
|
|
28
|
+
for item in data.get("checks", []):
|
|
29
|
+
specs.append(CheckSpec(
|
|
30
|
+
name=item["name"],
|
|
31
|
+
command=item["command"],
|
|
32
|
+
version_arg=item.get("version_arg", "--version"),
|
|
33
|
+
min_version=item.get("min_version"),
|
|
34
|
+
))
|
|
35
|
+
return specs
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def run_all(specs: list[CheckSpec]) -> list[CheckResult]:
|
|
39
|
+
# 检查项可并行执行(requirements.md 2.4 #5);I/O 密集用线程足够
|
|
40
|
+
with ThreadPoolExecutor(max_workers=8) as pool:
|
|
41
|
+
return list(pool.map(run_check, specs))
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""envcheck 报告输出:table / json。
|
|
2
|
+
|
|
3
|
+
HTML 报告属 v0.2+ 路线图(requirements.md 2.6 Later),v0.1 不实现。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
|
|
10
|
+
from .checks import CheckResult
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def report_table(results: list[CheckResult], suggest_fix: bool) -> str:
|
|
14
|
+
lines = []
|
|
15
|
+
width = max((len(r.name) for r in results), default=4)
|
|
16
|
+
for r in results:
|
|
17
|
+
mark = "OK " if r.ok else "FAIL"
|
|
18
|
+
ver = r.version or "-"
|
|
19
|
+
lines.append(f"[{mark}] {r.name:<{width}} {ver:<12} {r.detail}")
|
|
20
|
+
if suggest_fix:
|
|
21
|
+
fixes = [s for r in results if (s := r.suggestion())]
|
|
22
|
+
if fixes:
|
|
23
|
+
lines.append("")
|
|
24
|
+
lines.append("修复建议:")
|
|
25
|
+
lines.extend(f" - {f}" for f in fixes)
|
|
26
|
+
return "\n".join(lines)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def report_json(results: list[CheckResult], suggest_fix: bool) -> str:
|
|
30
|
+
payload = []
|
|
31
|
+
for r in results:
|
|
32
|
+
entry = {
|
|
33
|
+
"name": r.name,
|
|
34
|
+
"found": r.found,
|
|
35
|
+
"path": r.path,
|
|
36
|
+
"version": r.version,
|
|
37
|
+
"ok": r.ok,
|
|
38
|
+
"detail": r.detail,
|
|
39
|
+
}
|
|
40
|
+
if suggest_fix:
|
|
41
|
+
entry["suggestion"] = r.suggestion()
|
|
42
|
+
payload.append(entry)
|
|
43
|
+
return json.dumps(payload, ensure_ascii=False, indent=2)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def render(results: list[CheckResult], fmt: str, suggest_fix: bool) -> str:
|
|
47
|
+
if fmt == "json":
|
|
48
|
+
return report_json(results, suggest_fix)
|
|
49
|
+
return report_table(results, suggest_fix)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: devkit-tools
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: 面向开发者的命令行工具包:数据格式转换 + 环境诊断
|
|
5
|
+
Project-URL: Homepage, https://github.com/yukinpost/devkit-cli
|
|
6
|
+
Project-URL: Issues, https://github.com/yukinpost/devkit-cli/issues
|
|
7
|
+
Author-email: yukinpost <jasonliaojx98@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: cli,convert,envcheck,json,toml,yaml
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Requires-Dist: click>=8.1
|
|
20
|
+
Requires-Dist: packaging>=23.0
|
|
21
|
+
Requires-Dist: pyyaml>=6.0
|
|
22
|
+
Requires-Dist: tomli-w>=1.0
|
|
23
|
+
Requires-Dist: tomli>=2.0; python_version < '3.11'
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest-cov>=4.1; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest>=7.4; extra == 'dev'
|
|
28
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
29
|
+
Requires-Dist: types-pyyaml; extra == 'dev'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# devkit-tools
|
|
33
|
+
|
|
34
|
+
[](https://github.com/yukinpost/devkit-cli/actions/workflows/ci.yml)
|
|
35
|
+
|
|
36
|
+
面向开发者的命令行工具包。v0.1 提供两个开箱即用的子命令:
|
|
37
|
+
|
|
38
|
+
- **`convert`** — 在 JSON / YAML / TOML / CSV 之间互相转换
|
|
39
|
+
- **`envcheck`** — 诊断开发环境中必备工具的安装与版本
|
|
40
|
+
|
|
41
|
+
> 路线图模块(`templater` / `watch` / `gitstats`)见 [docs/requirements.md](docs/requirements.md),将在 v0.2+ 实现。
|
|
42
|
+
|
|
43
|
+
## 安装
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install devkit-tools
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
需要 Python 3.10+。
|
|
50
|
+
|
|
51
|
+
## convert
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# 文件互转(输出格式由扩展名推断)
|
|
55
|
+
devkit convert config.json config.yaml
|
|
56
|
+
|
|
57
|
+
# 显式指定格式
|
|
58
|
+
devkit convert data.txt --from json --to toml
|
|
59
|
+
|
|
60
|
+
# 管道模式
|
|
61
|
+
cat data.json | devkit convert --to yaml
|
|
62
|
+
|
|
63
|
+
# 美化输出
|
|
64
|
+
devkit convert data.json --to json --pretty
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
支持矩阵与边界(如 CSV 仅支持二维表)见 [docs/requirements.md](docs/requirements.md) 2.2.1。
|
|
68
|
+
|
|
69
|
+
## envcheck
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# 用预置检查项诊断环境
|
|
73
|
+
devkit envcheck
|
|
74
|
+
|
|
75
|
+
# 自定义检查项 + 输出修复建议
|
|
76
|
+
devkit envcheck --config examples/config/dev-toolkit.yml --suggest-fix
|
|
77
|
+
|
|
78
|
+
# JSON 报告(便于 CI 消费)
|
|
79
|
+
devkit envcheck --format json
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
任一检查未通过时返回非零退出码。`--suggest-fix` 只输出建议,**不会修改你的系统**。
|
|
83
|
+
|
|
84
|
+
## 开发
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
pip install -e ".[dev]"
|
|
88
|
+
pytest # 测试 + 覆盖率
|
|
89
|
+
ruff check . # lint
|
|
90
|
+
mypy # 类型检查
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
dev_toolkit/__init__.py,sha256=KGDCUdaySLJ91l-9PXsq40jJxBg6OCf3GdZdIWGEbgc,80
|
|
2
|
+
dev_toolkit/__main__.py,sha256=8hDtWlaFZK24KhfNq_ZKgtXqYHsDQDetukOCMlsbW0Q,59
|
|
3
|
+
dev_toolkit/cli.py,sha256=myujRTrg5as8eL4i6DNifRjQDZHVrRVKKCAeKofSVw8,386
|
|
4
|
+
dev_toolkit/convert/__init__.py,sha256=Ji1m1-_F737VfIOW8Z2h5XAgJMOGUXPl_1bMcKzG3pA,152
|
|
5
|
+
dev_toolkit/convert/cli.py,sha256=HppjOsQEGF2kxPeHJJ1UTHSu7zXixEHm6p9uQ2XwSco,2002
|
|
6
|
+
dev_toolkit/convert/core.py,sha256=C3XQFdmeg-kwNl0abWq-1-JIyeWvggW6mEER3AY_CBU,1268
|
|
7
|
+
dev_toolkit/convert/handlers.py,sha256=o0TK-7PQik5wWd1_Piuc4y7FkMXSE23_ApTuEand5U8,3445
|
|
8
|
+
dev_toolkit/envcheck/__init__.py,sha256=mrhO5oQtI41nE6BDGpvi-i9yQsQT8fs35xo3zQSp-Uo,111
|
|
9
|
+
dev_toolkit/envcheck/checks.py,sha256=zVEIpgaf25cuh2ibzmfuo_hm3frt5l6xxKMYQwICF0w,2137
|
|
10
|
+
dev_toolkit/envcheck/cli.py,sha256=Rn7mwmodFjI0UMlM3d4Idfypz0v2HDtfa1LYaN5-HWw,1040
|
|
11
|
+
dev_toolkit/envcheck/core.py,sha256=0N-p9EotwnarUpNFDjW3NKQ4nWB_e1zzHCuIWi0XBlY,1314
|
|
12
|
+
dev_toolkit/envcheck/reporters.py,sha256=AHY-cGd_yNe3WsZdx9eKP_vhNpPsLBAWqPiP4x95PYE,1457
|
|
13
|
+
devkit_tools-0.1.0.dist-info/METADATA,sha256=NhxPCD78d-gt6DxPL2DTKQQ_ICzumYmnXaYJk-9dzCM,2761
|
|
14
|
+
devkit_tools-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
15
|
+
devkit_tools-0.1.0.dist-info/entry_points.txt,sha256=NMEr1Y1mN_WFDDEdPrUxhsByqtJOCHYVm6ZC7-NsVRg,47
|
|
16
|
+
devkit_tools-0.1.0.dist-info/licenses/LICENSE,sha256=6vA3xWebNN8urkgFj8MH5DLC3mGwtMg8PPD1MRtw_kY,1067
|
|
17
|
+
devkit_tools-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jason Liao
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|