1 |
jgs |
122 |
#!/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 |
|
|
|