#!/usr/bin/python3 | |
import os | |
import sys | |
import xml.etree.ElementTree as ET | |
from string import Template | |
class MethodParam: | |
_type = '' | |
_name = '' | |
_defval = '' | |
_in = False | |
_out = False | |
_isMultiCallback = False | |
_isSingleCallback = False | |
class TemplateOwn(Template): | |
delimiter = '$%' | |
pattern = ''' | |
\$%(?: | |
(?P<escaped>\$\%) | # Escape sequence of two delimiters | |
(?P<named>[_a-z][_a-z0-9]*)%\$ | # delimiter and a Python identifier | |
{(?P<braced>[_a-z][_a-z0-9]*)} | # delimiter and a braced identifier | |
(?P<invalid>) # Other ill-formed delimiter exprs | |
) | |
''' | |
def getText(e): | |
return "".join(e.itertext()) | |
def processFile(file): | |
try: | |
dom1 = ET.parse(file).getroot() | |
except FileNotFoundError: | |
print('Can\'t open:', file) | |
headerFileInfo = dom1[0].findall('location')[0].attrib['file'] | |
headerRelPath = os.path.dirname(headerFileInfo).split('/')[-1] + '/' + os.path.basename(headerFileInfo) | |
for sectDef in dom1.findall('.//memberdef'): | |
if sectDef.attrib['kind'] != 'variable' or sectDef.find('.//jsonapi') == None: | |
continue | |
instanceName = sectDef.find('name').text | |
typeName = sectDef.find('type/ref').text | |
typeFilePath = sectDef.find('type/ref').attrib['refid'] | |
try: | |
dom2 = ET.parse(doxPrefix + typeFilePath + '.xml').getroot() | |
except FileNotFoundError: | |
print('Can\'t open:', oxPrefix + typeFilePath + '.xml') | |
for member in dom2.findall('.//member'): | |
refid = member.attrib['refid'] | |
methodName = member.find('name').text | |
requiresAuth = True | |
defFilePath = refid.split('_')[0] + '.xml' | |
defFile = defFilePath | |
print('Looking for', typeName, methodName, 'into', typeFilePath) | |
try: | |
defDoc = ET.parse(doxPrefix + defFilePath).getroot() | |
except FileNotFoundError: | |
print('Can\'t open:', doxPrefix + defFilePath) | |
memberdef = None | |
for tmpMBD in defDoc.findall('.//memberdef'): | |
tmpId = tmpMBD.attrib['id'] | |
tmpKind = tmpMBD.attrib['kind'] | |
tmpJsonApiTagList = tmpMBD.findall('.//jsonapi') | |
if len(tmpJsonApiTagList) != 0 and tmpId == refid and tmpKind == 'function': | |
tmpJsonApiTag = tmpJsonApiTagList[0] | |
tmpAccessValue = None | |
if 'access' in tmpJsonApiTag.attrib: | |
tmpAccessValue = tmpJsonApiTag.attrib['access'] | |
requiresAuth = 'unauthenticated' != tmpAccessValue; | |
if 'manualwrapper' != tmpAccessValue: | |
memberdef = tmpMBD | |
break | |
if memberdef == None: | |
continue | |
apiPath = '/' + instanceName + '/' + methodName | |
retvalType = getText(memberdef.find('type')) | |
# Apparently some xml declarations include new lines ('\n') and/or multiple spaces | |
# Strip them using python magic | |
retvalType = ' '.join(retvalType.split()) | |
paramsMap = {} | |
orderedParamNames = [] | |
hasInput = False | |
hasOutput = False | |
hasSingleCallback = False | |
hasMultiCallback = False | |
callbackName = '' | |
callbackParams = '' | |
for tmpPE in memberdef.findall('param'): | |
mp = MethodParam() | |
pName = getText(tmpPE.find('declname')) | |
tmpDefval = tmpPE.find('defval') | |
mp._defval = getText(tmpDefval) if tmpDefval != None else '' | |
pType = getText(tmpPE.find('type')) | |
if pType.startswith('const '): pType = pType[6:] | |
if pType.startswith('std::function'): | |
if pType.endswith('&'): pType = pType[:-1] | |
if pName.startswith('multiCallback'): | |
mp._isMultiCallback = True | |
hasMultiCallback = True | |
elif pName.startswith('callback'): | |
mp._isSingleCallback = True | |
hasSingleCallback = True | |
callbackName = pName | |
callbackParams = pType | |
else: | |
pType = pType.replace('&', '').replace(' ', '') | |
# Apparently some xml declarations include new lines ('\n') and/or multiple spaces | |
# Strip them using python magic | |
pType = ' '.join(pType.split()) | |
mp._defval = ' '.join(mp._defval.split()) | |
mp._type = pType | |
mp._name = pName | |
paramsMap[pName] = mp | |
orderedParamNames.append(pName) | |
for tmpPN in memberdef.findall('.//parametername'): | |
tmpParam = paramsMap[tmpPN.text] | |
tmpD = tmpPN.attrib['direction'] if 'direction' in tmpPN.attrib else '' | |
if 'in' in tmpD: | |
tmpParam._in = True | |
hasInput = True | |
if 'out' in tmpD: | |
tmpParam._out = True | |
hasOutput = True | |
# Params sanity check | |
for pmKey in paramsMap: | |
pm = paramsMap[pmKey] | |
if not (pm._isMultiCallback or pm._isSingleCallback or pm._in or pm._out): | |
print('ERROR', 'Parameter:', pm._name, 'of:', apiPath, | |
'declared in:', headerRelPath, | |
'miss doxygen parameter direction attribute!', | |
defFile) | |
sys.exit() | |
functionCall = '\t\t' | |
if retvalType != 'void': | |
functionCall += retvalType + ' retval = ' | |
hasOutput = True | |
functionCall += instanceName + '->' + methodName + '(' | |
functionCall += ', '.join(orderedParamNames) + ');\n' | |
print(instanceName, apiPath, retvalType, typeName, methodName) | |
for pn in orderedParamNames: | |
mp = paramsMap[pn] | |
print('\t', mp._type, mp._name, mp._in, mp._out) | |
inputParamsDeserialization = '' | |
if hasInput: | |
inputParamsDeserialization += '\t\t{\n' | |
inputParamsDeserialization += '\t\t\tRsGenericSerializer::SerializeContext& ctx(cReq);\n' | |
inputParamsDeserialization += '\t\t\tRsGenericSerializer::SerializeJob j(RsGenericSerializer::FROM_JSON);\n'; | |
outputParamsSerialization = '' | |
if hasOutput: | |
outputParamsSerialization += '\t\t{\n' | |
outputParamsSerialization += '\t\t\tRsGenericSerializer::SerializeContext& ctx(cAns);\n' | |
outputParamsSerialization += '\t\t\tRsGenericSerializer::SerializeJob j(RsGenericSerializer::TO_JSON);\n'; | |
paramsDeclaration = '' | |
for pn in orderedParamNames: | |
mp = paramsMap[pn] | |
paramsDeclaration += '\t\t' + mp._type + ' ' + mp._name | |
if mp._defval != '': | |
paramsDeclaration += ' = ' + mp._defval | |
paramsDeclaration += ';\n' | |
if mp._in: | |
inputParamsDeserialization += '\t\t\tRS_SERIAL_PROCESS(' | |
inputParamsDeserialization += mp._name + ');\n' | |
if mp._out: | |
outputParamsSerialization += '\t\t\tRS_SERIAL_PROCESS(' | |
outputParamsSerialization += mp._name + ');\n' | |
if hasInput: | |
inputParamsDeserialization += '\t\t}\n' | |
if retvalType != 'void': | |
outputParamsSerialization += '\t\t\tRS_SERIAL_PROCESS(retval);\n' | |
if hasOutput: | |
outputParamsSerialization += '\t\t}\n' | |
captureVars = '' | |
sessionEarlyClose = '' | |
if hasSingleCallback: | |
sessionEarlyClose = 'session->close();' | |
sessionDelayedClose = '' | |
if hasMultiCallback: | |
sessionDelayedClose = 'mService.schedule( [session](){session->close();}, std::chrono::seconds(maxWait+120) );' | |
captureVars = 'this' | |
callbackParamsSerialization = '' | |
if hasSingleCallback or hasMultiCallback or (callbackParams.find('(') + 2 < callbackParams.find(')')): | |
cbs = '' | |
callbackParams = callbackParams.split('(')[1] | |
callbackParams = callbackParams.split(')')[0] | |
cbs += '\t\t\tRsGenericSerializer::SerializeContext ctx;\n' | |
for cbPar in callbackParams.split(','): | |
isConst = cbPar.startswith('const ') | |
pSep = ' ' | |
isRef = '&' in cbPar | |
if isRef: pSep = '&' | |
sepIndex = cbPar.rfind(pSep) + 1 | |
cpt = cbPar[0:sepIndex][6:] | |
cpn = cbPar[sepIndex:] | |
cbs += '\t\t\tRsTypeSerializer::serial_process(' | |
cbs += 'RsGenericSerializer::TO_JSON, ctx, ' | |
if isConst: | |
cbs += 'const_cast<' | |
cbs += cpt | |
cbs += '>(' | |
cbs += cpn | |
if isConst: cbs += ')' | |
cbs += ', "' | |
cbs += cpn | |
cbs += '" );\n' | |
callbackParamsSerialization += cbs | |
substitutionsMap = dict() | |
substitutionsMap['paramsDeclaration'] = paramsDeclaration | |
substitutionsMap['inputParamsDeserialization'] = inputParamsDeserialization | |
substitutionsMap['outputParamsSerialization'] = outputParamsSerialization | |
substitutionsMap['instanceName'] = instanceName | |
substitutionsMap['functionCall'] = functionCall | |
substitutionsMap['apiPath'] = apiPath | |
substitutionsMap['sessionEarlyClose'] = sessionEarlyClose | |
substitutionsMap['sessionDelayedClose'] = sessionDelayedClose | |
substitutionsMap['captureVars'] = captureVars | |
substitutionsMap['callbackName'] = callbackName | |
substitutionsMap['callbackParams'] = callbackParams | |
substitutionsMap['callbackParamsSerialization'] = callbackParamsSerialization | |
substitutionsMap['requiresAuth'] = 'true' if requiresAuth else 'false' | |
# print(substitutionsMap) | |
templFilePath = sourcePath | |
if hasMultiCallback or hasSingleCallback: | |
templFilePath += '/async-method-wrapper-template.cpp.tmpl' | |
else: | |
templFilePath += '/method-wrapper-template.cpp.tmpl' | |
templFile = open(templFilePath, 'r') | |
wrapperDef = TemplateOwn(templFile.read()) | |
tmp = wrapperDef.substitute(substitutionsMap) | |
wrappersDefFile.write(tmp) | |
cppApiIncludesSet.add('#include "' + headerRelPath + '"\n') | |
if len(sys.argv) != 3: | |
print('Usage:', sys.argv[0], 'SOURCE_PATH OUTPUT_PATH') | |
sys.exit() | |
sourcePath = str(sys.argv[1]) | |
outputPath = str(sys.argv[2]) | |
doxPrefix = outputPath + '/xml/' | |
try: | |
wrappersDefFile = open(outputPath + '/jsonapi-wrappers.inl', 'w') | |
except FileNotFoundError: | |
print('Can\'t open:', outputPath + '/jsonapi-wrappers.inl') | |
try: | |
cppApiIncludesFile = open(outputPath + '/jsonapi-includes.inl', 'w'); | |
except FileNotFoundError: | |
print('Can\'t open:', outputPath + '/jsonapi-includes.inl') | |
cppApiIncludesSet = set() | |
try: | |
for file in os.listdir(doxPrefix): | |
if file.endswith("8h.xml"): | |
processFile(os.path.join(doxPrefix, file)) | |
except FileNotFoundError: | |
print('Can\'t list:', doxPrefix) | |
for incl in cppApiIncludesSet: | |
cppApiIncludesFile.write(incl) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment