blob: a117942eb2c12c6b4cf57a7bbe3a9948bba1b8ae [file] [log] [blame]
Daniel Dunbar33b51182008-08-29 01:07:08 +00001#!/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 = '&nbsp;(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()