-
-
Save sehraf/23cbc8ba076b63634fee0235d74cff4b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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