feat: sap-cli skill v2.1.0 — self-contained distributable package

- assets/: sap-cli source (v2.1.0, 26 commands, 16 object types)
- references/: tool constraints + error handling (self-contained)
- scripts/setup.py: one-click install/config/verify
- SKILL.md: full command reference + dual-platform install guide
- VERSION: 2.1.0

Built from D:/Codespace/sap-cli via scripts/pack_skill.py
This commit is contained in:
2026-06-13 20:52:24 +08:00
commit 1e39b6da88
49 changed files with 7172 additions and 0 deletions
+126
View File
@@ -0,0 +1,126 @@
"""依赖分析命令:analyze。"""
from __future__ import annotations
import argparse
import logging
import os
import re
from sapcli.client import ADTClient
logger = logging.getLogger("sapcli.commands.analyze")
# ABAP 依赖模式
_DEPENDENCY_PATTERNS: list[tuple[str, re.Pattern[str]]] = [
("TYPE REF TO", re.compile(r"TYPE\s+REF\s+TO\s+(\w+)", re.IGNORECASE)),
("CALL METHOD", re.compile(r"CALL\s+METHOD\s+(\w+)", re.IGNORECASE)),
("CALL METHOD (static)", re.compile(r"(\w+)=>(\w+)", re.IGNORECASE)),
("CREATE OBJECT", re.compile(r"CREATE\s+OBJECT\s+\w+\s+TYPE\s+(\w+)", re.IGNORECASE)),
("PERFORM", re.compile(r"PERFORM\s+(\w+)", re.IGNORECASE)),
("CALL FUNCTION", re.compile(r"CALL\s+FUNCTION\s+\'(\w+)\'", re.IGNORECASE)),
("SELECT ... FROM", re.compile(r"FROM\s+(\w+)", re.IGNORECASE)),
("LIKE", re.compile(r"LIKE\s+(\w+)", re.IGNORECASE)),
("TYPE", re.compile(r"TYPE\s+(\w+)", re.IGNORECASE)),
]
# 已知的 ABAP 内置类型(不需要追踪)
_BUILTIN_TYPES = frozenset({
"STRING", "CHAR", "NUMC", "INT1", "INT2", "INT4", "INT8",
"FLOAT", "DECFLOAT16", "DECFLOAT34", "DEC", "CURR", "QUAN",
"RAW", "LRAW", "RAWSTRING", "DATS", "TIMS", "SSTRING",
"XSTRING", "X", "C", "N", "D", "T", "I", "F", "P",
"ANY", "DATA", "REF", "OBJECT", "STRUCTURE", "TABLE",
"STANDARD", "SORTED", "HASHED", "INDEX", "SY",
"ABAP_BOOL", "ABAP_TRUE", "ABAP_FALSE", "BOOLEAN",
"XSDBOOLEAN", "FLAG", "CHAR1", "CHAR10",
})
def cmd_analyze(args: argparse.Namespace, client: ADTClient) -> None:
"""依赖自动分析。
解析本地 .abap 文件中的 TYPE REF TO / CALL METHOD / PERFORM 等,
自动生成 depends_on 列表。
"""
project_path: str = getattr(args, "path", ".")
print("=" * 60)
print(" sap-cli 依赖分析")
print("=" * 60)
print(f" 项目路径: {project_path}")
if not os.path.isdir(project_path):
print(f"\n ✗ 目录不存在: {project_path}")
return
# 扫描所有 .abap 文件
abap_files: list[str] = []
for root, dirs, files in os.walk(project_path):
for fname in files:
if fname.endswith(".abap"):
abap_files.append(os.path.join(root, fname))
if not abap_files:
print("\n 未找到 .abap 文件")
return
print(f"\n → 扫描到 {len(abap_files)} 个 .abap 文件\n")
# 分析每个文件
all_deps: dict[str, dict[str, list[str]]] = {}
for filepath in abap_files:
rel_path = os.path.relpath(filepath, project_path)
file_base = os.path.splitext(os.path.basename(filepath))[0].upper()
with open(filepath, "r", encoding="utf-8") as f:
source = f.read()
deps = _analyze_dependencies(source)
all_deps[file_base] = {"file": rel_path, "deps": deps}
if deps:
print(f" 📄 {rel_path}")
print(f"{', '.join(deps)}")
else:
print(f" 📄 {rel_path} (无外部依赖)")
# 汇总
print(f"\n {'' * 50}")
total_deps = sum(len(v["deps"]) for v in all_deps.values())
files_with_deps = sum(1 for v in all_deps.values() if v["deps"])
print(f" 总计: {len(abap_files)} 个文件, {files_with_deps} 个有外部依赖, {total_deps} 个依赖关系")
# 输出 depends_on 配置建议
if total_deps > 0:
print(f"\n 建议的 manifest.json depends_on 配置:")
print(f" {'' * 50}")
for name, info in sorted(all_deps.items()):
if info["deps"]:
deps_str = ", ".join(f'"{d}"' for d in sorted(info["deps"]))
print(f' "{name}": [{deps_str}]')
def _analyze_dependencies(source: str) -> list[str]:
"""分析 ABAP 源码中的依赖关系。
Args:
source: ABAP 源码字符串。
Returns:
依赖对象名称列表(去重)。
"""
deps: set[str] = set()
for pattern_name, pattern in _DEPENDENCY_PATTERNS:
for match in pattern.finditer(source):
name = match.group(1).upper()
# 过滤内置类型和短名称
if name in _BUILTIN_TYPES:
continue
if len(name) < 3:
continue
# 只追踪 Z/Y 开头的自定义对象(更精确)
if name.startswith("Z") or name.startswith("Y"):
deps.add(name)
return sorted(deps)