Wiki

root/cobra/trunk/Source/DocGenerator.cobra

Revision 2519, 14.7 KB (checked in by Charles.Esterbrook, 12 months ago)

Bug fixes for -doc-lib.

  • Property svn:eol-style set to native
Line 
1"""
2The main test case for the doc generator is the Cobra compiler itself:
3
4cd Source
5cobra -doc -files:files-to-compile.text
6
7You can also try a library like:
8cobra -doc-lib:Cobra.Lang
9cobra -doc-lib:System
10cobra -doc-lib:System.Core
11cobra -doc-lib:System.Web
12cobra -doc-lib:mscorlib
13
14HTML output can be validated at http://validator.w3.org/
15"""
16
17
18use System.Reflection
19
20
21class GenerateHtmlDocVisitor inherits Visitor
22    """
23    Quick characteristics:
24   
25    * Writes HTML summary information for namespaces, modules and types.
26    * Results go in a 'gen-docs' subdirectory of the current directory.
27    * Each namespace gets a file named "nsFoo.html".
28    * Each module gets a file named "Foo.html"
29    * Each type is written in the same file as the module that declared it.
30
31    Example invocations:
32   
33    cobra -doc Utils.cobra
34    cobra -doc -files:files-to-compile.text
35    cobra -doc-lib:System.Web
36
37    Potential options:
38        * Show private
39        * Summary vs. complete source
40
41    Features:
42        * Outputs Foo.html for each module
43        * Handles classes, structs, interfaces, extensions and their members
44        * Generates an index.html file
45
46    Other notes:
47   
48    * This functionality differs from the syntax highlighter at
49      http://cobra-language.com/trac/cobra/wiki/PygmentsHighlighter
50      and also via "cobra -highlight"
51      * Highlighters are oriented towards presenting code snippets and files online, while this
52        tool is oriented towards library documentation.
53      * Pygments is used by 3rd party programs like Trac; this tool is not.
54      * This doc generator does not expose implementation (no statements or expressions).
55      * This doc tool will eventually be concerned with cross references and linking.
56
57
58    to-do:
59
60    * In output,
61        [ ] wrap box members in a div so they have padding, etc.
62        [ ] write member contracts
63        [ ] write member tests. note this would require handling all expressions and statements!
64        [ ] on members, write is names. 'protected' only on non-underscored members. no 'shared'. include everything else
65        [ ] in index
66            [ ] alpha list of types (including nested types)
67            [ ] inheritance outline of types
68        [ ] cross reference member names with types that declare them
69        [ ] provide some fancy JavaScript options like hiding doc strings, tests, etc.
70        [ ] highlight `as` keyword
71        [ ] need anchor names for types and members so they can be linked to
72        [ ] link types
73        [ ] link "override" to the base method
74        [ ] embed the stylesheet
75        [ ] merge styles with the syntax highlighter?
76    [ ] Copy the stylesheet into the html directory. Locate the style sheet in the current directory or next to the cobra.exe file.
77    [ ] What if you want a doc gen for something specific like:
78        System.String
79        System.Collections.Generic
80    [ ] How should options be provided to the -doc command?
81   
82    Also, see TODO comments in the code.
83    """
84
85    var _tw as TextWriter?
86    var _indent = 0
87    var _isBOL = false
88    var _writeCount = 0
89        """
90        Can be used to tell if a method that was called wrote anything.
91        """
92
93    var _moduleList = List<of List<of String>>()
94
95    cue init(willVisitModule as Predicate<of dynamic>)
96        base.init
97        _willVisitModule = willVisitModule
98        Directory.createDirectory(.targetDirectory)
99
100    get willVisitModule from var as Predicate<of dynamic>
101
102    get methodName as String is override
103        return 'gen'
104
105    get targetDirectory as String
106        return 'gen-docs'
107
108    ## File output
109
110    def startFile(name as String) as String
111        """
112        Returns the filename of the newly created file (not including its path).
113        """
114        fileName = name + '.html'
115        path = Path.combine(.targetDirectory, fileName)
116        print 'Writing [path]'
117        _tw = File.createText(path)
118        .writeLine('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">')
119        .writeLine('<html xmlns="http://www.w3.org/1999/xhtml">')
120        .writeLine('<head>')
121        .writeLine('<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />')
122        .writeLine('<title>[name]</title>')
123        .writeLine('<link rel="stylesheet" href="../styles-cobra-doc.css" type="text/css" />')
124        .writeLine('<link rel="stylesheet" href="../styles/styles-cobra-doc.css" type="text/css" />')
125        .writeLine('<link rel="stylesheet" href="styles-cobra-doc.css" type="text/css" />')
126        .writeLine('</head>')
127        .writeLine('<body>')
128        return fileName
129
130    def finishFile
131        .writeLine('</body> </html>')
132        _tw.close
133        _tw = nil
134
135    def write(s as String)
136        if _isBOL and _indent, for i in _indent, _tw.write('    ')
137        _tw.write(s)
138        _isBOL = false
139        _writeCount += 1
140
141    def writeLine
142        .writeLine('')
143
144    def writeLine(s as String)
145        if _isBOL and _indent, for i in _indent, _tw.write('    ')
146        _tw.writeLine(s)
147        _isBOL = true
148        _writeCount += 1
149
150    def writeDocString(s as String?)
151        if s
152            if s.trim == '', return
153            _tw.write('<span class="ds">')
154            for i in _indent, _tw.write('    ')
155            _tw.writeLine('"""')
156            _isBOL = true
157            s = s.replace('\r', '')
158            while s.endsWith('\n'), s = s[:-1]
159            # s = s.replace('\t','xxx.')  # for debugging
160            s = .encode(s)
161            # print
162            for line in s.split(c'\n')
163                # print '([line])'
164                .writeLine(line.trim)  # to-do: The .trim is wrong. It's to fix the parser problem of leaving indentation in doc string contents.
165            .writeLine('"""</span>')
166            .writeLine
167
168    def encode(s as String) as String
169        """ Encode a string for inclusion in HTML output. """
170        return CobraCore.htmlEncode(s)
171
172    def indent
173        _indent += 1
174
175    def outdent
176        _indent -= 1
177
178    def typeRef(type as IType?) as String
179        """ Return a reference to a type in HTML. """
180        s = if(type, .encode(type.name), '(nil type)')
181        if ',' in s and ', ' not in s
182            # Dictionary<of Type,MethodInfo> --> Dictionary<of Type, MethodInfo>
183            s = s.replace(',', ', ')
184        return '<span class="type-ref">[s]</span>'
185
186    ## Generate
187
188    def dispatch(obj as Object?)
189        if true
190            base.dispatch(obj)
191        else
192            try
193                base.dispatch(obj)
194            catch exc as Exception
195                while exc inherits TargetInvocationException and exc.innerException, exc = exc.innerException to !
196                print
197                print ' *** exception for obj [obj]:'
198                print ' ### [exc.typeOf.name]: [exc.message]'
199
200    def gen(c as Compiler)
201        # .dispatch(c.globalNS)
202        .dispatch(c.modules)
203        .writeIndex
204
205    def writeIndex
206        .startFile('index')
207        .writeLine('<div class="sansSerif">')
208        try
209            modules = _moduleList
210            if modules.count
211                .writeLine('<div class="heading">Files</div>')
212                modules.sort(ref .compareModuleNames)
213                for pair in _moduleList
214                    .writeLine('<a href="[pair[1]]">[.encode(pair[0])]</a> <br />')
215        finally
216            .writeLine('</div> <!-- div.sansSerif -->')
217            .finishFile
218   
219    def compareModuleNames(a as List<of String>, b as List<of String>) as int
220        return a[0].toLower.compareTo(b[0].toLower)
221
222    def gen(obj as Object?)
223        if obj is nil, return
224        msg = '*** Unbound visitation for type [obj.getType]: [obj]'
225        print msg
226        .writeLine(.encode(msg))
227
228    def gen(mod as Module)
229        # This is to capture "native" modules like SharpModule
230        pass
231
232    def gen(mod as AssemblyModule)
233        check = .willVisitModule
234        if check(mod), _gen(mod)
235
236    def gen(mod as CobraModule)
237        check = .willVisitModule
238        if check(mod), _gen(mod)
239   
240    def _gen(mod as Module)
241        fileName = .startFile(Path.getFileNameWithoutExtension(mod.fileName) to !)
242        .writeLine('<div class="mono">')
243        .writeLine('<div class="pre">')
244        _moduleList.add([mod.fileName, fileName])
245        try
246            .writeDocString(mod.docString to !)
247            for decl in (mod to dynamic).topNameSpace.declsInOrder
248                .dispatch(decl)
249        finally
250            .writeLine('<br />')
251            .writeLine('</div> <!-- div.pre -->')
252            .writeLine('</div> <!-- div.mono -->')
253            .finishFile
254
255    def gen(ns as NameSpace)
256        for decl in ns.declsInOrder, .dispatch(decl)
257
258    # to-do: can merge code in the following three after they fully mature
259
260    def gen(cl as Class)
261        .writeLine('<div class="typeDecl"><span class="kwtd">class</span> <span class="bdn">[cl.name]</span>')
262        .writeIsNames(cl)
263        # to-do: inherits, implements, generic constraints
264        .writeDocString(cl.docString)
265        .writeBoxMembers(cl)
266
267    def gen(inter as Interface)
268        .writeLine('<div class="typeDecl"><span class="kwtd">interface</span> <span class="bdn">[inter.name]</span>')
269        .writeIsNames(inter)
270        # to-do: inherits, generic constraints
271        .writeDocString(inter.docString)
272        .writeBoxMembers(inter)
273
274    def gen(struc as Struct)
275        .writeLine('<div class="typeDecl"><span class="kwtd">struct</span> <span class="bdn">[struc.name]</span>')
276        .writeIsNames(struc)
277        # to-do: implements, generic constraints
278        .writeDocString(struc.docString)
279        .writeBoxMembers(struc)
280
281    def gen(ext as Extension)
282        .writeLine('<div class="typeDecl"><span class="kwtd">extend</span> <span class="bdn">[if(ext.extendedBox, ext.extendedBox.name, "(no extended box)")]</span>')
283        .writeIsNames(ext)
284        .writeDocString(ext.docString)
285        .writeBoxMembers(ext)
286
287    def writeIsNames(box as Box)
288        .writeIsNames(box.isNames)
289   
290    def writeIsNames(isNames as String*)
291        if isNames.toList.count
292            .write('is ')
293            sep = ''
294            for name in isNames
295                .write('[sep][name]')
296                sep = ', '
297            .writeLine
298
299    def writeBoxMembers(box as Box)
300        .writeLine
301        .indent
302        didWriteMembers = false
303
304        typeDecls = .sorted(for decl in box.declsInOrder where decl inherits IType)
305        if typeDecls.count
306            didWriteMembers = true
307            .writeBoxMembers(typeDecls)
308
309        sharedDecls = .sorted(for decl in box.declsInOrder where decl.isShared and not (decl to Object) inherits IType)
310        if sharedDecls.count
311            if didWriteMembers, .writeLine
312            else, didWriteMembers = true
313            .writeLine('<span class="kwsh">shared</span>')  # to-do: write this only if shared decls get written. See class Parser output for an example.
314            .writeLine
315            .indent
316            .writeBoxMembers(sharedDecls)
317            .outdent
318            .writeLine
319
320        nonsharedDecls = .sorted(for decl in box.declsInOrder where not decl.isShared)
321        if nonsharedDecls.count
322            if didWriteMembers, .writeLine
323            else, didWriteMembers = true
324            .writeBoxMembers(nonsharedDecls)
325
326        if not didWriteMembers
327            .writeLine('<span class="wkpa">pass</span>')
328
329        .outdent
330        .write('</div>')
331
332    def writeBoxMembers(decls as IList<of IBoxMember>)
333        for i in decls.count
334            decl = decls[i]
335            writeCount = _writeCount
336            .dispatch(decl)
337            if _writeCount > writeCount  # if wrote anything ...
338                # Write the doc string
339                if decl.docString and decl.docString.length
340                    .indent
341                    .writeDocString(decl.docString to !)
342                    .outdent               
343                else
344                    _tw.writeLine('<span class="ds"> </span>')
345                # Put a blank line when switching to a different type of member declaration (say from properties to methods)
346                if i < decls.count-1 and decls[i+1].getType is not decl.getType
347                    .writeLine
348
349    def gen(bc as BoxConst)
350        if bc.isPublic or bc.isProtected
351            if bc.initExpr inherits SharpExpr, init = (bc.initExpr to SharpExpr).sharpSource
352            else if bc.initExpr, init = bc.initExpr.toCobraSource
353            else, init = '(unknown)'
354            .writeLine('<span class="kwmd">const</span> <span class="constName">[bc.name]</span> = [init] (as [.typeRef(bc.type)])')
355            # to-do: is names, attributes, etc.
356
357    def gen(bv as BoxVar)
358        if bv.isPublic or bv.isProtected
359            .writeLine('<span class="kwmd">var</span> <span class="varName">[bv.name]</span> as [.typeRef(bv.type)]')
360            # to-do: is names, attributes, etc.
361
362    def gen(evt as BoxEvent)
363        .writeLine('<span class="kwmd">event</span> <span class="eventName">[evt.name]</span> as [.typeRef(evt.handlerType)]')
364        # to-do: is names, attributes, etc.
365       
366    def gen(method as AbstractMethod)
367        _gen(method, 'def', method.name)
368       
369    def gen(init as Initializer)
370        _gen(init, 'cue', 'init')
371
372    def _gen(method as AbstractMethod, keyword as String, name as String)
373        .write('<span class="kwmd">[keyword]</span> <span class="methodName">[name]</span>')
374        if method.params.count
375            .write('(')
376            sep = ''
377            for param in method.params
378                .write(sep)
379                .dispatch(param)
380                sep = ', '
381            .write(')')
382        .writeLine
383
384    def gen(param as Param)
385        if param.isInOut, dir = 'inout '
386        else if param.isOut, dir = 'out '
387        else, dir = ''
388        .write('[param.name] as [dir]')
389        try
390            type = param.type to dynamic
391        catch Exception
392#           type = '(Unknown type due to exception: [exc])'
393            type = '(unknown)'
394        success
395            type = .typeRef(type)
396        .write(type.toString)
397        # to-do: styles
398
399    def gen(prop as Property)
400        if prop.getPart and not prop.setPart
401            .write('<span class="kwmd">get</span>')
402        else if prop.setPart and not prop.getPart
403            .write('<span class="kwmd">set</span>')
404        else
405            .write('<span class="kwmd">pro</span>')
406        .writeLine(' [prop.name] as [.typeRef(prop.resultType)]')
407
408    def gen(indexer as Indexer)
409        if indexer.getPart and not indexer.setPart
410            .write('<span class="kwmd">get</span>')
411        else if indexer.setPart and not indexer.getPart
412            .write('<span class="kwmd">set</span>')
413        else
414            .write('<span class="kwmd">pro</span>')
415        .write(' \[')
416        sep = ''
417        for param in indexer.params
418            .write(sep)
419            .dispatch(param)
420            sep = ', '
421        .write(']')     
422        .writeLine(' as [.typeRef(indexer.resultType)]')
423
424    def gen(ed as EnumDecl)
425        .writeLine('<div class="typeDecl"><span class="kwtd">enum</span> <span class="bdn">[ed.name]</span>')
426        .writeIsNames(ed.isNames)
427        .indent
428        for decl in ed.declsInOrder
429            .writeLine(decl.name)
430        .outdent
431        .write('</div>')
432
433
434    ## Sorting members
435
436    def sorted(decls as IList<of IBoxMember>) as List<of IBoxMember>
437        """
438        Return the box members in a logically sorted order.
439        First order is by type of member (enums, vars, constructors, props, indexers, methods).
440        Second order is alphabetical.
441        Third order is parameter count.
442        """
443        t = List<of IBoxMember>(decls)
444        t.sort(ref .compareMembers)
445        return t
446
447    def compareMembers(a as IBoxMember, b as IBoxMember) as int is shared
448        if a.getType is b.getType
449            diff = a.name.compareTo(b.name)  # note that Cobra disallows member names that differ only by case
450            if diff == 0
451                if a inherits AbstractMethod
452                    diff = a.params.count.compareTo((b to AbstractMethod).params.count)
453                else if a inherits Indexer
454                    # b must also be an indexer since their names were the same
455                    diff = a.params.count.compareTo((b to Indexer).params.count)
456            else if a.name.startsWith('_') and not b.name.startsWith('_')
457                diff = .compareNamesWithFirstUnderscored(a.name, b.name)
458            else if not a.name.startsWith('_') and b.name.startsWith('_')
459                diff = -1 * .compareNamesWithFirstUnderscored(b.name, a.name)
460        else
461            diff = .rank(a).compareTo(.rank(b))
462        return diff
463
464    def compareNamesWithFirstUnderscored(a as String, b as String) as int is shared
465        require
466            a.startsWith('_')
467            not b.startsWith('_')
468        body
469            a = a[1:]
470            if a == b, return 1  # underscored/protected member comes last
471            else, return a.compareTo(b)
472           
473    def rank(obj as IBoxMember) as int is shared
474        if obj inherits EnumDecl, return 0
475        if obj inherits MethodSig, return 10
476        if obj inherits Box, return 20
477        if obj inherits BoxConst, return 25
478        if obj inherits BoxVar, return 30
479        if obj inherits Initializer, return 40
480        if obj inherits BoxEvent, return 45
481        if obj inherits Property, return 50
482        if obj inherits Indexer, return 60
483        if obj inherits Method, return 70
484        throw FallThroughException(obj)
Note: See TracBrowser for help on using the browser.