diff --git a/src/pyflowx/cli/folderback.py b/src/pyflowx/cli/folderback.py index 0de7b91..5144b0f 100644 --- a/src/pyflowx/cli/folderback.py +++ b/src/pyflowx/cli/folderback.py @@ -66,19 +66,10 @@ def backup_folder(src: str, dst: str, max_zip: int = 5) -> None: zip_target(src_path, dst_path, max_zip) -# ============================================================================ -# TaskSpec 定义 -# ============================================================================ - -folderback_default: px.TaskSpec = px.TaskSpec( - "folderback_default", - fn=lambda: backup_folder(".", "./backup", 5), -) - - -# ============================================================================ -# CLI Runner -# ============================================================================ +@px.task +def folderback_default() -> None: + """备份当前目录到 ./backup.""" + backup_folder(".", "./backup", 5) def main() -> None: diff --git a/src/pyflowx/cli/folderzip.py b/src/pyflowx/cli/folderzip.py index 644ccc1..fc28ffd 100644 --- a/src/pyflowx/cli/folderzip.py +++ b/src/pyflowx/cli/folderzip.py @@ -57,16 +57,10 @@ def zip_folders(cwd: str = ".") -> None: archive_folder(dir_path) -# ============================================================================ -# TaskSpec 定义 -# ============================================================================ - -folderzip_default: px.TaskSpec = px.TaskSpec("folderzip_default", fn=lambda: zip_folders(".")) - - -# ============================================================================ -# CLI Runner -# ============================================================================ +@px.task +def folderzip_default() -> None: + """压缩当前目录下的所有文件夹.""" + zip_folders(".") def main() -> None: diff --git a/src/pyflowx/cli/gittool.py b/src/pyflowx/cli/gittool.py index f7fb116..b6f30a0 100644 --- a/src/pyflowx/cli/gittool.py +++ b/src/pyflowx/cli/gittool.py @@ -46,7 +46,12 @@ def init_sub_dirs() -> None: ) -isub: px.TaskSpec = px.TaskSpec("isub", fn=init_sub_dirs) +@px.task(name="isub") +def isub() -> None: + """初始化子目录的Git仓库.""" + init_sub_dirs() + + push: px.TaskSpec = px.TaskSpec("push", cmd=["git", "push"]) pull: px.TaskSpec = px.TaskSpec("pull", cmd=["git", "pull"]) kill_tgit: px.TaskSpec = px.TaskSpec("task_kill", cmd=["taskkill", "/f", "/t", "/im", "tgitcache.exe"]) @@ -73,11 +78,11 @@ def main() -> None: px.TaskSpec("add", cmd=["git", "add", "."], conditions=(lambda _: has_files(),)), px.TaskSpec("commit", cmd=["git", "commit", "-m", "chore: update"], depends_on=("add",)), ]), - # 清理 - "c": px.Graph.from_specs([ + # 清理(chain: clean → status) + "c": px.Graph().chain( px.TaskSpec("clean", cmd=["git", "clean", "-xfd", *EXCLUDE_CMDS]), - px.TaskSpec("status", cmd=["git", "status", "--porcelain"], depends_on=("clean",)), - ]), + px.TaskSpec("status", cmd=["git", "status", "--porcelain"]), + ), # 初始化、添加并提交 "i": px.Graph.from_specs([ px.TaskSpec("init", cmd=["git", "init"], conditions=(lambda _: not_has_git_repo(),)), diff --git a/src/pyflowx/cli/pymake.py b/src/pyflowx/cli/pymake.py index cbbfcdc..bb50120 100644 --- a/src/pyflowx/cli/pymake.py +++ b/src/pyflowx/cli/pymake.py @@ -9,25 +9,14 @@ from __future__ import annotations import pyflowx as px from pyflowx.conditions import Constants - -def maturin_build_cmd() -> list[str]: - """获取 maturin 构建命令(根据平台自动添加参数). - - Returns - ------- - list[str] - 完整的 maturin 构建命令列表. - """ - command = ["maturin", "build", "-r"].copy() - if Constants.IS_WINDOWS: - command.extend(["--target", "x86_64-win7-windows-msvc", "-Zbuild-std", "-i", "python3.8"]) - return command - +MATURIN_BUILD_COMMAND = ["maturin", "build", "-r"] +if Constants.IS_WINDOWS: + MATURIN_BUILD_COMMAND.extend(["--target", "x86_64-win7-windows-msvc", "-Zbuild-std", "-i", "python3.8"]) # 扁平注册所有任务(px.cmd 自动从命令前两段推导 name) tasks: list[px.TaskSpec] = [ px.cmd(["uv", "build"]), - px.cmd(maturin_build_cmd(), name="maturin_build"), + px.cmd(MATURIN_BUILD_COMMAND), px.cmd(["uv", "sync"]), px.cmd(["gitt", "c"], name="git_clean"), px.cmd( @@ -42,20 +31,40 @@ tasks: list[px.TaskSpec] = [ ["pytest", "--cov", "-n", "8", "--dist", "loadfile", "--tb=short", "-v", "--color=yes", "--durations=10"], name="test_coverage", ), - px.cmd(["pyrefly", "check", "."], name="pyrefly_check"), + px.cmd(["pyrefly", "check", "."]), px.cmd(["git", "add", "-A"], name="git_add_all"), - px.cmd(["bumpversion"], name="bumpversion"), - px.cmd(["bumpversion", "minor"], name="bumpversion_minor"), - px.cmd(["git", "push"], name="git_push"), + px.cmd(["bumpversion"]), + px.cmd(["bumpversion", "minor"]), + px.cmd(["git", "push"]), px.cmd(["git", "push", "--tags"], name="git_push_tags"), px.cmd(["hatch", "publish"], name="publish_python"), px.cmd(["twine", "upload", "--disable-progress-bar"], name="twine_publish"), ] -# 单任务别名(alias 名与任务名相同):直接用 TaskSpec,避免 str 自引用 -_doc = px.cmd(["sphinx-build", "-b", "html", "docs", "docs/_build"], name="doc") -_lint = px.cmd(["ruff", "check", "--fix", "--unsafe-fixes"], name="lint") -_tox = px.cmd(["tox", "-p", "auto"], name="tox") +# 单任务别名(alias 名与任务名相同):直接内联 TaskSpec,避免 str 自引用 +aliases: dict[str, str | list[str | px.TaskSpec] | px.TaskSpec | px.Graph] = { + # 构建命令 + "b": "uv_build", + "bc": "maturin_build", + "ba": ["b", "bc"], + # 安装命令 + "sync": "uv_sync", + # 清理命令 + "c": "git_clean", + # 开发工具 + "bump": ["c", "tc", "git_add_all", "bumpversion"], + "bumpmi": "bumpversion_minor", + "cov": ["git_clean", "test_coverage"], + "doc": px.cmd(["sphinx-build", "-b", "html", "docs", "docs/_build"], name="doc"), + "lint": px.cmd(["ruff", "check", "--fix", "--unsafe-fixes"], name="lint"), + "pb": ["twine_publish", "publish_python"], + "t": "test", + "tf": "test_fast", + "tc": ["pyrefly_check", "lint"], + "tox": px.cmd(["tox", "-p", "auto"], name="tox"), + # 发布命令 + "p": ["git_clean", "git_push", "git_push_tags"], +} def main() -> None: @@ -103,32 +112,5 @@ def main() -> None: pymake lint # 格式化代码 pymake type # 类型检查 """ - runner = px.CliRunner( - strategy="sequential", - description="PyMake - Python 构建工具", - tasks=tasks, - aliases={ - # 构建命令 - "b": "uv_build", - "bc": "maturin_build", - "ba": ["b", "bc"], - # 安装命令 - "sync": "uv_sync", - # 清理命令 - "c": "git_clean", - # 开发工具 - "bump": ["c", "tc", "git_add_all", "bumpversion"], - "bumpmi": "bumpversion_minor", - "cov": ["git_clean", "test_coverage"], - "doc": _doc, - "lint": _lint, - "pb": ["twine_publish", "publish_python"], - "t": "test", - "tf": "test_fast", - "tc": ["pyrefly_check", "lint"], - "tox": _tox, - # 发布命令 - "p": ["git_clean", "git_push", "git_push_tags"], - }, - ) + runner = px.CliRunner(strategy="sequential", description="PyMake - Python 构建工具", tasks=tasks, aliases=aliases) runner.run_cli() diff --git a/tests/cli/test_pymake.py b/tests/cli/test_pymake.py index 5ceed32..94d3ba8 100644 --- a/tests/cli/test_pymake.py +++ b/tests/cli/test_pymake.py @@ -7,78 +7,20 @@ from unittest.mock import patch import pytest from pyflowx.cli import pymake -from pyflowx.conditions import Constants - - -# ---------------------------------------------------------------------- # -# maturin_build_cmd -# ---------------------------------------------------------------------- # -class TestMaturinBuildCmd: - """Test maturin_build_cmd function.""" - - def test_returns_list(self) -> None: - """Should return a list.""" - cmd = pymake.maturin_build_cmd() - assert isinstance(cmd, list) - - def test_contains_maturin_build(self) -> None: - """Should contain 'maturin' and 'build'.""" - cmd = pymake.maturin_build_cmd() - assert "maturin" in cmd - assert "build" in cmd - - def test_contains_release_flag(self) -> None: - """Should contain release flag '-r'.""" - cmd = pymake.maturin_build_cmd() - assert "-r" in cmd - - def test_windows_includes_target(self) -> None: - """On Windows, should include target-specific flags.""" - cmd = pymake.maturin_build_cmd() - if Constants.IS_WINDOWS: - assert "--target" in cmd - assert "x86_64-win7-windows-msvc" in cmd - assert "-Zbuild-std" in cmd - assert "-i" in cmd - assert "python3.8" in cmd - else: - # On non-Windows, should not include Windows-specific flags - assert "--target" not in cmd - - def test_does_not_mutate_on_multiple_calls(self) -> None: - """Multiple calls should return independent lists.""" - cmd1 = pymake.maturin_build_cmd() - cmd2 = pymake.maturin_build_cmd() - assert cmd1 == cmd2 - # Mutating one should not affect the other - cmd1.append("extra") - assert "extra" not in cmd2 - - def test_non_windows_excludes_target_flags(self) -> None: - """On non-Windows, should not include Windows-specific flags (覆盖 22->32 分支).""" - from unittest.mock import patch - - with patch.object(pymake.Constants, "IS_WINDOWS", False): - cmd = pymake.maturin_build_cmd() - assert "maturin" in cmd - assert "build" in cmd - assert "-r" in cmd - assert "--target" not in cmd - assert "-Zbuild-std" not in cmd # ---------------------------------------------------------------------- # # TaskSpec definitions # ---------------------------------------------------------------------- # def _find_task(name: str) -> pymake.px.TaskSpec: - """从 pymake.tasks 或单任务别名变量中查找指定名称的 TaskSpec.""" + """从 pymake.tasks 或 aliases 中查找指定名称的 TaskSpec.""" for spec in pymake.tasks: if spec.name == name: return spec - # 单任务别名变量(_doc/_lint/_tox) - alias_map = {"doc": pymake._doc, "lint": pymake._lint, "tox": pymake._tox} - if name in alias_map: - return alias_map[name] + # 单任务别名(doc/lint/tox)内联在 aliases dict 中 + value = pymake.aliases.get(name) + if isinstance(value, pymake.px.TaskSpec): + return value raise KeyError(f"任务 {name!r} 未找到")