#!/usr/bin/env python # # $Id: docset_MultiHTMLFile.py,v 1.17 2003/03/16 20:18:24 doughellmann Exp $ # # Copyright 2002 Doug Hellmann. # # # All Rights Reserved # # Permission to use, copy, modify, and distribute this software and # its documentation for any purpose and without fee is hereby # granted, provided that the above copyright notice appear in all # copies and that both that copyright notice and this permission # notice appear in supporting documentation, and that the name of Doug # Hellmann not be used in advertising or publicity pertaining to # distribution of the software without specific, written prior # permission. # # DOUG HELLMANN DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, # INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN # NO EVENT SHALL DOUG HELLMANN BE LIABLE FOR ANY SPECIAL, INDIRECT OR # CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS # OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN # CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # """Documentation set which writes output to multiple files. """ __rcs_info__ = { # # Creation Information # 'module_name' : '$RCSfile: docset_MultiHTMLFile.py,v $', 'rcs_id' : '$Id: docset_MultiHTMLFile.py,v 1.17 2003/03/16 20:18:24 doughellmann Exp $', 'creator' : 'Doug Hellmann ', 'project' : 'HappyDoc', 'created' : 'Sun, 17-Nov-2002 13:32:00 EST', # # Current Information # 'author' : '$Author: doughellmann $', 'version' : '$Revision: 1.17 $', 'date' : '$Date: 2003/03/16 20:18:24 $', } try: __version__ = __rcs_info__['version'].split(' ')[1] except: __version__ = '0.0' # # Import system modules # import os import pprint try: from cStringIO import StringIO except: from StringIO import StringIO import time import token # # Import Local modules # import happydoclib from happydoclib.docset import base from happydoclib.sysmodules import getPythonSystemModuleURL from happydoclib.trace import trace # # Module # TRACE_LEVEL=2 def entryPoint(): "Return info about this module to the dynamic loader." return { 'name':'MultiHTMLFile', 'factory':MultiHTMLFileDocSet, } class MultiHTMLFileDocSet(base.MultiFileDocSet): """Documentation set written to multiple HTML files. """ pageBackgroundColor='#ffffff' levelOneHeadingBackgroundColor='#88bbee' levelOneHeadingForegroundColor='#000000' levelTwoHeadingBackgroundColor='#99ccff' levelTwoHeadingForegroundColor='#000000' codeForegroundColor='#000088' def getOutputFilenameForPackageTreeNode(self, packageTreeNode, includePath=1): """Returns a filename where documentation for packageTreeNode should be written. The filename will be in the output directory, possibly in a subdirectory based on the path from the input root to the input file. For example:: input_directory : /foo/input containing : /foo/input/bar.py output_directory : /foo/output results in : /foo/output/input/bar.py """ trace.into('MultiHTMLFileDocSet', 'getOutputFilenameForPackageTreeNode', outputLevel=TRACE_LEVEL) filename = base.MultiFileDocSet.getOutputFilenameForPackageTreeNode( self, packageTreeNode, includePath=includePath, ) if packageTreeNode.getMimeType() == ('application/x-directory', None): # # This is a directory. # filename_with_extension = os.path.join(filename, 'index.html') else: # # This is not a directory (file, module, class, etc.). # filename_with_extension = '%s.html' % filename trace.outof(filename_with_extension, outputLevel=TRACE_LEVEL) return filename_with_extension def getOutputFilenameForSymbol(self, packageTreeNode, symbolName, includePath=1): """Returns a filename where documentation for symbolName should be written. The filename will be in the output directory, possibly in a subdirectory based on the path from the input root to the input file. """ package_output_name = self.getOutputFilenameForPackageTreeNode( packageTreeNode, includePath=includePath, ) name, ext = os.path.splitext(package_output_name) output_name = '%s_%s.html' % (name, symbolName) return output_name def _writeBreadcrumbs(self, output, sourceNode, breadcrumbNode): """Write breadcrumb links from the root down to packageTreeNode. This method actually handles the recursion. """ trace.into('MultiHTMLFile', '_writeBreadcrumbs', sourceNode=sourceNode.getName(), breadcrumbNode=(breadcrumbNode and breadcrumbNode.getName()), outputLevel=TRACE_LEVEL, ) if not breadcrumbNode: trace.outof(outputLevel=TRACE_LEVEL) return # # Write the preceding part of the breadcrumbs # self._writeBreadcrumbs(output, sourceNode, breadcrumbNode.getParent()) # # Write our breadcrumb # if breadcrumbNode.getName() != '__init__.py': if sourceNode == breadcrumbNode: ref = breadcrumbNode.getName() else: ref = self._getAnchorTagForPackageTreeNode(sourceNode, breadcrumbNode, ) output.write('/ %s '% ref) trace.write('/ %s '% ref, outputLevel=TRACE_LEVEL) #output.write('\n') else: trace.write('Skipping __init__.py', outputLevel=TRACE_LEVEL) trace.outof(outputLevel=TRACE_LEVEL) return def writeBreadcrumbs(self, output, packageTreeNode): """Write breadcrumb links from the root down to packageTreeNode. """ # # Begin breadcrumbs. # output.write('\n') output.write('

\n') self._writeBreadcrumbs(output, packageTreeNode, packageTreeNode) output.write('

\n') output.write('\n\n') return def writeFileHeader(self, output, packageTreeNode, title='', subtitle=''): """Given an open output stream, write a header using the title and subtitle. """ title_bg = self.levelOneHeadingBackgroundColor title_fg = self.levelOneHeadingForegroundColor bgcolor = self.pageBackgroundColor root = 'need root URL' # # HTML header and Body tag. # output.write(''' %(title)s ''' % locals()) # # Outline table # output.write('''
%(title)s %(subtitle)s
''' % locals()) # # Breadcrumbs # if packageTreeNode.getParent() is not None: self.writeBreadcrumbs(output, packageTreeNode) return def writeFileFooter(self, output): """Given an open output stream, write a footer using the title and subtitle. """ date_str = time.ctime(time.time()) app_version = happydoclib.cvsProductVersion() output.write('''

This document was automatically generated %(date_str)s by HappyDoc version %(app_version)s ''' % locals()) return def writeDescriptiveList(self, output, descriptiveList): """Write a list including descriptions. Arguments output -- Open output stream. descriptiveList -- Sequence of (name, description, description_format) values to be included in the output list. """ output.write('\n') output.write('\n') for name, text, text_format in descriptiveList: output.write(''' ''') output.write('

%(name)s

''' % locals()) self.writeText(output, text, text_format) output.write('''
') return def writeList(self, output, listElements): """Write a formatted list of values to the output. """ output.write('

\n') for list_element in listElements: output.write('%s
\n' % list_element) output.write('

\n') return def _getAnchorTagForPackageTreeNode(self, source, destination, title=None, internalTarget=None): """Return a anchor tag to be included in the documentation of source that points to the destination. """ if title is None: title = destination.getName() href = self._computeRelativeHREF(source, destination) if internalTarget: href='%s#%s' % (href, internalTarget) return '%s' % (href, title) def writeSectionTitle(self, output, title, subtitle, anchor=None): """Generate the text and styles to begin a new section. """ bgcolor = self.levelTwoHeadingBackgroundColor fgcolor = self.levelTwoHeadingForegroundColor output.write(''' %(title)s  %(subtitle)s  ''' % locals()) #output.write('%s\n' % (level, title, level)) return def pushSectionLevel(self, output, title, subtitle='', anchor=''): """Increase the section level. Generate the text and styles to begin a new section one deeper than the previous level. """ output.write( '\n' ) self.writeSectionTitle(output, title=title, subtitle=subtitle, anchor=anchor, ) return def writeSectionFooter(self, output): """Generate the text and styles to close the current section. """ output.write('\n') return def popSectionLevel(self, output): """Decrease the section level. Generate the text and styles to close the current section and change the section level to the next higher level. """ self.writeSectionFooter(output) output.write('
') return def writeTOCReferencesSection(self, output, packageTreeNode, title, moduleList, ): """Write a list of references in the table of contents. Arguments output -- Open output stream on which to write. packageTreeNode -- The node for which the section is being written. title -- Title of the reference section. moduleList -- A list of nodes to be included in the reference section. """ descriptive_list = [(self._getAnchorTagForPackageTreeNode(packageTreeNode, node), ) + node.getSummaryAndFormat() for node in moduleList ] if descriptive_list: self.pushSectionLevel(output, title) descriptive_list.sort() self.writeDescriptiveList(output, descriptive_list) if descriptive_list: self.popSectionLevel(output) return def writeTOCFile(self, packageTreeNode): """Write the table of contents for a directory. The packageTreeNode is a directory, and the table of contents for that directory should be written as appropriate. """ trace.into('MultiHTMLFile', 'writeTOCFile', packageTreeNode=packageTreeNode, outputLevel=TRACE_LEVEL, ) output_filename = self.getOutputFilenameForPackageTreeNode(packageTreeNode) output = self.openOutput(output_filename, packageTreeNode, title=self.title, subtitle=packageTreeNode.getRelativeFilename(), ) # # Description # readme_text, text_format = packageTreeNode.getDocStringAndFormat() self.writeText(output, readme_text, text_format) # # Content from __init__.py # try: init_node = packageTreeNode['__init__.py'] except KeyError: pass else: self.writePythonFileInfoToOutput(output, init_node) # # Write out references to text files we would have # converted. # text_files = packageTreeNode.getSubNodes(['text/plain', 'text/x-structured', 'text/html', ]) self.writeTOCReferencesSection( output, packageTreeNode, 'Other documentation', text_files, ) # # Write out references to Python files we parsed. # self.writeTOCReferencesSection( output, packageTreeNode, 'Python files', [ node for node in packageTreeNode.getSubNodes(['text/x-python']) if node.getName() != '__init__.py' ], ) # # Write out references to subdirectories # directories = packageTreeNode.getSubNodes(['application/x-directory']) #directories = [ d for d in directories # if d[1].items() # ] self.writeTOCReferencesSection( output, packageTreeNode, 'Subdirectories', directories, ) self.closeOutput(output) trace.outof(outputLevel=TRACE_LEVEL) return def writeImportWithFrom(self, output, pacakgeTreeNode, moduleReference, symbolReferences): """Write an import statement: 'from X import Y' """ output.write('from %s import %s
' % (moduleReference, ', '.join(symbolReferences), ) ) return def writeImport(self, output, packageTreeNode, moduleReference): """Write an import statement: 'import X' """ output.write('import %s
' % moduleReference) return def writeImportForPythonSystemModule(self, output, packageTreeNode, name, symbols, url, ): """Write an import statement for a Python system module. Handles either 'from X import Y' or 'import X'. The module name is a link to the Python documentation on http://www.python.org. """ ref = '%s' % (url, name) if symbols: self.writeImportWithFrom(output, packageTreeNode, ref, symbols, ) else: self.writeImport(output, packageTreeNode, ref, ) return def writeImportForKnownModule(self, output, packageTreeNode, name, referencedModule, symbols, ): """Write an import statement for a module known to HappyDoc. Handles either 'from X import Y' or 'import X'. The module name is a link to the documentation for that module within the HappyDoc-generated output. """ # # Compute the href from here to there. # ref = self._getAnchorTagForPackageTreeNode( packageTreeNode, referencedModule, title=name, ) if symbols: symbol_refs = [] for symbol in symbols: symbol_module = referencedModule.findNodeFromDottedName(symbol) if symbol_module is not None: symbol_ref = self._getAnchorTagForPackageTreeNode( packageTreeNode, symbol_module, title=symbol, ) else: symbol_ref = symbol symbol_refs.append( symbol_ref ) self.writeImportWithFrom(output, packageTreeNode, ref, symbol_refs, ) else: self.writeImport(output, packageTreeNode, ref, ) return def writePythonFileImportsToOutput(self, output, packageTreeNode): """Writes the list of imported modules for the packageTreeNode. """ import_data = packageTreeNode.module_info.getImportData() if import_data: self.pushSectionLevel(output, 'Imported Modules') output.write('

\n') for name, symbols in import_data: ref = None # # Check if the name is a Python system module. # url = getPythonSystemModuleURL(name) if url: self.writeImportForPythonSystemModule(output, packageTreeNode, name, symbols, url, ) continue # # Check to see if the name is another module we know about. # referenced_module = packageTreeNode.findNodeFromDottedName(name) if referenced_module is not None: self.writeImportForKnownModule(output, packageTreeNode, name, referenced_module, symbols, ) continue # # Default to the module name for the reference. # if symbols: self.writeImportWithFrom(output, packageTreeNode, name, symbols, ) else: self.writeImport(output, packageTreeNode, name, ) output.write('

\n') self.popSectionLevel(output) return def writePreformatted(self, output, text): """Write text as a preformatted section. """ output.write('
\n')
        output.write(text)
        if text and text[-1] != '\n':
            output.write('\n')
        output.write('
\n') return def writeFunctionParameter(self, output, name, info): '''Write a function parameter to the output. No indenting or formatting is performed. The output looks like:: name or name=default Parameters: name -- name of the parameter info -- tuple of (default_specified, default_value, default_value_type) concerning the default value of the parameter output -- destination for written output ''' output.write(name) default_specified, default_value, default_value_type = info if default_specified: output.write('=') if default_value_type == token.STRING: output.write(`default_value`) elif default_value_type == token.NUMBER: output.write(str(default_value)) else: #print 'FUNCTION DEFAULT VALUE (%s, %s): "%s"' % ( # type(default_value), # default_value_type or 'Unknown', # default_value) output.write(str(default_value)) return def writeFunctionSignature(self, output, packageTreeNode, function, ): """Write the function signature for 'function' to 'output'. Parameters output -- Where to write. pacakgeTreeNode -- The part of the input we are processing. function -- Instance of FunctionInfo from parseinfo module. """ function_name = function.getName() signature_buffer = StringIO() signature_buffer.write('%s (' % function_name) parameter_names = function.getParameterNames() if parameter_names: if len(parameter_names) <= 2: for param in parameter_names: param_info = function.getParameterInfo(param) signature_buffer.write(' ') self.writeFunctionParameter(signature_buffer, param, param_info, ) if param != parameter_names[-1]: signature_buffer.write(',') signature_buffer.write(' ') else: signature_buffer.write('\n') indent = 8 #len(name) + 3 for param in parameter_names: signature_buffer.write(' ' * indent) param_info = function.getParameterInfo(param) self.writeFunctionParameter(signature_buffer, param, param_info, ) signature_buffer.write(',\n') signature_buffer.write('%s' % (' ' * indent)) signature_buffer.write(')\n') self.writePreformatted(output, signature_buffer.getvalue()) return def writeExceptionListForFunction(self, output, function): """Write the list of exceptions raised by a function. Parameters output -- Where to write. function -- FunctionInfo from parseinfo module. listHeader -- Header for list being generated. """ exception_names = function.getExceptionNames() if not exception_names: return if self.sort_names: exception_names.sort() exception_list = [] for name in exception_names: #exception_class = self.getClassInfo(name) exception_class = None if exception_class: # FIXME - Need a way to get a reference to a class in the # scanned input! ref = formatter.getReference( exception_class, #output_reduced_name, output.name, ) else: #ref = formatter.getPythonReference( name ) ref = name exception_list.append(ref) self.pushSectionLevel(output, 'Exceptions') self.writeList(output, exception_list) self.popSectionLevel(output) return def writeOneFunctionToOutput(self, output, packageTreeNode, functionInfo, ): """Write all of the information for one function to the output stream. """ self.pushSectionLevel(output, title='', subtitle=functionInfo.getName(), anchor=functionInfo.getName(), ) # # Signature # self.writeFunctionSignature(output, packageTreeNode, functionInfo) # # Description # docstring_text = functionInfo.getDocString() docstring_format = functionInfo.getDocStringFormat() self.writeText(output, docstring_text, docstring_format) # # Exceptions # self.writeExceptionListForFunction(output, functionInfo) self.popSectionLevel(output) return def writeFunctionsToOutput(self, output, packageTreeNode): """Writes information about functions in this module to the output stream. """ function_names = self._filterNames(packageTreeNode.module_info.getFunctionNames()) if not function_names: return if self.sort_names: function_names.sort() # # Section header # self.pushSectionLevel(output, 'Functions') # # Functions # for function_name in function_names: self.writeOneFunctionToOutput( output, packageTreeNode, packageTreeNode.module_info.getFunctionInfo(function_name), ) self.popSectionLevel(output) return def _getBaseClassTree(self, linkSource, moduleTreeNode, classTreeNode, className): trace.into('MultiHTMLFile', '_getBaseClassTree', linkSource=linkSource.getName(), moduleTreeNode=moduleTreeNode.getName(), classTreeNode=(classTreeNode and classTreeNode.getName()), the_className=className, outputLevel=TRACE_LEVEL, ) # # Find the list of base classes of the current class # if classTreeNode is None: base_class_names = [] else: try: class_info = classTreeNode.code_info except AttributeError: base_class_names = [] else: base_class_names = self._filterNames(class_info.getBaseClassNames()) # # Build the subtrees from our base classes # base_class_trees = [] for base_class_name in base_class_names: base_class_node = moduleTreeNode.findNodeFromDottedName( base_class_name, ) base_class_tree = self._getBaseClassTree( linkSource, moduleTreeNode, base_class_node, base_class_name, ) base_class_trees.append( base_class_tree ) # # Set up the reference for this node # if classTreeNode: ref = self._getAnchorTagForPackageTreeNode( source=linkSource, destination=classTreeNode, title=className, ) else: ref = className trace.outof(outputLevel=TRACE_LEVEL) return (ref, base_class_trees) def writeTree(self, output, treeRoot, indent=0): output.write('%s%s
' % ('  ' * indent, treeRoot[0])) for subtree in treeRoot[1]: self.writeTree(output, subtree, indent+1) return def writeBaseClassNames(self, output, packageTreeNode, classInfo): base_class_tree = self._getBaseClassTree( linkSource=packageTreeNode, moduleTreeNode=packageTreeNode.getParent(), classTreeNode=packageTreeNode, className=classInfo.getName(), ) #self.writeList(output, base_class_names) output.write('

\n') self.writeTree(output, base_class_tree) output.write('

\n') return def writeOneClassToOutput(self, output, packageTreeNode): """Writes information about one class to the output stream. """ class_info = packageTreeNode.code_info # # Description # docstring_text = class_info.getDocString() docstring_format = class_info.getDocStringFormat() self.writeText(output, docstring_text, docstring_format) # # Base classes # base_class_names = self._filterNames(class_info.getBaseClassNames()) if base_class_names: self.pushSectionLevel(output, 'Base Classes') self.writeBaseClassNames(output, packageTreeNode, class_info) self.popSectionLevel(output) # # Methods # method_names = self._filterNames(class_info.getMethodNames()) if method_names: if self.sort_names: method_names.sort() self.pushSectionLevel(output, 'Methods') for method_name in method_names: method_info = class_info.getMethodInfo(method_name) self.writeOneFunctionToOutput( output, packageTreeNode, method_info, ) self.popSectionLevel(output) return def writeClassListForModule(self, output, packageTreeNode): """Write descriptions of all of the classes to the output stream. """ #class_names = self._filterNames(packageTreeNode.module_info.getClassNames()) classes = packageTreeNode.getSubNodes(['application/x-class']) class_map = {} for c in classes: class_map[c.getName()] = c class_names = self._filterNames(class_map.keys()) if self.sort_names: class_names.sort() descriptive_list = [] for class_name in class_names: #symbol_output_name = self.getOutputFilenameForSymbol( # packageTreeNode, # class_name, # includePath=0, # ) class_node = class_map[class_name] #symbol_output_name = self.getOutputFilenameForPackageTreeNode( # class_node, # ) #ref = '%s' % (symbol_output_name, class_name) ref = self._getAnchorTagForPackageTreeNode( source=packageTreeNode, destination=class_node, title=class_name, ) class_info = packageTreeNode.module_info.getClassInfo(class_name) class_info_summary, class_info_format = class_info.getSummaryAndFormat() descriptive_list.append( (ref, class_info_summary, class_info_format) ) self.pushSectionLevel(output, 'Classes') self.writeDescriptiveList(output, descriptive_list) self.popSectionLevel(output) return def writePythonFileInfoToOutput(self, output, packageTreeNode): """Writes parts of the Python file information to the output stream. """ self.writePythonFileImportsToOutput(output, packageTreeNode) self.writeFunctionsToOutput(output, packageTreeNode) #self.writeClassesToOutput(output, packageTreeNode) # # Write a list of the classes to the current output file # self.writeClassListForModule(output, packageTreeNode) return def processPythonFile(self, packageTreeNode): """Handler for text/x-python nodes. """ trace.into('MultiHTMLFileDocSet', 'processPythonFile', packageTreeNode=packageTreeNode, outputLevel=TRACE_LEVEL, ) node_name = packageTreeNode.getName() if node_name == '__init__.py': # # Skip the __init__.py file, since it will # be handled as part of the package. # trace.write('skipping __init__.py', outputLevel=TRACE_LEVEL) trace.outof(outputLevel=TRACE_LEVEL) return canonical_path = packageTreeNode.getPath(1) canonical_filename = apply(os.path.join, canonical_path) output_filename = self.getOutputFilenameForPackageTreeNode(packageTreeNode) self.statusMessage('Documenting: "%s"\n to: "%s"' % ( canonical_filename, output_filename, )) output = self.openOutput(output_filename, packageTreeNode, title=self.title, subtitle=packageTreeNode.getRelativeFilename(), ) # # Summary/module docstring # readme_text = packageTreeNode.module_info.getDocString() readme_text = self._unquoteString(readme_text) text_format = packageTreeNode.module_info.getDocStringFormat() self.writeText(output, readme_text, text_format) self.writePythonFileInfoToOutput(output, packageTreeNode) self.closeOutput(output) trace.outof(outputLevel=TRACE_LEVEL) return def processPythonClass(self, packageTreeNode): """Writes information about classes in this module to the output stream. """ #print 'Processing class: %s' % packageTreeNode.getName() # # Open a new output stream for the class. # #class_output_name = self.getOutputFilenameForSymbol( # packageTreeNode, # class_name, # includePath=1, # ) class_output_name = self.getOutputFilenameForPackageTreeNode( packageTreeNode, ) #print ' output file:', class_output_name class_output = self.openOutput( class_output_name, packageTreeNode, title=self.title, subtitle='Class: %s' % packageTreeNode.getName(), ) # # Write class documentation # self.writeOneClassToOutput( class_output, packageTreeNode, ) # # Close the class' output stream # self.closeOutput(class_output) return