mirror of
https://github.com/wxWidgets/Phoenix.git
synced 2026-01-05 19:40:12 +01:00
* Add the pi generator (for "Python Interface" files used by WingIDE and maybe others.)
* Some improvements in docstring generation * refactor and shuffle some code around git-svn-id: https://svn.wxwidgets.org/svn/wx/wxPython/Phoenix/trunk@69497 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
400
etgtools/pi_generator.py
Normal file
400
etgtools/pi_generator.py
Normal file
@@ -0,0 +1,400 @@
|
||||
#---------------------------------------------------------------------------
|
||||
# Name: etgtools/pi_generator.py
|
||||
# Author: Robin Dunn
|
||||
#
|
||||
# Created: 18-Oct-2011
|
||||
# Copyright: (c) 2011 by Total Control Software
|
||||
# License: wxWindows License
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
"""
|
||||
This generator will create "Python Interface" files, which define a skeleton
|
||||
verison of the classes, functions, attributes, docstrings, etc. as Python
|
||||
code. This is useful for enabling some introspcetion of things located in
|
||||
extension modules where there is less information available for
|
||||
introspection. The .pi files are used by WingIDE for assisting with code
|
||||
completion, displaying docstrings in the source assistant panel, etc.
|
||||
"""
|
||||
|
||||
import sys, os, re
|
||||
import extractors
|
||||
import generators
|
||||
from generators import nci
|
||||
from tweaker_tools import removeWxPrefix, magicMethods
|
||||
from cStringIO import StringIO
|
||||
|
||||
|
||||
phoenixRoot = os.path.abspath(os.path.split(__file__)[0]+'/..')
|
||||
header = """\
|
||||
#---------------------------------------------------------------------------
|
||||
# This file is generated by wxPython's PI generator. Do not edit by hand.
|
||||
#
|
||||
# (The *.pi files are used by WingIDE to provide more information than it is
|
||||
# able to glean from introspection of extension types and methods.)
|
||||
#
|
||||
# Copyright: (c) 2011 by Total Control Software
|
||||
# License: wxWindows License
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
"""
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
class PiWrapperGenerator(generators.WrapperGeneratorBase):
|
||||
|
||||
def generate(self, module, destFile=None):
|
||||
stream = StringIO()
|
||||
|
||||
# generate SIP code from the module and its objects
|
||||
self.generateModule(module, stream)
|
||||
|
||||
# Write the contents of the stream to the destination file
|
||||
if not destFile:
|
||||
name = module.module + '.pi'
|
||||
if name.startswith('_'):
|
||||
name = name[1:]
|
||||
destFile = os.path.join(phoenixRoot, 'wx', name)
|
||||
|
||||
if not os.path.exists(destFile):
|
||||
# create the file and write the header
|
||||
f = file(destFile, 'wt')
|
||||
f.write(header)
|
||||
f.close()
|
||||
|
||||
self.writeSection(destFile, module.name, stream.getvalue())
|
||||
|
||||
|
||||
def writeSection(self, destFile, sectionName, sectionText):
|
||||
"""
|
||||
Read all the lines from destFile, remove those currently between
|
||||
begin/end markers for sectionName (if any), and write the lines back
|
||||
to the file with the new text in sectionText.
|
||||
"""
|
||||
sectionBeginLine = -1
|
||||
sectionEndLine = -1
|
||||
sectionBeginMarker = '#-- begin-' + sectionName
|
||||
sectionEndMarker = '#-- end-' + sectionName
|
||||
|
||||
lines = file(destFile, 'rt').readlines()
|
||||
for idx, line in enumerate(lines):
|
||||
if line.startswith(sectionBeginMarker):
|
||||
sectionBeginLine = idx
|
||||
if line.startswith(sectionEndMarker):
|
||||
sectionEndLine = idx
|
||||
|
||||
if sectionBeginLine == -1:
|
||||
# not there already, add to the end
|
||||
lines.append(sectionBeginMarker + '\n')
|
||||
lines.append(sectionText)
|
||||
lines.append(sectionEndMarker + '\n')
|
||||
else:
|
||||
# replace the existing lines
|
||||
lines[sectionBeginLine+1:sectionEndLine] = [sectionText]
|
||||
|
||||
f = file(destFile, 'wt')
|
||||
f.writelines(lines)
|
||||
#f.write('\n')
|
||||
f.close()
|
||||
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
def generateModule(self, module, stream):
|
||||
"""
|
||||
Generate code for each of the top-level items in the module.
|
||||
"""
|
||||
assert isinstance(module, extractors.ModuleDef)
|
||||
|
||||
methodMap = {
|
||||
extractors.ClassDef : self.generateClass,
|
||||
extractors.DefineDef : self.generateDefine,
|
||||
extractors.FunctionDef : self.generateFunction,
|
||||
extractors.EnumDef : self.generateEnum,
|
||||
extractors.GlobalVarDef : self.generateGlobalVar,
|
||||
extractors.TypedefDef : self.generateTypedef,
|
||||
extractors.WigCode : self.generateWigCode,
|
||||
extractors.PyCodeDef : self.generatePyCode,
|
||||
extractors.CppMethodDef : self.generateCppMethod,
|
||||
extractors.CppMethodDef_sip : self.generateCppMethod_sip,
|
||||
}
|
||||
|
||||
for item in module:
|
||||
if item.ignored:
|
||||
continue
|
||||
function = methodMap[item.__class__]
|
||||
function(item, stream)
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
def generateEnum(self, enum, stream, indent=''):
|
||||
assert isinstance(enum, extractors.EnumDef)
|
||||
if enum.ignored:
|
||||
return
|
||||
for v in enum.items:
|
||||
if v.ignored:
|
||||
continue
|
||||
name = v.pyName or v.name
|
||||
stream.write('%s%s = 0\n' % (indent, name))
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
def generateGlobalVar(self, globalVar, stream):
|
||||
assert isinstance(globalVar, extractors.GlobalVarDef)
|
||||
if globalVar.ignored:
|
||||
return
|
||||
name = globalVar.pyName or globalVar.name
|
||||
if guessTypeInt(globalVar):
|
||||
valTyp = '0'
|
||||
elif guessTypeFloat(globalVar):
|
||||
valTyp = '0.0'
|
||||
elif guessTypeStr(globalVar):
|
||||
valTyp = '""'
|
||||
else:
|
||||
valTyp = removeWxPrefix(globalVar.type) + '()'
|
||||
|
||||
stream.write('%s = %s\n' % (name, valTyp))
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
def generateDefine(self, define, stream):
|
||||
assert isinstance(define, extractors.DefineDef)
|
||||
if define.ignored:
|
||||
return
|
||||
# we're assuming that all #defines that are not ignored are integer values
|
||||
stream.write('%s = 0\n' % (define.pyName or define.name))
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
def generateTypedef(self, typedef, stream):
|
||||
assert isinstance(typedef, extractors.TypedefDef)
|
||||
if typedef.ignored:
|
||||
return
|
||||
# write nothing for this one
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
def generateWigCode(self, wig, stream, indent=''):
|
||||
assert isinstance(wig, extractors.WigCode)
|
||||
# write nothing for this one
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
def generatePyCode(self, pc, stream, indent=''):
|
||||
assert isinstance(pc, extractors.PyCodeDef)
|
||||
stream.write('\n')
|
||||
stream.write(nci(pc.code, len(indent)))
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
def generateFunction(self, function, stream):
|
||||
assert isinstance(function, extractors.FunctionDef)
|
||||
stream.write('\ndef %s' % function.pyName)
|
||||
if function.overloads:
|
||||
stream.write('(*args, **kw)')
|
||||
else:
|
||||
stream.write(function.pyArgsString)
|
||||
stream.write(':\n')
|
||||
stream.write(' """\n')
|
||||
stream.write(nci(function.pyDocstring, 4))
|
||||
stream.write(' """\n')
|
||||
|
||||
def generateParameters(self, parameters, stream, indent):
|
||||
def _lastParameter(idx):
|
||||
if idx == len(parameters)-1:
|
||||
return True
|
||||
for i in range(idx+1, len(parameters)):
|
||||
if not parameters[i].ignored:
|
||||
return False
|
||||
return True
|
||||
|
||||
for idx, param in enumerate(parameters):
|
||||
if param.ignored:
|
||||
continue
|
||||
stream.write(param.name)
|
||||
if param.default:
|
||||
stream.write('=%s' % param.default)
|
||||
if not _lastParameter(idx):
|
||||
stream.write(', ')
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
def generateClass(self, klass, stream, indent=''):
|
||||
assert isinstance(klass, extractors.ClassDef)
|
||||
if klass.ignored:
|
||||
return
|
||||
|
||||
# class declaration
|
||||
klassName = klass.pyName or klass.name
|
||||
stream.write('\n%sclass %s' % (indent, klassName))
|
||||
if klass.bases:
|
||||
stream.write('(')
|
||||
bases = [removeWxPrefix(b) for b in klass.bases]
|
||||
stream.write(', '.join(bases))
|
||||
stream.write(')')
|
||||
stream.write(':\n')
|
||||
indent2 = indent + ' '*4
|
||||
|
||||
# docstring
|
||||
stream.write('%s"""\n' % indent2)
|
||||
stream.write(nci(klass.pyDocstring, len(indent2)))
|
||||
stream.write('%s"""\n' % indent2)
|
||||
|
||||
# generate nested classes
|
||||
for item in klass.innerclasses:
|
||||
self.generateClass(item, stream, indent2)
|
||||
|
||||
# Split the items into public and protected groups
|
||||
ctors = [i for i in klass if
|
||||
isinstance(i, extractors.MethodDef) and
|
||||
i.protection == 'public' and (i.isCtor or i.isDtor)]
|
||||
public = [i for i in klass if i.protection == 'public' and i not in ctors]
|
||||
protected = [i for i in klass if i.protection == 'protected']
|
||||
|
||||
dispatch = {
|
||||
extractors.MemberVarDef : self.generateMemberVar,
|
||||
extractors.PropertyDef : self.generateProperty,
|
||||
extractors.PyPropertyDef : self.generatePyProperty,
|
||||
extractors.MethodDef : self.generateMethod,
|
||||
extractors.EnumDef : self.generateEnum,
|
||||
extractors.CppMethodDef : self.generateCppMethod,
|
||||
extractors.CppMethodDef_sip : self.generateCppMethod_sip,
|
||||
extractors.PyMethodDef : self.generatePyMethod,
|
||||
extractors.PyCodeDef : self.generatePyCode,
|
||||
extractors.WigCode : self.generateWigCode,
|
||||
}
|
||||
|
||||
for item in ctors:
|
||||
if item.isCtor:
|
||||
self.generateMethod(item, stream, indent2,
|
||||
name='__init__', docstring=klass.pyDocstring)
|
||||
|
||||
for item in public:
|
||||
f = dispatch[item.__class__]
|
||||
f(item, stream, indent2)
|
||||
|
||||
for item in protected:
|
||||
f = dispatch[item.__class__]
|
||||
f(item, stream, indent2)
|
||||
|
||||
stream.write('%s# end of class %s\n\n' % (indent, klassName))
|
||||
|
||||
|
||||
def generateMemberVar(self, memberVar, stream, indent):
|
||||
assert isinstance(memberVar, extractors.MemberVarDef)
|
||||
if memberVar.ignored:
|
||||
return
|
||||
stream.write('%s%s = property(None, None)\n' % (indent, memberVar.name))
|
||||
|
||||
|
||||
def generateProperty(self, prop, stream, indent):
|
||||
assert isinstance(prop, extractors.PropertyDef)
|
||||
if prop.ignored:
|
||||
return
|
||||
stream.write('%s%s = property(None, None)\n' % (indent, prop.name))
|
||||
|
||||
|
||||
def generatePyProperty(self, prop, stream, indent):
|
||||
assert isinstance(prop, extractors.PyPropertyDef)
|
||||
if prop.ignored:
|
||||
return
|
||||
stream.write('%s%s = property(None, None)\n' % (indent, prop.name))
|
||||
|
||||
|
||||
def generateMethod(self, method, stream, indent, name=None, docstring=None):
|
||||
assert isinstance(method, extractors.MethodDef)
|
||||
if method.ignored:
|
||||
return
|
||||
|
||||
name = name or method.pyName or method.name
|
||||
if name in magicMethods:
|
||||
name = magicMethods[name]
|
||||
|
||||
# write the method declaration
|
||||
if method.isStatic:
|
||||
stream.write('\n%s@staticmethod' % indent)
|
||||
stream.write('\n%sdef %s' % (indent, name))
|
||||
if method.overloads:
|
||||
if not method.isStatic:
|
||||
stream.write('(self, *args, **kw)')
|
||||
else:
|
||||
stream.write('(*args, **kw)')
|
||||
else:
|
||||
argsString = method.pyArgsString
|
||||
if not argsString:
|
||||
argsString = '()'
|
||||
if not method.isStatic:
|
||||
if argsString[:2] == '()':
|
||||
argsString = '(self)' + argsString[2:]
|
||||
else:
|
||||
argsString = '(self, ' + argsString[1:]
|
||||
if '->' in argsString:
|
||||
pos = argsString.rfind(')')
|
||||
argsString = argsString[:pos+1]
|
||||
stream.write(argsString)
|
||||
stream.write(':\n')
|
||||
indent2 = indent + ' '*4
|
||||
|
||||
# docstring
|
||||
if not docstring:
|
||||
if hasattr(method, 'pyDocstring'):
|
||||
docstring = method.pyDocstring
|
||||
else:
|
||||
docstring = ""
|
||||
stream.write('%s"""\n' % indent2)
|
||||
stream.write(nci(docstring, len(indent2)))
|
||||
stream.write('%s"""\n' % indent2)
|
||||
|
||||
|
||||
|
||||
def generateCppMethod(self, method, stream, indent=''):
|
||||
assert isinstance(method, extractors.CppMethodDef)
|
||||
self.generateMethod(method, stream, indent)
|
||||
|
||||
|
||||
def generateCppMethod_sip(self, method, stream, indent=''):
|
||||
assert isinstance(method, extractors.CppMethodDef_sip)
|
||||
self.generateMethod(method, stream, indent)
|
||||
|
||||
|
||||
def generatePyMethod(self, pm, stream, indent):
|
||||
assert isinstance(pm, extractors.PyMethodDef)
|
||||
if pm.ignored:
|
||||
return
|
||||
stream.write('\n%sdef %s' % (indent, pm.name))
|
||||
stream.write(pm.argsString)
|
||||
stream.write(':\n')
|
||||
indent2 = indent + ' '*4
|
||||
|
||||
stream.write('%s"""\n' % indent2)
|
||||
stream.write(nci(pm.pyDocstring, len(indent2)))
|
||||
stream.write('%s"""\n' % indent2)
|
||||
|
||||
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# helpers
|
||||
|
||||
def guessTypeInt(v):
|
||||
if isinstance(v, extractors.EnumValueDef):
|
||||
return True
|
||||
if isinstance(v, extractors.DefineDef) and '"' not in v.value:
|
||||
return True
|
||||
type = v.type.replace('const', '')
|
||||
type = type.replace(' ', '')
|
||||
if type in ['int', 'long', 'byte', 'size_t']:
|
||||
return True
|
||||
if 'unsigned' in type:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def guessTypeFloat(v):
|
||||
type = v.type.replace('const', '')
|
||||
type = type.replace(' ', '')
|
||||
if type in ['float', 'double', 'wxDouble']:
|
||||
return True
|
||||
return False
|
||||
|
||||
def guessTypeStr(v):
|
||||
if hasattr(v, 'value') and '"' in v.value:
|
||||
return True
|
||||
if 'wxString' in v.type:
|
||||
return True
|
||||
return False
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
Reference in New Issue
Block a user