File indexing completed on 2024-06-09 04:34:04
0001 #!/usr/bin/env python2.7 0002 # This file is part of KDevelop 0003 # SPDX-FileCopyrightText: 2011 Victor Varvariuc <victor.varvariuc@gmail.com> 0004 # SPDX-FileCopyrightText: 2011 Sven Brauch <svenbrauch@googlemail.com> 0005 0006 import sys 0007 from xml.dom import minidom 0008 0009 0010 class NoDefaultValue(): 0011 '''Unique object for marking absence of a default value for a function argument.''' 0012 0013 def indentCode(code, level): 0014 '''Indent given source code.''' 0015 return '\n'.join(' ' * level + line for line in code.splitlines()) 0016 0017 def getNodeNames(node): 0018 '''Return node name. Chop the beginning of the name if it starts with parent's prefix: 0019 QWidget.RenderFlags.__init__ -> __init__''' 0020 name = node.attributes['name'].value 0021 parentNode = node.parentNode 0022 if parentNode.nodeName == 'Enum': # for unem members name prefix is not enum's name, but its parent's 0023 parentNode = parentNode.parentNode 0024 0025 parentName = [] 0026 while parentNode.nodeName == 'Class': 0027 parentName.append(parentNode.attributes['name'].value) 0028 parentNode = parentNode.parentNode 0029 parentName = list(reversed(parentName)) 0030 parentName = '.'.join(parentName) + '.' 0031 0032 if name.startswith(parentName): 0033 return name[len(parentName):], name 0034 return name, name 0035 0036 0037 def parseEnum(enumNode): 0038 '''Parse Enum node and return its source code.''' 0039 enumMembers = [] 0040 enumName, enumFullName = getNodeNames(enumNode) 0041 for node in enumNode.childNodes: 0042 if node.nodeType == node.ELEMENT_NODE: 0043 if node.nodeName == 'EnumMember': 0044 #print getNodeNames(node) 0045 enumMemberName = getNodeNames(node)[0] 0046 if enumMemberName == 'None': 0047 enumMemberName = '__kdevpythondocumentation_builtin_None' 0048 enumMembers.append(enumMemberName) 0049 else: 0050 print 'Unknown sub-node in Enum %s: %s' % (enumFullName, node.nodeName) 0051 text = '# Enum %s\n' % enumFullName 0052 for enumMember in enumMembers: 0053 text += '%s = 0\n' % enumMember 0054 return text 0055 0056 0057 def parseFunction(functionNode): 0058 '''Parse Function node and return its code.''' 0059 params = [] 0060 retType = 'void' 0061 funcName, funcFullName = getNodeNames(functionNode) 0062 for node in functionNode.childNodes: 0063 if node.nodeType == node.ELEMENT_NODE: 0064 if not node.hasAttribute('typename'): 0065 node.attributes['typename'] = 'unknown' 0066 if node.nodeName == 'Argument': 0067 argType = node.attributes['typename'].value 0068 try: 0069 argName = '*args' if argType == '...' else node.attributes['name'].value 0070 except KeyError: # no `name` attribute - it's function return value type 0071 retType = argType 0072 else: 0073 try: # some arguments have default values 0074 defaultValue = node.attributes['default'].value 0075 except KeyError: 0076 defaultValue = NoDefaultValue 0077 try: 0078 defaultValue = defaultValue.replace("QString", "str") 0079 defaultValue = defaultValue.replace("QChar", "bytes") 0080 except AttributeError: 0081 pass 0082 params.append((argType, argName, defaultValue)) 0083 else: 0084 print 'Unhandled sub-node type in function %s: %s' % (funcFullName, node.nodeName) 0085 0086 descr = 'abstract ' if 'abstract' in functionNode.attributes.keys() else '' 0087 descr += 'static ' if 'static' in functionNode.attributes.keys() else '' 0088 0089 namesUsed = set(['self', 'exec', 'print', 'from', 'in', 'def', 'if', 'for', 0090 'while', 'return', 'raise', 'pass', 'global', 'del']) # reserved words which cannot be used as argument names 0091 if funcFullName in namesUsed: 0092 funcFullName += '_' 0093 0094 # function parameters in description 0095 paramsStr = [] 0096 for p in params: 0097 paramsStr.append('{} {}'.format(*p) if p[2] is NoDefaultValue else '{} {} = {}'.format(*p)) 0098 descr += '%s %s(%s)' % (retType, funcFullName, ', '.join(paramsStr)) 0099 0100 # function parameters in function definition 0101 paramsStr = ['self'] if functionNode.parentNode.nodeName == 'Class' else [] # add `self` first parameter for methods 0102 hadDefault = False # there has been a default argument previously 0103 for _, argName, defaultValue in params: 0104 while argName in namesUsed: # some function arguments have same names... 0105 print 'Adjusting arg name: `%s` in `%s`' % (argName, funcFullName) # http://www.riverbankcomputing.co.uk/static/Docs/PyQt4/html/qdatetime.html#QDateTime-5 0106 argName = argName + '_' 0107 namesUsed.add(argName) 0108 if defaultValue is NoDefaultValue and not hadDefault: 0109 paramsStr.append(argName) 0110 else: 0111 if defaultValue is NoDefaultValue or not defaultValue.replace('(','').replace(')','').isalnum(): 0112 defaultValue = "None" 0113 paramsStr.append('{} = {}'.format(argName, defaultValue)) 0114 hadDefault = True 0115 0116 text = "def %s(%s):\n '''%s'''\n" % (funcName, ', '.join(paramsStr), descr) 0117 if retType == 'void': 0118 return text # do not print `return None` statement 0119 if retType.startswith('list-of-'): 0120 retType = '[' + retType[8:] + '()]' 0121 elif retType.find("-or-") != -1: 0122 a, b = retType.split("-or-") 0123 retType = "{0}() if True else {1}()".format(a, b) 0124 elif retType.startswith("dict-of-"): 0125 q = retType[8:].split('-') 0126 key, value = q[0], q[1] 0127 retType = '{' + key + "():" + value + "()}" 0128 else: 0129 retType += '()' 0130 retType = retType.replace("QString", "str") 0131 retType = retType.replace("QChar", "bytes") 0132 retType = retType.replace("const", "") 0133 0134 return text + ' return %s' % retType 0135 0136 0137 def parseClass(classNode): 0138 '''Parse Class node and return its api python code.''' 0139 className, classFullName = getNodeNames(classNode) 0140 try: 0141 parentClasses = classNode.attributes['inherits'].value.split() 0142 except KeyError: 0143 parentClasses = [] 0144 text = 'class %s(%s):\n """"""\n' % (className, ', '.join(parentClasses)) 0145 for node in classNode.childNodes: 0146 if node.nodeType == node.ELEMENT_NODE: 0147 if node.nodeName == 'Member': 0148 text += ' %s = None # %s - member\n' % (getNodeNames(node)[0], node.attributes['typename'].value) 0149 elif node.nodeName == 'Function': 0150 name = getNodeNames(node)[0] 0151 if name not in ('exec', 'print'): # skip these invalid for Python names 0152 text += indentCode(parseFunction(node), 1) + '\n' 0153 elif node.nodeName == 'Enum': 0154 text += indentCode(parseEnum(node), 1) + '\n\n' 0155 elif node.nodeName == 'Class': 0156 text += indentCode(parseClass(node), 1) + '\n' 0157 elif node.nodeName == 'Signal': 0158 text += ' %s = pyqtSignal() # %s - signal\n' % (getNodeNames(node)[0], node.attributes['sig'].value) 0159 else: 0160 print 'Unhandled sub-node type in class %s: %s' % (classFullName, node.nodeName) 0161 return text 0162 0163 0164 def convertXmlToPy(inFilePath): 0165 print '\nConverting .xml PyQt5 module (%s) to .py' % inFilePath 0166 0167 print 'Parsing xml...' 0168 dom = minidom.parse(inFilePath) 0169 0170 module = dom.firstChild 0171 assert module.nodeName == 'Module' 0172 moduleName = module.attributes['name'].value 0173 print 'Module name:', moduleName 0174 0175 outFilePath = moduleName + '.py' 0176 stats = {} 0177 0178 with open(outFilePath, 'w') as file: 0179 file.write('class pyqtSignal():\n def connect(self, targetSignal): pass\n def emit(self, *args): pass\n') 0180 file.write('from QtCore import *\n\n') 0181 file.write('from QtWidgets import *\n\n') 0182 file.write('import datetime\n\n') 0183 for node in module.childNodes: 0184 if node.nodeType != node.ELEMENT_NODE: 0185 continue # skip non element nodes 0186 nodeName = node.nodeName 0187 stats[nodeName] = stats.setdefault(nodeName, 0) + 1 # stats 0188 if nodeName == 'Class': 0189 file.write(parseClass(node) + '\n\n') 0190 elif nodeName == 'Function': 0191 if not 'extends' in node.attributes.keys(): # skip module level functions, which extend unpresent here class 0192 file.write(parseFunction(node) + '\n\n') 0193 elif nodeName == 'Member': 0194 file.write('%s = None # %s member\n\n' % (getNodeNames(node)[0], node.attributes['typename'].value)) 0195 elif nodeName == 'Enum': 0196 file.write(parseEnum(node) + '\n\n') 0197 else: 0198 print 'Unhandled node type in the module:', nodeName 0199 0200 print 'Stats:', stats 0201 0202 0203 files = sys.argv[1:] # ['QtGui', 'QtCore'] # modules to convert 0204 0205 for fileName in files: 0206 convertXmlToPy(fileName)