feat: 新增 13 个 CLI 工具的 YAML 配置并修复 _ops 函数注册
CI / Lint, Typecheck & Test (push) Has been cancelled

- 在 cli/configs/ 下创建 13 个 YAML 工作流配置, 覆盖 filedate/filelevel/folderback/
  folderzip/autofmt/bumpversion/piptool/gittool/pdftool/screenshot/lscalc/
  sshcopyid/packtool 工具, 共 51 个 job (cmd 与 fn 混合)
- yaml_loader 模块级导入 _ops 子模块, 使 YAML fn 字段可引用注册函数,
  try/except 守卫避免最小安装场景下的 ImportError
- 修复 test_registry 的 clear_registry fixture: 保存/恢复 _REGISTRY 原始状态,
  避免 teardown 清空 _ops 自动注册的函数导致 TestOpsModules 失败
This commit is contained in:
2026-07-04 18:35:20 +08:00
parent 6931f36fd1
commit 5d0b211a44
16 changed files with 349 additions and 1 deletions
+3
View File
@@ -0,0 +1,3 @@
"""Placeholder for configs package."""
from __future__ import annotations
+21
View File
@@ -0,0 +1,21 @@
# autofmt - 自动格式化工具
# 用法:
# yamlrun autofmt.yaml --job fmt --var TARGET=.
# yamlrun autofmt.yaml --job lint --var TARGET=.
# yamlrun autofmt.yaml --job doc --var ROOT_DIR=.
# yamlrun autofmt.yaml --job sync --var ROOT_DIR=.
strategy: thread
variables:
TARGET: "."
ROOT_DIR: "."
jobs:
fmt:
cmd: ["ruff", "format", "${TARGET}"]
lint:
cmd: ["ruff", "check", "--fix", "--unsafe-fixes", "${TARGET}"]
doc:
fn: auto_add_docstrings
args: ["${ROOT_DIR}"]
sync:
fn: sync_pyproject_config
args: ["${ROOT_DIR}"]
+11
View File
@@ -0,0 +1,11 @@
# bumpversion - 版本号自动管理工具
# 用法:
# yamlrun bumpversion.yaml --var PART=patch
# yamlrun bumpversion.yaml --var PART=minor
strategy: sequential
variables:
PART: patch
jobs:
bump:
fn: bump_file_version
args: ["${FILE}", "${PART}"]
+18
View File
@@ -0,0 +1,18 @@
# filedate - 文件日期处理工具
# 用法:
# yamlrun filedate.yaml --var FILES='["file1.txt","file2.txt"]'
# 或通过 Python 入口: filedate add file1.txt file2.txt
strategy: thread
variables:
FILES: []
jobs:
add:
fn: process_files_date
args: ["${FILES}"]
kwargs:
clear: false
clear:
fn: process_files_date
args: ["${FILES}"]
kwargs:
clear: true
+11
View File
@@ -0,0 +1,11 @@
# filelevel - 文件等级重命名工具
# 用法:
# yamlrun filelevel.yaml --var FILES='["file.txt"]' --var LEVEL=2
strategy: thread
variables:
FILES: []
LEVEL: 0
jobs:
set:
fn: process_files_level
args: ["${FILES}", "${LEVEL}"]
+15
View File
@@ -0,0 +1,15 @@
# folderback - 文件夹备份工具
# 用法:
# yamlrun folderback.yaml # 备份当前目录到 ./backup
# yamlrun folderback.yaml --var SRC=./project --var DST=./backup
strategy: thread
variables:
SRC: "."
DST: "./backup"
MAX_ZIP: 5
jobs:
backup:
fn: backup_folder
args: ["${SRC}", "${DST}"]
kwargs:
max_zip: ${MAX_ZIP}
+11
View File
@@ -0,0 +1,11 @@
# folderzip - 文件夹压缩工具
# 用法:
# yamlrun folderzip.yaml # 压缩当前目录下所有文件夹
# yamlrun folderzip.yaml --var CWD=./project
strategy: thread
variables:
CWD: "."
jobs:
zip:
fn: zip_folders
args: ["${CWD}"]
+55
View File
@@ -0,0 +1,55 @@
# gittool - Git 执行工具
# 用法:
# yamlrun gittool.yaml --job a # 添加并提交
# yamlrun gittool.yaml --job c # 清理
# yamlrun gittool.yaml --job i # 初始化、添加并提交
# yamlrun gittool.yaml --job p # 推送
strategy: thread
jobs:
a:
needs: [add]
cmd: ["git", "commit", "-m", "chore: update"]
add:
cmd: ["git", "add", "."]
c:
needs: [clean]
cmd: ["git", "status", "--porcelain"]
clean:
cmd:
- "git"
- "clean"
- "-xfd"
- "-e"
- ".vscode"
- "-e"
- ".idea"
- "-e"
- ".editorconfig"
- "-e"
- ".trae"
- "-e"
- ".qoder"
- "-e"
- ".venv"
- "-e"
- ".git"
- "-e"
- ".tox"
- "-e"
- ".pytest_cache"
- "-e"
- "node_modules"
- "-e"
- ".ruff_cache"
i:
needs: [init, add_all]
cmd: ["git", "commit", "-m", "init commit"]
init:
cmd: ["git", "init"]
add_all:
needs: [init]
cmd: ["git", "add", "."]
p:
cmd: ["git", "push"]
pl:
cmd: ["git", "pull"]
+21
View File
@@ -0,0 +1,21 @@
# lscalc - LS-DYNA 计算工具
# 用法:
# yamlrun lscalc.yaml --job run --var INPUT_FILE=input.k --var NCPU=4
# yamlrun lscalc.yaml --job status
strategy: thread
variables:
INPUT_FILE: input.k
NCPU: 4
jobs:
run:
fn: run_ls_dyna
args: ["${INPUT_FILE}"]
kwargs:
ncpu: ${NCPU}
mpi:
fn: run_ls_dyna_mpi
args: ["${INPUT_FILE}"]
kwargs:
ncpu: ${NCPU}
status:
fn: check_ls_dyna_status
+32
View File
@@ -0,0 +1,32 @@
# packtool - Python 打包工具
# 用法:
# yamlrun packtool.yaml --job src --var PROJECT_DIR=. --var OUTPUT_DIR=.pypack
# yamlrun packtool.yaml --job wheel --var PROJECT_DIR=. --var OUTPUT_DIR=dist
strategy: thread
variables:
PROJECT_DIR: "."
OUTPUT_DIR: ".pypack"
LIB_DIR: "libs"
DEPENDENCIES: []
VERSION: "3.10"
OUTPUT_FILE: "package.zip"
SOURCE_DIR: "."
jobs:
src:
fn: pack_source
args: ["${PROJECT_DIR}", "${OUTPUT_DIR}"]
deps:
fn: pack_dependencies
args: ["${LIB_DIR}", "${DEPENDENCIES}"]
wheel:
fn: pack_wheel
args: ["${PROJECT_DIR}", "${OUTPUT_DIR}"]
embed:
fn: install_embed_python
args: ["${VERSION}", "${OUTPUT_DIR}"]
zip:
fn: create_zip_package
args: ["${SOURCE_DIR}", "${OUTPUT_FILE}"]
clean:
fn: clean_build_dir
args: ["${OUTPUT_DIR}"]
+70
View File
@@ -0,0 +1,70 @@
# pdftool - PDF 文件工具集
# 用法:
# yamlrun pdftool.yaml --job m --var INPUTS='["a.pdf","b.pdf"]' --var OUTPUT=merged.pdf
# yamlrun pdftool.yaml --job c --var INPUT=input.pdf --var OUTPUT=compressed.pdf
strategy: thread
variables:
INPUT: input.pdf
INPUTS: []
OUTPUT: output.pdf
OUTPUT_DIR: output
PASSWORD: ""
TEXT: CONFIDENTIAL
ROTATION: 90
MARGINS: [10, 10, 10, 10]
DPI: 300
LANG: chi_sim+eng
ORDER: []
jobs:
m:
fn: pdf_merge
args: ["${INPUTS}", "${OUTPUT}"]
s:
fn: pdf_split
args: ["${INPUT}", "${OUTPUT_DIR}"]
c:
fn: pdf_compress
args: ["${INPUT}", "${OUTPUT}"]
e:
fn: pdf_encrypt
args: ["${INPUT}", "${OUTPUT}", "${PASSWORD}"]
d:
fn: pdf_decrypt
args: ["${INPUT}", "${OUTPUT}", "${PASSWORD}"]
xt:
fn: pdf_extract_text
args: ["${INPUT}", "${OUTPUT}"]
xi:
fn: pdf_extract_images
args: ["${INPUT}", "${OUTPUT_DIR}"]
w:
fn: pdf_add_watermark
args: ["${INPUT}", "${OUTPUT}"]
kwargs:
text: "${TEXT}"
r:
fn: pdf_rotate
args: ["${INPUT}", "${OUTPUT}"]
kwargs:
rotation: ${ROTATION}
crop:
fn: pdf_crop
args: ["${INPUT}", "${OUTPUT}"]
kwargs:
margins: "${MARGINS}"
i:
fn: pdf_info
args: ["${INPUT}"]
ocr:
fn: pdf_ocr
args: ["${INPUT}", "${OUTPUT}"]
kwargs:
lang: "${LANG}"
img:
fn: pdf_to_images
args: ["${INPUT}", "${OUTPUT_DIR}"]
kwargs:
dpi: ${DPI}
repair:
fn: pdf_repair
args: ["${INPUT}", "${OUTPUT}"]
+29
View File
@@ -0,0 +1,29 @@
# piptool - pip 包管理工具
# 用法:
# yamlrun piptool.yaml --job i --var PACKAGES='["requests"]'
# yamlrun piptool.yaml --job u --var PACKAGES='["requests"]'
# yamlrun piptool.yaml --job f
strategy: thread
variables:
PACKAGES: []
OFFLINE: false
jobs:
i:
cmd: ["pip", "install", "${PACKAGES}"]
u:
fn: pip_uninstall
args: ["${PACKAGES}"]
r:
fn: pip_reinstall
args: ["${PACKAGES}"]
kwargs:
offline: ${OFFLINE}
d:
fn: pip_download
args: ["${PACKAGES}"]
kwargs:
offline: ${OFFLINE}
up:
cmd: ["python", "-m", "pip", "install", "--upgrade", "pip"]
f:
fn: pip_freeze
+16
View File
@@ -0,0 +1,16 @@
# screenshot - 截图工具
# 用法:
# yamlrun screenshot.yaml --job full
# yamlrun screenshot.yaml --job area --var FILENAME=custom.png
strategy: thread
variables:
FILENAME: null
jobs:
full:
fn: take_screenshot_full
kwargs:
filename: "${FILENAME}"
area:
fn: take_screenshot_area
kwargs:
filename: "${FILENAME}"
+19
View File
@@ -0,0 +1,19 @@
# sshcopyid - SSH 密钥部署工具
# 用法:
# yamlrun sshcopyid.yaml --var HOSTNAME=server --var USERNAME=user --var PASSWORD=pass
strategy: thread
variables:
HOSTNAME: ""
USERNAME: ""
PASSWORD: ""
PORT: 22
KEYPATH: "~/.ssh/id_rsa.pub"
TIMEOUT: 30
jobs:
deploy:
fn: ssh_copy_id
args: ["${HOSTNAME}", "${USERNAME}", "${PASSWORD}"]
kwargs:
port: ${PORT}
keypath: "${KEYPATH}"
timeout: ${TIMEOUT}
+10
View File
@@ -106,6 +106,16 @@ from pyflowx.conditions import BuiltinConditions
from pyflowx.registry import get_fn as _get_fn
from pyflowx.task import Condition, RetryPolicy, TaskSpec
# 导入 _ops 模块以注册 CLI 工具函数, 使 YAML fn 字段可引用
# 使用 try/except 避免最小安装场景下的导入失败
try:
from pyflowx.cli._ops import dev as _dev # noqa: F401
from pyflowx.cli._ops import files as _files # noqa: F401
from pyflowx.cli._ops import media as _media # noqa: F401
from pyflowx.cli._ops import system as _system # noqa: F401
except ImportError: # pragma: no cover
pass
# 矩阵占位符: ${{ matrix.key }}
_MATRIX_PLACEHOLDER = re.compile(r"\$\{\{\s*matrix\.(\w+)\s*\}\}")
+7 -1
View File
@@ -12,11 +12,17 @@ from pyflowx.registry import _REGISTRY, FnRegistry, get_fn, has_fn, register_fn
@pytest.fixture
def clear_registry() -> Any:
"""每个测试前后清空 registry, 避免测试间相互污染."""
"""每个测试前后清空 registry, 但保留 _ops 模块自动注册的函数.
_ops 模块在首次导入时注册函数, 模块不会重新执行, 因此清空后无法重新注册.
保存原始状态并在 teardown 时恢复, 确保 TestOpsModules 等不使用此 fixture 的
测试仍能看到 _ops 的注册.
"""
saved = dict(_REGISTRY)
_REGISTRY.clear()
yield
_REGISTRY.clear()
_REGISTRY.update(saved)
class TestRegisterFn: