/[escript]/trunk/doc/doxygen/pythfilter
ViewVC logotype

Contents of /trunk/doc/doxygen/pythfilter

Parent Directory Parent Directory | Revision Log Revision Log


Revision 343 - (show annotations)
Tue Dec 13 03:39:42 2005 UTC (17 years, 3 months ago) by jgs
File size: 19899 byte(s)
create build directory structure for documentation
1 #!/usr/bin/env python
2
3 # pythfilter.py v1.5.5, written by Matthias Baas (baas@ira.uka.de)
4
5 # Doxygen filter which can be used to document Python source code.
6 # Classes (incl. methods) and functions can be documented.
7 # Every comment that begins with ## is literally turned into an
8 # Doxygen comment. Consecutive comment lines are turned into
9 # comment blocks (-> /** ... */).
10 # All the stuff is put inside a namespace with the same name as
11 # the source file.
12
13 # Conversions:
14 # ============
15 # ##-blocks -> /** ... */
16 # "class name(base): ..." -> "class name : public base {...}"
17 # "def name(params): ..." -> "name(params) {...}"
18
19 # Changelog:
20 # 21.01.2003: Raw (r"") or unicode (u"") doc string will now be properly
21 # handled. (thanks to Richard Laager for the patch)
22 # 22.12.2003: Fixed a bug where no function names would be output for "def"
23 # blocks that were not in a class.
24 # (thanks to Richard Laager for the patch)
25 # 12.12.2003: Implemented code to handle static and class methods with
26 # this logic: Methods with "self" as the first argument are
27 # non-static. Methods with "cls" are Python class methods,
28 # which translate into static methods for Doxygen. Other
29 # methods are assumed to be static methods. As should be
30 # obvious, this logic doesn't take into account if the method
31 # is actually setup as a classmethod() or a staticmethod(),
32 # just if it follows the normal conventions.
33 # (thanks to Richard Laager for the patch)
34 # 11.12.2003: Corrected #includes to use os.path.sep instead of ".". Corrected
35 # namespace code to use "::" instead of ".".
36 # (thanks to Richard Laager for the patch)
37 # 11.12.2003: Methods beginning with two underscores that end with
38 # something other than two underscores are considered private
39 # and are handled accordingly.
40 # (thanks to Richard Laager for the patch)
41 # 03.12.2003: The first parameter of class methods (self) is removed from
42 # the documentation.
43 # 03.11.2003: The module docstring will be used as namespace documentation
44 # (thanks to Joe Bronkema for the patch)
45 # 08.07.2003: Namespaces get a default documentation so that the namespace
46 # and its contents will show up in the generated documentation.
47 # 05.02.2003: Directories will be delted during synchronization.
48 # 31.01.2003: -f option & filtering entire directory trees.
49 # 10.08.2002: In base classes the '.' will be replaced by '::'
50 # 18.07.2002: * and ** will be translated into arguments
51 # 18.07.2002: Argument lists may contain default values using constructors.
52 # 18.06.2002: Support for ## public:
53 # 21.01.2002: from ... import will be translated to "using namespace ...;"
54 # TODO: "from ... import *" vs "from ... import names"
55 # TODO: Using normal imports: name.name -> name::name
56 # 20.01.2002: #includes will be placed in front of the namespace
57
58 ######################################################################
59
60 # The program is written as a state machine with the following states:
61 #
62 # - OUTSIDE The current position is outside any comment,
63 # class definition or function.
64 #
65 # - BUILD_COMMENT Begins with first "##".
66 # Ends with the first token that is no "##"
67 # at the same column as before.
68 #
69 # - BUILD_CLASS_DECL Begins with "class".
70 # Ends with ":"
71 # - BUILD_CLASS_BODY Begins just after BUILD_CLASS_DECL.
72 # The first following token (which is no comment)
73 # determines indentation depth.
74 # Ends with a token that has a smaller indendation.
75 #
76 # - BUILD_DEF_DECL Begins with "def".
77 # Ends with ":".
78 # - BUILD_DEF_BODY Begins just after BUILD_DEF_DECL.
79 # The first following token (which is no comment)
80 # determines indentation depth.
81 # Ends with a token that has a smaller indendation.
82
83 import getopt
84 import glob
85 import os.path
86 import shutil
87 import string
88 import sys
89 import token
90 import tokenize
91
92 from stat import *
93
94 OUTSIDE = 0
95 BUILD_COMMENT = 1
96 BUILD_CLASS_DECL = 2
97 BUILD_CLASS_BODY = 3
98 BUILD_DEF_DECL = 4
99 BUILD_DEF_BODY = 5
100 IMPORT = 6
101 IMPORT_OP = 7
102 IMPORT_APPEND = 8
103
104 # Output file stream
105 outfile = sys.stdout
106
107 # Output buffer
108 outbuffer = []
109
110 out_row = 0
111 out_col = 0
112
113 # Variables used by rec_name_n_param()
114 name = ""
115 param = ""
116 doc_string = ""
117 record_state = 0
118 bracket_counter = 0
119
120 # Tuple: (row,column)
121 class_spos = (0,0)
122 def_spos = (0,0)
123 import_spos = (0,0)
124
125 # Which import was used? ("import" or "from")
126 import_token = ""
127
128 # Comment block buffer
129 comment_block = []
130 comment_finished = 0
131
132 # Imported modules
133 modules = []
134
135 # Program state
136 stateStack = [OUTSIDE]
137
138 # Keep track of whether module has a docstring
139 module_has_docstring = False
140
141 # Keep track of member protection
142 protection_level = "public"
143 private_member = False
144
145 # Keep track of the module namespace
146 namespace = ""
147
148 ######################################################################
149 # Output string s. '\n' may only be at the end of the string (not
150 # somewhere in the middle).
151 #
152 # In: s - String
153 # spos - Startpos
154 ######################################################################
155 def output(s,spos, immediate=0):
156 global outbuffer, out_row, out_col, outfile
157
158 os = string.rjust(s,spos[1]-out_col+len(s))
159 if immediate:
160 outfile.write(os)
161 else:
162 outbuffer.append(os)
163 if (s[-1:]=="\n"):
164 out_row = out_row+1
165 out_col = 0
166 else:
167 out_col = spos[1]+len(s)
168
169
170 ######################################################################
171 # Records a name and parameters. The name is either a class name or
172 # a function name. Then the parameter is either the base class or
173 # the function parameters.
174 # The name is stored in the global variable "name", the parameters
175 # in "param".
176 # The variable "record_state" holds the current state of this internal
177 # state machine.
178 # The recording is started by calling start_recording().
179 #
180 # In: type, tok
181 ######################################################################
182 def rec_name_n_param(type, tok):
183 global record_state,name,param,doc_string,bracket_counter
184 s = record_state
185 # State 0: Do nothing.
186 if (s==0):
187 return
188 # State 1: Remember name.
189 elif (s==1):
190 name = tok
191 record_state = 2
192 # State 2: Wait for opening bracket or colon
193 elif (s==2):
194 if (tok=='('):
195 bracket_counter = 1
196 record_state=3
197 if (tok==':'): record_state=4
198 # State 3: Store parameter (or base class) and wait for an ending bracket
199 elif (s==3):
200 if (tok=='*' or tok=='**'):
201 tok=''
202 if (tok=='('):
203 bracket_counter = bracket_counter+1
204 if (tok==')'):
205 bracket_counter = bracket_counter-1
206 if bracket_counter==0:
207 record_state=4
208 else:
209 param=param+tok
210 # State 4: Look for doc string
211 elif (s==4):
212 if (type==token.NEWLINE or type==token.INDENT or type==token.SLASHEQUAL):
213 return
214 elif (tok==":"):
215 return
216 elif (type==token.STRING):
217 while tok[:1]=='r' or tok[:1]=='u':
218 tok=tok[1:]
219 while tok[:1]=='"':
220 tok=tok[1:]
221 while tok[-1:]=='"':
222 tok=tok[:-1]
223 doc_string=tok
224 record_state=0
225
226 ######################################################################
227 # Starts the recording of a name & param part.
228 # The function rec_name_n_param() has to be fed with tokens. After
229 # the necessary tokens are fed the name and parameters can be found
230 # in the global variables "name" und "param".
231 ######################################################################
232 def start_recording():
233 global record_state,param,name, doc_string
234 record_state=1
235 name=""
236 param=""
237 doc_string=""
238
239 ######################################################################
240 # Test if recording is finished
241 ######################################################################
242 def is_recording_finished():
243 global record_state
244 return record_state==0
245
246 ######################################################################
247 ## Gather comment block
248 ######################################################################
249 def gather_comment(type,tok,spos):
250 global comment_block,comment_finished
251 if (type!=tokenize.COMMENT):
252 comment_finished = 1
253 else:
254 # Output old comment block if a new one is started.
255 if (comment_finished):
256 print_comment(spos)
257 comment_finished=0
258 if (tok[0:2]=="##" and tok[0:3]!="###"):
259 comment_block.append(tok[2:])
260
261 ######################################################################
262 ## Output comment block and empty buffer.
263 ######################################################################
264 def print_comment(spos):
265 global comment_block,comment_finished
266 if (comment_block!=[]):
267 output("/**\n",spos)
268 for c in comment_block:
269 output(c,spos)
270 output("*/\n",spos)
271 comment_block = []
272 comment_finished = 0
273
274 ######################################################################
275 def set_state(s):
276 global stateStack
277 stateStack[len(stateStack)-1]=s
278
279 ######################################################################
280 def get_state():
281 global stateStack
282 return stateStack[len(stateStack)-1]
283
284 ######################################################################
285 def push_state(s):
286 global stateStack
287 stateStack.append(s)
288
289 ######################################################################
290 def pop_state():
291 global stateStack
292 stateStack.pop()
293
294
295 ######################################################################
296 def tok_eater(type, tok, spos, epos, line):
297 global stateStack,name,param,class_spos,def_spos,import_spos
298 global doc_string, modules, import_token, module_has_docstring
299 global protection_level, private_member
300
301 rec_name_n_param(type,tok)
302 if (string.replace(string.strip(tok)," ","")=="##private:"):
303 protection_level = "private"
304 output("private:\n",spos)
305 elif (string.replace(string.strip(tok)," ","")=="##protected:"):
306 protection_level = "protected"
307 output("protected:\n",spos)
308 elif (string.replace(string.strip(tok)," ","")=="##public:"):
309 protection_level = "public"
310 output("public:\n",spos)
311 else:
312 gather_comment(type,tok,spos)
313
314 state = get_state()
315
316 # sys.stderr.write("%d: %s\n"%(state, tok))
317
318 # OUTSIDE
319 if (state==OUTSIDE):
320 if (tok=="class"):
321 start_recording()
322 class_spos = spos
323 push_state(BUILD_CLASS_DECL)
324 elif (tok=="def"):
325 start_recording()
326 def_spos = spos
327 push_state(BUILD_DEF_DECL)
328 elif (tok=="import") or (tok=="from"):
329 import_token = tok
330 import_spos = spos
331 modules = []
332 push_state(IMPORT)
333 elif (spos[1] == 0 and tok[:3] == '"""'):
334 # Capture module docstring as namespace documentation
335 module_has_docstring = True
336 comment_block.append("\\namespace %s\n" % namespace)
337 comment_block.append(tok[3:-3])
338 print_comment(spos)
339
340 # IMPORT
341 elif (state==IMPORT):
342 if (type==token.NAME):
343 modules.append(tok)
344 set_state(IMPORT_OP)
345 # IMPORT_OP
346 elif (state==IMPORT_OP):
347 if (tok=="."):
348 set_state(IMPORT_APPEND)
349 elif (tok==","):
350 set_state(IMPORT)
351 else:
352 for m in modules:
353 output('#include "'+m.replace('.',os.path.sep)+'.py"\n', import_spos, immediate=1)
354 if import_token=="from":
355 output('using namespace '+m.replace('.', '::')+';\n', import_spos)
356 pop_state()
357 # IMPORT_APPEND
358 elif (state==IMPORT_APPEND):
359 if (type==token.NAME):
360 modules[len(modules)-1]+="."+tok
361 set_state(IMPORT_OP)
362 # BUILD_CLASS_DECL
363 elif (state==BUILD_CLASS_DECL):
364 if (is_recording_finished()):
365 s = "class "+name
366 if (param!=""): s = s+" : public "+param.replace('.','::')
367 if (doc_string!=""): comment_block.append(doc_string)
368 print_comment(class_spos)
369 output(s+"\n",class_spos)
370 output("{\n",(class_spos[0]+1,class_spos[1]))
371 protection_level = "public"
372 output(" public:\n",(class_spos[0]+2,class_spos[1]))
373 set_state(BUILD_CLASS_BODY)
374 # BUILD_CLASS_BODY
375 elif (state==BUILD_CLASS_BODY):
376 if (type!=token.INDENT and type!=token.NEWLINE and type!=40 and
377 type!=tokenize.NL and type!=tokenize.COMMENT and
378 (spos[1]<=class_spos[1])):
379 output("}; // end of class\n",(out_row+1,class_spos[1]))
380 pop_state()
381 elif (tok=="def"):
382 start_recording()
383 def_spos = spos
384 push_state(BUILD_DEF_DECL)
385 # BUILD_DEF_DECL
386 elif (state==BUILD_DEF_DECL):
387 if (is_recording_finished()):
388 s = ''
389 # Do we document a class method? then remove the 'self' parameter
390 if BUILD_CLASS_BODY in stateStack:
391 params = param.split(",")
392 if params[0] == 'self':
393 param = string.join(params[1:], ",")
394 else:
395 s = 'static '
396 if params[0] == 'cls':
397 param = string.join(params[1:], ",")
398 s = s+name+"("+param+");\n"
399 if len(name) > 1 \
400 and name[0:2] == '__' \
401 and name[len(name)-2:len(name)] != '__' \
402 and protection_level != 'private':
403 private_member = True
404 output(" private:\n",(def_spos[0]+2,def_spos[1]))
405 else:
406 s = name+"("+param+");\n"
407 if (doc_string!=""): comment_block.append(doc_string)
408 print_comment(def_spos)
409 output(s,def_spos)
410 # output("{\n",(def_spos[0]+1,def_spos[1]))
411 set_state(BUILD_DEF_BODY)
412 # BUILD_DEF_BODY
413 elif (state==BUILD_DEF_BODY):
414 if (type!=token.INDENT and type!=token.NEWLINE \
415 and type!=40 and type!=tokenize.NL \
416 and (spos[1]<=def_spos[1])):
417 # output("} // end of method/function\n",(out_row+1,def_spos[1]))
418 if private_member and protection_level != 'private':
419 private_member = False
420 output(" " + protection_level + ":\n",(def_spos[0]+2,def_spos[1]))
421 pop_state()
422 # else:
423 # output(tok,spos)
424
425
426 def dump(filename):
427 f = open(filename)
428 r = f.readlines()
429 for s in r:
430 sys.stdout.write(s)
431
432 def filter(filename):
433 global name, module_has_docstring
434
435 path,name = os.path.split(filename)
436 root,ext = os.path.splitext(name)
437
438 output("namespace "+root+" {\n",(0,0))
439
440 # set module name for tok_eater to use if there's a module doc string
441 name = root
442
443 sys.stderr.write('Filtering "'+filename+'"...')
444 f = open(filename)
445 tokenize.tokenize(f.readline, tok_eater)
446 f.close()
447 print_comment((0,0))
448
449 output("\n",(0,0))
450 output("} // end of namespace\n",(0,0))
451
452 if not module_has_docstring:
453 # Put in default namespace documentation
454 output('/** \\namespace '+root+' \n',(0,0))
455 output(' \\brief Module "%s" */\n'%(root),(0,0))
456
457 for s in outbuffer:
458 outfile.write(s)
459
460
461 def filterFile(filename, out=sys.stdout):
462 global outfile
463
464 outfile = out
465
466 try:
467 root,ext = os.path.splitext(filename)
468
469 if ext==".py":
470 filter(filename)
471 else:
472 dump(filename)
473
474 sys.stderr.write("OK\n")
475 except IOError,e:
476 sys.stderr.write(e[1]+"\n")
477
478
479 ######################################################################
480
481 # preparePath
482 def preparePath(path):
483 """Prepare a path.
484
485 Checks if the path exists and creates it if it does not exist.
486 """
487 if not os.path.exists(path):
488 parent = os.path.dirname(path)
489 if parent!="":
490 preparePath(parent)
491 os.mkdir(path)
492
493 # isNewer
494 def isNewer(file1,file2):
495 """Check if file1 is newer than file2.
496
497 file1 must be an existing file.
498 """
499 if not os.path.exists(file2):
500 return True
501 return os.stat(file1)[ST_MTIME]>os.stat(file2)[ST_MTIME]
502
503 # convert
504 def convert(srcpath, destpath):
505 """Convert a Python source tree into a C+ stub tree.
506
507 All *.py files in srcpath (including sub-directories) are filtered
508 and written to destpath. If destpath exists, only the files
509 that have been modified are filtered again. Files that were deleted
510 from srcpath are also deleted in destpath if they are still present.
511 The function returns the number of processed *.py files.
512 """
513 count=0
514 sp = os.path.join(srcpath,"*")
515 sfiles = glob.glob(sp)
516 dp = os.path.join(destpath,"*")
517 dfiles = glob.glob(dp)
518 leftovers={}
519 for df in dfiles:
520 leftovers[os.path.basename(df)]=1
521
522 for srcfile in sfiles:
523 basename = os.path.basename(srcfile)
524 if basename in leftovers:
525 del leftovers[basename]
526
527 # Is it a subdirectory?
528 if os.path.isdir(srcfile):
529 sdir = os.path.join(srcpath,basename)
530 ddir = os.path.join(destpath,basename)
531 count+=convert(sdir, ddir)
532 continue
533 # Check the extension (only *.py will be converted)
534 root, ext = os.path.splitext(srcfile)
535 if ext.lower()!=".py":
536 continue
537
538 destfile = os.path.join(destpath,basename)
539 if destfile==srcfile:
540 print "WARNING: Input and output names are identical!"
541 sys.exit(1)
542
543 count+=1
544 # sys.stdout.write("%s\015"%(srcfile))
545
546 if isNewer(srcfile, destfile):
547 preparePath(os.path.dirname(destfile))
548 # out=open(destfile,"w")
549 # filterFile(srcfile, out)
550 # out.close()
551 os.system("python %s -f %s>%s"%(sys.argv[0],srcfile,destfile))
552
553 # Delete obsolete files in destpath
554 for df in leftovers:
555 dname=os.path.join(destpath,df)
556 if os.path.isdir(dname):
557 try:
558 shutil.rmtree(dname)
559 except:
560 print "Can't remove obsolete directory '%s'"%dname
561 else:
562 try:
563 os.remove(dname)
564 except:
565 print "Can't remove obsolete file '%s'"%dname
566
567 return count
568
569
570 ######################################################################
571 ######################################################################
572 ######################################################################
573
574 filter_file = False
575
576 try:
577 opts, args = getopt.getopt(sys.argv[1:], "hf", ["help"])
578 except getopt.GetoptError,e:
579 print e
580 sys.exit(1)
581
582 for o,a in opts:
583 if o=="-f":
584 filter_file = True
585
586 if filter_file:
587 # Filter the specified file and print the result to stdout
588 filename = string.join(args)
589 filterFile(filename)
590 else:
591
592 if len(args)!=2:
593 sys.stderr.write("%s options input output\n"%(os.path.basename(sys.argv[0])))
594 sys.exit(1)
595
596 # Filter an entire Python source tree
597 print '"%s" -> "%s"\n'%(args[0],args[1])
598 c=convert(args[0],args[1])
599 print "%d files"%(c)
600

Properties

Name Value
svn:eol-style native
svn:keywords Author Date Id Revision

  ViewVC Help
Powered by ViewVC 1.1.26