diff --git a/etgtools/sip_generator.py b/etgtools/sip_generator.py index f63afe5d..f5603790 100644 --- a/etgtools/sip_generator.py +++ b/etgtools/sip_generator.py @@ -50,17 +50,34 @@ class SipWrapperGenerator(generators.WrapperGeneratorBase): """) if module.name == module.module: stream.write(""" -%%Module %s.%s +%%Module(name=%s.%s, use_argument_names=True) + %%Copying Copyright: (c) 2010 by Total Control Software Licence: wxWindows license %%End -%%RealArgNames """ % (module.package, module.name)) + + if module.name.startswith('_'): + doc = '' + if module.docstring: + doc = '\n"""\n%s\n"""\n' % module.docstring + stream.write("""\ +%%Extract pycode +# This file is generated by wxPython's SIP generator. Do not edit by hand. +# +# Copyright: (c) 2010 by Total Control Software +# Licence: wxWindows license +%s +from %s import * + +%%End + +""" % (doc, module.name)) else: - stream.write("//\n// This file is included from %s.sip\n//\n" % module.module) + stream.write("//\n// This file will be included by %s.sip\n//\n" % module.module) stream.write(divider) @@ -118,6 +135,7 @@ class SipWrapperGenerator(generators.WrapperGeneratorBase): extractors.GlobalVarDef : self.generateGlobalVar, extractors.TypedefDef : self.generateTypedef, extractors.WigCode : self.generateWigCode, + extractors.PyCodeDef : self.generatePyCode, } for item in module: @@ -194,11 +212,21 @@ class SipWrapperGenerator(generators.WrapperGeneratorBase): #----------------------------------------------------------------------- def generateWigCode(self, wig, stream, indent=''): assert isinstance(wig, extractors.WigCode) - lines = [indent+line for line in wig.code.split('\n')] - stream.write('\n'.join(lines)) + stream.write(nci(wig.code, len(indent), False)) stream.write('\n\n') + #----------------------------------------------------------------------- + def generatePyCode(self, pc, stream, indent=''): + assert isinstance(pc, extractors.PyCodeDef) + if hasattr(pc, 'klass') and pc.klass.generatingInClass: + pc.klass.generateAfterClass.append(pc) + else: + stream.write('%Extract pycode\n') + stream.write(nci(pc.code, 4)) + stream.write('\n%End\n\n') + + #----------------------------------------------------------------------- def generateClass(self, klass, stream, indent=''): assert isinstance(klass, extractors.ClassDef) @@ -218,6 +246,9 @@ class SipWrapperGenerator(generators.WrapperGeneratorBase): stream.write('%s #include <%s>\n' % (indent, inc)) stream.write('%s%%End\n' % indent) stream.write('\n%spublic:\n' % indent) + + # is the generator currently inside the class or after it? + klass.generatingInClass = True # Split the items into public and protected groups ctors = [i for i in klass if @@ -232,6 +263,8 @@ class SipWrapperGenerator(generators.WrapperGeneratorBase): extractors.MethodDef : self.generateMethod, extractors.EnumDef : self.generateEnum, extractors.CppMethodDef : self.generateCppMethod, + extractors.PyMethodDef : self.generatePyMethod, + extractors.PyCodeDef : self.generatePyCode, extractors.WigCode : self.generateWigCode, # TODO: nested classes too? } @@ -250,28 +283,37 @@ class SipWrapperGenerator(generators.WrapperGeneratorBase): f(item, stream, indent + ' '*4) if klass.convertFromPyObject: - stream.write('%s%%ConvertToTypeCode\n' % indent) - lines = [indent+l for l in klass.convertFromPyObject.split('\n')] - stream.write('\n'.join(lines)) - stream.write('%s%%End\n' % indent) + self.generateConvertCode('%ConvertToTypeCode', + klass.convertFromPyObject, + stream, indent + ' '*4) if klass.convertToPyObject: - stream.write('%s%%ConvertFromTypeCode\n' % indent) - lines = [indent+l for l in klass.convertToPyObject.split('\n')] - stream.write('\n'.join(lines)) - stream.write('%s%%End\n' % indent) + self.generateConvertCode('%ConvertFromTypeCode', + klass.convertToPyObject, + stream, indent + ' '*4) stream.write('%s}; // end of class %s\n\n\n' % (indent, klass.name)) + # Now generate anything that was deferred until after the class is finished + klass.generatingInClass = False + for item in klass.generateAfterClass: + f = dispatch[item.__class__] + f(item, stream, indent) + + + + def generateConvertCode(self, kind, code, stream, indent): + stream.write('%s%s\n' % (indent, kind)) + stream.write(nci(code, len(indent)+4)) + stream.write('%s%%End\n' % indent) - def generateMemberVar(self, memberVar, stream, indent): assert isinstance(memberVar, extractors.MemberVarDef) if memberVar.ignored: return stream.write('%s%s %s' % (indent, memberVar.type, memberVar.name)) - stream.write('%s;\n' % self.annotate(memberVar)) + stream.write('%s;\n\n' % self.annotate(memberVar)) def generateProperty(self, prop, stream, indent): @@ -320,12 +362,24 @@ class SipWrapperGenerator(generators.WrapperGeneratorBase): (indent, method.type, method.name, method.argsString, self.annotate(method))) stream.write('%s%%MethodCode\n' % indent) - lines = [indent+l for l in method.body.split('\n')] - stream.write('\n'.join(lines)) - if len(lines) == 1: - stream.write('\n%s' % indent) - stream.write('%End\n\n') + stream.write(nci(method.body, len(indent)+4)) + stream.write('%s%%End\n\n' % indent) + + def generatePyMethod(self, pm, stream, indent): + assert isinstance(pm, extractors.PyMethodDef) + if pm.klass.generatingInClass: + pm.klass.generateAfterClass.append(pm) + else: + klassName = pm.klass.pyName or pm.klass.name + stream.write("%Extract pycode\n") + stream.write("def _%s_%s%s:\n" % (klassName, pm.name, pm.argsString)) + if pm.briefDoc: + stream.write(nci('"""\n%s\n"""\n' % pm.briefDoc, 4)) + stream.write(nci(pm.body, 4)) + stream.write('%s.%s = _%s_%s\n' % (klassName, pm.name, klassName, pm.name)) + stream.write('\n%End\n\n') + #----------------------------------------------------------------------- def annotate(self, item): @@ -383,3 +437,48 @@ class SipWrapperGenerator(generators.WrapperGeneratorBase): return '' #--------------------------------------------------------------------------- +# helpers and utilities + +def nci(text, numSpaces=0, stripLeading=True): + """ + Normalize Code Indents + + First use the count of leading spaces on the first line and remove that + many spaces from the front of all lines, and then indent each line by + adding numSpaces spaces. This is used so we can convert the arbitrary + indents that might be used by the treaker code into what is expected for + the context we are generating for. + """ + def _getLeadingSpaceCount(line): + count = 0 + for c in line: + assert c != '\t', "Use spaces for indent, not tabs" + if c != ' ': + break + count += 1 + else: + assert False, "First line should not be empty." + return count + + def _allSpaces(text): + for c in text: + if c != ' ': + return False + return True + + + lines = text.rstrip().split('\n') + if stripLeading: + numStrip = _getLeadingSpaceCount(lines[0]) + else: + numStrip = 0 + + for idx, line in enumerate(lines): + assert _allSpaces(line[:numStrip]), "Indentation inconsistent with first line" + lines[idx] = ' '*numSpaces + line[numStrip:] + + newText = '\n'.join(lines) + '\n' + return newText + + +#---------------------------------------------------------------------------