- 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
122 lines
3.6 KiB
Python
122 lines
3.6 KiB
Python
"""目录扫描模块。
|
|
|
|
扫描项目根目录下按约定的子目录结构,识别 ABAP 开发对象。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import os
|
|
from dataclasses import dataclass
|
|
|
|
logger = logging.getLogger("sapcli.scanner")
|
|
|
|
DIRECTORY_TYPE_MAP: dict[str, str] = {
|
|
"reports": "report",
|
|
"classes": "class",
|
|
"interfaces": "interface",
|
|
"functions": "function",
|
|
"domains": "domain",
|
|
"dataelements": "dataelement",
|
|
"tables": "table",
|
|
"structures": "structure",
|
|
"tabletypes": "tabletype",
|
|
"includes": "include",
|
|
"cdsviews": "cdsview",
|
|
"messageclasses": "messageclass",
|
|
"views": "view",
|
|
"searchhelps": "searchhelp",
|
|
"lockobjects": "lockobject",
|
|
}
|
|
|
|
FUNCTIONS_DIR = "functions"
|
|
|
|
|
|
@dataclass
|
|
class ScannedObject:
|
|
"""扫描到的对象。"""
|
|
|
|
name: str # 大写对象名,function 类型含 / (GROUP/FUNC)
|
|
type: str # 对象类型
|
|
file: str # 相对于项目根目录的路径
|
|
|
|
|
|
def scan_project(project_path: str) -> list[ScannedObject]:
|
|
"""扫描项目目录,返回识别到的所有对象列表。
|
|
|
|
遍历已知目录名,每个目录下找 .abap 文件。
|
|
functions/ 特殊处理:子目录 = 函数组名。
|
|
"""
|
|
results: list[ScannedObject] = []
|
|
|
|
for dir_name, obj_type in DIRECTORY_TYPE_MAP.items():
|
|
dir_path = os.path.join(project_path, dir_name)
|
|
if not os.path.isdir(dir_path):
|
|
logger.debug("目录不存在,跳过: %s", dir_path)
|
|
continue
|
|
|
|
if dir_name == FUNCTIONS_DIR:
|
|
_scan_functions(dir_path, results)
|
|
else:
|
|
_scan_flat_directory(dir_path, dir_name, obj_type, results)
|
|
|
|
logger.info("扫描完成: %d 个对象", len(results))
|
|
return results
|
|
|
|
|
|
def _scan_flat_directory(
|
|
dir_path: str,
|
|
dir_name: str,
|
|
obj_type: str,
|
|
results: list[ScannedObject],
|
|
) -> None:
|
|
"""扫描普通目录(一对一:文件名 = 对象名)。"""
|
|
for filename in sorted(os.listdir(dir_path)):
|
|
if not filename.endswith(".abap"):
|
|
continue
|
|
# 隐藏文件跳过
|
|
if filename.startswith("."):
|
|
continue
|
|
|
|
obj_name = filename[:-5].upper() # 去掉 .abap,转大写
|
|
rel_path = f"{dir_name}/{filename}"
|
|
|
|
results.append(ScannedObject(
|
|
name=obj_name,
|
|
type=obj_type,
|
|
file=rel_path,
|
|
))
|
|
logger.debug("扫描到: %s (%s) → %s", obj_name, obj_type, rel_path)
|
|
|
|
|
|
def _scan_functions(dir_path: str, results: list[ScannedObject]) -> None:
|
|
"""扫描 functions/ 目录(特殊处理:子目录 = 函数组名)。
|
|
|
|
目录结构: functions/{组名小写}/{模块名小写}.abap
|
|
对象名: {组名大写}/{模块名大写}
|
|
"""
|
|
for group_dir_name in sorted(os.listdir(dir_path)):
|
|
group_dir = os.path.join(dir_path, group_dir_name)
|
|
if not os.path.isdir(group_dir):
|
|
continue
|
|
if group_dir_name.startswith("."):
|
|
continue
|
|
|
|
group_name = group_dir_name.upper()
|
|
|
|
for filename in sorted(os.listdir(group_dir)):
|
|
if not filename.endswith(".abap"):
|
|
continue
|
|
if filename.startswith("."):
|
|
continue
|
|
|
|
func_name = filename[:-5].upper()
|
|
obj_name = f"{group_name}/{func_name}"
|
|
rel_path = f"{FUNCTIONS_DIR}/{group_dir_name}/{filename}"
|
|
|
|
results.append(ScannedObject(
|
|
name=obj_name,
|
|
type="function",
|
|
file=rel_path,
|
|
))
|
|
logger.debug("扫描到: %s (function) → %s", obj_name, rel_path)
|