| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596 |
- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
- """
- CLI 参数兼容工具。
- 背景:
- - data_modules 下的 CLI 普遍使用 argparse + subparsers。
- - argparse 的全局参数(例如 --project-root)要求出现在子命令之前:
- python -m data_modules.index_manager --project-root X get-core-entities
- 但实际写作流程里(skills/agents 文档、工具调用)经常把 --project-root 放在子命令之后:
- python -m data_modules.index_manager get-core-entities --project-root X
- 这会直接报 "unrecognized arguments"(见 issues7 日志)。
- 这里提供一个轻量的 argv 预处理:把 --project-root 从任意位置提取出来并前置,
- 让原有 argparse 定义无需大改即可兼容两种写法。
- """
- from __future__ import annotations
- import json
- import sys
- from pathlib import Path
- from typing import Any
- from typing import List, Optional, Tuple
- def _extract_flag_value(argv: List[str], flag: str) -> Tuple[Optional[str], List[str]]:
- """
- Extract a flag value from argv.
- Supports:
- - --flag VALUE
- - --flag=VALUE
- Returns:
- - (value, remaining_argv)
- - value uses the *last* occurrence when repeated.
- - if a dangling `--flag` has no value, it is kept in remaining_argv for argparse to raise.
- """
- value: Optional[str] = None
- rest: List[str] = []
- i = 0
- while i < len(argv):
- token = argv[i]
- if token == flag:
- if i + 1 < len(argv):
- value = argv[i + 1]
- i += 2
- continue
- # Dangling flag; keep it so argparse can error out properly.
- rest.append(token)
- i += 1
- continue
- if token.startswith(flag + "="):
- value = token.split("=", 1)[1]
- i += 1
- continue
- rest.append(token)
- i += 1
- return value, rest
- def normalize_global_project_root(argv: List[str], *, flag: str = "--project-root") -> List[str]:
- """
- Normalize argv so a global `--project-root` (when present) is moved before subcommands.
- This makes argparse+subparsers accept both:
- - `... --project-root X cmd ...`
- - `... cmd ... --project-root X`
- """
- value, rest = _extract_flag_value(argv, flag)
- if value is None:
- return argv
- return [flag, value] + rest
- def load_json_arg(raw: str) -> Any:
- """
- 解析 CLI 传入的 JSON 参数,支持两种形式:
- - 直接 JSON 字符串:'{"a":1}'
- - @ 文件路径:'@data.json'(从文件读取 JSON,避免 shell 引号地狱)
- - 特例:'@-' 表示从 stdin 读取
- """
- if raw is None:
- raise ValueError("missing json arg")
- text = str(raw).strip()
- if text.startswith("@"):
- target = text[1:].strip()
- if not target:
- raise ValueError("invalid json arg: '@' without path")
- if target == "-":
- content = sys.stdin.read()
- else:
- content = Path(target).read_text(encoding="utf-8")
- return json.loads(content)
- return json.loads(text)
|