1#!/usr/bin/python 2 3import os 4import re 5import time 6from pprint import pprint 7 8### 9 10c99URL = 'http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf' 11c99TOC = [('Foreword', 'xi'), 12('Introduction', 'xiv'), 13('1. Scope', '1'), 14('2. Normative references', '2'), 15('3. Terms, definitions, and symbols', '3'), 16('4. Conformance', '7'), 17('5. Environment', '9'), 18('5.1 Conceptual models', '9'), 19('5.1.1 Translation environment', '9'), 20('5.1.2 Execution environments', '11'), 21('5.2 Environmental considerations', '17'), 22('5.2.1 Character sets', '17'), 23('5.2.2 Character display semantics', '19'), 24('5.2.3 Signals and interrupts', '20'), 25('5.2.4 Environmental limits', '20'), 26('6. Language', '29'), 27('6.1 Notation', '29'), 28('6.2 Concepts', '29'), 29('6.2.1 Scopes of identifiers', '29'), 30('6.2.2 Linkages of identifiers', '30'), 31('6.2.3 Name spaces of identifiers', '31'), 32('6.2.4 Storage durations of objects', '32'), 33('6.2.5 Types', '33'), 34('6.2.6 Representations of types', '37'), 35('6.2.7 Compatible type and composite type', '40'), 36('6.3 Conversions', '42'), 37('6.3.1 Arithmetic operands', '42'), 38('6.3.2 Other operands', '46'), 39('6.4 Lexical elements', '49'), 40('6.4.1 Keywords', '50'), 41('6.4.2 Identifiers', '51'), 42('6.4.3 Universal character names', '53'), 43('6.4.4 Constants', '54'), 44('6.4.5 String literals', '62'), 45('6.4.6 Punctuators', '63'), 46('6.4.7 Header names', '64'), 47('6.4.8 Preprocessing numbers', '65'), 48('6.4.9 Comments', '66'), 49('6.5 Expressions', '67'), 50('6.5.1 Primary expressions', '69'), 51('6.5.2 Postfix operators', '69'), 52('6.5.3 Unary operators', '78'), 53('6.5.4 Cast operators', '81'), 54('6.5.5 Multiplicative operators', '82'), 55('6.5.6 Additive operators', '82'), 56('6.5.7 Bitwise shift operators', '84'), 57('6.5.8 Relational operators', '85'), 58('6.5.9 Equality operators', '86'), 59('6.5.10 Bitwise AND operator', '87'), 60('6.5.11 Bitwise exclusive OR operator', '88'), 61('6.5.12 Bitwise inclusive OR operator', '88'), 62('6.5.13 Logical AND operator', '89'), 63('6.5.14 Logical OR operator', '89'), 64('6.5.15 Conditional operator', '90'), 65('6.5.16 Assignment operators', '91'), 66('6.5.17 Comma operator', '94'), 67('6.6 Constant expressions', '95'), 68('6.7 Declarations', '97'), 69('6.7.1 Storage-class specifiers', '98'), 70('6.7.2 Type specifiers', '99'), 71('6.7.3 Type qualifiers', '108'), 72('6.7.4 Function specifiers', '112'), 73('6.7.5 Declarators', '114'), 74('6.7.6 Type names', '122'), 75('6.7.7 Type definitions', '123'), 76('6.7.8 Initialization', '125'), 77('6.8 Statements and blocks', '131'), 78('6.8.1 Labeled statements', '131'), 79('6.8.2 Compound statement', '132'), 80('6.8.3 Expression and null statements', '132'), 81('6.8.4 Selection statements', '133'), 82('6.8.5 Iteration statements', '135'), 83('6.8.6 Jump statements', '136'), 84('6.9 External definitions', '140'), 85('6.9.1 Function definitions', '141'), 86('6.9.2 External object definitions', '143'), 87('6.10 Preprocessing directives', '145'), 88('6.10.1 Conditional inclusion', '147'), 89('6.10.2 Source file inclusion', '149'), 90('6.10.3 Macro replacement', '151'), 91('6.10.4 Line control', '158'), 92('6.10.5 Error directive', '159'), 93('6.10.6 Pragma directive', '159'), 94('6.10.7 Null directive', '160'), 95('6.10.8 Predefined macro names', '160'), 96('6.10.9 Pragma operator', '161'), 97('6.11 Future language directions', '163'), 98('6.11.1 Floating types', '163'), 99('6.11.2 Linkages of identifiers', '163'), 100('6.11.3 External names', '163'), 101('6.11.4 Character escape sequences', '163'), 102('6.11.5 Storage-class specifiers', '163'), 103('6.11.6 Function declarators', '163'), 104('6.11.7 Function definitions', '163'), 105('6.11.8 Pragma directives', '163'), 106('6.11.9 Predefined macro names', '163'), 107('7. Library', '164'), 108('7.1 Introduction', '164'), 109('7.1.1 Definitions of terms', '164'), 110('7.1.2 Standard headers', '165'), 111('7.1.3 Reserved identifiers', '166'), 112('7.1.4 Use of library functions', '166'), 113('7.2 Diagnostics <assert.h>', '169'), 114('7.2.1 Program diagnostics', '169'), 115('7.3 Complex arithmetic <complex.h>', '170'), 116('7.3.1 Introduction', '170'), 117('7.3.2 Conventions', '170'), 118('7.3.3 Branch cuts', '171'), 119('7.3.4 The CX_LIMITED_RANGE pragma', '171'), 120('7.3.5 Trigonometric functions', '172'), 121('7.3.6 Hyperbolic functions', '174'), 122('7.3.7 Exponential and logarithmic functions', '176'), 123('7.3.8 Power and absolute-value functions', '177'), 124('7.3.9 Manipulation functions', '178'), 125('7.4 Character handling <ctype.h>', '181'), 126('7.4.1 Character classification functions', '181'), 127('7.4.2 Character case mapping functions', '184'), 128('7.5 Errors <errno.h>', '186'), 129('7.6 Floating-point environment <fenv.h>', '187'), 130('7.6.1 The FENV_ACCESS pragma', '189'), 131('7.6.2 Floating-point exceptions', '190'), 132('7.6.3 Rounding', '193'), 133('7.6.4 Environment', '194'), 134('7.7 Characteristics of floating types <float.h>', '197'), 135('7.8 Format conversion of integer types <inttypes.h>', '198'), 136('7.8.1 Macros for format specifiers', '198'), 137('7.8.2 Functions for greatest-width integer types', '199'), 138('7.9 Alternative spellings <iso646.h>', '202'), 139('7.10 Sizes of integer types <limits.h>', '203'), 140('7.11 Localization <locale.h>', '204'), 141('7.11.1 Locale control', '205'), 142('7.11.2 Numeric formatting convention inquiry', '206'), 143('7.12 Mathematics <math.h>', '212'), 144('7.12.1 Treatment of error conditions', '214'), 145('7.12.2 The FP_CONTRACT pragma', '215'), 146('7.12.3 Classification macros', '216'), 147('7.12.4 Trigonometric functions', '218'), 148('7.12.5 Hyperbolic functions', '221'), 149('7.12.6 Exponential and logarithmic functions', '223'), 150('7.12.7 Power and absolute-value functions', '228'), 151('7.12.8 Error and gamma functions', '230'), 152('7.12.9 Nearest integer functions', '231'), 153('7.12.10 Remainder functions', '235'), 154('7.12.11 Manipulation functions', '236'), 155('7.12.12 Maximum, minimum, and positive difference functions', '238'), 156('7.12.13 Floating multiply-add', '239'), 157('7.12.14 Comparison macros', '240'), 158('7.13 Nonlocal jumps <setjmp.h>', '243'), 159('7.13.1 Save calling environment', '243'), 160('7.13.2 Restore calling environment', '244'), 161('7.14 Signal handling <signal.h>', '246'), 162('7.14.1 Specify signal handling', '247'), 163('7.14.2 Send signal', '248'), 164('7.15 Variable arguments <stdarg.h>', '249'), 165('7.15.1 Variable argument list access macros', '249'), 166('7.16 Boolean type and values <stdbool.h>', '253'), 167('7.17 Common definitions <stddef.h>', '254'), 168('7.18 Integer types <stdint.h>', '255'), 169('7.18.1 Integer types', '255'), 170('7.18.2 Limits of specified-width integer types', '257'), 171('7.18.3 Limits of other integer types', '259'), 172('7.18.4 Macros for integer constants', '260'), 173('7.19 Input/output <stdio.h>', '262'), 174('7.19.1 Introduction', '262'), 175('7.19.2 Streams', '264'), 176('7.19.3 Files', '266'), 177('7.19.4 Operations on files', '268'), 178('7.19.5 File access functions', '270'), 179('7.19.6 Formatted input/output functions', '274'), 180('7.19.7 Character input/output functions', '296'), 181('7.19.8 Direct input/output functions', '301'), 182('7.19.9 File positioning functions', '302'), 183('7.19.10 Error-handling functions', '304'), 184('7.20 General utilities <stdlib.h>', '306'), 185('7.20.1 Numeric conversion functions', '307'), 186('7.20.2 Pseudo-random sequence generation functions', '312'), 187('7.20.3 Memory management functions', '313'), 188('7.20.4 Communication with the environment', '315'), 189('7.20.5 Searching and sorting utilities', '318'), 190('7.20.6 Integer arithmetic functions', '320'), 191('7.20.7 Multibyte/wide character conversion functions', '321'), 192('7.20.8 Multibyte/wide string conversion functions', '323'), 193('7.21 String handling <string.h>', '325'), 194('7.21.1 String function conventions', '325'), 195('7.21.2 Copying functions', '325'), 196('7.21.3 Concatenation functions', '327'), 197('7.21.4 Comparison functions', '328'), 198('7.21.5 Search functions', '330'), 199('7.21.6 Miscellaneous functions', '333'), 200('7.22 Type-generic math <tgmath.h>', '335'), 201('7.23 Date and time <time.h>', '338'), 202('7.23.1 Components of time', '338'), 203('7.23.2 Time manipulation functions', '339'), 204('7.23.3 Time conversion functions', '341'), 205('7.24 Extended multibyte and wide character utilities <wchar.h>', '348'), 206('7.24.1 Introduction', '348'), 207('7.24.2 Formatted wide character input/output functions', '349'), 208('7.24.3 Wide character input/output functions', '367'), 209('7.24.4 General wide string utilities', '371'), 210('7.24.5 Wide character time conversion functions', '385'), 211('7.24.6 Extended multibyte/wide character conversion utilities', '386'), 212('7.25 Wide character classification and mapping utilities <wctype.h>', 213 '393'), 214('7.25.1 Introduction', '393'), 215('7.25.2 Wide character classification utilities', '394'), 216('7.25.3 Wide character case mapping utilities', '399'), 217('7.26 Future library directions', '401'), 218('7.26.1 Complex arithmetic <complex.h>', '401'), 219('7.26.2 Character handling <ctype.h>', '401'), 220('7.26.3 Errors <errno.h>', '401'), 221('7.26.4 Format conversion of integer types <inttypes.h>', '401'), 222('7.26.5 Localization <locale.h>', '401'), 223('7.26.6 Signal handling <signal.h>', '401'), 224('7.26.7 Boolean type and values <stdbool.h>', '401'), 225('7.26.8 Integer types <stdint.h>', '401'), 226('7.26.9 Input/output <stdio.h>', '402'), 227('7.26.10 General utilities <stdlib.h>', '402'), 228('7.26.11 String handling <string.h>', '402'), 229('<wchar.h>', '402'), 230('<wctype.h>', '402'), 231('Annex A (informative) Language syntax summary', '403'), 232('A.1 Lexical grammar', '403'), 233('A.2 Phrase structure grammar', '409'), 234('A.3 Preprocessing directives', '416'), 235('Annex B (informative) Library summary', '418'), 236('B.1 Diagnostics <assert.h>', '418'), 237('B.2 Complex <complex.h>', '418'), 238('B.3 Character handling <ctype.h>', '420'), 239('B.4 Errors <errno.h>', '420'), 240('B.5 Floating-point environment <fenv.h>', '420'), 241('B.6 Characteristics of floating types <float.h>', '421'), 242('B.7 Format conversion of integer types <inttypes.h>', '421'), 243('B.8 Alternative spellings <iso646.h>', '422'), 244('B.9 Sizes of integer types <limits.h>', '422'), 245('B.10 Localization <locale.h>', '422'), 246('B.11 Mathematics <math.h>', '422'), 247('B.12 Nonlocal jumps <setjmp.h>', '427'), 248('B.13 Signal handling <signal.h>', '427'), 249('B.14 Variable arguments <stdarg.h>', '427'), 250('B.15 Boolean type and values <stdbool.h>', '427'), 251('B.16 Common definitions <stddef.h>', '428'), 252('B.17 Integer types <stdint.h>', '428'), 253('B.18 Input/output <stdio.h>', '428'), 254('B.19 General utilities <stdlib.h>', '430'), 255('B.20 String handling <string.h>', '432'), 256('B.21 Type-generic math <tgmath.h>', '433'), 257('B.22 Date and time <time.h>', '433'), 258('B.23 Extended multibyte/wide character utilities <wchar.h>', '434'), 259('B.24 Wide character classification and mapping utilities <wctype.h>', 260 '436'), 261('Annex C (informative) Sequence points', '438'), 262('Annex D (normative) Universal character names for identifiers', '439'), 263('Annex E (informative) Implementation limits', '441'), 264('Annex F (normative) IEC 60559 floating-point arithmetic', '443'), 265('F.1 Introduction', '443'), 266('F.2 Types', '443'), 267('F.3 Operators and functions', '444'), 268('F.4 Floating to integer conversion', '446'), 269('F.5 Binary-decimal conversion', '446'), 270('F.6 Contracted expressions', '447'), 271('F.7 Floating-point environment', '447'), 272('F.8 Optimization', '450'), 273('F.9 Mathematics <math.h>', '453'), 274('Annex G (informative) IEC 60559-compatible complex arithmetic', '466'), 275('G.1 Introduction', '466'), 276('G.2 Types', '466'), 277('G.3 Conventions', '466'), 278('G.4 Conversions', '467'), 279('G.5 Binary operators', '467'), 280('G.6 Complex arithmetic <complex.h>', '471'), 281('G.7 Type-generic math <tgmath.h>', '479'), 282('Annex H (informative) Language independent arithmetic', '480'), 283('H.1 Introduction', '480'), 284('H.2 Types', '480'), 285('H.3 Notification', '484'), 286('Annex I (informative) Common warnings', '486'), 287('Annex J (informative) Portability issues', '488'), 288('J.1 Unspecified behavior', '488'), 289('J.2 Undefined behavior', '491'), 290('J.3 Implementation-defined behavior', '504'), 291('J.4 Locale-specific behavior', '511'), 292('J.5 Common extensions', '512'), 293('Bibliography', '515'), 294('Index', '517')] 295 296kDocuments = { 297 'C99' : (c99URL, c99TOC, 12) 298} 299 300def findClosestTOCEntry(data, target): 301 offset = data[2] 302 best = None 303 for (name,page) in data[1]: 304 if ' ' in name: 305 section,name = name.split(' ',1) 306 if section == 'Annex': 307 section,name = name.split(' ',1) 308 section = 'Annex '+section 309 else: 310 section = None 311 try: 312 page = int(page) + offset 313 except: 314 page = 1 315 try: 316 spec = SpecIndex.fromstring(section) 317 except: 318 spec = None 319 320 # Meh, could be better... 321 if spec is not None: 322 dist = spec - target 323 if best is None or dist < best[0]: 324 best = (dist, (section, name, page)) 325 return best[1] 326 327# What a hack. Slow to boot. 328doxyLineRefRE = re.compile(r"<a name=\"l([0-9]+)\"></a>") 329def findClosestLineReference(clangRoot, doxyName, target): 330 try: 331 f = open(os.path.join(clangRoot, 'docs', 'doxygen', 'html', doxyName)) 332 except: 333 return None 334 335 best = None 336 for m in doxyLineRefRE.finditer(f.read()): 337 line = int(m.group(1), 10) 338 dist = abs(line - target) 339 if best is None or dist < best[0]: 340 best = (dist,'l'+m.group(1)) 341 f.close() 342 if best is not None: 343 return best[1] 344 return None 345 346### 347 348nameAndSpecRefRE = re.compile(r"(C99|C90|C\+\+|H\&S) (([0-9]+)(\.[0-9]+)*(p[0-9]+)?)") 349loneSpecRefRE = re.compile(r" (([0-9]+)(\.[0-9]+){2,100}(p[0-9]+)?)") 350def scanFile(path, filename): 351 try: 352 f = open(path) 353 except IOError: 354 print >>sys.stderr,'WARNING: Unable to open:',path 355 return 356 357 try: 358 for i,ln in enumerate(f): 359 ignore = set() 360 for m in nameAndSpecRefRE.finditer(ln): 361 section = m.group(2) 362 name = m.group(1) 363 if section.endswith('.'): 364 section = section[:-1] 365 yield RefItem(name, section, filename, path, i+1) 366 ignore.add(section) 367 for m in loneSpecRefRE.finditer(ln): 368 section = m.group(1) 369 if section.endswith('.'): 370 section = section[:-1] 371 if section not in ignore: 372 yield RefItem(None, section, filename, path, i+1) 373 finally: 374 f.close() 375 376### 377 378class SpecIndex: 379 @staticmethod 380 def fromstring(str): 381 secs = str.split('.') 382 paragraph = None 383 if 'p' in secs[-1]: 384 secs[-1],p = secs[-1].split('p',1) 385 paragraph = int(p) 386 indices = map(int, secs) 387 return SpecIndex(indices, paragraph) 388 389 def __init__(self, indices, paragraph=None): 390 assert len(indices)>0 391 self.indices = tuple(indices) 392 self.paragraph = paragraph 393 394 def __str__(self): 395 s = '.'.join(map(str,self.indices)) 396 if self.paragraph is not None: 397 s += '.p%d'%(self.paragraph,) 398 return s 399 400 def __repr__(self): 401 return 'SpecIndex(%s, %s)'%(self.indices, self.paragraph) 402 403 def __cmp__(self, b): 404 return cmp((self.indices,self.paragraph), 405 (b.indices,b.paragraph)) 406 407 def __hash__(self): 408 return hash((self.indices,self.paragraph)) 409 410 def __sub__(self, indices): 411 def sub(a,b): 412 a = a or 0 413 b = b or 0 414 return abs(a-b) 415 return map(sub,self.indices,indices) 416 417class RefItem: 418 def __init__(self, name, section, filename, path, line): 419 self.name = name 420 self.section = SpecIndex.fromstring(section) 421 self.filename = filename 422 self.path = path 423 self.line = line 424 425 def __str__(self): 426 if self.name is not None: 427 return '%s %s'%(self.name, self.section) 428 else: 429 return '--- %s'%(self.section,) 430 431 def __repr__(self): 432 return 'RefItem(%s, %r, "%s", "%s", %d)'%(self.name, 433 self.section, 434 self.filename, 435 self.path, 436 self.line) 437 438 def __cmp__(self, b): 439 return cmp((self.name,self.section,self.filename,self.path,self.line), 440 (b.name,b.section,self.filename,self.path,self.line)) 441 442 def __hash__(self): 443 return hash((self.name,self.section,self.filename,self.path,self.line)) 444 445### 446 447def sorted(l): 448 l = list(l) 449 l.sort() 450 return l 451 452def getRevision(path): 453 import svn, svn.core, svn.client 454 455 revision = [None] 456 457 def info_cb(path, info, pool): 458 revision[0] = info.rev 459 460 try: 461 root = os.path.abspath(path) 462 svn.core.apr_initialize() 463 pool = svn.core.svn_pool_create(None) 464 ctx = svn.client.svn_client_ctx_t() 465 svn.client.svn_client_info(root, 466 None, 467 None, 468 info_cb, 469 False, 470 ctx, 471 pool) 472 svn.core.svn_pool_destroy(pool) 473 except: 474 pass 475 476 return revision[0] 477 478def buildRefTree(references): 479 root = (None, {}, []) 480 481 def getNode(keys): 482 if not keys: 483 return root 484 key,parent = keys[-1],getNode(keys[:-1]) 485 node = parent[1].get(key) 486 if node is None: 487 parent[1][key] = node = (key, {}, []) 488 return node 489 490 for ref in references: 491 n = getNode((ref.name,) + ref.section.indices) 492 n[2].append(ref) 493 494 def flatten((key, children, data)): 495 children = sorted(map(flatten,children.values())) 496 return (key, children, sorted(data)) 497 498 return flatten(root) 499 500def preorder(node,parents=(),first=True): 501 (key,children,data) = node 502 if first: 503 yield parents+(node,) 504 for c in children: 505 for item in preorder(c, parents+(node,)): 506 yield item 507 508def main(): 509 global options 510 from optparse import OptionParser 511 parser = OptionParser("usage: %prog [options] CLANG_ROOT <output-dir>") 512 513 (options, args) = parser.parse_args() 514 515 if len(args) != 2: 516 parser.error("incorrect number of arguments") 517 518 references = [] 519 root,outputDir = args 520 for (dirpath, dirnames, filenames) in os.walk(root): 521 for filename in filenames: 522 name,ext = os.path.splitext(filename) 523 if ext in ('.c', '.cpp', '.h', '.def'): 524 fullpath = os.path.join(dirpath, filename) 525 references.extend(list(scanFile(fullpath, filename))) 526 527 refTree = buildRefTree(references) 528 529 specs = {} 530 for ref in references: 531 spec = specs[ref.name] = specs.get(ref.name,{}) 532 items = spec[ref.section] = spec.get(ref.section,[]) 533 items.append(ref) 534 535 print 'Found %d references.'%(len(references),) 536 537 referencesPath = os.path.join(outputDir,'references.html') 538 print 'Writing: %s'%(referencesPath,) 539 f = open(referencesPath,'w') 540 print >>f, '<html><head><title>clang: Specification References</title></head>' 541 print >>f, '<body>' 542 print >>f, '\t<h2>Specification References</h2>' 543 for i,node in enumerate(refTree[1]): 544 specName = node[0] or 'Unknown' 545 print >>f, '<a href="#spec%d">%s</a><br>'%(i,specName) 546 for i,node in enumerate(refTree[1]): 547 specName = node[0] or 'Unknown' 548 print >>f, '<hr>' 549 print >>f, '<a name="spec%d">'%(i,) 550 print >>f, '<h3>Document: %s</h3>'%(specName or 'Unknown',) 551 print >>f, '<table border="1" cellspacing="2" width="80%">' 552 print >>f, '<tr><th width="20%">Name</th><th>References</th></tr>' 553 docData = kDocuments.get(specName) 554 for path in preorder(node,first=False): 555 if not path[-1][2]: 556 continue 557 components = '.'.join([str(p[0]) for p in path[1:]]) 558 print >>f, '\t<tr>' 559 tocEntry = None 560 if docData is not None: 561 tocEntry = findClosestTOCEntry(docData, [p[0] for p in path[1:]]) 562 if tocEntry is not None: 563 section,name,page = tocEntry 564 # If section is exact print the TOC name 565 if page is not None: 566 linkStr = '<a href="%s#page=%d">%s</a> (pg.%d)'%(docData[0],page,components,page) 567 else: 568 linkStr = components 569 if section == components: 570 print >>f, '\t\t<td valign=top>%s<br>%s</td>'%(linkStr,name) 571 else: 572 print >>f, '\t\t<td valign=top>%s</td>'%(linkStr,) 573 else: 574 print >>f, '\t\t<td valign=top>%s</td>'%(components,) 575 print >>f, '\t\t<td valign=top>' 576 for item in path[-1][2]: 577 # XXX total hack 578 relativePath = item.path[len(root):] 579 if relativePath.startswith('/'): 580 relativePath = relativePath[1:] 581 # XXX this is broken, how does doxygen mangle w/ multiple 582 # refs? Can we just read its map? 583 filename = os.path.basename(relativePath) 584 doxyName = '%s-source.html'%(filename.replace('.','_8'),) 585 # Grrr, why can't doxygen write line number references. 586 lineReference = findClosestLineReference(root,doxyName,item.line) 587 if lineReference is not None: 588 linkStr = 'http://clang.llvm.org/doxygen/%s#%s'%(doxyName,lineReference) 589 else: 590 linkStr = 'http://clang.llvm.org/doxygen/%s'%(doxyName,) 591 if item.section.paragraph is not None: 592 paraText = ' (p%d)'%(item.section.paragraph,) 593 else: 594 paraText = '' 595 print >>f,'<a href="%s">%s:%d</a>%s<br>'%(linkStr,relativePath,item.line,paraText) 596 print >>f, '\t\t</td>' 597 print >>f, '\t</tr>' 598 print >>f, '</table>' 599 print >>f, '<hr>' 600 print >>f, 'Generated: %s<br>'%(time.strftime('%Y-%m-%d %H:%M'),) 601 print >>f, 'SVN Revision: %s'%(getRevision(root),) 602 print >>f, '</body>' 603 f.close() 604 605if __name__=='__main__': 606 main() 607