SKILL.md(唯一来源 = sap-cli/skill-src/SKILL.md.tmpl):
- 本仓 SKILL.md 自此为构建产物,勿手工编辑;手改后再构建会被漂移护栏拦下
- 并入模板独有内容:调用方式节 + 补齐 parser 真实命令(clone/enhancement/
history/unit-test/unlock/transport create,原表只 9 个,实际 31 个)
- 版本号改为 {{VERSION}} 注入(原字面量停在 v2.2.1/v2.3.0,与工具实际版本脱节)
- 修 1 处违反铁律 5 的示例(--path ./src → ./src/TMP/class/)
assets/(工具代码,此前严重过时):
- 9 个文件与源码不一致,scanner.py 尤甚:3818B → 6664B
- 修复前从本仓装出的工具**不支持三层 src 结构**,铁律 5 实际跑不通
- transport.py 反向漂移(分发版多 transport create,源码无)已随源码回迁对齐
VERSION 2.5.1;references/ 三份规则与源码一致(无差异)
190 lines
5.3 KiB
Python
190 lines
5.3 KiB
Python
"""sap-cli 应用入口:解析参数、初始化连接、路由命令。"""
|
|
from __future__ import annotations
|
|
|
|
import io
|
|
import logging
|
|
import os
|
|
import sys
|
|
|
|
import requests
|
|
|
|
from sapcli.config import load_config
|
|
from sapcli.client import ADTClient
|
|
from sapcli.commands import (
|
|
cmd_create,
|
|
cmd_delete,
|
|
cmd_unlock,
|
|
cmd_download,
|
|
cmd_info,
|
|
cmd_init,
|
|
cmd_refresh,
|
|
cmd_sync,
|
|
cmd_sync_all,
|
|
cmd_config,
|
|
cmd_list,
|
|
cmd_whereused,
|
|
cmd_search,
|
|
cmd_transport,
|
|
cmd_check,
|
|
cmd_format,
|
|
cmd_diff,
|
|
cmd_package,
|
|
cmd_cds,
|
|
cmd_analyze,
|
|
cmd_scaffold,
|
|
cmd_show_table,
|
|
cmd_read_table,
|
|
cmd_run_program,
|
|
cmd_activate,
|
|
cmd_upload,
|
|
cmd_syntax_check,
|
|
cmd_unit_test,
|
|
cmd_history,
|
|
cmd_clone,
|
|
cmd_enhancement,
|
|
)
|
|
from sapcli.auth import cmd_auth_login, cmd_auth_logout, cmd_auth_status
|
|
from sapcli.exceptions import SapCliError, ConfigError
|
|
from sapcli.cli.parser import build_parser
|
|
|
|
|
|
def _setup_encoding() -> None:
|
|
"""确保 stdout/stderr 使用 UTF-8 编码。"""
|
|
if hasattr(sys.stdout, "buffer"):
|
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
|
|
if hasattr(sys.stderr, "buffer"):
|
|
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace")
|
|
|
|
|
|
def _setup_logging() -> None:
|
|
"""配置日志(写入文件,不影响 stdout)。"""
|
|
log_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", "log")
|
|
os.makedirs(log_dir, exist_ok=True)
|
|
log_file = os.path.join(log_dir, "adt_tools.log")
|
|
logging.basicConfig(
|
|
level=logging.DEBUG,
|
|
format="%(asctime)s | %(levelname)-7s | %(message)s",
|
|
handlers=[
|
|
logging.FileHandler(log_file, encoding="utf-8", mode="a"),
|
|
],
|
|
)
|
|
|
|
|
|
def _suppress_ssl_warnings() -> None:
|
|
"""禁用 urllib3 的 InsecureRequestWarning。"""
|
|
requests.packages.urllib3.disable_warnings(
|
|
requests.packages.urllib3.exceptions.InsecureRequestWarning
|
|
)
|
|
|
|
|
|
def main() -> None:
|
|
"""sap-cli 主入口。"""
|
|
_setup_encoding()
|
|
_setup_logging()
|
|
_suppress_ssl_warnings()
|
|
|
|
parser = build_parser()
|
|
args = parser.parse_args()
|
|
|
|
if not args.command:
|
|
parser.print_help()
|
|
sys.exit(1)
|
|
|
|
# config 命令不需要 SAP 连接
|
|
if args.command == "config":
|
|
cmd_config(args)
|
|
return
|
|
|
|
# auth 不需要 SAP 连接
|
|
if args.command == "auth":
|
|
auth_action = getattr(args, "auth_action", None)
|
|
if auth_action == "login":
|
|
cmd_auth_login(args)
|
|
elif auth_action == "logout":
|
|
cmd_auth_logout(args)
|
|
else:
|
|
cmd_auth_status(args)
|
|
return
|
|
|
|
# clone 自行按 --from/--to profile 构建源/目标连接,跳过默认登录
|
|
if args.command == "clone":
|
|
try:
|
|
cmd_clone(args, None)
|
|
except SapCliError as e:
|
|
print(f"\n ✗ {e}")
|
|
sys.exit(1)
|
|
return
|
|
|
|
# 加载配置
|
|
try:
|
|
sap_cfg, loaded_from = load_config(args.config, profile=getattr(args, "profile", None))
|
|
if loaded_from:
|
|
logging.getLogger("sapcli").info("Config loaded from: %s", loaded_from)
|
|
except ConfigError as e:
|
|
print(f" ✗ {e}")
|
|
print(f" 请创建配置文件 (config.ini) 或设置环境变量:")
|
|
print(f" SAP_HOST, SAP_CLIENT, SAP_USER, SAP_PASSWORD")
|
|
sys.exit(1)
|
|
|
|
# 建立 SAP 连接
|
|
verify_ssl = getattr(args, "verify_ssl", False)
|
|
client = ADTClient(sap_cfg.host, sap_cfg.client, sap_cfg.user, sap_cfg.password, verify_ssl=verify_ssl)
|
|
|
|
print("\n → 正在登录...")
|
|
client.login()
|
|
print(" ✓ 登录成功\n")
|
|
|
|
# 命令路由表
|
|
command_map = {
|
|
"download": cmd_download,
|
|
"sync": cmd_sync_all if getattr(args, "all", False) else cmd_sync,
|
|
"info": cmd_info,
|
|
"delete": cmd_delete,
|
|
"unlock": cmd_unlock,
|
|
"create": cmd_create,
|
|
"init": cmd_init,
|
|
"refresh": cmd_refresh,
|
|
"list": cmd_list,
|
|
"whereused": cmd_whereused,
|
|
"search": cmd_search,
|
|
"transport": cmd_transport,
|
|
"check": cmd_check,
|
|
"format": cmd_format,
|
|
"activate": cmd_activate,
|
|
"upload": cmd_upload,
|
|
"syntax-check": cmd_syntax_check,
|
|
"diff": cmd_diff,
|
|
"package": cmd_package,
|
|
"cds": cmd_cds,
|
|
"analyze": cmd_analyze,
|
|
"scaffold": cmd_scaffold,
|
|
"show-table": cmd_show_table,
|
|
"read-table": cmd_read_table,
|
|
"run-program": cmd_run_program,
|
|
"unit-test": cmd_unit_test,
|
|
"history": cmd_history,
|
|
"enhancement": cmd_enhancement,
|
|
}
|
|
|
|
# 批量 sync 参数校验
|
|
if args.command == "sync" and getattr(args, "all", False):
|
|
if args.name and args.type:
|
|
print(" ✗ --all 模式下不需要 --name 和 --type 参数")
|
|
sys.exit(1)
|
|
elif args.command == "sync" and not getattr(args, "all", False):
|
|
if not args.name or not args.type:
|
|
print(" ✗ 单对象模式需要 --name 和 --type 参数")
|
|
sys.exit(1)
|
|
|
|
# 执行命令
|
|
handler = command_map.get(args.command)
|
|
if handler is None:
|
|
print(f" ✗ 未知命令: {args.command}")
|
|
sys.exit(1)
|
|
|
|
try:
|
|
handler(args, client)
|
|
except SapCliError as e:
|
|
print(f"\n ✗ {e}")
|
|
sys.exit(1)
|