base.py :  » Development » HappyDoc » HappyDoc3-r3_1 » happydoclib » docset » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Development » HappyDoc 
HappyDoc » HappyDoc3 r3_1 » happydoclib » docset » base.py
#!/usr/bin/env python
#
# $Id: base.py,v 1.20 2006/12/05 13:10:45 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.
#

"""Base classes for documentation sets.

"""

__rcs_info__ = {
    #
    #  Creation Information
    #
    'module_name'  : '$RCSfile: base.py,v $',
    'rcs_id'       : '$Id: base.py,v 1.20 2006/12/05 13:10:45 doughellmann Exp $',
    'creator'      : 'Doug Hellmann',
    'project'      : 'HappyDoc',
    'created'      : 'Sun, 17-Nov-2002 13:17:17 EST',

    #
    #  Current Information
    #
    'author'       : '$Author: doughellmann $',
    'version'      : '$Revision: 1.20 $',
    'date'         : '$Date: 2006/12/05 13:10:45 $',
}
try:
    __version__ = __rcs_info__['version'].split(' ')[1]
except:
    __version__ = '0.0'

#
# Import system modules
#
import os
import shutil

#
# Import Local modules
#
import happydoclib
from happydoclib.utils import *
from happydoclib.trace import trace
from happydoclib.docstring import getConverterFactoryForFile,\
     getConverterFactory

#
# Module
#

TRACE_LEVEL=2

class DocSetBase:
    """Base class for documentation sets.

    This is the base class for all documentation set classes.  The
    methods defined here are required of all docset classes.  Only the
    'write' method is actually used by the main application.
    """

    def __init__(self, scanner,
                 title,
                 outputDirectory,
                 statusMessageFunc=None,
                 extraParameters={},
                 ):
        """Basic Documentation Set

        Parameters

            scanner -- A directory tree scanner.

            title -- the title of the documentation set

            outputDirectory -- The base directory for writing the
                               output files.

            statusMessageFunc -- function which will print a status
                                 message for the user
            
            extraParameters -- Dictionary containing parameters
                               specified on the command line by
                               the user.

        """
        trace.into('DocSetBase', '__init__',
                   scanner=scanner,
                   title=title,
                   outputDirectory=outputDirectory,
                   statusMessageFunc=statusMessageFunc,
                   extraParameters=extraParameters,
                   outputLevel=TRACE_LEVEL,                   
                   )
        #
        # Store parameters
        #
        self.scanner = scanner
        self.title = title
        self.output_directory = outputDirectory
        self.status_message_func = statusMessageFunc

        self.statusMessage('Initializing documentation set %s' % title)

        self.statusMessage('NEED TO HANDLE extraParameters in DocSetBase')

        trace.outof(outputLevel=TRACE_LEVEL)
        return

    def statusMessage(self, message='', verboseLevel=1):
        "Print a status message for the user."
        if self.status_message_func:
            self.status_message_func(message, verboseLevel)
        return

    def warningMessage(self, message=''):
        self.statusMessage('WARNING: %s' % message, 0)
        return

    def write(self):
        """Called by the application to cause the docset to be written.
        """
        raise NotImplementedError('%s.write' % self.__class__.__name__)



    
    
class DocSet(DocSetBase):
    #
    # This class extends the DocSetBase with a few more convenience
    # methods.  Most docsets will actually subclass from DocSet or one
    # of its descendants rather than from DocSet directly.
    #
    # The basic extension is that this class provides a 'write' method
    # which walks the scanner tree, determines the appropriate writer
    # method for each node, and calls the writer.  Subclasses need only
    # provide writers and
    #
    """Docset Parameters

    Pass parameters to the docset using the syntax:

      docset_<argument>=value

    Common parameters for all documentation sets

        includeComments -- Boolean.  False means to skip the
                           comment parsing step in the parser.
                           Default is True.
            
        includePrivateNames -- Boolean.  False means to ignore
                               names beginning with _.  Default
                               is True.
    """

    #
    # Override this with a mapping from mime-type to the
    # method name to be called to handle writing a node
    # of that type.
    #
    mimetype_writer_mapping = {}

    def __init__(self, scanner,
                 title,
                 outputDirectory,
                 includeComments=1,
                 includePrivateNames=1,
                 sortNames=0,
                 statusMessageFunc=None,
                 extraParameters={},
                 ):
        """Basic Documentation Set

        Parameters

            scanner -- A directory tree scanner.

            title -- the title of the documentation set

            outputDirectory -- The base directory for writing the
                               output files.

            includeComments -- Boolean.  False means to skip the
                               comment parsing step in the parser.
                               Default is True.
            
            includePrivateNames -- Boolean.  False means to ignore
                                   names beginning with _.  Default
                                   is True.

            sortNames=0 -- Boolean.  True means to sort names before
                           generating output.  Default is False.

            statusMessageFunc -- function which will print a status
                                 message for the user
            
            extraParameters -- Dictionary containing parameters
                               specified on the command line by
                               the user.

        """
        trace.into('DocSet', '__init__',
                   scanner=scanner,
                   title=title,
                   outputDirectory=outputDirectory,
                   includeComments=includeComments,
                   includePrivateNames=includePrivateNames,
                   sortNames=0,
                   statusMessageFunc=statusMessageFunc,
                   extraParameters=extraParameters,
                   outputLevel=TRACE_LEVEL,
                   )
        DocSetBase.__init__(
            self,
            scanner=scanner,
            title=title,
            outputDirectory=outputDirectory,
            statusMessageFunc=statusMessageFunc,
            extraParameters=extraParameters,
            )
        #
        # Store parameters
        #
        self.include_comments = includeComments
        self.include_private_names = includePrivateNames
        self.sort_names = sortNames

        self._initializeWriters()
        
        trace.outof(outputLevel=TRACE_LEVEL)
        return

    def _filterNames(self, nameList):
        """Remove names which should be ignored.

        Parameters

          nameList -- List of strings representing names of methods,
          classes, functions, etc.
        
        This method returns a list based on the contents of nameList.
        If private names are being ignored, they are removed before
        the list is returned.

        """        
        if not self.include_private_names:
            #nameList = filter(lambda x: ( (x[0] != '_') or (x[:2] == '__') ),
            #                  nameList)
            nameList = [ name
                         for name in nameList
                         if name and ((name[0] != '_') or (name[:2] == '__'))
                         ]
        return nameList

    def _skipInputFile(self, packageTreeNode):
        """False writer method used to notify the user that a node is being
        skipped because the real writer is unknown.
        """
        mimetype, encoding = packageTreeNode.getMimeType()
        full_name = packageTreeNode.getCanonicalName()
        self.statusMessage('Skipping %s with unrecognized mimetype %s' % (
            full_name,
            mimetype,
            ))
        return

    def getWriterForNode(self, packageTreeNode):
        """Returns the writer to be used for the node.
        """
        mimetype, encoding = packageTreeNode.getMimeType()

        writer_name = self.mimetype_writer_mapping.get(mimetype)
        if writer_name:
            writer = getattr(self, writer_name)

        else:
            #
            # Unrecognized file.
            #
            writer = self._skipInputFile

        return writer
    
    def writeCB(self, packageTreeNode):
        """Callback used when walking the scanned package tree.
        """
        trace.into('MultiHTMLFileDocSet', 'writeCB',
                   packageTreeNode=packageTreeNode,
                   outputLevel=TRACE_LEVEL,
                   )

        writer = self.getWriterForNode(packageTreeNode)
        writer(packageTreeNode)
        trace.outof(outputLevel=TRACE_LEVEL)
        return

    def write(self):
        """Called by the application to cause the docset to be written.
        """
        self.scanner.walk(self.writeCB)
        return

    def _initializeWriters(self):
        """Hook to allow subclasses to register writers without having to
        override __init__ with all of its arguments.
        """
        return

    def registerWriter(self, mimetype, writerName):
        """Register a writer for the specified mimetype.
        """
        #print '%s -> %s' % (mimetype, writerName)
        self.mimetype_writer_mapping[mimetype] = writerName
        return


    
class MultiFileDocSet(DocSet):
    """Base class for documentation sets which write to multiple files.

    This class further extends the DocSet class by adding several
    convenience methods for handling files, as well as a few basic
    handlers.
    
    """

    CONVERTER_HEADER_START_LEVEL = 4

    mimetype_extension_mapping = {
        'text/x-python'     : { 'remove_existing':1,},
        'text/plain'        : { 'remove_existing':1,},
        'text/x-structured' : { 'remove_existing':1,},
        'text/html'         : { 'remove_existing':1,},
        }

    def _initializeWriters(self):
        """Hook to allow subclasses to register writers without having to
        override __init__ with all of its arguments.
        """

        DocSet._initializeWriters(self)
        
        mimetype_writers = [
            ('application/x-directory' , 'processDirectory'),
            ('text/x-python'           , 'processPythonFile'),
            ('application/x-class'     , 'processPythonClass'),
            ('text/plain'              , 'processPlainTextFile'),
            ('text/x-structured'       , 'processPlainTextFile'),
            ('text/html'               , 'copyInputFileToOutput'),
            ('image/gif'               , 'copyInputFileToOutput'),
            ('image/jpeg'              , 'copyInputFileToOutput'),
            ('image/png'               , 'copyInputFileToOutput'),
            ('application/x-function'  , 'noopHandler'),
            ]
        for mimetype, writer_name in mimetype_writers:
            self.registerWriter(mimetype, writer_name)
        return

    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('MultiFileDocSet', 'getOutputFilenameForPackageTreeNode',
                   packageTreeNode=packageTreeNode,
                   includePath=includePath,
                   outputLevel=TRACE_LEVEL,
                   )
        
        mimetype, encoding = packageTreeNode.getMimeType()
        trace.writeVar(mimetype=mimetype,
                       outputLevel=TRACE_LEVEL)
        settings = self.mimetype_extension_mapping.get(mimetype, {})
        trace.writeVar(settings=settings,
                       outputLevel=TRACE_LEVEL)

        if includePath:
            #
            # Get the input filename, relative to the root of the input.
            #
            input_filename = packageTreeNode.getRelativeFilename()

            #
            # Add the output directory to the front of the input
            # filename.
            #
            output_filename = os.path.join(self.output_directory, input_filename)
            
        else:
            input_filename = packageTreeNode.getRelativeFilename()
            output_filename = os.path.basename(input_filename)

        if settings.get('remove_existing'):
            output_filename, ignore = os.path.splitext(output_filename)

        #
        # Normalize the path, in case it includes /./ and the like.
        #
        normalized_output_filename = os.path.normpath(output_filename)
            
        trace.outof(normalized_output_filename,
                    outputLevel=TRACE_LEVEL)
        return normalized_output_filename

    def copyInputFileToOutput(self, packageTreeNode):
        """Copy the input file to the appropriate place in the output.
        """
        input_filename = packageTreeNode.getInputFilename()
        output_filename = self.getOutputFilenameForPackageTreeNode(packageTreeNode)

        #
        # Make sure the directory exists.
        #
        output_dirname = os.path.dirname(output_filename)
        self.rmkdir(output_dirname)

        #
        # Copy the file
        #
        self.statusMessage('Copying: %s\n     To: %s' % (
            input_filename,
            output_filename,
            ))
        shutil.copyfile(input_filename, output_filename)
        return
    
    def rmkdir(self, path):
        "Create a directory and all of its children."
        if not path:
            return
        parts = os.path.split(path)
        if len(parts) > 1:
            parent, child = parts
            if not isSomethingThatLooksLikeDirectory(parent):
                self.rmkdir(parent)
        if not isSomethingThatLooksLikeDirectory(path):
            os.mkdir(path)
        return

    def noopHandler(self, packageTreeNode):
        """Handler that does nothing.
        """
        return

    def processDirectory(self, packageTreeNode):
        """Handler for application/x-directory nodes.

        Creates the output directory and writes the table of contents
        file.
        """
        trace.into('MultiFileDocSet', 'processDirectory',
                   packageTreeNode=packageTreeNode,
                   outputLevel=TRACE_LEVEL,
                   )
        
        canonical_path = packageTreeNode.getPath(1)
        canonical_filename = apply(os.path.join, canonical_path)
        output_filename = self.getOutputFilenameForPackageTreeNode(packageTreeNode)
        output_dirname = os.path.dirname(output_filename)
            
        self.statusMessage('Directory  : "%s"\n         to: "%s"' % (
            canonical_filename,
            output_filename,
            ))

        if os.path.isdir(output_dirname):
            self.statusMessage('\tExists')
        else:
            self.rmkdir(output_dirname)
            self.statusMessage('\tCreated')

        self.writeTOCFile(packageTreeNode)
        
        trace.outof(outputLevel=TRACE_LEVEL)
        return

    def writeTOCFile(self, packageTreeNode):
        """Write the table of contents for a directory.

        Subclasses must implement this method.

        The packageTreeNode is a directory, and the table of contents
        for that directory should be written as appropriate.
        """
        raise NotImplementedError('writeTOCFile')

    def writeFileHeader(self, output, packageTreeNode, title='', subtitle=''):
        """Given an open output stream, write a header using the title and subtitle.

        Subclasses must implement this method.
        """
        raise NotImplementedError('writeFileHeader')

    def writeFileFooter(self, output):
        """Given an open output stream, write a footer using the title and subtitle.

        Subclasses must implement this method.
        """
        raise NotImplementedError('writeFileFooter')

    def openOutput(self, name, packageTreeNode, title='', subtitle=''):
        """Open the output stream from the name.

        Opens the output stream and writes a header using title and
        subtitle.  Returns the stream.
        """
        
        directory, basename = os.path.split(name)
        if not os.path.exists(directory):
            self.rmkdir(directory)
            
        f = open(name, 'wt')
        self.writeFileHeader(f, packageTreeNode, title=title, subtitle=subtitle)
        return f

    def closeOutput(self, output):
        """Close the output stream.

        Writes a footer to the output stream and then closes it.
        """
        self.writeFileFooter(output)
        output.close()
        return
    
    def _unquoteString(self, str):
        "Remove surrounding quotes from a string."
        str = str.strip()
        while ( str
                and
                (str[0] == str[-1])
                and
                str[0] in ('"', "'")
                ):
            str = str[1:-1]
        return str

    def formatText(self, text, textFormat):
        """Returns text formatted appropriately for output by this docset.

        Arguments:

          'text' -- String to be formatted.

          'textFormat' -- String identifying the format of 'text' so
          the formatter can use a docstring converter to convert the
          body of 'text' to the appropriate output format.

          'quote=1' -- Boolean option to control whether the text
          should be quoted to escape special characters.

        """
        text = self._unquoteString(text)
        #
        # Get a text converter
        #
        converter_factory = getConverterFactory(textFormat)
        converter = converter_factory()
        #
        # Do we need to quote the text?
        #
        #if self._html_quote_text and quote:
        #    text = converter.quote(text, 'html')
        #
        # Convert and write the text.
        #
        html = converter.convert(text, 'html',
                                 level=self.CONVERTER_HEADER_START_LEVEL)
        return html

    def writeText(self, output, text, textFormat):
        """Format and write the 'text' to the 'output'.

        Arguments:

          'output' -- Stream to which 'text' should be written.

          'text' -- String to be written.

          'textFormat' -- String identifying the format of 'text' so
          the formatter can use a docstring converter to convert the
          body of 'text' to the appropriate output format.

          'quote=1' -- Boolean option to control whether the text
          should be quoted to escape special characters.

        """
        if not text:
            return
        html = self.formatText(text, textFormat)
        output.write(html)
        return

    def processPythonFile(self, packageTreeNode):
        """Handler for text/x-python nodes.
        """
        raise NotImplementedError('processPythonFile')

    def processPlainTextFile(self, packageTreeNode):
        """Handler for text/x-structured and text/plain nodes.

        Converts the input file to the output file format and
        generates the output.  The output directory is assumed to
        already exist.
        """
        trace.into('MultiFileDocSet', 'processPlainTextFile',
                   packageTreeNode=packageTreeNode,
                   outputLevel=TRACE_LEVEL,
                   )
        
        canonical_path = packageTreeNode.getPath(1)
        canonical_filename = apply(os.path.join, canonical_path)
        output_filename = self.getOutputFilenameForPackageTreeNode(packageTreeNode)
        
        self.statusMessage('Translating: "%s"\n         to: "%s"' % (
            canonical_filename,
            output_filename,
            ))

        converter_factory = getConverterFactoryForFile(canonical_filename)
        converter = converter_factory()

        input_file = converter.getExternalDocumentationFile(canonical_filename)

        raw_body = str(input_file)

        #
        # FIXME - This needs to be handled more abstractly!
        #
        cooked_body = converter.convert(raw_body, 'html', level=3)

        output_file = self.openOutput(
            output_filename,
            packageTreeNode,
            title=self.title,
            subtitle=packageTreeNode.getRelativeFilename(),
            )
        output_file.write(cooked_body)
        
        self.closeOutput(output_file)
        
        trace.outof(outputLevel=TRACE_LEVEL)
        return

    def _computeRelativeHREF(self, source, destination):
        """Compute the HREF to point from the output file of source to destination.
        """
        trace.into('MultiHTMLFileDocSet', '_computeRelativeHREF',
                   source=source.getName(),
                   destination=destination.getName(),
                   outputLevel=TRACE_LEVEL,
                   )
        
        relative_path = source.getPathToNode(destination)
        trace.writeVar(relative_path=relative_path,
                       outputLevel=TRACE_LEVEL)
        if not relative_path:
            output_name = self.getOutputFilenameForPackageTreeNode(
                destination,
                includePath=0,
                )
            trace.outof(output_name, outputLevel=TRACE_LEVEL)
            return output_name

        destination_mimetype = destination.getMimeType()[0]
        source_mimetype = source.getMimeType()[0]

        #
        # Pointing to a class defined by source module.
        #
        if ( (len(relative_path) == 1)
             and
             (destination_mimetype == 'application/x-class')
             and
             (source_mimetype == 'text/x-python')
             and
             (source.get(destination.getName()) is not None)
             ):
            trace.write('adding source to relative path',
                        outputLevel=TRACE_LEVEL)
            relative_path = (source.getName(), relative_path[0])

        destination_name = destination.getName()
        if relative_path[-1] == destination_name:
            #
            # Need to replace with output name.
            #
            output_name = self.getOutputFilenameForPackageTreeNode(
                destination,
                includePath=0,
                )
            trace.write('Replacing %s with %s' % (relative_path[-1],
                                                  output_name,
                                                  ),
                        outputLevel=TRACE_LEVEL,
                        )
            relative_path = relative_path[:-1] + (output_name,)

        #
        # If the destination is a directory, add 'index.html' to the end.
        #
        #print destination.getName(), destination.getMimeType()
        #if destination.getMimeType() == ('application/x-directory', None):
        #    print 'adding index.html'
        #    relative_path += ('index.html',)
        #    print relative_path

        href = '/'.join(relative_path)
        trace.outof(href, outputLevel=TRACE_LEVEL)
        return href
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.