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:
@@ -0,0 +1,277 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from sapcli.utils.xml_utils import ddl_escape as _ddl_escape
|
||||
from sapcli.utils.xml_utils import xml_escape as _xml_escape
|
||||
|
||||
|
||||
@dataclass
|
||||
class DomainDefinition:
|
||||
datatype: str = "CHAR"
|
||||
length: int = 10
|
||||
decimals: int = 0
|
||||
lowercase: bool = False
|
||||
fix_values: list[dict[str, str]] = field(default_factory=list)
|
||||
|
||||
def to_xml(self, name: str, description: str, package: str = "$TMP") -> str:
|
||||
ns = (
|
||||
'xmlns:doma="http://www.sap.com/dictionary/domain"'
|
||||
' xmlns:adtcore="http://www.sap.com/adt/core"'
|
||||
)
|
||||
header = (
|
||||
f'<doma:domain {ns}'
|
||||
f' adtcore:name="{_xml_escape(name)}"'
|
||||
f' adtcore:type="DOMA/DD"'
|
||||
f' adtcore:description="{_xml_escape(description)}"'
|
||||
f' adtcore:language="EN">'
|
||||
f'<adtcore:packageRef adtcore:name="{_xml_escape(package)}"/>'
|
||||
)
|
||||
type_info = (
|
||||
"<doma:content>"
|
||||
"<doma:typeInformation>"
|
||||
f"<doma:datatype>{self.datatype}</doma:datatype>"
|
||||
f"<doma:length>{self.length:06d}</doma:length>"
|
||||
f"<doma:decimals>{self.decimals:06d}</doma:decimals>"
|
||||
"</doma:typeInformation>"
|
||||
"<doma:outputInformation>"
|
||||
f"<doma:length>{self.length:06d}</doma:length>"
|
||||
"<doma:style>00</doma:style>"
|
||||
"<doma:conversionExit/>"
|
||||
f"<doma:signExists>false</doma:signExists>"
|
||||
f"<doma:lowercase>{'true' if self.lowercase else 'false'}</doma:lowercase>"
|
||||
"<doma:ampmFormat>false</doma:ampmFormat>"
|
||||
"</doma:outputInformation>"
|
||||
)
|
||||
fix_xml = ""
|
||||
if self.fix_values:
|
||||
fix_xml = "<doma:valueInformation><doma:valueTableRef/><doma:appendExists>false</doma:appendExists><doma:fixValues>"
|
||||
for i, fv in enumerate(self.fix_values, 1):
|
||||
low = fv.get("low", "")
|
||||
high = fv.get("high", "")
|
||||
text = fv.get("text", "")
|
||||
fix_xml += (
|
||||
f"<doma:fixValue>"
|
||||
f"<doma:position>{i:04d}</doma:position>"
|
||||
f"<doma:low>{low}</doma:low>"
|
||||
f"<doma:high>{high}</doma:high>"
|
||||
f"<doma:text>{text}</doma:text>"
|
||||
f"</doma:fixValue>"
|
||||
)
|
||||
fix_xml += "</doma:fixValues></doma:valueInformation>"
|
||||
|
||||
return f"<?xml version=\"1.0\" encoding=\"UTF-8\"?>{header}{type_info}{fix_xml}</doma:content></doma:domain>"
|
||||
|
||||
|
||||
@dataclass
|
||||
class DataElementDefinition:
|
||||
datatype: str = "CHAR"
|
||||
length: int = 10
|
||||
decimals: int = 0
|
||||
domain_name: str = ""
|
||||
|
||||
def to_xml(self, name: str, description: str, package: str = "$TMP") -> str:
|
||||
ns = (
|
||||
'xmlns:blue="http://www.sap.com/wbobj/dictionary/dtel"'
|
||||
' xmlns:adtcore="http://www.sap.com/adt/core"'
|
||||
)
|
||||
header = (
|
||||
f'<blue:wbobj {ns}'
|
||||
f' adtcore:name="{_xml_escape(name)}"'
|
||||
f' adtcore:type="DTEL/DE"'
|
||||
f' adtcore:description="{_xml_escape(description)}"'
|
||||
f' adtcore:language="EN">'
|
||||
f'<adtcore:packageRef adtcore:name="{_xml_escape(package)}"/>'
|
||||
)
|
||||
type_block = (
|
||||
'<dtel:dataElement xmlns:dtel="http://www.sap.com/adt/dictionary/dataelements">'
|
||||
)
|
||||
if self.domain_name:
|
||||
type_block += (
|
||||
f"<dtel:typeKind>domain</dtel:typeKind>"
|
||||
f"<dtel:typeName>{self.domain_name}</dtel:typeName>"
|
||||
"<dtel:dataType/>"
|
||||
"<dtel:dataTypeLength/>"
|
||||
"<dtel:dataTypeDecimals/>"
|
||||
)
|
||||
else:
|
||||
type_block += (
|
||||
"<dtel:typeKind>predefinedAbapType</dtel:typeKind>"
|
||||
"<dtel:typeName/>"
|
||||
f"<dtel:dataType>{self.datatype}</dtel:dataType>"
|
||||
f"<dtel:dataTypeLength>{self.length:06d}</dtel:dataTypeLength>"
|
||||
f"<dtel:dataTypeDecimals>{self.decimals:06d}</dtel:dataTypeDecimals>"
|
||||
)
|
||||
type_block += (
|
||||
"<dtel:shortFieldLabel/>"
|
||||
"<dtel:shortFieldLength>00</dtel:shortFieldLength>"
|
||||
"<dtel:shortFieldMaxLength>10</dtel:shortFieldMaxLength>"
|
||||
"<dtel:mediumFieldLabel/>"
|
||||
"<dtel:mediumFieldLength>00</dtel:mediumFieldLength>"
|
||||
"<dtel:mediumFieldMaxLength>20</dtel:mediumFieldMaxLength>"
|
||||
"<dtel:longFieldLabel/>"
|
||||
"<dtel:longFieldLength>00</dtel:longFieldLength>"
|
||||
"<dtel:longFieldMaxLength>40</dtel:longFieldMaxLength>"
|
||||
"<dtel:headingFieldLabel/>"
|
||||
"<dtel:headingFieldLength>00</dtel:headingFieldLength>"
|
||||
"<dtel:headingFieldMaxLength>55</dtel:headingFieldMaxLength>"
|
||||
"<dtel:searchHelp/>"
|
||||
"<dtel:searchHelpParameter/>"
|
||||
"<dtel:setGetParameter/>"
|
||||
"<dtel:defaultComponentName/>"
|
||||
"<dtel:deactivateInputHistory>false</dtel:deactivateInputHistory>"
|
||||
"<dtel:changeDocument>false</dtel:changeDocument>"
|
||||
"<dtel:leftToRightDirection>false</dtel:leftToRightDirection>"
|
||||
"<dtel:deactivateBIDIFiltering>false</dtel:deactivateBIDIFiltering>"
|
||||
"</dtel:dataElement>"
|
||||
)
|
||||
return f"<?xml version=\"1.0\" encoding=\"UTF-8\"?>{header}{type_block}</blue:wbobj>"
|
||||
|
||||
|
||||
@dataclass
|
||||
class TableField:
|
||||
name: str
|
||||
type_name: str
|
||||
is_key: bool = False
|
||||
not_null: bool = False
|
||||
|
||||
def to_ddl(self) -> str:
|
||||
parts = [f" {self.name:40s}: {self.type_name}"]
|
||||
if self.not_null:
|
||||
parts.append("not null")
|
||||
return " ".join(parts) + ";"
|
||||
|
||||
|
||||
@dataclass
|
||||
class TableDefinition:
|
||||
fields: list[TableField] = field(default_factory=list)
|
||||
label: str = ""
|
||||
enhancement_category: str = "#NOT_CLASSIFIED"
|
||||
delivery_class: str = "#A"
|
||||
data_maintenance: str = "#LIMITED"
|
||||
table_category: str = "#TRANSPARENT"
|
||||
|
||||
def to_ddl(self, name: str, description: str) -> str:
|
||||
lines = []
|
||||
lines.append(f"@EndUserText.label : '{_ddl_escape(description)}'")
|
||||
lines.append(f"@AbapCatalog.enhancementCategory : {self.enhancement_category}")
|
||||
lines.append(f"@AbapCatalog.tableCategory : {self.table_category}")
|
||||
lines.append(f"@AbapCatalog.deliveryClass : {self.delivery_class}")
|
||||
lines.append(f"@AbapCatalog.dataMaintenance : {self.data_maintenance}")
|
||||
lines.append(f"define table {name.lower()} {{")
|
||||
for f in self.fields:
|
||||
lines.append(f.to_ddl())
|
||||
lines.append("}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
@dataclass
|
||||
class StructureDefinition:
|
||||
fields: list[TableField] = field(default_factory=list)
|
||||
enhancement_category: str = "#NOT_CLASSIFIED"
|
||||
|
||||
def to_ddl(self, name: str, description: str) -> str:
|
||||
lines = []
|
||||
lines.append(f"@EndUserText.label : '{_ddl_escape(description)}'")
|
||||
lines.append(f"@AbapCatalog.enhancementCategory : {self.enhancement_category}")
|
||||
lines.append(f"define structure {name.lower()} {{")
|
||||
for f in self.fields:
|
||||
lines.append(f.to_ddl())
|
||||
lines.append("}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
@dataclass
|
||||
class TableTypeDefinition:
|
||||
line_type: str = ""
|
||||
key_type: str = "#USER_DEFINED"
|
||||
access_mode: str = "#STANDARD"
|
||||
key_fields: list[str] = field(default_factory=list)
|
||||
|
||||
def to_xml(self, name: str, description: str, package: str = "$TMP") -> str:
|
||||
ns = (
|
||||
'xmlns:ttyp="http://www.sap.com/adt/dictionary/tabletypes"'
|
||||
' xmlns:adtcore="http://www.sap.com/adt/core"'
|
||||
)
|
||||
header = (
|
||||
f'<ttyp:tableType {ns}'
|
||||
f' adtcore:name="{_xml_escape(name)}"'
|
||||
f' adtcore:type="TTYP"'
|
||||
f' adtcore:description="{_xml_escape(description)}"'
|
||||
f' adtcore:language="EN">'
|
||||
f'<adtcore:packageRef adtcore:name="{_xml_escape(package)}"/>'
|
||||
)
|
||||
body = (
|
||||
"<ttyp:content>"
|
||||
"<ttyp:accessMode>"
|
||||
f"<ttyp:mode>{self.access_mode}</ttyp:mode>"
|
||||
"</ttyp:accessMode>"
|
||||
"<ttyp:keyDefinition>"
|
||||
f"<ttyp:definition>{self.key_type}</ttyp:definition>"
|
||||
"</ttyp:keyDefinition>"
|
||||
"<ttyp:rowType>"
|
||||
f"<ttyp:typeKind>structured</ttyp:typeKind>"
|
||||
f"<ttyp:typeName>{self.line_type}</ttyp:typeName>"
|
||||
"</ttyp:rowType>"
|
||||
"</ttyp:content>"
|
||||
)
|
||||
return f"<?xml version=\"1.0\" encoding=\"UTF-8\"?>{header}{body}</ttyp:tableType>"
|
||||
|
||||
|
||||
def load_definition_from_json(path: str, obj_type: str) -> str | None:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
return build_definition(data, obj_type)
|
||||
|
||||
|
||||
def build_definition(data: dict, obj_type: str) -> str | None:
|
||||
if obj_type == "domain":
|
||||
return _build_domain(data)
|
||||
elif obj_type == "dataelement":
|
||||
return _build_dataelement(data)
|
||||
elif obj_type in ("table", "structure"):
|
||||
return None
|
||||
elif obj_type == "tabletype":
|
||||
return _build_tabletype(data)
|
||||
return None
|
||||
|
||||
|
||||
def _build_domain(data: dict) -> str:
|
||||
defn = DomainDefinition(
|
||||
datatype=data.get("datatype", "CHAR"),
|
||||
length=data.get("length", 10),
|
||||
decimals=data.get("decimals", 0),
|
||||
lowercase=data.get("lowercase", False),
|
||||
fix_values=data.get("fix_values", []),
|
||||
)
|
||||
name = data.get("name", "ZDOMAIN")
|
||||
description = data.get("description", name)
|
||||
package = data.get("package", "$TMP")
|
||||
return defn.to_xml(name, description, package)
|
||||
|
||||
|
||||
def _build_dataelement(data: dict) -> str:
|
||||
defn = DataElementDefinition(
|
||||
datatype=data.get("datatype", "CHAR"),
|
||||
length=data.get("length", 10),
|
||||
decimals=data.get("decimals", 0),
|
||||
domain_name=data.get("domain_name", ""),
|
||||
)
|
||||
name = data.get("name", "ZDATAELEMENT")
|
||||
description = data.get("description", name)
|
||||
package = data.get("package", "$TMP")
|
||||
return defn.to_xml(name, description, package)
|
||||
|
||||
|
||||
def _build_tabletype(data: dict) -> str:
|
||||
defn = TableTypeDefinition(
|
||||
line_type=data.get("line_type", ""),
|
||||
key_type=data.get("key_type", "#USER_DEFINED"),
|
||||
access_mode=data.get("access_mode", "#STANDARD"),
|
||||
key_fields=data.get("key_fields", []),
|
||||
)
|
||||
name = data.get("name", "ZTABLETYPE")
|
||||
description = data.get("description", name)
|
||||
package = data.get("package", "$TMP")
|
||||
return defn.to_xml(name, description, package)
|
||||
Reference in New Issue
Block a user