xen-vtx-unstable

diff docs/pythfilter.py @ 6774:4d899a738d59

merge?
author cl349@firebug.cl.cam.ac.uk
date Tue Sep 13 15:05:49 2005 +0000 (2005-09-13)
parents e939d5c5e646
children e7c7196fa329 8ca0f98ba8e2
line diff
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/docs/pythfilter.py	Tue Sep 13 15:05:49 2005 +0000
     1.3 @@ -0,0 +1,658 @@
     1.4 +#!/usr/bin/env python
     1.5 +
     1.6 +# pythfilter.py v1.5.5, written by Matthias Baas (baas@ira.uka.de)
     1.7 +
     1.8 +# Doxygen filter which can be used to document Python source code.
     1.9 +# Classes (incl. methods) and functions can be documented.
    1.10 +# Every comment that begins with ## is literally turned into an
    1.11 +# Doxygen comment. Consecutive comment lines are turned into
    1.12 +# comment blocks (-> /** ... */).
    1.13 +# All the stuff is put inside a namespace with the same name as
    1.14 +# the source file.
    1.15 +
    1.16 +# Conversions:
    1.17 +# ============
    1.18 +# ##-blocks                  ->  /** ... */
    1.19 +# "class name(base): ..."    ->  "class name : public base {...}"
    1.20 +# "def name(params): ..."    ->  "name(params) {...}"
    1.21 +
    1.22 +# Changelog:
    1.23 +# 21.01.2003: Raw (r"") or unicode (u"") doc string will now be properly
    1.24 +#             handled. (thanks to Richard Laager for the patch)
    1.25 +# 22.12.2003: Fixed a bug where no function names would be output for "def"
    1.26 +#             blocks that were not in a class.
    1.27 +#             (thanks to Richard Laager for the patch)
    1.28 +# 12.12.2003: Implemented code to handle static and class methods with
    1.29 +#             this logic: Methods with "self" as the first argument are
    1.30 +#             non-static. Methods with "cls" are Python class methods,
    1.31 +#             which translate into static methods for Doxygen. Other
    1.32 +#             methods are assumed to be static methods. As should be
    1.33 +#             obvious, this logic doesn't take into account if the method
    1.34 +#             is actually setup as a classmethod() or a staticmethod(),
    1.35 +#             just if it follows the normal conventions.
    1.36 +#             (thanks to Richard Laager for the patch)
    1.37 +# 11.12.2003: Corrected #includes to use os.path.sep instead of ".". Corrected
    1.38 +#             namespace code to use "::" instead of ".".
    1.39 +#             (thanks to Richard Laager for the patch)
    1.40 +# 11.12.2003: Methods beginning with two underscores that end with
    1.41 +#             something other than two underscores are considered private
    1.42 +#             and are handled accordingly.
    1.43 +#             (thanks to Richard Laager for the patch)
    1.44 +# 03.12.2003: The first parameter of class methods (self) is removed from
    1.45 +#             the documentation.
    1.46 +# 03.11.2003: The module docstring will be used as namespace documentation
    1.47 +#             (thanks to Joe Bronkema for the patch)
    1.48 +# 08.07.2003: Namespaces get a default documentation so that the namespace
    1.49 +#             and its contents will show up in the generated documentation.
    1.50 +# 05.02.2003: Directories will be delted during synchronization.
    1.51 +# 31.01.2003: -f option & filtering entire directory trees.
    1.52 +# 10.08.2002: In base classes the '.' will be replaced by '::'
    1.53 +# 18.07.2002: * and ** will be translated into arguments
    1.54 +# 18.07.2002: Argument lists may contain default values using constructors.
    1.55 +# 18.06.2002: Support for ## public:
    1.56 +# 21.01.2002: from ... import will be translated to "using namespace ...;"
    1.57 +#             TODO: "from ... import *" vs "from ... import names"
    1.58 +#             TODO: Using normal imports: name.name -> name::name
    1.59 +# 20.01.2002: #includes will be placed in front of the namespace
    1.60 +
    1.61 +######################################################################
    1.62 +
    1.63 +# The program is written as a state machine with the following states:
    1.64 +#
    1.65 +# - OUTSIDE               The current position is outside any comment,
    1.66 +#                         class definition or function.
    1.67 +#
    1.68 +# - BUILD_COMMENT         Begins with first "##".
    1.69 +#                         Ends with the first token that is no "##"
    1.70 +#                         at the same column as before.
    1.71 +#
    1.72 +# - BUILD_CLASS_DECL      Begins with "class".
    1.73 +#                         Ends with ":"
    1.74 +# - BUILD_CLASS_BODY      Begins just after BUILD_CLASS_DECL.
    1.75 +#                         The first following token (which is no comment)
    1.76 +#                         determines indentation depth.
    1.77 +#                         Ends with a token that has a smaller indendation.
    1.78 +#
    1.79 +# - BUILD_DEF_DECL        Begins with "def".
    1.80 +#                         Ends with ":".
    1.81 +# - BUILD_DEF_BODY        Begins just after BUILD_DEF_DECL.
    1.82 +#                         The first following token (which is no comment)
    1.83 +#                         determines indentation depth.
    1.84 +#                         Ends with a token that has a smaller indendation.
    1.85 +
    1.86 +import getopt
    1.87 +import glob
    1.88 +import os.path
    1.89 +import re
    1.90 +import shutil
    1.91 +import string
    1.92 +import sys
    1.93 +import token
    1.94 +import tokenize
    1.95 +
    1.96 +from stat import *
    1.97 +
    1.98 +OUTSIDE          = 0
    1.99 +BUILD_COMMENT    = 1
   1.100 +BUILD_CLASS_DECL = 2
   1.101 +BUILD_CLASS_BODY = 3
   1.102 +BUILD_DEF_DECL   = 4
   1.103 +BUILD_DEF_BODY   = 5
   1.104 +IMPORT           = 6
   1.105 +IMPORT_OP        = 7
   1.106 +IMPORT_APPEND    = 8
   1.107 +
   1.108 +# Output file stream
   1.109 +outfile = sys.stdout
   1.110 +
   1.111 +# Output buffer
   1.112 +outbuffer = []
   1.113 +
   1.114 +out_row = 1
   1.115 +out_col = 0
   1.116 +
   1.117 +# Variables used by rec_name_n_param()
   1.118 +name         = ""
   1.119 +param        = ""
   1.120 +doc_string   = ""
   1.121 +record_state = 0
   1.122 +bracket_counter = 0
   1.123 +
   1.124 +# Tuple: (row,column)
   1.125 +class_spos  = (0,0)
   1.126 +def_spos    = (0,0)
   1.127 +import_spos = (0,0)
   1.128 +
   1.129 +# Which import was used? ("import" or "from")
   1.130 +import_token = ""
   1.131 +
   1.132 +# Comment block buffer
   1.133 +comment_block = []
   1.134 +comment_finished = 0
   1.135 +
   1.136 +# Imported modules
   1.137 +modules = []
   1.138 +
   1.139 +# Program state
   1.140 +stateStack = [OUTSIDE]
   1.141 +
   1.142 +# Keep track of whether module has a docstring
   1.143 +module_has_docstring = False
   1.144 +
   1.145 +# Keep track of member protection
   1.146 +protection_level = "public"
   1.147 +private_member = False
   1.148 +
   1.149 +# Keep track of the module namespace
   1.150 +namespace = ""
   1.151 +
   1.152 +######################################################################
   1.153 +# Output string s. '\n' may only be at the end of the string (not
   1.154 +# somewhere in the middle).
   1.155 +#
   1.156 +# In: s    - String
   1.157 +#     spos - Startpos
   1.158 +######################################################################
   1.159 +def output(s,spos, immediate=0):
   1.160 +    global outbuffer, out_row, out_col, outfile
   1.161 +
   1.162 +    os = string.rjust(s,spos[1]-out_col+len(s))
   1.163 +
   1.164 +    if immediate:
   1.165 +        outfile.write(os)
   1.166 +    else:
   1.167 +        outbuffer.append(os)
   1.168 +
   1.169 +    assert -1 == string.find(s[0:-2], "\n"), s
   1.170 +
   1.171 +    if (s[-1:]=="\n"):
   1.172 +        out_row = out_row+1
   1.173 +        out_col = 0
   1.174 +    else:
   1.175 +        out_col = spos[1]+len(s)
   1.176 +
   1.177 +
   1.178 +######################################################################
   1.179 +# Records a name and parameters. The name is either a class name or
   1.180 +# a function name. Then the parameter is either the base class or
   1.181 +# the function parameters.
   1.182 +# The name is stored in the global variable "name", the parameters
   1.183 +# in "param".
   1.184 +# The variable "record_state" holds the current state of this internal
   1.185 +# state machine.
   1.186 +# The recording is started by calling start_recording().
   1.187 +#
   1.188 +# In: type, tok
   1.189 +######################################################################
   1.190 +def rec_name_n_param(type, tok):
   1.191 +    global record_state,name,param,doc_string,bracket_counter
   1.192 +    s = record_state
   1.193 +    # State 0: Do nothing.
   1.194 +    if   (s==0):
   1.195 +         return
   1.196 +    # State 1: Remember name.
   1.197 +    elif (s==1):
   1.198 +        name = tok
   1.199 +        record_state = 2
   1.200 +    # State 2: Wait for opening bracket or colon
   1.201 +    elif (s==2):
   1.202 +        if (tok=='('):
   1.203 +            bracket_counter = 1
   1.204 +            record_state=3
   1.205 +        if (tok==':'): record_state=4
   1.206 +    # State 3: Store parameter (or base class) and wait for an ending bracket
   1.207 +    elif (s==3):
   1.208 +        if (tok=='*' or tok=='**'):
   1.209 +            tok=''
   1.210 +        if (tok=='('):
   1.211 +            bracket_counter = bracket_counter+1
   1.212 +        if (tok==')'):
   1.213 +            bracket_counter = bracket_counter-1
   1.214 +        if bracket_counter==0:
   1.215 +            record_state=4
   1.216 +        else:
   1.217 +            param=param+tok
   1.218 +    # State 4: Look for doc string
   1.219 +    elif (s==4):
   1.220 +        if (type==token.NEWLINE or type==token.INDENT or type==token.SLASHEQUAL):
   1.221 +            return
   1.222 +        elif (tok==":"):
   1.223 +            return
   1.224 +        elif (type==token.STRING):
   1.225 +            while tok[:1]=='r' or tok[:1]=='u':
   1.226 +                tok=tok[1:]
   1.227 +            while tok[:1]=='"':
   1.228 +                tok=tok[1:]
   1.229 +            while tok[-1:]=='"':
   1.230 +                tok=tok[:-1]
   1.231 +            doc_string=tok
   1.232 +        record_state=0
   1.233 +
   1.234 +######################################################################
   1.235 +# Starts the recording of a name & param part.
   1.236 +# The function rec_name_n_param() has to be fed with tokens. After
   1.237 +# the necessary tokens are fed the name and parameters can be found
   1.238 +# in the global variables "name" und "param".
   1.239 +######################################################################
   1.240 +def start_recording():
   1.241 +    global record_state,param,name, doc_string
   1.242 +    record_state=1
   1.243 +    name=""
   1.244 +    param=""
   1.245 +    doc_string=""
   1.246 +
   1.247 +######################################################################
   1.248 +# Test if recording is finished
   1.249 +######################################################################
   1.250 +def is_recording_finished():
   1.251 +    global record_state
   1.252 +    return record_state==0
   1.253 +
   1.254 +######################################################################
   1.255 +## Gather comment block
   1.256 +######################################################################
   1.257 +def gather_comment(type,tok,spos):
   1.258 +    global comment_block,comment_finished
   1.259 +    if (type!=tokenize.COMMENT):
   1.260 +        comment_finished = 1
   1.261 +    else:
   1.262 +        # Output old comment block if a new one is started.
   1.263 +        if (comment_finished):
   1.264 +            print_comment(spos)
   1.265 +            comment_finished=0
   1.266 +        if (tok[0:2]=="##" and tok[0:3]!="###"):
   1.267 +            append_comment_lines(tok[2:])
   1.268 +
   1.269 +######################################################################
   1.270 +## Output comment block and empty buffer.
   1.271 +######################################################################
   1.272 +def print_comment(spos):
   1.273 +    global comment_block,comment_finished
   1.274 +    if (comment_block!=[]):
   1.275 +        output("/** ",spos)
   1.276 +        for c in comment_block:
   1.277 +            output(c,spos)
   1.278 +        output("*/\n",spos)
   1.279 +    comment_block    = []
   1.280 +    comment_finished = 0
   1.281 +
   1.282 +######################################################################
   1.283 +def set_state(s):
   1.284 +    global stateStack
   1.285 +    stateStack[len(stateStack)-1]=s
   1.286 +
   1.287 +######################################################################
   1.288 +def get_state():
   1.289 +    global stateStack
   1.290 +    return stateStack[len(stateStack)-1]
   1.291 +
   1.292 +######################################################################
   1.293 +def push_state(s):
   1.294 +    global stateStack
   1.295 +    stateStack.append(s)
   1.296 +
   1.297 +######################################################################
   1.298 +def pop_state():
   1.299 +    global stateStack
   1.300 +    stateStack.pop()
   1.301 +
   1.302 +
   1.303 +######################################################################
   1.304 +def tok_eater(type, tok, spos, epos, line):
   1.305 +    global stateStack,name,param,class_spos,def_spos,import_spos
   1.306 +    global doc_string, modules, import_token, module_has_docstring
   1.307 +    global protection_level, private_member
   1.308 +    global out_row
   1.309 +
   1.310 +    while out_row + 1 < spos[0]:
   1.311 +        output("\n", (0, 0))
   1.312 +
   1.313 +    rec_name_n_param(type,tok)
   1.314 +    if (string.replace(string.strip(tok)," ","")=="##private:"):
   1.315 +         protection_level = "private"
   1.316 +         output("private:\n",spos)
   1.317 +    elif (string.replace(string.strip(tok)," ","")=="##protected:"):
   1.318 +         protection_level = "protected"
   1.319 +         output("protected:\n",spos)
   1.320 +    elif (string.replace(string.strip(tok)," ","")=="##public:"):
   1.321 +         protection_level = "public"
   1.322 +         output("public:\n",spos)
   1.323 +    else:
   1.324 +         gather_comment(type,tok,spos)
   1.325 +
   1.326 +    state = get_state()
   1.327 +
   1.328 +#    sys.stderr.write("%d: %s\n"%(state, tok))
   1.329 +
   1.330 +    # OUTSIDE
   1.331 +    if   (state==OUTSIDE):
   1.332 +        if  (tok=="class"):
   1.333 +            start_recording()
   1.334 +            class_spos = spos
   1.335 +            push_state(BUILD_CLASS_DECL)
   1.336 +        elif (tok=="def"):
   1.337 +            start_recording()
   1.338 +            def_spos = spos
   1.339 +            push_state(BUILD_DEF_DECL)
   1.340 +        elif (tok=="import") or (tok=="from"):
   1.341 +            import_token = tok
   1.342 +            import_spos = spos
   1.343 +            modules     = []
   1.344 +            push_state(IMPORT)
   1.345 +        elif (spos[1] == 0 and tok[:3] == '"""'):
   1.346 +            # Capture module docstring as namespace documentation
   1.347 +            module_has_docstring = True
   1.348 +            append_comment_lines("\\namespace %s\n" % namespace)
   1.349 +            append_comment_lines(tok[3:-3])
   1.350 +            print_comment(spos)
   1.351 +
   1.352 +    # IMPORT
   1.353 +    elif (state==IMPORT):
   1.354 +        if (type==token.NAME):
   1.355 +            modules.append(tok)
   1.356 +            set_state(IMPORT_OP)
   1.357 +    # IMPORT_OP
   1.358 +    elif (state==IMPORT_OP):
   1.359 +        if (tok=="."):
   1.360 +            set_state(IMPORT_APPEND)
   1.361 +        elif (tok==","):
   1.362 +            set_state(IMPORT)
   1.363 +        else:
   1.364 +            for m in modules:
   1.365 +                output('#include "'+m.replace('.',os.path.sep)+'.py"\n', import_spos, immediate=1)
   1.366 +                if import_token=="from":
   1.367 +                    output('using namespace '+m.replace('.', '::')+';\n', import_spos)
   1.368 +            pop_state()
   1.369 +    # IMPORT_APPEND
   1.370 +    elif (state==IMPORT_APPEND):
   1.371 +        if (type==token.NAME):
   1.372 +            modules[len(modules)-1]+="."+tok
   1.373 +            set_state(IMPORT_OP)
   1.374 +    # BUILD_CLASS_DECL
   1.375 +    elif (state==BUILD_CLASS_DECL):
   1.376 +        if (is_recording_finished()):
   1.377 +            s = "class "+name
   1.378 +            if (param!=""): s = s+" : public "+param.replace('.','::')
   1.379 +            if (doc_string!=""):
   1.380 +                append_comment_lines(doc_string)
   1.381 +            print_comment(class_spos)
   1.382 +            output(s+"\n",class_spos)
   1.383 +            output("{\n",(class_spos[0]+1,class_spos[1]))
   1.384 +            protection_level = "public"
   1.385 +            output("  public:\n",(class_spos[0]+2,class_spos[1]))
   1.386 +            set_state(BUILD_CLASS_BODY)
   1.387 +    # BUILD_CLASS_BODY
   1.388 +    elif (state==BUILD_CLASS_BODY):
   1.389 +        if (type!=token.INDENT and type!=token.NEWLINE and type!=40 and
   1.390 +            type!=tokenize.NL and type!=tokenize.COMMENT and
   1.391 +            (spos[1]<=class_spos[1])):
   1.392 +            output("}; // end of class\n",(out_row+1,class_spos[1]))
   1.393 +            pop_state()
   1.394 +        elif (tok=="def"):
   1.395 +            start_recording()
   1.396 +            def_spos = spos
   1.397 +            push_state(BUILD_DEF_DECL)
   1.398 +    # BUILD_DEF_DECL
   1.399 +    elif (state==BUILD_DEF_DECL):
   1.400 +        if (is_recording_finished()):
   1.401 +            param = param.replace("\n", " ")
   1.402 +            param = param.replace("=", " = ")
   1.403 +            params = param.split(",")
   1.404 +            if BUILD_CLASS_BODY in stateStack:
   1.405 +                if len(name) > 1 \
   1.406 +                   and name[0:2] == '__' \
   1.407 +                   and name[len(name)-2:len(name)] != '__' \
   1.408 +                   and protection_level != 'private':
   1.409 +                       private_member = True
   1.410 +                       output("  private:\n",(def_spos[0]+2,def_spos[1]))
   1.411 +
   1.412 +            if (doc_string != ""):
   1.413 +                append_comment_lines(doc_string)
   1.414 +
   1.415 +            print_comment(def_spos)
   1.416 +
   1.417 +            output_function_decl(name, params)
   1.418 +#       output("{\n",(def_spos[0]+1,def_spos[1]))
   1.419 +            set_state(BUILD_DEF_BODY)
   1.420 +    # BUILD_DEF_BODY
   1.421 +    elif (state==BUILD_DEF_BODY):
   1.422 +        if (type!=token.INDENT and type!=token.NEWLINE \
   1.423 +            and type!=40 and type!=tokenize.NL \
   1.424 +            and (spos[1]<=def_spos[1])):
   1.425 +#            output("} // end of method/function\n",(out_row+1,def_spos[1]))
   1.426 +            if private_member and protection_level != 'private':
   1.427 +                private_member = False
   1.428 +                output("  " + protection_level + ":\n",(def_spos[0]+2,def_spos[1]))
   1.429 +            pop_state()
   1.430 +#       else:
   1.431 +#            output(tok,spos)
   1.432 +
   1.433 +
   1.434 +def output_function_decl(name, params):
   1.435 +    global def_spos
   1.436 +
   1.437 +    # Do we document a class method? then remove the 'self' parameter
   1.438 +    if params[0] == 'self':
   1.439 +        preamble = ''
   1.440 +        params = params[1:]
   1.441 +    else:
   1.442 +        preamble = 'static '
   1.443 +        if params[0] == 'cls':
   1.444 +            params = params[1:]
   1.445 +
   1.446 +    param_string = string.join(params, ", Type ")
   1.447 +
   1.448 +    if param_string == '':
   1.449 +        param_string = '(' + param_string + ');\n'
   1.450 +    else:
   1.451 +        param_string = '(Type ' + param_string + ');\n'
   1.452 +
   1.453 +    output(preamble, def_spos)
   1.454 +    output(name, def_spos)
   1.455 +    output(param_string, def_spos)
   1.456 +
   1.457 +
   1.458 +def append_comment_lines(lines):
   1.459 +    map(append_comment_line, doc_string.split('\n'))
   1.460 +
   1.461 +paramRE = re.compile(r'(@param \w+):')
   1.462 +
   1.463 +def append_comment_line(line):
   1.464 +    global paramRE
   1.465 +    
   1.466 +    comment_block.append(paramRE.sub(r'\1', line) + '\n')
   1.467 +
   1.468 +def dump(filename):
   1.469 +    f = open(filename)
   1.470 +    r = f.readlines()
   1.471 +    for s in r:
   1.472 +        sys.stdout.write(s)
   1.473 +
   1.474 +def filter(filename):
   1.475 +    global name, module_has_docstring, source_root
   1.476 +
   1.477 +    path,name = os.path.split(filename)
   1.478 +    root,ext  = os.path.splitext(name)
   1.479 +
   1.480 +    if source_root and path.find(source_root) == 0:
   1.481 +        path = path[len(source_root):]
   1.482 +
   1.483 +        if path[0] == os.sep:
   1.484 +            path = path[1:]
   1.485 +
   1.486 +        ns = path.split(os.sep)
   1.487 +    else:
   1.488 +        ns = []
   1.489 +
   1.490 +    ns.append(root)
   1.491 +
   1.492 +    for n in ns:
   1.493 +        output("namespace " + n + " {\n",(0,0))
   1.494 +
   1.495 +    # set module name for tok_eater to use if there's a module doc string
   1.496 +    name = root
   1.497 +
   1.498 +#    sys.stderr.write('Filtering "'+filename+'"...')
   1.499 +    f = open(filename)
   1.500 +    tokenize.tokenize(f.readline, tok_eater)
   1.501 +    f.close()
   1.502 +    print_comment((0,0))
   1.503 +
   1.504 +    output("\n",(0,0))
   1.505 +    
   1.506 +    for n in ns:
   1.507 +        output("}  // end of namespace\n",(0,0))
   1.508 +
   1.509 +    if not module_has_docstring:
   1.510 +        # Put in default namespace documentation
   1.511 +        output('/** \\namespace '+root+' \n',(0,0))
   1.512 +        output('    \\brief Module "%s" */\n'%(root),(0,0))
   1.513 +
   1.514 +    for s in outbuffer:
   1.515 +        outfile.write(s)
   1.516 +
   1.517 +
   1.518 +def filterFile(filename, out=sys.stdout):
   1.519 +    global outfile
   1.520 +
   1.521 +    outfile = out
   1.522 +
   1.523 +    try:
   1.524 +        root,ext  = os.path.splitext(filename)
   1.525 +
   1.526 +        if ext==".py":
   1.527 +            filter(filename)
   1.528 +        else:
   1.529 +            dump(filename)
   1.530 +
   1.531 +#        sys.stderr.write("OK\n")
   1.532 +    except IOError,e:
   1.533 +        sys.stderr.write(e[1]+"\n")
   1.534 +
   1.535 +
   1.536 +######################################################################
   1.537 +
   1.538 +# preparePath
   1.539 +def preparePath(path):
   1.540 +    """Prepare a path.
   1.541 +
   1.542 +    Checks if the path exists and creates it if it does not exist.
   1.543 +    """
   1.544 +    if not os.path.exists(path):
   1.545 +        parent = os.path.dirname(path)
   1.546 +        if parent!="":
   1.547 +            preparePath(parent)
   1.548 +        os.mkdir(path)
   1.549 +
   1.550 +# isNewer
   1.551 +def isNewer(file1,file2):
   1.552 +    """Check if file1 is newer than file2.
   1.553 +
   1.554 +    file1 must be an existing file.
   1.555 +    """
   1.556 +    if not os.path.exists(file2):
   1.557 +        return True
   1.558 +    return os.stat(file1)[ST_MTIME]>os.stat(file2)[ST_MTIME]
   1.559 +
   1.560 +# convert
   1.561 +def convert(srcpath, destpath):
   1.562 +    """Convert a Python source tree into a C+ stub tree.
   1.563 +
   1.564 +    All *.py files in srcpath (including sub-directories) are filtered
   1.565 +    and written to destpath. If destpath exists, only the files
   1.566 +    that have been modified are filtered again. Files that were deleted
   1.567 +    from srcpath are also deleted in destpath if they are still present.
   1.568 +    The function returns the number of processed *.py files.
   1.569 +    """
   1.570 +    count=0
   1.571 +    sp = os.path.join(srcpath,"*")
   1.572 +    sfiles = glob.glob(sp)
   1.573 +    dp = os.path.join(destpath,"*")
   1.574 +    dfiles = glob.glob(dp)
   1.575 +    leftovers={}
   1.576 +    for df in dfiles:
   1.577 +        leftovers[os.path.basename(df)]=1
   1.578 +
   1.579 +    for srcfile in sfiles:
   1.580 +        basename = os.path.basename(srcfile)
   1.581 +        if basename in leftovers:
   1.582 +            del leftovers[basename]
   1.583 +
   1.584 +        # Is it a subdirectory?
   1.585 +        if os.path.isdir(srcfile):
   1.586 +            sdir = os.path.join(srcpath,basename)
   1.587 +            ddir = os.path.join(destpath,basename)
   1.588 +            count+=convert(sdir, ddir)
   1.589 +            continue
   1.590 +        # Check the extension (only *.py will be converted)
   1.591 +        root, ext = os.path.splitext(srcfile)
   1.592 +        if ext.lower()!=".py":
   1.593 +            continue
   1.594 +
   1.595 +        destfile = os.path.join(destpath,basename)
   1.596 +        if destfile==srcfile:
   1.597 +            print "WARNING: Input and output names are identical!"
   1.598 +            sys.exit(1)
   1.599 +
   1.600 +        count+=1
   1.601 +#        sys.stdout.write("%s\015"%(srcfile))
   1.602 +
   1.603 +        if isNewer(srcfile, destfile):
   1.604 +            preparePath(os.path.dirname(destfile))
   1.605 +#            out=open(destfile,"w")
   1.606 +#            filterFile(srcfile, out)
   1.607 +#            out.close()
   1.608 +            os.system("python %s -f %s>%s"%(sys.argv[0],srcfile,destfile))
   1.609 +
   1.610 +    # Delete obsolete files in destpath
   1.611 +    for df in leftovers:
   1.612 +        dname=os.path.join(destpath,df)
   1.613 +        if os.path.isdir(dname):
   1.614 +            try:
   1.615 +                shutil.rmtree(dname)
   1.616 +            except:
   1.617 +                print "Can't remove obsolete directory '%s'"%dname
   1.618 +        else:
   1.619 +            try:
   1.620 +                os.remove(dname)
   1.621 +            except:
   1.622 +                print "Can't remove obsolete file '%s'"%dname
   1.623 +
   1.624 +    return count
   1.625 +
   1.626 +
   1.627 +######################################################################
   1.628 +######################################################################
   1.629 +######################################################################
   1.630 +
   1.631 +filter_file = False
   1.632 +source_root = None
   1.633 +
   1.634 +try:
   1.635 +    opts, args = getopt.getopt(sys.argv[1:], "hfr:", ["help"])
   1.636 +except getopt.GetoptError,e:
   1.637 +    print e
   1.638 +    sys.exit(1)
   1.639 +
   1.640 +for o,a in opts:
   1.641 +    if o=="-f":
   1.642 +        filter_file = True
   1.643 +
   1.644 +    if o=="-r":
   1.645 +        source_root = os.path.abspath(a)
   1.646 +
   1.647 +if filter_file:
   1.648 +    # Filter the specified file and print the result to stdout
   1.649 +    filename = string.join(args)
   1.650 +    filterFile(os.path.abspath(filename))
   1.651 +else:
   1.652 +
   1.653 +    if len(args)!=2:
   1.654 +        sys.stderr.write("%s options input output\n"%(os.path.basename(sys.argv[0])))
   1.655 +        sys.exit(1)
   1.656 +
   1.657 +    # Filter an entire Python source tree
   1.658 +    print '"%s" -> "%s"\n'%(args[0],args[1])
   1.659 +    c=convert(args[0],args[1])
   1.660 +    print "%d files"%(c)
   1.661 +