| 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 re
|
|---|
| 87 | import shutil
|
|---|
| 88 | import string
|
|---|
| 89 | import sys
|
|---|
| 90 | import token
|
|---|
| 91 | import tokenize
|
|---|
| 92 |
|
|---|
| 93 | from stat import *
|
|---|
| 94 |
|
|---|
| 95 | OUTSIDE = 0
|
|---|
| 96 | BUILD_COMMENT = 1
|
|---|
| 97 | BUILD_CLASS_DECL = 2
|
|---|
| 98 | BUILD_CLASS_BODY = 3
|
|---|
| 99 | BUILD_DEF_DECL = 4
|
|---|
| 100 | BUILD_DEF_BODY = 5
|
|---|
| 101 | IMPORT = 6
|
|---|
| 102 | IMPORT_OP = 7
|
|---|
| 103 | IMPORT_APPEND = 8
|
|---|
| 104 |
|
|---|
| 105 | # Output file stream
|
|---|
| 106 | outfile = sys.stdout
|
|---|
| 107 |
|
|---|
| 108 | # Output buffer
|
|---|
| 109 | outbuffer = []
|
|---|
| 110 |
|
|---|
| 111 | out_row = 1
|
|---|
| 112 | out_col = 0
|
|---|
| 113 |
|
|---|
| 114 | # Variables used by rec_name_n_param()
|
|---|
| 115 | name = ""
|
|---|
| 116 | param = ""
|
|---|
| 117 | doc_string = ""
|
|---|
| 118 | record_state = 0
|
|---|
| 119 | bracket_counter = 0
|
|---|
| 120 |
|
|---|
| 121 | # Tuple: (row,column)
|
|---|
| 122 | class_spos = (0,0)
|
|---|
| 123 | def_spos = (0,0)
|
|---|
| 124 | import_spos = (0,0)
|
|---|
| 125 |
|
|---|
| 126 | # Which import was used? ("import" or "from")
|
|---|
| 127 | import_token = ""
|
|---|
| 128 |
|
|---|
| 129 | # Comment block buffer
|
|---|
| 130 | comment_block = []
|
|---|
| 131 | comment_finished = 0
|
|---|
| 132 |
|
|---|
| 133 | # Imported modules
|
|---|
| 134 | modules = []
|
|---|
| 135 |
|
|---|
| 136 | # Program state
|
|---|
| 137 | stateStack = [OUTSIDE]
|
|---|
| 138 |
|
|---|
| 139 | # Keep track of whether module has a docstring
|
|---|
| 140 | module_has_docstring = False
|
|---|
| 141 |
|
|---|
| 142 | # Keep track of member protection
|
|---|
| 143 | protection_level = "public"
|
|---|
| 144 | private_member = False
|
|---|
| 145 |
|
|---|
| 146 | # Keep track of the module namespace
|
|---|
| 147 | namespace = ""
|
|---|
| 148 |
|
|---|
| 149 | ######################################################################
|
|---|
| 150 | # Output string s. '\n' may only be at the end of the string (not
|
|---|
| 151 | # somewhere in the middle).
|
|---|
| 152 | #
|
|---|
| 153 | # In: s - String
|
|---|
| 154 | # spos - Startpos
|
|---|
| 155 | ######################################################################
|
|---|
| 156 | def output(s,spos, immediate=0):
|
|---|
| 157 | global outbuffer, out_row, out_col, outfile
|
|---|
| 158 |
|
|---|
| 159 | os = string.rjust(s,spos[1]-out_col+len(s))
|
|---|
| 160 |
|
|---|
| 161 | if immediate:
|
|---|
| 162 | outfile.write(os)
|
|---|
| 163 | else:
|
|---|
| 164 | outbuffer.append(os)
|
|---|
| 165 |
|
|---|
| 166 | assert -1 == string.find(s[0:-2], "\n"), s
|
|---|
| 167 |
|
|---|
| 168 | if (s[-1:]=="\n"):
|
|---|
| 169 | out_row = out_row+1
|
|---|
| 170 | out_col = 0
|
|---|
| 171 | else:
|
|---|
| 172 | out_col = spos[1]+len(s)
|
|---|
| 173 |
|
|---|
| 174 |
|
|---|
| 175 | ######################################################################
|
|---|
| 176 | # Records a name and parameters. The name is either a class name or
|
|---|
| 177 | # a function name. Then the parameter is either the base class or
|
|---|
| 178 | # the function parameters.
|
|---|
| 179 | # The name is stored in the global variable "name", the parameters
|
|---|
| 180 | # in "param".
|
|---|
| 181 | # The variable "record_state" holds the current state of this internal
|
|---|
| 182 | # state machine.
|
|---|
| 183 | # The recording is started by calling start_recording().
|
|---|
| 184 | #
|
|---|
| 185 | # In: type, tok
|
|---|
| 186 | ######################################################################
|
|---|
| 187 | def rec_name_n_param(type, tok):
|
|---|
| 188 | global record_state,name,param,doc_string,bracket_counter
|
|---|
| 189 | s = record_state
|
|---|
| 190 | # State 0: Do nothing.
|
|---|
| 191 | if (s==0):
|
|---|
| 192 | return
|
|---|
| 193 | # State 1: Remember name.
|
|---|
| 194 | elif (s==1):
|
|---|
| 195 | name = tok
|
|---|
| 196 | record_state = 2
|
|---|
| 197 | # State 2: Wait for opening bracket or colon
|
|---|
| 198 | elif (s==2):
|
|---|
| 199 | if (tok=='('):
|
|---|
| 200 | bracket_counter = 1
|
|---|
| 201 | record_state=3
|
|---|
| 202 | if (tok==':'): record_state=4
|
|---|
| 203 | # State 3: Store parameter (or base class) and wait for an ending bracket
|
|---|
| 204 | elif (s==3):
|
|---|
| 205 | if (tok=='*' or tok=='**'):
|
|---|
| 206 | tok=''
|
|---|
| 207 | if (tok=='('):
|
|---|
| 208 | bracket_counter = bracket_counter+1
|
|---|
| 209 | if (tok==')'):
|
|---|
| 210 | bracket_counter = bracket_counter-1
|
|---|
| 211 | if bracket_counter==0:
|
|---|
| 212 | record_state=4
|
|---|
| 213 | else:
|
|---|
| 214 | param=param+tok
|
|---|
| 215 | # State 4: Look for doc string
|
|---|
| 216 | elif (s==4):
|
|---|
| 217 | if (type==token.NEWLINE or type==token.INDENT or type==token.SLASHEQUAL):
|
|---|
| 218 | return
|
|---|
| 219 | elif (tok==":"):
|
|---|
| 220 | return
|
|---|
| 221 | elif (type==token.STRING):
|
|---|
| 222 | while tok[:1]=='r' or tok[:1]=='u':
|
|---|
| 223 | tok=tok[1:]
|
|---|
| 224 | while tok[:1]=='"':
|
|---|
| 225 | tok=tok[1:]
|
|---|
| 226 | while tok[-1:]=='"':
|
|---|
| 227 | tok=tok[:-1]
|
|---|
| 228 | doc_string=tok
|
|---|
| 229 | record_state=0
|
|---|
| 230 |
|
|---|
| 231 | ######################################################################
|
|---|
| 232 | # Starts the recording of a name & param part.
|
|---|
| 233 | # The function rec_name_n_param() has to be fed with tokens. After
|
|---|
| 234 | # the necessary tokens are fed the name and parameters can be found
|
|---|
| 235 | # in the global variables "name" und "param".
|
|---|
| 236 | ######################################################################
|
|---|
| 237 | def start_recording():
|
|---|
| 238 | global record_state,param,name, doc_string
|
|---|
| 239 | record_state=1
|
|---|
| 240 | name=""
|
|---|
| 241 | param=""
|
|---|
| 242 | doc_string=""
|
|---|
| 243 |
|
|---|
| 244 | ######################################################################
|
|---|
| 245 | # Test if recording is finished
|
|---|
| 246 | ######################################################################
|
|---|
| 247 | def is_recording_finished():
|
|---|
| 248 | global record_state
|
|---|
| 249 | return record_state==0
|
|---|
| 250 |
|
|---|
| 251 | ######################################################################
|
|---|
| 252 | ## Gather comment block
|
|---|
| 253 | ######################################################################
|
|---|
| 254 | def gather_comment(type,tok,spos):
|
|---|
| 255 | global comment_block,comment_finished
|
|---|
| 256 | if (type!=tokenize.COMMENT):
|
|---|
| 257 | comment_finished = 1
|
|---|
| 258 | else:
|
|---|
| 259 | # Output old comment block if a new one is started.
|
|---|
| 260 | if (comment_finished):
|
|---|
| 261 | print_comment(spos)
|
|---|
| 262 | comment_finished=0
|
|---|
| 263 | if (tok[0:2]=="##" and tok[0:3]!="###"):
|
|---|
| 264 | append_comment_lines(tok[2:])
|
|---|
| 265 |
|
|---|
| 266 | ######################################################################
|
|---|
| 267 | ## Output comment block and empty buffer.
|
|---|
| 268 | ######################################################################
|
|---|
| 269 | def print_comment(spos):
|
|---|
| 270 | global comment_block,comment_finished
|
|---|
| 271 | if (comment_block!=[]):
|
|---|
| 272 | output("/** ",spos)
|
|---|
| 273 | for c in comment_block:
|
|---|
| 274 | output(c,spos)
|
|---|
| 275 | output("*/\n",spos)
|
|---|
| 276 | comment_block = []
|
|---|
| 277 | comment_finished = 0
|
|---|
| 278 |
|
|---|
| 279 | ######################################################################
|
|---|
| 280 | def set_state(s):
|
|---|
| 281 | global stateStack
|
|---|
| 282 | stateStack[len(stateStack)-1]=s
|
|---|
| 283 |
|
|---|
| 284 | ######################################################################
|
|---|
| 285 | def get_state():
|
|---|
| 286 | global stateStack
|
|---|
| 287 | return stateStack[len(stateStack)-1]
|
|---|
| 288 |
|
|---|
| 289 | ######################################################################
|
|---|
| 290 | def push_state(s):
|
|---|
| 291 | global stateStack
|
|---|
| 292 | stateStack.append(s)
|
|---|
| 293 |
|
|---|
| 294 | ######################################################################
|
|---|
| 295 | def pop_state():
|
|---|
| 296 | global stateStack
|
|---|
| 297 | stateStack.pop()
|
|---|
| 298 |
|
|---|
| 299 |
|
|---|
| 300 | ######################################################################
|
|---|
| 301 | def tok_eater(type, tok, spos, epos, line):
|
|---|
| 302 | global stateStack,name,param,class_spos,def_spos,import_spos
|
|---|
| 303 | global doc_string, modules, import_token, module_has_docstring
|
|---|
| 304 | global protection_level, private_member
|
|---|
| 305 | global out_row
|
|---|
| 306 |
|
|---|
| 307 | while out_row + 1 < spos[0]:
|
|---|
| 308 | output("\n", (0, 0))
|
|---|
| 309 |
|
|---|
| 310 | rec_name_n_param(type,tok)
|
|---|
| 311 | if (string.replace(string.strip(tok)," ","")=="##private:"):
|
|---|
| 312 | protection_level = "private"
|
|---|
| 313 | output("private:\n",spos)
|
|---|
| 314 | elif (string.replace(string.strip(tok)," ","")=="##protected:"):
|
|---|
| 315 | protection_level = "protected"
|
|---|
| 316 | output("protected:\n",spos)
|
|---|
| 317 | elif (string.replace(string.strip(tok)," ","")=="##public:"):
|
|---|
| 318 | protection_level = "public"
|
|---|
| 319 | output("public:\n",spos)
|
|---|
| 320 | else:
|
|---|
| 321 | gather_comment(type,tok,spos)
|
|---|
| 322 |
|
|---|
| 323 | state = get_state()
|
|---|
| 324 |
|
|---|
| 325 | # sys.stderr.write("%d: %s\n"%(state, tok))
|
|---|
| 326 |
|
|---|
| 327 | # OUTSIDE
|
|---|
| 328 | if (state==OUTSIDE):
|
|---|
| 329 | if (tok=="class"):
|
|---|
| 330 | start_recording()
|
|---|
| 331 | class_spos = spos
|
|---|
| 332 | push_state(BUILD_CLASS_DECL)
|
|---|
| 333 | elif (tok=="def"):
|
|---|
| 334 | start_recording()
|
|---|
| 335 | def_spos = spos
|
|---|
| 336 | push_state(BUILD_DEF_DECL)
|
|---|
| 337 | elif (tok=="import") or (tok=="from"):
|
|---|
| 338 | import_token = tok
|
|---|
| 339 | import_spos = spos
|
|---|
| 340 | modules = []
|
|---|
| 341 | push_state(IMPORT)
|
|---|
| 342 | elif (spos[1] == 0 and tok[:3] == '"""'):
|
|---|
| 343 | # Capture module docstring as namespace documentation
|
|---|
| 344 | module_has_docstring = True
|
|---|
| 345 | append_comment_lines("\\namespace %s\n" % namespace)
|
|---|
| 346 | append_comment_lines(tok[3:-3])
|
|---|
| 347 | print_comment(spos)
|
|---|
| 348 |
|
|---|
| 349 | # IMPORT
|
|---|
| 350 | elif (state==IMPORT):
|
|---|
| 351 | if (type==token.NAME):
|
|---|
| 352 | modules.append(tok)
|
|---|
| 353 | set_state(IMPORT_OP)
|
|---|
| 354 | # IMPORT_OP
|
|---|
| 355 | elif (state==IMPORT_OP):
|
|---|
| 356 | if (tok=="."):
|
|---|
| 357 | set_state(IMPORT_APPEND)
|
|---|
| 358 | elif (tok==","):
|
|---|
| 359 | set_state(IMPORT)
|
|---|
| 360 | else:
|
|---|
| 361 | for m in modules:
|
|---|
| 362 | output('#include "'+m.replace('.',os.path.sep)+'.py"\n', import_spos, immediate=1)
|
|---|
| 363 | if import_token=="from":
|
|---|
| 364 | output('using namespace '+m.replace('.', '::')+';\n', import_spos)
|
|---|
| 365 | pop_state()
|
|---|
| 366 | # IMPORT_APPEND
|
|---|
| 367 | elif (state==IMPORT_APPEND):
|
|---|
| 368 | if (type==token.NAME):
|
|---|
| 369 | modules[len(modules)-1]+="."+tok
|
|---|
| 370 | set_state(IMPORT_OP)
|
|---|
| 371 | # BUILD_CLASS_DECL
|
|---|
| 372 | elif (state==BUILD_CLASS_DECL):
|
|---|
| 373 | if (is_recording_finished()):
|
|---|
| 374 | s = "class "+name
|
|---|
| 375 | if (param!=""): s = s+" : public "+param.replace('.','::')
|
|---|
| 376 | if (doc_string!=""):
|
|---|
| 377 | append_comment_lines(doc_string)
|
|---|
| 378 | print_comment(class_spos)
|
|---|
| 379 | output(s+"\n",class_spos)
|
|---|
| 380 | output("{\n",(class_spos[0]+1,class_spos[1]))
|
|---|
| 381 | protection_level = "public"
|
|---|
| 382 | output(" public:\n",(class_spos[0]+2,class_spos[1]))
|
|---|
| 383 | set_state(BUILD_CLASS_BODY)
|
|---|
| 384 | # BUILD_CLASS_BODY
|
|---|
| 385 | elif (state==BUILD_CLASS_BODY):
|
|---|
| 386 | if (type!=token.INDENT and type!=token.NEWLINE and type!=40 and
|
|---|
| 387 | type!=tokenize.NL and type!=tokenize.COMMENT and
|
|---|
| 388 | (spos[1]<=class_spos[1])):
|
|---|
| 389 | output("}; // end of class\n",(out_row+1,class_spos[1]))
|
|---|
| 390 | pop_state()
|
|---|
| 391 | elif (tok=="def"):
|
|---|
| 392 | start_recording()
|
|---|
| 393 | def_spos = spos
|
|---|
| 394 | push_state(BUILD_DEF_DECL)
|
|---|
| 395 | # BUILD_DEF_DECL
|
|---|
| 396 | elif (state==BUILD_DEF_DECL):
|
|---|
| 397 | if (is_recording_finished()):
|
|---|
| 398 | param = param.replace("\n", " ")
|
|---|
| 399 | param = param.replace("=", " = ")
|
|---|
| 400 | params = param.split(",")
|
|---|
| 401 | if BUILD_CLASS_BODY in stateStack:
|
|---|
| 402 | if len(name) > 1 \
|
|---|
| 403 | and name[0:2] == '__' \
|
|---|
| 404 | and name[len(name)-2:len(name)] != '__' \
|
|---|
| 405 | and protection_level != 'private':
|
|---|
| 406 | private_member = True
|
|---|
| 407 | output(" private:\n",(def_spos[0]+2,def_spos[1]))
|
|---|
| 408 |
|
|---|
| 409 | if (doc_string != ""):
|
|---|
| 410 | append_comment_lines(doc_string)
|
|---|
| 411 |
|
|---|
| 412 | print_comment(def_spos)
|
|---|
| 413 |
|
|---|
| 414 | output_function_decl(name, params)
|
|---|
| 415 | # output("{\n",(def_spos[0]+1,def_spos[1]))
|
|---|
| 416 | set_state(BUILD_DEF_BODY)
|
|---|
| 417 | # BUILD_DEF_BODY
|
|---|
| 418 | elif (state==BUILD_DEF_BODY):
|
|---|
| 419 | if (type!=token.INDENT and type!=token.NEWLINE \
|
|---|
| 420 | and type!=40 and type!=tokenize.NL \
|
|---|
| 421 | and (spos[1]<=def_spos[1])):
|
|---|
| 422 | # output("} // end of method/function\n",(out_row+1,def_spos[1]))
|
|---|
| 423 | if private_member and protection_level != 'private':
|
|---|
| 424 | private_member = False
|
|---|
| 425 | output(" " + protection_level + ":\n",(def_spos[0]+2,def_spos[1]))
|
|---|
| 426 | pop_state()
|
|---|
| 427 | # else:
|
|---|
| 428 | # output(tok,spos)
|
|---|
| 429 |
|
|---|
| 430 |
|
|---|
| 431 | def output_function_decl(name, params):
|
|---|
| 432 | global def_spos
|
|---|
| 433 |
|
|---|
| 434 | # Do we document a class method? then remove the 'self' parameter
|
|---|
| 435 | if params[0] == 'self':
|
|---|
| 436 | preamble = ''
|
|---|
| 437 | params = params[1:]
|
|---|
| 438 | else:
|
|---|
| 439 | preamble = 'static '
|
|---|
| 440 | if params[0] == 'cls':
|
|---|
| 441 | params = params[1:]
|
|---|
| 442 |
|
|---|
| 443 | param_string = string.join(params, ", Type ")
|
|---|
| 444 |
|
|---|
| 445 | if param_string == '':
|
|---|
| 446 | param_string = '(' + param_string + ');\n'
|
|---|
| 447 | else:
|
|---|
| 448 | param_string = '(Type ' + param_string + ');\n'
|
|---|
| 449 |
|
|---|
| 450 | output(preamble, def_spos)
|
|---|
| 451 | output(name, def_spos)
|
|---|
| 452 | output(param_string, def_spos)
|
|---|
| 453 |
|
|---|
| 454 |
|
|---|
| 455 | def append_comment_lines(lines):
|
|---|
| 456 | map(append_comment_line, doc_string.split('\n'))
|
|---|
| 457 |
|
|---|
| 458 | paramRE = re.compile(r'(@param \w+):')
|
|---|
| 459 |
|
|---|
| 460 | def append_comment_line(line):
|
|---|
| 461 | global paramRE
|
|---|
| 462 |
|
|---|
| 463 | comment_block.append(paramRE.sub(r'\1', line) + '\n')
|
|---|
| 464 |
|
|---|
| 465 | def dump(filename):
|
|---|
| 466 | f = open(filename)
|
|---|
| 467 | r = f.readlines()
|
|---|
| 468 | for s in r:
|
|---|
| 469 | sys.stdout.write(s)
|
|---|
| 470 |
|
|---|
| 471 | def filter(filename):
|
|---|
| 472 | global name, module_has_docstring, source_root
|
|---|
| 473 |
|
|---|
| 474 | path,name = os.path.split(filename)
|
|---|
| 475 | root,ext = os.path.splitext(name)
|
|---|
| 476 |
|
|---|
| 477 | if source_root and path.find(source_root) == 0:
|
|---|
| 478 | path = path[len(source_root):]
|
|---|
| 479 |
|
|---|
| 480 | if path[0] == os.sep:
|
|---|
| 481 | path = path[1:]
|
|---|
| 482 |
|
|---|
| 483 | ns = path.split(os.sep)
|
|---|
| 484 | else:
|
|---|
| 485 | ns = []
|
|---|
| 486 |
|
|---|
| 487 | ns.append(root)
|
|---|
| 488 |
|
|---|
| 489 | for n in ns:
|
|---|
| 490 | output("namespace " + n + " {\n",(0,0))
|
|---|
| 491 |
|
|---|
| 492 | # set module name for tok_eater to use if there's a module doc string
|
|---|
| 493 | name = root
|
|---|
| 494 |
|
|---|
| 495 | # sys.stderr.write('Filtering "'+filename+'"...')
|
|---|
| 496 | f = open(filename)
|
|---|
| 497 | tokenize.tokenize(f.readline, tok_eater)
|
|---|
| 498 | f.close()
|
|---|
| 499 | print_comment((0,0))
|
|---|
| 500 |
|
|---|
| 501 | output("\n",(0,0))
|
|---|
| 502 |
|
|---|
| 503 | for n in ns:
|
|---|
| 504 | output("} // end of namespace\n",(0,0))
|
|---|
| 505 |
|
|---|
| 506 | if not module_has_docstring:
|
|---|
| 507 | # Put in default namespace documentation
|
|---|
| 508 | output('/** \\namespace '+root+' \n',(0,0))
|
|---|
| 509 | output(' \\brief Module "%s" */\n'%(root),(0,0))
|
|---|
| 510 |
|
|---|
| 511 | for s in outbuffer:
|
|---|
| 512 | outfile.write(s)
|
|---|
| 513 |
|
|---|
| 514 |
|
|---|
| 515 | def filterFile(filename, out=sys.stdout):
|
|---|
| 516 | global outfile
|
|---|
| 517 |
|
|---|
| 518 | outfile = out
|
|---|
| 519 |
|
|---|
| 520 | try:
|
|---|
| 521 | root,ext = os.path.splitext(filename)
|
|---|
| 522 |
|
|---|
| 523 | if ext==".py":
|
|---|
| 524 | filter(filename)
|
|---|
| 525 | else:
|
|---|
| 526 | dump(filename)
|
|---|
| 527 |
|
|---|
| 528 | # sys.stderr.write("OK\n")
|
|---|
| 529 | except IOError,e:
|
|---|
| 530 | sys.stderr.write(e[1]+"\n")
|
|---|
| 531 |
|
|---|
| 532 |
|
|---|
| 533 | ######################################################################
|
|---|
| 534 |
|
|---|
| 535 | # preparePath
|
|---|
| 536 | def preparePath(path):
|
|---|
| 537 | """Prepare a path.
|
|---|
| 538 |
|
|---|
| 539 | Checks if the path exists and creates it if it does not exist.
|
|---|
| 540 | """
|
|---|
| 541 | if not os.path.exists(path):
|
|---|
| 542 | parent = os.path.dirname(path)
|
|---|
| 543 | if parent!="":
|
|---|
| 544 | preparePath(parent)
|
|---|
| 545 | os.mkdir(path)
|
|---|
| 546 |
|
|---|
| 547 | # isNewer
|
|---|
| 548 | def isNewer(file1,file2):
|
|---|
| 549 | """Check if file1 is newer than file2.
|
|---|
| 550 |
|
|---|
| 551 | file1 must be an existing file.
|
|---|
| 552 | """
|
|---|
| 553 | if not os.path.exists(file2):
|
|---|
| 554 | return True
|
|---|
| 555 | return os.stat(file1)[ST_MTIME]>os.stat(file2)[ST_MTIME]
|
|---|
| 556 |
|
|---|
| 557 | # convert
|
|---|
| 558 | def convert(srcpath, destpath):
|
|---|
| 559 | """Convert a Python source tree into a C+ stub tree.
|
|---|
| 560 |
|
|---|
| 561 | All *.py files in srcpath (including sub-directories) are filtered
|
|---|
| 562 | and written to destpath. If destpath exists, only the files
|
|---|
| 563 | that have been modified are filtered again. Files that were deleted
|
|---|
| 564 | from srcpath are also deleted in destpath if they are still present.
|
|---|
| 565 | The function returns the number of processed *.py files.
|
|---|
| 566 | """
|
|---|
| 567 | count=0
|
|---|
| 568 | sp = os.path.join(srcpath,"*")
|
|---|
| 569 | sfiles = glob.glob(sp)
|
|---|
| 570 | dp = os.path.join(destpath,"*")
|
|---|
| 571 | dfiles = glob.glob(dp)
|
|---|
| 572 | leftovers={}
|
|---|
| 573 | for df in dfiles:
|
|---|
| 574 | leftovers[os.path.basename(df)]=1
|
|---|
| 575 |
|
|---|
| 576 | for srcfile in sfiles:
|
|---|
| 577 | basename = os.path.basename(srcfile)
|
|---|
| 578 | if basename in leftovers:
|
|---|
| 579 | del leftovers[basename]
|
|---|
| 580 |
|
|---|
| 581 | # Is it a subdirectory?
|
|---|
| 582 | if os.path.isdir(srcfile):
|
|---|
| 583 | sdir = os.path.join(srcpath,basename)
|
|---|
| 584 | ddir = os.path.join(destpath,basename)
|
|---|
| 585 | count+=convert(sdir, ddir)
|
|---|
| 586 | continue
|
|---|
| 587 | # Check the extension (only *.py will be converted)
|
|---|
| 588 | root, ext = os.path.splitext(srcfile)
|
|---|
| 589 | if ext.lower()!=".py":
|
|---|
| 590 | continue
|
|---|
| 591 |
|
|---|
| 592 | destfile = os.path.join(destpath,basename)
|
|---|
| 593 | if destfile==srcfile:
|
|---|
| 594 | print "WARNING: Input and output names are identical!"
|
|---|
| 595 | sys.exit(1)
|
|---|
| 596 |
|
|---|
| 597 | count+=1
|
|---|
| 598 | # sys.stdout.write("%s\015"%(srcfile))
|
|---|
| 599 |
|
|---|
| 600 | if isNewer(srcfile, destfile):
|
|---|
| 601 | preparePath(os.path.dirname(destfile))
|
|---|
| 602 | # out=open(destfile,"w")
|
|---|
| 603 | # filterFile(srcfile, out)
|
|---|
| 604 | # out.close()
|
|---|
| 605 | os.system("python %s -f %s>%s"%(sys.argv[0],srcfile,destfile))
|
|---|
| 606 |
|
|---|
| 607 | # Delete obsolete files in destpath
|
|---|
| 608 | for df in leftovers:
|
|---|
| 609 | dname=os.path.join(destpath,df)
|
|---|
| 610 | if os.path.isdir(dname):
|
|---|
| 611 | try:
|
|---|
| 612 | shutil.rmtree(dname)
|
|---|
| 613 | except:
|
|---|
| 614 | print "Can't remove obsolete directory '%s'"%dname
|
|---|
| 615 | else:
|
|---|
| 616 | try:
|
|---|
| 617 | os.remove(dname)
|
|---|
| 618 | except:
|
|---|
| 619 | print "Can't remove obsolete file '%s'"%dname
|
|---|
| 620 |
|
|---|
| 621 | return count
|
|---|
| 622 |
|
|---|
| 623 |
|
|---|
| 624 | ######################################################################
|
|---|
| 625 | ######################################################################
|
|---|
| 626 | ######################################################################
|
|---|
| 627 |
|
|---|
| 628 | filter_file = False
|
|---|
| 629 | source_root = None
|
|---|
| 630 |
|
|---|
| 631 | try:
|
|---|
| 632 | opts, args = getopt.getopt(sys.argv[1:], "hfr:", ["help"])
|
|---|
| 633 | except getopt.GetoptError,e:
|
|---|
| 634 | print e
|
|---|
| 635 | sys.exit(1)
|
|---|
| 636 |
|
|---|
| 637 | for o,a in opts:
|
|---|
| 638 | if o=="-f":
|
|---|
| 639 | filter_file = True
|
|---|
| 640 |
|
|---|
| 641 | if o=="-r":
|
|---|
| 642 | source_root = os.path.abspath(a)
|
|---|
| 643 |
|
|---|
| 644 | if filter_file:
|
|---|
| 645 | # Filter the specified file and print the result to stdout
|
|---|
| 646 | filename = string.join(args)
|
|---|
| 647 | filterFile(os.path.abspath(filename))
|
|---|
| 648 | else:
|
|---|
| 649 |
|
|---|
| 650 | if len(args)!=2:
|
|---|
| 651 | sys.stderr.write("%s options input output\n"%(os.path.basename(sys.argv[0])))
|
|---|
| 652 | sys.exit(1)
|
|---|
| 653 |
|
|---|
| 654 | # Filter an entire Python source tree
|
|---|
| 655 | print '"%s" -> "%s"\n'%(args[0],args[1])
|
|---|
| 656 | c=convert(args[0],args[1])
|
|---|
| 657 | print "%d files"%(c)
|
|---|
| 658 |
|
|---|