"""项目模板命令: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}")