初始化本地仓库
This commit is contained in:
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import List, Tuple
|
||||
|
||||
INDEX_BLOCK_RE = re.compile(
|
||||
r"#\[OA\\Get\((?P<oa_get>.*?)\)\]\s*"
|
||||
r"#\[Permission\((?P<permission>.*?)\)\]\s*"
|
||||
r"#\[.*?\]\s*"
|
||||
r"public function index\(Request \$request\): \\support\\Response\s*\{(?P<index_body>.*?)\}",
|
||||
re.S,
|
||||
)
|
||||
|
||||
UPDATE_METHOD_RE = re.compile(
|
||||
r"(?P<indent>\s*)public function update\(Request \$request\): \\support\\Response\s*\{(?P<body>.*?)\n(?P=indent)\}",
|
||||
re.S,
|
||||
)
|
||||
|
||||
EXPORT_METHOD_RE = re.compile(r"public function export\(Request \$request\): \\support\\Response")
|
||||
EXTENDS_CRUD_RE = re.compile(r"class\s+\w+\s+extends\s+Crud")
|
||||
LIST_PERMISSION_RE = re.compile(r"['\"]([^'\"]+:list)['\"]")
|
||||
PATH_RE = re.compile(r"path:\s*['\"]([^'\"]+)['\"]")
|
||||
TAGS_RE = re.compile(r"tags:\s*(\[[^\]]*\])")
|
||||
|
||||
SKIP_PATH_KEYWORDS = (
|
||||
'/monitor/online-user',
|
||||
'/monitor/server',
|
||||
'/monitor/redis',
|
||||
'/login',
|
||||
'/profile',
|
||||
)
|
||||
|
||||
|
||||
def detect_newline(text: str) -> str:
|
||||
return '\r\n' if '\r\n' in text else '\n'
|
||||
|
||||
|
||||
def normalize_permission(raw: str) -> str | None:
|
||||
match = LIST_PERMISSION_RE.search(raw)
|
||||
return match.group(1) if match else None
|
||||
|
||||
|
||||
def build_export_block(path: str, tags_expr: str, permission_code: str, newline: str, indent: str) -> str:
|
||||
export_path = f"{path.rstrip('/')}/export"
|
||||
lines = [
|
||||
'',
|
||||
f"{indent}#[OA\\Post(",
|
||||
f"{indent} path: '{export_path}',",
|
||||
f"{indent} summary: '导出',",
|
||||
f"{indent} tags: {tags_expr}",
|
||||
f"{indent})]",
|
||||
f"{indent}#[Permission(code: '{permission_code}', description: '导出')]",
|
||||
f"{indent}#[SimpleResponse(schema: [], example: [])]",
|
||||
f"{indent}public function export(Request $request): \\support\\Response",
|
||||
f"{indent}{{",
|
||||
f"{indent} return parent::export($request);",
|
||||
f"{indent}}}",
|
||||
]
|
||||
return newline.join(lines)
|
||||
|
||||
|
||||
def should_skip_path(path: str) -> bool:
|
||||
return any(keyword in path for keyword in SKIP_PATH_KEYWORDS)
|
||||
|
||||
|
||||
def patch_file(file_path: Path, apply: bool) -> Tuple[str, str]:
|
||||
original = file_path.read_text(encoding='utf-8')
|
||||
|
||||
if not EXTENDS_CRUD_RE.search(original):
|
||||
return 'skip', 'not-crud'
|
||||
|
||||
if EXPORT_METHOD_RE.search(original):
|
||||
return 'skip', 'has-export'
|
||||
|
||||
if '$this->service' not in original:
|
||||
return 'skip', 'no-service-assign'
|
||||
|
||||
index_match = INDEX_BLOCK_RE.search(original)
|
||||
if not index_match:
|
||||
return 'skip', 'no-standard-index'
|
||||
|
||||
index_body = index_match.group('index_body')
|
||||
if 'parent::index($request)' not in index_body:
|
||||
return 'skip', 'custom-index'
|
||||
|
||||
permission_code = normalize_permission(index_match.group('permission'))
|
||||
if not permission_code:
|
||||
return 'skip', 'no-list-permission'
|
||||
|
||||
path_match = PATH_RE.search(index_match.group('oa_get'))
|
||||
if not path_match:
|
||||
return 'skip', 'no-path'
|
||||
path = path_match.group(1)
|
||||
|
||||
if should_skip_path(path):
|
||||
return 'skip', 'skip-special-path'
|
||||
|
||||
tags_match = TAGS_RE.search(index_match.group('oa_get'))
|
||||
if not tags_match:
|
||||
return 'skip', 'no-tags'
|
||||
tags_expr = tags_match.group(1)
|
||||
|
||||
update_match = UPDATE_METHOD_RE.search(original)
|
||||
if not update_match:
|
||||
return 'skip', 'no-update-method'
|
||||
|
||||
update_body = update_match.group('body')
|
||||
if 'parent::update($request)' not in update_body:
|
||||
return 'skip', 'custom-update'
|
||||
|
||||
indent = update_match.group('indent')
|
||||
newline = detect_newline(original)
|
||||
export_block = build_export_block(path, tags_expr, permission_code, newline, indent)
|
||||
|
||||
insert_pos = update_match.end()
|
||||
patched = original[:insert_pos] + export_block + original[insert_pos:]
|
||||
|
||||
if patched == original:
|
||||
return 'skip', 'no-change'
|
||||
|
||||
if apply:
|
||||
file_path.write_text(patched, encoding='utf-8', newline='')
|
||||
return 'patched', path
|
||||
|
||||
return 'preview', path
|
||||
|
||||
|
||||
def collect_targets(backend_root: Path) -> List[Path]:
|
||||
return sorted(
|
||||
path for path in backend_root.rglob('*.php')
|
||||
if 'vendor' not in path.parts
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description='批量为标准 CRUD 控制器补 export() 路由')
|
||||
parser.add_argument('--apply', action='store_true', help='真正写入文件;默认仅预览')
|
||||
parser.add_argument('--backend-root', default=None, help='backend 根目录路径,默认自动推断')
|
||||
args = parser.parse_args()
|
||||
|
||||
script_path = Path(__file__).resolve()
|
||||
backend_root = Path(args.backend_root).resolve() if args.backend_root else script_path.parents[2]
|
||||
|
||||
if not backend_root.exists():
|
||||
print(f'[ERROR] backend 根目录不存在: {backend_root}')
|
||||
return 1
|
||||
|
||||
results: List[Tuple[str, str, str]] = []
|
||||
for file_path in collect_targets(backend_root):
|
||||
status, detail = patch_file(file_path, args.apply)
|
||||
if status in {'patched', 'preview'}:
|
||||
results.append((status, detail, str(file_path)))
|
||||
|
||||
mode = 'APPLY' if args.apply else 'PREVIEW'
|
||||
print(f'=== {mode} RESULT ===')
|
||||
if not results:
|
||||
print('没有发现可补 export() 的标准 CRUD 控制器。')
|
||||
return 0
|
||||
|
||||
for status, detail, file_name in results:
|
||||
print(f'[{status}] {detail} -> {file_name}')
|
||||
|
||||
print(f'总计: {len(results)} 个文件')
|
||||
if not args.apply:
|
||||
print('提示: 追加 --apply 才会真正写入文件')
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user