#!/usr/bin/env python
#
# Time-stamp: <01/03/18 09:08:06 dhellmann>
#
# COPYRIGHT
#
#   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.
#
# DISCLAIMER
#
#   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_multiplefile.py,v $',
    'creator':'Doug Hellmann <doughellmann@bigfoot.com>',
    'project':'HappyDoc',
    'created':'Sun, 26-Mar-2000 11:19:54 EST',
    #
    #  Current Information
    #
    'author':'$Author: doughellmann $',
    'version':'$Revision: 1.31 $',
    'date':'$Date: 2001/03/18 18:22:16 $',
    'locker':'$Locker:  $',
    }

#
# Import system modules
#
import sys
import os
import string
import re
try:
    from cStringIO import StringIO
except:
    from StringIO import StringIO
import token
import symbol
import glob
import parser

#
# Import Local modules
#
import optiontools
import StructuredText
import happydocset
import hdpath

#
# Module
#

def entryPoint():
    "Return info about this module to the dynamic loader."
    return { 'name':'multifile_docset',
             'factory':MultiFileDocSet,
             }

#
# Do not use multiple lines in the class docstring, since it shows up as
# part of the generated help message for the app and multiple lines throws
# off the formatting.
#
class MultiFileDocSet(happydocset.DocSet):
    """Documentation set written to multiple files.

    Parameters

      *Adds no additional parameters not understood by DocSet.*
    
    """

    def getStructuredTextFile(self, filename):
        """Get the contents of a prewritten StructuredText file.
        """
        oneliner = ''
        file_contents = ''
        try:
            file_contents = open(filename, 'rt').read()
        except IOError, msg:
            if hdpath.exists(filename):
                error_level = 0
            else:
                error_level = 2
            self.statusMessage( '  Could not open %s (%s)' % (filename, str(msg)),
                                error_level)
        else:
            st = StructuredText.StructuredText(file_contents)
            oneliner = str(st.structure[0][0])
        return oneliner, file_contents

    def getFullOutputNameForObject(self, infoObject=None):
        "Returns the output destination for documentation about this object."
        return self._formatter.getFullOutputNameForObject(None)

    def write(self):
        "Write the documentation set to the output."
        self.statusMessage('Beginning to write...')
        #
        # Get the name of and open the docset root file
        #
        self._root_name = self._formatter.getFullOutputNameForObject(None)
        self._root_node = self.openOutput( self._root_name, self._title, '' )
        #
        # Get the description for the docset from the
        # file specified.
        #
        description_dir = '.'
        if self._contained_names:
            first_name = self._contained_names[0]
            if hdpath.isdir(first_name):
                description_dir = first_name
            elif hdpath.exists(first_name):
                description_dir = hdpath.dirname(first_name)
        self._writeDescription( description_dir, self._root_node )
        #
        # Write the output
        #
        self._writePackages()
        self._writeModules()
        self._writePrewrittenFiles()
        self._writeTOC()
        self._writeIndex()
        #
        # Close things up
        #
        self.close()
        return

    def _writeDescription(self, directoryName, output):
        """Write the contents of the description file in 'directoryName' to 'output'.
        """
        if self._description_filename and (self._description_filename != '-'):
            description = None
            description_filename = hdpath.join( directoryName,
                                                self._description_filename )
            self.statusMessage('Looking for docset description in %s' % \
                               description_filename,
                               3)
            summary, description = self.getStructuredTextFile( description_filename )
            if description:
                self._formatter.writeText( description, output, 0 )
        return
    
    def _writeTOC(self):
        "Output the TOC."
        self.statusMessage()
        self.statusMessage('Writing table of contents...')
        formatter = self._formatter
        #
        # Open a new section and list
        #
        formatter.comment('BEGIN: TOC', self._root_node)

        #
        # Build directory contents information for modules
        # and packages
        #
        subnodes_by_dir = {}
        for all_set in ( self._all_modules,
                         self._all_packages,
                         ):
            items = all_set.items()
            subnode_ids = map(lambda x, f=formatter: (hdpath.dirname(x[1].getFileName()),
                                                      x[1].getFileName(),
                                                      x[0],
                                                      ),
                              items)
            for dir_name, file_name, subnode_name in subnode_ids:
                l = subnodes_by_dir.get(dir_name, [])
                l.append( (file_name, subnode_name, all_set) )
                subnodes_by_dir[dir_name] = l
        
        #
        # Write Module references to TOC
        #
        dirs = subnodes_by_dir.keys()
        dirs.sort()
        if dirs:
            formatter.comment('BEGIN: TOC->Modules', self._root_node)
            formatter.pushSectionLevel(self._root_node)
            formatter.sectionHeader(self._root_node, 'Modules and Packages')

        for dir_name in dirs:
            relative_dir_name = hdpath.removeRelativePrefix(dir_name)
            #
            # Open the section for this directory
            #
            subnode_set = subnodes_by_dir[dir_name]
            extra_files = self._directories_with_extra_docs.get(relative_dir_name, [])
            if dir_name and (dir_name[-1] != '/'):
                dir_name = dir_name + '/'
            formatter.descriptiveListHeader( self._root_node,
                                             dir_name)
            #
            # Write the list of extra files
            # which are in the directory being processed
            #
            for extra, summary in extra_files:
                #
                # Get the full name, including any fixup required
                # by the formatter.
                #
                full_extra_name = hdpath.join(dir_name, extra)
                full_extra_name = formatter.fixUpOutputFilename(full_extra_name)
                #
                # Add reference to the TOC list
                #
                ref = formatter.getReference( full_extra_name,
                                              self._root_name,
                                              extra )
                self.statusMessage('\tAdding reference to %s to TOC' % extra, 2)
                self.statusMessage('\tref="%s"' % ref, 3)
                formatter.descriptiveListItem(
                    self._root_node,
                    ref,
                    summary)
            #
            # Write the list of modules
            # which are in the directory being processed
            #
            subnode_set.sort()
            for file_name, subnode_name, subnode_dict in subnode_set:
                subnode = subnode_dict[subnode_name]
                self.statusMessage('\tAdding %s to TOC' % subnode_name, 2)
                #
                # Build a reference to the documentation for the subnode
                # based on the type of referencing supported.
                #
                if hasattr( subnode, 'getFullOutputNameForObject' ):
                    ref = formatter.getReference(
                        subnode.getFullOutputNameForObject(),
                        self._root_name,
                        subnode_name )
                else:
                    ref = formatter.getReference(subnode,
                                                 self._root_name,
                                                 )
                formatter.descriptiveListItem(
                    self._root_node,
                    ref,
                    subnode.getSummary()
                    )
            formatter.descriptiveListFooter(self._root_node)
        #
        # Close the Modules section
        #
        if dirs:
            formatter.sectionFooter(self._root_node)
            formatter.popSectionLevel(self._root_node)
            formatter.comment('END: TOC->Modules', self._root_node)

        #
        # Close TOC section
        #
        formatter.comment('END: TOC', self._root_node)
        return

    def _writeIndex(self):
        "Output the index."
        self.statusMessage()
        self.statusMessage('IMPLEMENT Writing index...')
        return

    def _writePrewrittenFiles(self):
        """Convert the format of the discovered pre-written files.

        Convert the format of the discovered pre-written files.
        and write out new files as part of the docset.
        """
        directories_with_extra_docs = {}
        formatter = self._formatter
        for filename in self._prewritten_files:
            summary, body = self.getStructuredTextFile(filename)
            if not body:
                continue
            #
            # Convert the file using the formatter.
            #
            output_filename = formatter.getFullOutputNameForFile(
                filename, usePrefix=1 )
            short_output_filename = output_filename[len(self.getDocsetBaseDirectory())+1:]
            self.statusMessage('\tRewriting %s\n\t       to %s' \
                               % (filename, short_output_filename),
                               2)
            output = self.openOutput( output_filename, self._title, '' )
            summary, body = self.getStructuredTextFile( filename )
            formatter.writeText( body, output )
            self.closeOutput( output )
            #
            # Generate a reference to the file in the TOC
            #
            relative_output_name = formatter.getOutputNameForFile( filename )
            dir, base = hdpath.split(relative_output_name)
            file_list = directories_with_extra_docs.get( dir, [] )
            file_list.append( (base, summary) )
            directories_with_extra_docs[dir] = file_list
        self._directories_with_extra_docs = directories_with_extra_docs
        return

    def _writePackages(self):
        "Output documentation for all packages."
        self.statusMessage()
        self.statusMessage('Writing package documentation...')
        package_items = self._all_packages.items()
        package_items.sort()
        for package_name, package in package_items:
            package.write()
        return
    
    def _writeModules(self):
        "Output documentation for all modules."
        self.statusMessage()
        self.statusMessage('Writing module documentation...')
        module_names = self._all_modules.keys()
        module_names.sort()
        for module_name in module_names:
            self._writeModule( module_name )
        return

    def _describeClassInModuleNode(self, output, class_output_file_name, class_info):
        ref = self._formatter.getReference(class_info, class_output_file_name)
        self._formatter.descriptiveListItem(output, ref, class_info.getSummary())
        return
    
    def _writeModule(self, module_name):
        "Output the documentation for the module named."
        module = self._all_modules[module_name]
        output_name = self._formatter.getFullOutputNameForObject(module)
        output = self.openOutput(output_name,
                                 'Module: %s' % module_name,
                                 module.getFileName())
        formatter = self._formatter
        formatter.comment('BEGIN: Module %s' % module_name, output)
        #
        # Write the doc string
        #
        formatter.writeText( module.getDocString(), output )
        #
        # Start the indented section
        #
        formatter.pushSectionLevel(output)
        #
        # Get some pre-formatted text we're going to reuse a lot
        #
        from_keyword = formatter.formatKeyword('from')
        import_keyword = formatter.formatKeyword('import')
        #
        # List the dependant modules
        #
        imported_modules = module.getImportData()
        if imported_modules:
            formatter.sectionHeader(output, 'Imported modules')
            formatter.listHeader(output, None, allowMultiColumn=0)
            for name, symbols in imported_modules:
                if self._all_modules.has_key(name):
                    i_module = self._all_modules[name]
                    ref = formatter.getReference(
                        i_module,
                        output_name
                        )
                    #print 'MODULE REFERENCE: '
                    #print '  FROM:', output.name
                    #print '    TO:', ref
                else:
                    ref = formatter.getPythonReference( name )
                    i_module = None
                if symbols:
                    if i_module:
                        #
                        # Process the list of imported names and
                        # generate references to the ones which are
                        # recognized by our parser.
                        #
                        import_list = []
                        for i_name in symbols:
                            try:
                                i_info = i_module.getClassInfo(i_name)
                                info_source = 'class'
                            except KeyError:
                                try:
                                    i_info = i_module.getFunctionInfo(i_name)
                                    info_source = 'function'
                                except KeyError:
                                    # do not have this name?
                                    import_list.append(i_name)
                                    continue

                            if info_source == 'class':
                                #
                                # Get a reference to the file documenting the
                                # class.
                                #
                                i_ref = formatter.getReference(
                                    i_info,
                                    output.name,
                                    )
                                import_list.append(i_ref)
                                
                            elif info_source == 'function':
                                #
                                # Get a reference to the file documenting the
                                # module containing the function, and include
                                # the reference *into* the file to get right at
                                # the function.
                                #
                                i_ref = formatter.getNamedReference(
                                    i_module,
                                    i_name,
                                    output.name,
                                    )
                                import_list.append(i_ref)
                                
                            else:
                                #
                                # We do not know how to convert the name to a
                                # meaningful reference, so just put the name
                                # in the list to be output.
                                #
                                #print 'UNKNOWN TYPE: ', i_name
                                import_list.append(i_name)
                                
                    else:
                        import_list = symbols
                        
                    formatter.listItem( output,
                                        '%s %s %s %s' % \
                                        ( from_keyword,
                                          ref,
                                          import_keyword,
                                          string.join(import_list, ', ')
                                          )
                                        )
                    
                else:
                    formatter.listItem( output,
                                        '%s %s' % (import_keyword, ref)
                                        )
                    
            formatter.listFooter(output)
            formatter.sectionFooter(output)
        #
        # Write the info for the functions in this module
        #
        function_names = self._filterNames(module.getFunctionNames())
        if function_names:
            formatter.comment('BEGIN: Functions of module %s' % module_name, output)
            formatter.sectionHeader( output, 'Functions' )
            function_names.sort()
            #
            # TOC list
            #
            formatter.listHeader( output )
            for function_name in function_names:
                formatter.listItem(
                    output,
                    formatter.getInternalReference(
                    module.getFunctionInfo(function_name)
                    )
                    )
            formatter.listFooter( output )
            #
            # Function descriptions
            #
            for function_name in function_names:
                self._writeFunction(function_name,
                                    module.getFunctionInfo,
                                    output)
            formatter.sectionFooter(output)
            formatter.comment('END: Functions of module %s' % module_name, output)
        #
        # Write the info for the classes in this module
        #
        class_names = self._filterNames(module.getClassNames())
        if class_names:
            formatter.comment('BEGIN: Classes of module %s' % module_name, output)
            formatter.sectionHeader( output, 'Classes' )
            formatter.descriptiveListHeader( output, None )
            class_names.sort()
            for class_name in class_names:
                c = module.getClassInfo(class_name)
                class_output_name = formatter.getFullOutputNameForObject(c)
                self._describeClassInModuleNode(output, class_output_name , c)
                class_output = self.openOutput(class_output_name,
                                               'Class: %s' % class_name,
                                               module.getFileName())
                self._writeClass( module, class_name, class_output )
                self.closeOutput(class_output)
            formatter.descriptiveListFooter(output)
            formatter.sectionFooter( output )
            formatter.comment('END: Classes of module %s' % module_name, output)
        #
        # Finish that indented level.
        #
        formatter.sectionFooter(output)
        formatter.popSectionLevel(output)
        formatter.comment('END: Module %s' % module_name, output)
        #
        # Close the output file
        #
        self.closeOutput(output)
        return


    
    def _writeBaseclassNames(self, parent, classInfo, output, indent=0):
        "Output the base class hierarchy for the given class."
        base_classes = classInfo.getBaseClassNames()
        formatter = self._formatter
        if base_classes:
            if indent: formatter.indent(output)
            formatter.listHeader(output, None, allowMultiColumn=0) 
            for name in base_classes:
                try:
                    child = parent.getClassInfo(name)
                except KeyError:
                    formatter.listItem( output, name )
                else:
                    formatter.listItem(
                        output,
                        formatter.getReference(child,
                                               output.name,
                                               )
                        )
                    if name != classInfo.getName():
                        self._writeBaseclassNames(parent, child, output, 1)
            formatter.listFooter(output)
            if indent: formatter.dedent(output)
        return

    def _writeClass(self, parent, class_name, output):
        "Output the documentation for the class in the parent object."
        class_info = parent.getClassInfo(class_name)
        formatter = self._formatter
        formatter.comment('BEGIN: Class %s' % class_name, output)
        formatter.writeText( class_info.getDocString(), output)
        #
        # Base class hierarchy
        #
        base_classes = class_info.getBaseClassNames()
        if base_classes:
            formatter.pushSectionLevel(output)
            formatter.sectionHeader(output, 'Base Classes')
            self._writeBaseclassNames(parent, class_info, output)
            formatter.sectionFooter(output)
            formatter.popSectionLevel(output)
        #
        # Start the indented section
        #
        formatter.pushSectionLevel(output)
        #
        # Write the info for the methods of this class
        #
        method_names = self._filterNames(class_info.getMethodNames())
        if method_names:
            formatter.sectionHeader( output, 'Methods' )
            method_names.sort()
            #
            # TOC list
            #
            formatter.listHeader( output )
            for method_name in method_names:
                formatter.listItem(
                    output,
                    formatter.getInternalReference(
                    class_info.getMethodInfo(method_name)
                    )
                    )
            formatter.listFooter( output )
            for method_name in method_names:
                self._writeFunction(method_name,
                                    class_info.getMethodInfo,
                                    output)
            formatter.sectionFooter(output)
        #
        # Finish that indented level.
        #
        formatter.sectionFooter(output)
        formatter.popSectionLevel(output)
        formatter.comment('END: Class %s' % class_name, output)
        return

    def _writeFunctionParameter(self, name, info, output):
        '''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
             
        '''
        formatter = self._formatter
        formatter.writeRaw(name, output)
        default_specified, default_value, default_value_type = info
        if default_specified:
            formatter.writeRaw('=', output)
            if default_value_type == token.STRING:
                formatter.writeRaw(`default_value`, output)
            elif default_value_type == token.NUMBER:
                formatter.writeRaw(str(default_value), output)
            else:
                #print 'FUNCTION DEFAULT VALUE (%s, %s): "%s"' % (
                #    type(default_value),
                #    default_value_type or 'Unknown',
                #    default_value)
                formatter.writeRaw(str(default_value), output)
        return

    def _writeFunctionSignature(self, function, output):
        """Write the function signature for 'function' to 'output'.

        Parameters

          function -- Instance of FunctionInfo from parseinfo module.

          output -- Where to write.
          
        """
        formatter = self._formatter
        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(param, param_info,
                                                 signature_buffer)
                    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(param, param_info,
                                                 signature_buffer)
                    signature_buffer.write(',\n')
                signature_buffer.write('%s' % (' ' * indent))
        signature_buffer.write(')\n')
        formatter.writeCode(signature_buffer.getvalue(), output)
        return


    def _writeExceptionListForFunction(self, output, function, listHeader):
        """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.

        """
        formatter = self._formatter
        formatter.pushSectionLevel(output)
        formatter.sectionHeader(output, 'Exceptions')
        formatter.listHeader(output, listHeader)
        exception_names = function.getExceptionNames()
        exception_names.sort()
        #output_reduced_name = output.name[len(self.getDocsetBaseDirectory())+1:]
        output_buffer = StringIO()
        for name in exception_names:
            exception_class = self.getClassInfo(name)
            if exception_class:
                ref = formatter.getReference( exception_class,
                                              #output_reduced_name,
                                              output.name,
                                              )
            else:
                ref = formatter.getPythonReference( name )
            #output_buffer.write('%s\n' % ref)
            formatter.listItem(output, ref)
        #formatter.writeCode(output_buffer.getvalue(), output)
        formatter.listFooter(output)
        formatter.sectionFooter(output)
        formatter.popSectionLevel(output)
        return

    def _writeFunction(self, function_name, getInfo, output):
        "Output the documentation for the function in the parent object."
        function = getInfo(function_name)
        #
        # Header
        #
        self._formatter.itemHeader( output, function )
        #
        # Function signature
        #
        self._writeFunctionSignature( function, output )
        #
        # Docstring
        #
        self._formatter.writeText( function.getDocString(), output )
        #
        # Exceptions
        #
        exception_names = function.getExceptionNames()
        if exception_names:
            self._writeExceptionListForFunction(output, function, None)
        return
