2 # -*- coding: iso-8859-1 -*-
3 # Copyright (C) 2007-2023 CEA/DEN, EDF R&D, OPEN CASCADE
5 # Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
6 # CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
8 # This library is free software; you can redistribute it and/or
9 # modify it under the terms of the GNU Lesser General Public
10 # License as published by the Free Software Foundation; either
11 # version 2.1 of the License, or (at your option) any later version.
13 # This library is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 # Lesser General Public License for more details.
18 # You should have received a copy of the GNU Lesser General Public
19 # License along with this library; if not, write to the Free Software
20 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
55 # Variables used by rec_name_n_param()
67 # Which import was used? ("import" or "from")
70 # Comment block buffer
78 stateStack = [OUTSIDE]
80 # Keep track of whether module has a docstring
81 module_has_docstring = False
83 # Keep track of member protection
84 protection_level = "public"
85 private_member = False
87 # Keep track of the module namespace
90 ######################################################################
91 # Output string s. '\n' may only be at the end of the string (not
92 # somewhere in the middle).
96 ######################################################################
97 def output(s,spos, immediate=0):
98 global outbuffer, out_row, out_col, outfile
100 os = string.rjust(s,spos[1]-out_col+len(s))
109 out_col = spos[1]+len(s)
112 ######################################################################
113 # Records a name and parameters. The name is either a class name or
114 # a function name. Then the parameter is either the base class or
115 # the function parameters.
116 # The name is stored in the global variable "name", the parameters
118 # The variable "record_state" holds the current state of this internal
120 # The recording is started by calling start_recording().
123 ######################################################################
124 def rec_name_n_param(type, tok):
125 global record_state,name,param,doc_string,bracket_counter
127 # State 0: Do nothing.
130 # State 1: Remember name.
134 # State 2: Wait for opening bracket or colon
139 if (tok==':'): record_state=4
140 # State 3: Store parameter (or base class) and wait for an ending bracket
142 if (tok=='*' or tok=='**'):
145 bracket_counter = bracket_counter+1
147 bracket_counter = bracket_counter-1
148 if bracket_counter==0:
152 # State 4: Look for doc string
154 if (type==token.NEWLINE or type==token.INDENT or type==token.SLASHEQUAL):
158 elif (type==token.STRING):
159 while tok[:1]=='r' or tok[:1]=='u':
168 ######################################################################
169 # Starts the recording of a name & param part.
170 # The function rec_name_n_param() has to be fed with tokens. After
171 # the necessary tokens are fed the name and parameters can be found
172 # in the global variables "name" und "param".
173 ######################################################################
174 def start_recording():
175 global record_state,param,name, doc_string
181 ######################################################################
182 # Test if recording is finished
183 ######################################################################
184 def is_recording_finished():
186 return record_state==0
188 ######################################################################
189 ## Gather comment block
190 ######################################################################
191 def gather_comment(type,tok,spos):
192 global comment_block,comment_finished
193 if (type!=tokenize.COMMENT):
196 # Output old comment block if a new one is started.
197 if (comment_finished):
200 if (tok[0:2]=="##" and tok[0:3]!="###"):
201 comment_block.append(tok[2:])
203 ######################################################################
204 ## Output comment block and empty buffer.
205 ######################################################################
206 def print_comment(spos):
207 global comment_block,comment_finished
208 if (comment_block!=[]):
210 for c in comment_block:
216 ######################################################################
219 stateStack[len(stateStack)-1]=s
221 ######################################################################
224 return stateStack[len(stateStack)-1]
226 ######################################################################
231 ######################################################################
237 ######################################################################
238 def tok_eater(type, tok, spos, epos, line):
239 global stateStack,name,param,class_spos,def_spos,import_spos
240 global doc_string, modules, import_token, module_has_docstring
241 global protection_level, private_member
243 rec_name_n_param(type,tok)
244 if (string.replace(string.strip(tok)," ","")=="##private:"):
245 protection_level = "private"
246 output("private:\n",spos)
247 elif (string.replace(string.strip(tok)," ","")=="##protected:"):
248 protection_level = "protected"
249 output("protected:\n",spos)
250 elif (string.replace(string.strip(tok)," ","")=="##public:"):
251 protection_level = "public"
252 output("public:\n",spos)
254 gather_comment(type,tok,spos)
258 # sys.stderr.write("%d: %s\n"%(state, tok))
265 push_state(BUILD_CLASS_DECL)
269 push_state(BUILD_DEF_DECL)
270 elif (tok=="import") or (tok=="from"):
275 elif (spos[1] == 0 and tok[:3] == '"""'):
276 # Capture module docstring as namespace documentation
277 module_has_docstring = True
278 #comment_block.append("\\namespace %s\n" % namespace)
279 comment_block.append(tok[3:-3])
283 elif (state==IMPORT):
284 if (type==token.NAME):
288 elif (state==IMPORT_OP):
290 set_state(IMPORT_APPEND)
295 output('#include "'+m.replace('.',os.sep)+'.py"\n', import_spos, immediate=1)
296 if import_token=="from":
297 output('using namespace '+m.replace('.', '::')+';\n', import_spos)
300 elif (state==IMPORT_APPEND):
301 if (type==token.NAME):
302 modules[len(modules)-1]+="."+tok
305 elif (state==BUILD_CLASS_DECL):
306 if (is_recording_finished()):
308 if (param!=""): s = s+" : public "+param.replace('.','::')
309 if (doc_string!=""): comment_block.append(doc_string)
310 print_comment(class_spos)
311 output(s+"\n",class_spos)
312 output("{\n",(class_spos[0]+1,class_spos[1]))
313 protection_level = "public"
314 output(" public:\n",(class_spos[0]+2,class_spos[1]))
315 set_state(BUILD_CLASS_BODY)
317 elif (state==BUILD_CLASS_BODY):
318 if (type!=token.INDENT and type!=token.NEWLINE and type!=40 and
319 type!=tokenize.NL and type!=tokenize.COMMENT and
320 (spos[1]<=class_spos[1])):
321 output("}; // end of class\n",(out_row+1,class_spos[1]))
326 push_state(BUILD_DEF_DECL)
328 elif (state==BUILD_DEF_DECL):
329 if (is_recording_finished()):
331 # Do we document a class method? then remove the 'self' parameter
332 if BUILD_CLASS_BODY in stateStack:
333 params = param.split(",")
334 if params[0] == 'self':
335 param = string.join(params[1:], ",")
338 if params[0] == 'cls':
339 param = string.join(params[1:], ",")
340 s = s+name+"("+param+");\n"
342 and name[0:2] == '__' \
343 and name[len(name)-2:len(name)] != '__' \
344 and protection_level != 'private':
345 private_member = True
346 output(" private:\n",(def_spos[0]+2,def_spos[1]))
348 s = name+"("+param+");\n"
349 if (doc_string!=""): comment_block.append(doc_string)
350 print_comment(def_spos)
352 # output("{\n",(def_spos[0]+1,def_spos[1]))
353 set_state(BUILD_DEF_BODY)
355 elif (state==BUILD_DEF_BODY):
356 if (type!=token.INDENT and type!=token.NEWLINE \
357 and type!=40 and type!=tokenize.NL \
358 and (spos[1]<=def_spos[1])):
359 # output("} // end of method/function\n",(out_row+1,def_spos[1]))
360 if private_member and protection_level != 'private':
361 private_member = False
362 output(" " + protection_level + ":\n",(def_spos[0]+2,def_spos[1]))
374 def filter(filename):
375 global name, module_has_docstring
376 global namespace,outbuffer
379 path,name = os.path.split(filename)
380 root,ext = os.path.splitext(name)
384 if root == "__init__":
387 root=namespace+"::"+root
391 output("namespace "+root+" {\n",(0,0))
393 # set module name for tok_eater to use if there's a module doc string
398 sys.stderr.write("namespace: "+namespace+'\n')
399 sys.stderr.write("root: "+root+'\n')
400 sys.stderr.write('Filtering "'+filename+'"...')
403 tokenize.tokenize(f.readline, tok_eater)
408 output("} // end of namespace\n",(0,0))
410 if not module_has_docstring:
411 # Put in default namespace documentation
412 output('/** \\namespace '+root+' \n',(0,0))
413 output(' \\brief Module "%s" */\n'%(root),(0,0))
418 module_has_docstring = False
421 def filterFile(filename, out=sys.stdout):
427 root,ext = os.path.splitext(filename)
430 list(filter(filename))
434 sys.stderr.write("OK\n")
436 sys.stderr.write(e[1]+"\n")
439 ######################################################################
442 def preparePath(path):
445 Checks if the path exists and creates it if it does not exist.
447 if not os.path.exists(path):
448 parent = os.path.dirname(path)
454 def isNewer(file1,file2):
455 """Check if file1 is newer than file2.
457 file1 must be an existing file.
459 if not os.path.exists(file2):
461 return os.stat(file1)[ST_MTIME]>os.stat(file2)[ST_MTIME]
464 def convert(srcpath, destpath):
465 """Convert a Python source tree into a C+ stub tree.
467 All *.py files in srcpath (including sub-directories) are filtered
468 and written to destpath. If destpath exists, only the files
469 that have been modified are filtered again. Files that were deleted
470 from srcpath are also deleted in destpath if they are still present.
471 The function returns the number of processed *.py files.
475 l=os.listdir(srcpath)
476 if "__init__.py" in l:
478 namespace=namespace+"::"+os.path.split(srcpath)[1]
480 namespace=os.path.split(srcpath)[1]
481 print("It's a package:",namespace)
482 sp = os.path.join(srcpath,"*")
483 sfiles = glob.glob(sp)
484 dp = os.path.join(destpath,"*")
485 dfiles = glob.glob(dp)
488 leftovers[os.path.basename(df)]=1
490 for srcfile in sfiles:
491 basename = os.path.basename(srcfile)
492 if basename in leftovers:
493 del leftovers[basename]
495 # Is it a subdirectory?
496 if os.path.isdir(srcfile):
498 sdir = os.path.join(srcpath,basename)
499 ddir = os.path.join(destpath,basename)
500 count+=convert(sdir, ddir)
503 # Check the extension (only *.py will be converted)
504 root, ext = os.path.splitext(srcfile)
505 if ext.lower()!=".py":
508 destfile = os.path.join(destpath,basename)
509 if destfile==srcfile:
510 print("WARNING: Input and output names are identical!")
514 # sys.stdout.write("%s\015"%(srcfile))
516 if isNewer(srcfile, destfile):
517 preparePath(os.path.dirname(destfile))
518 out=open(destfile,"w")
519 filterFile(srcfile, out)
521 # os.system("python %s -f %s>%s"%(sys.argv[0],srcfile,destfile))
523 # Delete obsolete files in destpath
525 dname=os.path.join(destpath,df)
526 if os.path.isdir(dname):
530 print("Can't remove obsolete directory '%s'"%dname)
535 print("Can't remove obsolete file '%s'"%dname)
540 ######################################################################
541 ######################################################################
542 ######################################################################
547 opts, args = getopt.getopt(sys.argv[1:], "hf", ["help"])
548 except getopt.GetoptError as e:
557 # Filter the specified file and print the result to stdout
558 filename = string.join(args)
563 sys.stderr.write("%s options input output\n"%(os.path.basename(sys.argv[0])))
566 # Filter an entire Python source tree
567 print('"%s" -> "%s"\n'%(args[0],args[1]))
568 c=convert(args[0],args[1])
569 print("%d files"%(c))