blob: a23cf98566ef5266929b8ce263e96b9496182e6a [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
Daniel Dunbar6aa9e8a2008-09-04 20:26:14 +0000357 for i,ln in enumerate(f):
358 ignore = set()
359 for m in nameAndSpecRefRE.finditer(ln):
360 section = m.group(2)
361 name = m.group(1)
362 if section.endswith('.'):
363 section = section[:-1]
364 yield RefItem(name, section, filename, path, i+1)
365 ignore.add(section)
366 for m in loneSpecRefRE.finditer(ln):
367 section = m.group(1)
368 if section.endswith('.'):
369 section = section[:-1]
370 if section not in ignore:
371 yield RefItem(None, section, filename, path, i+1)
Daniel Dunbar33b51182008-08-29 01:07:08 +0000372
373###
374
375class SpecIndex:
376 @staticmethod
377 def fromstring(str):
378 secs = str.split('.')
379 paragraph = None
380 if 'p' in secs[-1]:
381 secs[-1],p = secs[-1].split('p',1)
382 paragraph = int(p)
383 indices = map(int, secs)
384 return SpecIndex(indices, paragraph)
385
386 def __init__(self, indices, paragraph=None):
387 assert len(indices)>0
388 self.indices = tuple(indices)
389 self.paragraph = paragraph
390
391 def __str__(self):
392 s = '.'.join(map(str,self.indices))
393 if self.paragraph is not None:
394 s += '.p%d'%(self.paragraph,)
395 return s
396
397 def __repr__(self):
398 return 'SpecIndex(%s, %s)'%(self.indices, self.paragraph)
399
400 def __cmp__(self, b):
401 return cmp((self.indices,self.paragraph),
402 (b.indices,b.paragraph))
403
404 def __hash__(self):
405 return hash((self.indices,self.paragraph))
406
407 def __sub__(self, indices):
408 def sub(a,b):
409 a = a or 0
410 b = b or 0
411 return abs(a-b)
412 return map(sub,self.indices,indices)
413
414class RefItem:
415 def __init__(self, name, section, filename, path, line):
416 self.name = name
417 self.section = SpecIndex.fromstring(section)
418 self.filename = filename
419 self.path = path
420 self.line = line
421
422 def __str__(self):
423 if self.name is not None:
424 return '%s %s'%(self.name, self.section)
425 else:
426 return '--- %s'%(self.section,)
427
428 def __repr__(self):
429 return 'RefItem(%s, %r, "%s", "%s", %d)'%(self.name,
430 self.section,
431 self.filename,
432 self.path,
433 self.line)
434
435 def __cmp__(self, b):
436 return cmp((self.name,self.section,self.filename,self.path,self.line),
437 (b.name,b.section,self.filename,self.path,self.line))
438
439 def __hash__(self):
440 return hash((self.name,self.section,self.filename,self.path,self.line))
441
442###
443
444def sorted(l):
445 l = list(l)
446 l.sort()
447 return l
448
449def getRevision(path):
Daniel Dunbar6aa9e8a2008-09-04 20:26:14 +0000450 import subprocess
451 p = subprocess.Popen(['svn', 'info', path],
452 stdin=open('/dev/null','r'),
453 stdout=subprocess.PIPE)
454 for ln in p.stdout.read(1024).split('\n'):
455 if ln.startswith('Revision:'):
456 return ln.split(':',1)[1].strip()
457 return None
Daniel Dunbar33b51182008-08-29 01:07:08 +0000458
459def buildRefTree(references):
460 root = (None, {}, [])
461
462 def getNode(keys):
463 if not keys:
464 return root
465 key,parent = keys[-1],getNode(keys[:-1])
466 node = parent[1].get(key)
467 if node is None:
468 parent[1][key] = node = (key, {}, [])
469 return node
470
471 for ref in references:
472 n = getNode((ref.name,) + ref.section.indices)
473 n[2].append(ref)
474
475 def flatten((key, children, data)):
476 children = sorted(map(flatten,children.values()))
477 return (key, children, sorted(data))
478
479 return flatten(root)
480
481def preorder(node,parents=(),first=True):
482 (key,children,data) = node
483 if first:
484 yield parents+(node,)
485 for c in children:
486 for item in preorder(c, parents+(node,)):
487 yield item
488
489def main():
490 global options
491 from optparse import OptionParser
492 parser = OptionParser("usage: %prog [options] CLANG_ROOT <output-dir>")
493
494 (options, args) = parser.parse_args()
495
496 if len(args) != 2:
497 parser.error("incorrect number of arguments")
498
499 references = []
500 root,outputDir = args
501 for (dirpath, dirnames, filenames) in os.walk(root):
502 for filename in filenames:
503 name,ext = os.path.splitext(filename)
504 if ext in ('.c', '.cpp', '.h', '.def'):
505 fullpath = os.path.join(dirpath, filename)
506 references.extend(list(scanFile(fullpath, filename)))
507
508 refTree = buildRefTree(references)
509
510 specs = {}
511 for ref in references:
512 spec = specs[ref.name] = specs.get(ref.name,{})
513 items = spec[ref.section] = spec.get(ref.section,[])
514 items.append(ref)
515
516 print 'Found %d references.'%(len(references),)
517
518 referencesPath = os.path.join(outputDir,'references.html')
519 print 'Writing: %s'%(referencesPath,)
520 f = open(referencesPath,'w')
521 print >>f, '<html><head><title>clang: Specification References</title></head>'
522 print >>f, '<body>'
523 print >>f, '\t<h2>Specification References</h2>'
524 for i,node in enumerate(refTree[1]):
525 specName = node[0] or 'Unknown'
526 print >>f, '<a href="#spec%d">%s</a><br>'%(i,specName)
527 for i,node in enumerate(refTree[1]):
528 specName = node[0] or 'Unknown'
529 print >>f, '<hr>'
530 print >>f, '<a name="spec%d">'%(i,)
531 print >>f, '<h3>Document: %s</h3>'%(specName or 'Unknown',)
532 print >>f, '<table border="1" cellspacing="2" width="80%">'
533 print >>f, '<tr><th width="20%">Name</th><th>References</th></tr>'
534 docData = kDocuments.get(specName)
535 for path in preorder(node,first=False):
536 if not path[-1][2]:
537 continue
538 components = '.'.join([str(p[0]) for p in path[1:]])
539 print >>f, '\t<tr>'
540 tocEntry = None
541 if docData is not None:
542 tocEntry = findClosestTOCEntry(docData, [p[0] for p in path[1:]])
543 if tocEntry is not None:
544 section,name,page = tocEntry
545 # If section is exact print the TOC name
546 if page is not None:
547 linkStr = '<a href="%s#page=%d">%s</a> (pg.%d)'%(docData[0],page,components,page)
548 else:
549 linkStr = components
550 if section == components:
551 print >>f, '\t\t<td valign=top>%s<br>%s</td>'%(linkStr,name)
552 else:
553 print >>f, '\t\t<td valign=top>%s</td>'%(linkStr,)
554 else:
555 print >>f, '\t\t<td valign=top>%s</td>'%(components,)
556 print >>f, '\t\t<td valign=top>'
557 for item in path[-1][2]:
558 # XXX total hack
559 relativePath = item.path[len(root):]
560 if relativePath.startswith('/'):
561 relativePath = relativePath[1:]
562 # XXX this is broken, how does doxygen mangle w/ multiple
563 # refs? Can we just read its map?
564 filename = os.path.basename(relativePath)
565 doxyName = '%s-source.html'%(filename.replace('.','_8'),)
566 # Grrr, why can't doxygen write line number references.
567 lineReference = findClosestLineReference(root,doxyName,item.line)
568 if lineReference is not None:
569 linkStr = 'http://clang.llvm.org/doxygen/%s#%s'%(doxyName,lineReference)
570 else:
571 linkStr = 'http://clang.llvm.org/doxygen/%s'%(doxyName,)
572 if item.section.paragraph is not None:
573 paraText = '&nbsp;(p%d)'%(item.section.paragraph,)
574 else:
575 paraText = ''
576 print >>f,'<a href="%s">%s:%d</a>%s<br>'%(linkStr,relativePath,item.line,paraText)
577 print >>f, '\t\t</td>'
578 print >>f, '\t</tr>'
579 print >>f, '</table>'
580 print >>f, '<hr>'
581 print >>f, 'Generated: %s<br>'%(time.strftime('%Y-%m-%d %H:%M'),)
582 print >>f, 'SVN Revision: %s'%(getRevision(root),)
583 print >>f, '</body>'
584 f.close()
585
586if __name__=='__main__':
587 main()