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 = '&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()
607