""" The main test case for the doc generator is the Cobra compiler itself: cd Source cobra -doc -files:files-to-compile.text You can also try a library like: cobra -doc-lib:Cobra.Lang cobra -doc-lib:System cobra -doc-lib:System.Core cobra -doc-lib:System.Web cobra -doc-lib:mscorlib HTML output can be validated at http://validator.w3.org/ """ use System.Reflection class GenerateHtmlDocVisitor inherits Visitor """ Quick characteristics: * Writes HTML summary information for namespaces, modules and types. * Results go in a 'gen-docs' subdirectory of the current directory. * Each namespace gets a file named "nsFoo.html". * Each module gets a file named "Foo.html" * Each type is written in the same file as the module that declared it. Example invocations: cobra -doc Utils.cobra cobra -doc -files:files-to-compile.text cobra -doc-lib:System.Web Potential options: * Show private * Summary vs. complete source Features: * Outputs Foo.html for each module * Handles classes, structs, interfaces, extensions and their members * Generates an index.html file Other notes: * This functionality differs from the syntax highlighter at http://cobra-language.com/trac/cobra/wiki/PygmentsHighlighter and also via "cobra -highlight" * Highlighters are oriented towards presenting code snippets and files online, while this tool is oriented towards library documentation. * Pygments is used by 3rd party programs like Trac; this tool is not. * This doc generator does not expose implementation (no statements or expressions). * This doc tool will eventually be concerned with cross references and linking. to-do: * In output, [ ] wrap box members in a div so they have padding, etc. [ ] write member contracts [ ] write member tests. note this would require handling all expressions and statements! [ ] on members, write is names. 'protected' only on non-underscored members. no 'shared'. include everything else [ ] in index [ ] alpha list of types (including nested types) [ ] inheritance outline of types [ ] cross reference member names with types that declare them [ ] provide some fancy JavaScript options like hiding doc strings, tests, etc. [ ] highlight `as` keyword [ ] need anchor names for types and members so they can be linked to [ ] link types [ ] link "override" to the base method [ ] embed the stylesheet [ ] merge styles with the syntax highlighter? [ ] Copy the stylesheet into the html directory. Locate the style sheet in the current directory or next to the cobra.exe file. [ ] What if you want a doc gen for something specific like: System.String System.Collections.Generic [ ] How should options be provided to the -doc command? Also, see TODO comments in the code. """ var _tw as TextWriter? var _indent = 0 var _isBOL = false var _writeCount = 0 """ Can be used to tell if a method that was called wrote anything. """ var _moduleList = List>() cue init(willVisitModule as Predicate) base.init _willVisitModule = willVisitModule Directory.createDirectory(.targetDirectory) get willVisitModule from var as Predicate get methodName as String is override return 'gen' get targetDirectory as String return 'gen-docs' ## File output def startFile(name as String) as String """ Returns the filename of the newly created file (not including its path). """ fileName = name + '.html' path = Path.combine(.targetDirectory, fileName) print 'Writing [path]' _tw = File.createText(path) .writeLine('') .writeLine('') .writeLine('') .writeLine('') .writeLine('[name]') .writeLine('') .writeLine('') .writeLine('') .writeLine('') .writeLine('') return fileName def finishFile .writeLine(' ') _tw.close _tw = nil def write(s as String) if _isBOL and _indent, for i in _indent, _tw.write(' ') _tw.write(s) _isBOL = false _writeCount += 1 def writeLine .writeLine('') def writeLine(s as String) if _isBOL and _indent, for i in _indent, _tw.write(' ') _tw.writeLine(s) _isBOL = true _writeCount += 1 def writeDocString(s as String?) if s if s.trim == '', return _tw.write('') for i in _indent, _tw.write(' ') _tw.writeLine('"""') _isBOL = true s = s.replace('\r', '') while s.endsWith('\n'), s = s[:-1] # s = s.replace('\t','xxx.') # for debugging s = .encode(s) # print for line in s.split(c'\n') # print '([line])' .writeLine(line.trim) # to-do: The .trim is wrong. It's to fix the parser problem of leaving indentation in doc string contents. .writeLine('"""') .writeLine def encode(s as String) as String """ Encode a string for inclusion in HTML output. """ return CobraCore.htmlEncode(s) def indent _indent += 1 def outdent _indent -= 1 def typeRef(type as IType?) as String """ Return a reference to a type in HTML. """ s = if(type, .encode(type.name), '(nil type)') if ',' in s and ', ' not in s # Dictionary --> Dictionary s = s.replace(',', ', ') return '[s]' ## Generate def dispatch(obj as Object?) if true base.dispatch(obj) else try base.dispatch(obj) catch exc as Exception while exc inherits TargetInvocationException and exc.innerException, exc = exc.innerException to ! print print ' *** exception for obj [obj]:' print ' ### [exc.typeOf.name]: [exc.message]' def gen(c as Compiler) # .dispatch(c.globalNS) .dispatch(c.modules) .writeIndex def writeIndex .startFile('index') .writeLine('
') try modules = _moduleList if modules.count .writeLine('
Files
') modules.sort(ref .compareModuleNames) for pair in _moduleList .writeLine('[.encode(pair[0])]
') finally .writeLine('
') .finishFile def compareModuleNames(a as List, b as List) as int return a[0].toLower.compareTo(b[0].toLower) def gen(obj as Object?) if obj is nil, return msg = '*** Unbound visitation for type [obj.getType]: [obj]' print msg .writeLine(.encode(msg)) def gen(mod as Module) # This is to capture "native" modules like SharpModule pass def gen(mod as AssemblyModule) check = .willVisitModule if check(mod), _gen(mod) def gen(mod as CobraModule) check = .willVisitModule if check(mod), _gen(mod) def _gen(mod as Module) fileName = .startFile(Path.getFileNameWithoutExtension(mod.fileName) to !) .writeLine('
') .writeLine('
') _moduleList.add([mod.fileName, fileName]) try .writeDocString(mod.docString to !) for decl in (mod to dynamic).topNameSpace.declsInOrder .dispatch(decl) finally .writeLine('
') .writeLine('
') .writeLine('
') .finishFile def gen(ns as NameSpace) for decl in ns.declsInOrder, .dispatch(decl) # to-do: can merge code in the following three after they fully mature def gen(cl as Class) .writeLine('
class [cl.name]') .writeIsNames(cl) # to-do: inherits, implements, generic constraints .writeDocString(cl.docString) .writeBoxMembers(cl) def gen(inter as Interface) .writeLine('
interface [inter.name]') .writeIsNames(inter) # to-do: inherits, generic constraints .writeDocString(inter.docString) .writeBoxMembers(inter) def gen(struc as Struct) .writeLine('
struct [struc.name]') .writeIsNames(struc) # to-do: implements, generic constraints .writeDocString(struc.docString) .writeBoxMembers(struc) def gen(ext as Extension) .writeLine('
extend [if(ext.extendedBox, ext.extendedBox.name, "(no extended box)")]') .writeIsNames(ext) .writeDocString(ext.docString) .writeBoxMembers(ext) def writeIsNames(box as Box) .writeIsNames(box.isNames) def writeIsNames(isNames as String*) if isNames.toList.count .write('is ') sep = '' for name in isNames .write('[sep][name]') sep = ', ' .writeLine def writeBoxMembers(box as Box) .writeLine .indent didWriteMembers = false typeDecls = .sorted(for decl in box.declsInOrder where decl inherits IType) if typeDecls.count didWriteMembers = true .writeBoxMembers(typeDecls) sharedDecls = .sorted(for decl in box.declsInOrder where decl.isShared and not (decl to Object) inherits IType) if sharedDecls.count if didWriteMembers, .writeLine else, didWriteMembers = true .writeLine('shared') # to-do: write this only if shared decls get written. See class Parser output for an example. .writeLine .indent .writeBoxMembers(sharedDecls) .outdent .writeLine nonsharedDecls = .sorted(for decl in box.declsInOrder where not decl.isShared) if nonsharedDecls.count if didWriteMembers, .writeLine else, didWriteMembers = true .writeBoxMembers(nonsharedDecls) if not didWriteMembers .writeLine('pass') .outdent .write('
') def writeBoxMembers(decls as IList) for i in decls.count decl = decls[i] writeCount = _writeCount .dispatch(decl) if _writeCount > writeCount # if wrote anything ... # Write the doc string if decl.docString and decl.docString.length .indent .writeDocString(decl.docString to !) .outdent else _tw.writeLine(' ') # Put a blank line when switching to a different type of member declaration (say from properties to methods) if i < decls.count-1 and decls[i+1].getType is not decl.getType .writeLine def gen(bc as BoxConst) if bc.isPublic or bc.isProtected if bc.initExpr inherits SharpExpr, init = (bc.initExpr to SharpExpr).sharpSource else if bc.initExpr, init = bc.initExpr.toCobraSource else, init = '(unknown)' .writeLine('const [bc.name] = [init] (as [.typeRef(bc.type)])') # to-do: is names, attributes, etc. def gen(bv as BoxVar) if bv.isPublic or bv.isProtected .writeLine('var [bv.name] as [.typeRef(bv.type)]') # to-do: is names, attributes, etc. def gen(evt as BoxEvent) .writeLine('event [evt.name] as [.typeRef(evt.handlerType)]') # to-do: is names, attributes, etc. def gen(method as AbstractMethod) _gen(method, 'def', method.name) def gen(init as Initializer) _gen(init, 'cue', 'init') def _gen(method as AbstractMethod, keyword as String, name as String) .write('[keyword] [name]') if method.params.count .write('(') sep = '' for param in method.params .write(sep) .dispatch(param) sep = ', ' .write(')') .writeLine def gen(param as Param) if param.isInOut, dir = 'inout ' else if param.isOut, dir = 'out ' else, dir = '' .write('[param.name] as [dir]') try type = param.type to dynamic catch Exception # type = '(Unknown type due to exception: [exc])' type = '(unknown)' success type = .typeRef(type) .write(type.toString) # to-do: styles def gen(prop as Property) if prop.getPart and not prop.setPart .write('get') else if prop.setPart and not prop.getPart .write('set') else .write('pro') .writeLine(' [prop.name] as [.typeRef(prop.resultType)]') def gen(indexer as Indexer) if indexer.getPart and not indexer.setPart .write('get') else if indexer.setPart and not indexer.getPart .write('set') else .write('pro') .write(' \[') sep = '' for param in indexer.params .write(sep) .dispatch(param) sep = ', ' .write(']') .writeLine(' as [.typeRef(indexer.resultType)]') def gen(ed as EnumDecl) .writeLine('
enum [ed.name]') .writeIsNames(ed.isNames) .indent for decl in ed.declsInOrder .writeLine(decl.name) .outdent .write('
') ## Sorting members def sorted(decls as IList) as List """ Return the box members in a logically sorted order. First order is by type of member (enums, vars, constructors, props, indexers, methods). Second order is alphabetical. Third order is parameter count. """ t = List(decls) t.sort(ref .compareMembers) return t def compareMembers(a as IBoxMember, b as IBoxMember) as int is shared if a.getType is b.getType diff = a.name.compareTo(b.name) # note that Cobra disallows member names that differ only by case if diff == 0 if a inherits AbstractMethod diff = a.params.count.compareTo((b to AbstractMethod).params.count) else if a inherits Indexer # b must also be an indexer since their names were the same diff = a.params.count.compareTo((b to Indexer).params.count) else if a.name.startsWith('_') and not b.name.startsWith('_') diff = .compareNamesWithFirstUnderscored(a.name, b.name) else if not a.name.startsWith('_') and b.name.startsWith('_') diff = -1 * .compareNamesWithFirstUnderscored(b.name, a.name) else diff = .rank(a).compareTo(.rank(b)) return diff def compareNamesWithFirstUnderscored(a as String, b as String) as int is shared require a.startsWith('_') not b.startsWith('_') body a = a[1:] if a == b, return 1 # underscored/protected member comes last else, return a.compareTo(b) def rank(obj as IBoxMember) as int is shared if obj inherits EnumDecl, return 0 if obj inherits MethodSig, return 10 if obj inherits Box, return 20 if obj inherits BoxConst, return 25 if obj inherits BoxVar, return 30 if obj inherits Initializer, return 40 if obj inherits BoxEvent, return 45 if obj inherits Property, return 50 if obj inherits Indexer, return 60 if obj inherits Method, return 70 throw FallThroughException(obj)