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
+251
View File
@@ -0,0 +1,251 @@
"""项目模板命令:scaffold。"""
from __future__ import annotations
import argparse
import logging
import os
from sapcli.client import ADTClient
logger = logging.getLogger("sapcli.commands.scaffold")
def _alv_report_template(name: str, package: str) -> str:
"""生成 ALV 报表模板。"""
return (
f"REPORT {name.upper()}.\n"
f"\n"
f"TYPES: BEGIN OF ty_data,\n"
f" field1 TYPE char10,\n"
f" field2 TYPE char20,\n"
f" END OF ty_data.\n"
f"\n"
f"DATA: gt_data TYPE STANDARD TABLE OF ty_data,\n"
f" gs_data TYPE ty_data,\n"
f" go_alv TYPE REF TO cl_salv_table,\n"
f" go_msg TYPE REF TO cx_salv_msg.\n"
f"\n"
f"START-OF-SELECTION.\n"
f" PERFORM get_data.\n"
f" PERFORM display_alv.\n"
f"\n"
f"FORM get_data.\n"
f" \" TODO: 填充数据\n"
f" gs_data-field1 = '示例'.\n"
f" gs_data-field2 = '数据'.\n"
f" APPEND gs_data TO gt_data.\n"
f"ENDFORM.\n"
f"\n"
f"FORM display_alv.\n"
f" TRY.\n"
f" cl_salv_table=>factory(\n"
f" IMPORTING\n"
f" r_salv_table = go_alv\n"
f" CHANGING\n"
f" t_table = gt_data\n"
f" ).\n"
f" go_alv->display( ).\n"
f" CATCH cx_salv_msg INTO go_msg.\n"
f" MESSAGE go_msg->get_text( ) TYPE 'I'.\n"
f" ENDTRY.\n"
f"ENDFORM.\n"
)
def _bapi_wrapper_template(name: str, package: str) -> str:
"""生成 BAPI 包装类模板。"""
class_name = name.upper()
return (
f"CLASS {class_name} DEFINITION\n"
f" PUBLIC\n"
f" FINAL\n"
f" CREATE PUBLIC.\n"
f"\n"
f" PUBLIC SECTION.\n"
f" TYPES: BEGIN OF ty_result,\n"
f" success TYPE abap_bool,\n"
f" message TYPE string,\n"
f" END OF ty_result.\n"
f"\n"
f" CLASS-METHODS:\n"
f" call_bapi\n"
f" IMPORTING\n"
f" iv_param1 TYPE string OPTIONAL\n"
f" RETURNING\n"
f" VALUE(rs_result) TYPE ty_result.\n"
f"\n"
f" PRIVATE SECTION.\n"
f" CLASS-METHODS:\n"
f" _call_remote_bapi\n"
f" IMPORTING\n"
f" iv_param1 TYPE string\n"
f" EXPORTING\n"
f" ev_success TYPE abap_bool\n"
f" ev_message TYPE string.\n"
f"ENDCLASS.\n"
f"\n"
f"\n"
f"CLASS {class_name} IMPLEMENTATION.\n"
f"\n"
f" METHOD call_bapi.\n"
f" _call_remote_bapi(\n"
f" EXPORTING\n"
f" iv_param1 = iv_param1\n"
f" IMPORTING\n"
f" ev_success = rs_result-success\n"
f" ev_message = rs_result-message\n"
f" ).\n"
f" ENDMETHOD.\n"
f"\n"
f" METHOD _call_remote_bapi.\n"
f" \" TODO: 调用 BAPI 函数\n"
f" ev_success = abap_true.\n"
f" ev_message = '未实现'.\n"
f" ENDMETHOD.\n"
f"\n"
f"ENDCLASS.\n"
)
def _interface_class_template(name: str, package: str) -> str:
"""生成接口 + 实现类模板。"""
name_upper = name.upper()
if name_upper.startswith("Z"):
if_name = f"ZIF_{name_upper[2:]}"
else:
if_name = f"ZIF_{name_upper}"
cls_name = name_upper
return (
f"INTERFACE {if_name}\n"
f" PUBLIC.\n"
f" METHODS:\n"
f" execute\n"
f" RETURNING VALUE(rv_result) TYPE string.\n"
f"ENDINTERFACE.\n"
f"\n"
f"\n"
f"CLASS {cls_name} DEFINITION\n"
f" PUBLIC\n"
f" FINAL\n"
f" CREATE PUBLIC.\n"
f"\n"
f" PUBLIC SECTION.\n"
f" INTERFACES: {if_name}.\n"
f"\n"
f" PRIVATE SECTION.\n"
f" DATA: mv_state TYPE string.\n"
f"ENDCLASS.\n"
f"\n"
f"\n"
f"CLASS {cls_name} IMPLEMENTATION.\n"
f"\n"
f" METHOD {if_name}~execute.\n"
f" rv_result = 'Hello from {cls_name}'.\n"
f" ENDMETHOD.\n"
f"\n"
f"ENDCLASS.\n"
)
def _data_model_template(name: str, package: str) -> str:
"""生成数据模型模板(DDIC DDL)。"""
name_lower = name.lower()
return (
f"@EndUserText.label: '{name.upper()} 数据模型'\n"
f"@AbapCatalog.enhancementCategory: #NOT_CLASSIFIED\n"
f"@AbapCatalog.tableCategory: #TRANSPARENT\n"
f"@AbapCatalog.deliveryClass: #A\n"
f"@AbapCatalog.dataMaintenance: #LIMITED\n"
f"define table {name_lower} {{\n"
f" key_client : abap.clnt not null;\n"
f" key_id : abap.char10 not null;\n"
f" name : abap.char40;\n"
f" created_at : timestampl;\n"
f" changed_at : timestampl;\n"
f"}}\n"
)
# 模板注册表
_TEMPLATES: dict[str, dict] = {
"alv-report": {
"desc": "ALV 报表模板",
"file_name": "{name_lower}.abap",
"dir": "reports",
"generator": _alv_report_template,
},
"bapi-wrapper": {
"desc": "BAPI 包装类模板",
"file_name": "{name_lower}.abap",
"dir": "classes",
"generator": _bapi_wrapper_template,
},
"interface-class": {
"desc": "接口 + 实现类模板",
"file_name": "{name_lower}.abap",
"dir": "classes",
"generator": _interface_class_template,
},
"data-model": {
"desc": "数据模型模板 (DDIC DDL)",
"file_name": "{name_lower}.ddl",
"dir": "tables",
"generator": _data_model_template,
},
}
def cmd_scaffold(args: argparse.Namespace, client: ADTClient | None = None) -> None:
"""项目模板创建。"""
# 不指定模板时,列出可用模板
template = getattr(args, "template", None)
if not template:
print("=" * 60)
print(" sap-cli 可用模板")
print("=" * 60)
for key, info in _TEMPLATES.items():
desc = info.get("description", "")
print(f" {key:20s} {desc}")
print()
print(" 用法: sap-cli scaffold --name ZXXX --template <模板名>")
return
name: str = args.name
package: str = getattr(args, "package", "$TMP") or "$TMP"
output_dir: str = getattr(args, "path", ".") or "."
template_info = _TEMPLATES.get(template)
if not template_info:
print(f" ✗ 不支持的模板类型: {template}")
print(f" 可用模板: {', '.join(_TEMPLATES.keys())}")
return
print("=" * 60)
print(" sap-cli 项目模板创建")
print("=" * 60)
print(f" 对象名称: {name}")
print(f" 模板: {template} ({template_info['desc']})")
print(f" 包: {package}")
print(f" 输出目录: {output_dir}")
# 生成源码
name_lower = name.lower()
source = template_info["generator"](name, package)
# 确定输出路径
target_dir = os.path.join(output_dir, template_info["dir"])
if not os.path.isdir(target_dir):
os.makedirs(target_dir, exist_ok=True)
filename = template_info["file_name"].format(name_lower=name_lower)
filepath = os.path.join(target_dir, filename)
with open(filepath, "w", encoding="utf-8") as f:
f.write(source)
print(f"\n ✓ 模板已生成!")
print(f" 文件: {filepath}")
print(f" 大小: {len(source)} 字符")
print(f"\n 下一步:")
print(f" 1. 编辑 {filepath} 完善代码")
print(f" 2. 运行 sap-cli sync --name {name} --type report --path {filepath}")