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:
@@ -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)
|
||||
Reference in New Issue
Block a user