Files
wurangyu 1e39b6da88 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
2026-06-13 20:52:24 +08:00

127 lines
4.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""依赖分析命令: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)