- 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
252 lines
7.7 KiB
Python
252 lines
7.7 KiB
Python
"""项目模板命令: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}")
|