33 # Variables used by rec_name_n_param()
45 # Which import was used? ("import" or "from")
48 # Comment block buffer
56 stateStack = [OUTSIDE]
58 # Keep track of whether module has a docstring
59 module_has_docstring = False
61 # Keep track of member protection
62 protection_level = "public"
63 private_member = False
65 # Keep track of the module namespace
68 ######################################################################
69 # Output string s. '\n' may only be at the end of the string (not
70 # somewhere in the middle).
74 ######################################################################
75 def output(s,spos, immediate=0):
76 global outbuffer, out_row, out_col, outfile
78 os = string.rjust(s,spos[1]-out_col+len(s))
87 out_col = spos[1]+len(s)
90 ######################################################################
91 # Records a name and parameters. The name is either a class name or
92 # a function name. Then the parameter is either the base class or
93 # the function parameters.
94 # The name is stored in the global variable "name", the parameters
96 # The variable "record_state" holds the current state of this internal
98 # The recording is started by calling start_recording().
101 ######################################################################
102 def rec_name_n_param(type, tok):
103 global record_state,name,param,doc_string,bracket_counter
105 # State 0: Do nothing.
108 # State 1: Remember name.
112 # State 2: Wait for opening bracket or colon
117 if (tok==':'): record_state=4
118 # State 3: Store parameter (or base class) and wait for an ending bracket
120 if (tok=='*' or tok=='**'):
123 bracket_counter = bracket_counter+1
125 bracket_counter = bracket_counter-1
126 if bracket_counter==0:
130 # State 4: Look for doc string
132 if (type==token.NEWLINE or type==token.INDENT or type==token.SLASHEQUAL):
136 elif (type==token.STRING):
137 while tok[:1]=='r' or tok[:1]=='u':
146 ######################################################################
147 # Starts the recording of a name & param part.
148 # The function rec_name_n_param() has to be fed with tokens. After
149 # the necessary tokens are fed the name and parameters can be found
150 # in the global variables "name" und "param".
151 ######################################################################
152 def start_recording():
153 global record_state,param,name, doc_string
159 ######################################################################
160 # Test if recording is finished
161 ######################################################################
162 def is_recording_finished():
164 return record_state==0
166 ######################################################################
167 ## Gather comment block
168 ######################################################################
169 def gather_comment(type,tok,spos):
170 global comment_block,comment_finished
171 if (type!=tokenize.COMMENT):
174 # Output old comment block if a new one is started.
175 if (comment_finished):
178 if (tok[0:2]=="##" and tok[0:3]!="###"):
179 comment_block.append(tok[2:])
181 ######################################################################
182 ## Output comment block and empty buffer.
183 ######################################################################
184 def print_comment(spos):
185 global comment_block,comment_finished
186 if (comment_block!=[]):
188 for c in comment_block:
194 ######################################################################
197 stateStack[len(stateStack)-1]=s
199 ######################################################################
202 return stateStack[len(stateStack)-1]
204 ######################################################################
209 ######################################################################
215 ######################################################################
216 def tok_eater(type, tok, spos, epos, line):
217 global stateStack,name,param,class_spos,def_spos,import_spos
218 global doc_string, modules, import_token, module_has_docstring
219 global protection_level, private_member
221 rec_name_n_param(type,tok)
222 if (string.replace(string.strip(tok)," ","")=="##private:"):
223 protection_level = "private"
224 output("private:\n",spos)
225 elif (string.replace(string.strip(tok)," ","")=="##protected:"):
226 protection_level = "protected"
227 output("protected:\n",spos)
228 elif (string.replace(string.strip(tok)," ","")=="##public:"):
229 protection_level = "public"
230 output("public:\n",spos)
232 gather_comment(type,tok,spos)
236 # sys.stderr.write("%d: %s\n"%(state, tok))
243 push_state(BUILD_CLASS_DECL)
247 push_state(BUILD_DEF_DECL)
248 elif (tok=="import") or (tok=="from"):
253 elif (spos[1] == 0 and tok[:3] == '"""'):
254 # Capture module docstring as namespace documentation
255 module_has_docstring = True
256 #comment_block.append("\\namespace %s\n" % namespace)
257 comment_block.append(tok[3:-3])
261 elif (state==IMPORT):
262 if (type==token.NAME):
266 elif (state==IMPORT_OP):
268 set_state(IMPORT_APPEND)
273 output('#include "'+m.replace('.',os.sep)+'.py"\n', import_spos, immediate=1)
274 if import_token=="from":
275 output('using namespace '+m.replace('.', '::')+';\n', import_spos)
278 elif (state==IMPORT_APPEND):
279 if (type==token.NAME):
280 modules[len(modules)-1]+="."+tok
283 elif (state==BUILD_CLASS_DECL):
284 if (is_recording_finished()):
286 if (param!=""): s = s+" : public "+param.replace('.','::')
287 if (doc_string!=""): comment_block.append(doc_string)
288 print_comment(class_spos)
289 output(s+"\n",class_spos)
290 output("{\n",(class_spos[0]+1,class_spos[1]))
291 protection_level = "public"
292 output(" public:\n",(class_spos[0]+2,class_spos[1]))
293 set_state(BUILD_CLASS_BODY)
295 elif (state==BUILD_CLASS_BODY):
296 if (type!=token.INDENT and type!=token.NEWLINE and type!=40 and
297 type!=tokenize.NL and type!=tokenize.COMMENT and
298 (spos[1]<=class_spos[1])):
299 output("}; // end of class\n",(out_row+1,class_spos[1]))
304 push_state(BUILD_DEF_DECL)
306 elif (state==BUILD_DEF_DECL):
307 if (is_recording_finished()):
309 # Do we document a class method? then remove the 'self' parameter
310 if BUILD_CLASS_BODY in stateStack:
311 params = param.split(",")
312 if params[0] == 'self':
313 param = string.join(params[1:], ",")
316 if params[0] == 'cls':
317 param = string.join(params[1:], ",")
318 s = s+name+"("+param+");\n"
320 and name[0:2] == '__' \
321 and name[len(name)-2:len(name)] != '__' \
322 and protection_level != 'private':
323 private_member = True
324 output(" private:\n",(def_spos[0]+2,def_spos[1]))
326 s = name+"("+param+");\n"
327 if (doc_string!=""): comment_block.append(doc_string)
328 print_comment(def_spos)
330 # output("{\n",(def_spos[0]+1,def_spos[1]))
331 set_state(BUILD_DEF_BODY)
333 elif (state==BUILD_DEF_BODY):
334 if (type!=token.INDENT and type!=token.NEWLINE \
335 and type!=40 and type!=tokenize.NL \
336 and (spos[1]<=def_spos[1])):
337 # output("} // end of method/function\n",(out_row+1,def_spos[1]))
338 if private_member and protection_level != 'private':
339 private_member = False
340 output(" " + protection_level + ":\n",(def_spos[0]+2,def_spos[1]))
352 def filter(filename):
353 global name, module_has_docstring
354 global namespace,outbuffer
357 path,name = os.path.split(filename)
358 root,ext = os.path.splitext(name)
362 if root == "__init__":
365 root=namespace+"::"+root
369 output("namespace "+root+" {\n",(0,0))
371 # set module name for tok_eater to use if there's a module doc string
376 sys.stderr.write("namespace: "+namespace+'\n')
377 sys.stderr.write("root: "+root+'\n')
378 sys.stderr.write('Filtering "'+filename+'"...')
381 tokenize.tokenize(f.readline, tok_eater)
386 output("} // end of namespace\n",(0,0))
388 if not module_has_docstring:
389 # Put in default namespace documentation
390 output('/** \\namespace '+root+' \n',(0,0))
391 output(' \\brief Module "%s" */\n'%(root),(0,0))
396 module_has_docstring = False
399 def filterFile(filename, out=sys.stdout):
405 root,ext = os.path.splitext(filename)
412 sys.stderr.write("OK\n")
414 sys.stderr.write(e[1]+"\n")
417 ######################################################################
420 def preparePath(path):
423 Checks if the path exists and creates it if it does not exist.
425 if not os.path.exists(path):
426 parent = os.path.dirname(path)
432 def isNewer(file1,file2):
433 """Check if file1 is newer than file2.
435 file1 must be an existing file.
437 if not os.path.exists(file2):
439 return os.stat(file1)[ST_MTIME]>os.stat(file2)[ST_MTIME]
442 def convert(srcpath, destpath):
443 """Convert a Python source tree into a C+ stub tree.
445 All *.py files in srcpath (including sub-directories) are filtered
446 and written to destpath. If destpath exists, only the files
447 that have been modified are filtered again. Files that were deleted
448 from srcpath are also deleted in destpath if they are still present.
449 The function returns the number of processed *.py files.
453 l=os.listdir(srcpath)
454 if "__init__.py" in l:
456 namespace=namespace+"::"+os.path.split(srcpath)[1]
458 namespace=os.path.split(srcpath)[1]
459 print "It's a package:",namespace
460 sp = os.path.join(srcpath,"*")
461 sfiles = glob.glob(sp)
462 dp = os.path.join(destpath,"*")
463 dfiles = glob.glob(dp)
466 leftovers[os.path.basename(df)]=1
468 for srcfile in sfiles:
469 basename = os.path.basename(srcfile)
470 if basename in leftovers:
471 del leftovers[basename]
473 # Is it a subdirectory?
474 if os.path.isdir(srcfile):
476 sdir = os.path.join(srcpath,basename)
477 ddir = os.path.join(destpath,basename)
478 count+=convert(sdir, ddir)
481 # Check the extension (only *.py will be converted)
482 root, ext = os.path.splitext(srcfile)
483 if ext.lower()!=".py":
486 destfile = os.path.join(destpath,basename)
487 if destfile==srcfile:
488 print "WARNING: Input and output names are identical!"
492 # sys.stdout.write("%s\015"%(srcfile))
494 if isNewer(srcfile, destfile):
495 preparePath(os.path.dirname(destfile))
496 out=open(destfile,"w")
497 filterFile(srcfile, out)
499 # os.system("python %s -f %s>%s"%(sys.argv[0],srcfile,destfile))
501 # Delete obsolete files in destpath
503 dname=os.path.join(destpath,df)
504 if os.path.isdir(dname):
508 print "Can't remove obsolete directory '%s'"%dname
513 print "Can't remove obsolete file '%s'"%dname
518 ######################################################################
519 ######################################################################
520 ######################################################################
525 opts, args = getopt.getopt(sys.argv[1:], "hf", ["help"])
526 except getopt.GetoptError,e:
535 # Filter the specified file and print the result to stdout
536 filename = string.join(args)
541 sys.stderr.write("%s options input output\n"%(os.path.basename(sys.argv[0])))
544 # Filter an entire Python source tree
545 print '"%s" -> "%s"\n'%(args[0],args[1])
546 c=convert(args[0],args[1])