import os, sys, string, shutil, glob, tempfile
import distutils
from distutils.dist import Distribution
from distutils import filelist, util
from distutils.core import Command
from distutils.dep_util import newer
from distutils.command import install
from distutils.command import build, build_py
from distutils.command import install_lib
from distutils.command import sdist
from distutils.version import LooseVersion
from types import *
import tokenize
from install_data import Data_Files, install_Data_Files

if sys.hexversion >= 0x2000000:
    import pygettext, msgfmt


# Make sure that we are using the proper version of Distutils
# We make certain assumptions about the implementation

def EnsureVersion(version):
    """Checks Distutils version against specified version number"""
    # We need to use LooseVersions because of distutils in Python 2.1
    dist_version = LooseVersion(distutils.__version__)
    expected = LooseVersion(version)
    if expected > dist_version:
        print
        print 'Requires Distutils v%s or newer.  Found version %s.' % (
            expected,
            dist_version,
            )
        print
        sys.exit(1)

EnsureVersion('1.0')

class Localization:
    """Defines a message catalog for a particular package"""
    def __init__(self, package, domain, sources=None):
        self.package = package
        self.domain = domain
        # Messages are extracted from the sources to generate .po files
        # Used by the 'generate_l10n' command
        self.sources = sources or []

# Specification structure for generate_docs directive

class PyModules:
    def __init__(self, modulePrefix, dest, moduleList):
        self.modulePrefix = modulePrefix
        self.destDir = os.path.join(os.path.dirname(moduleList[0]),dest)
        self.pymodules = moduleList

class Document:
    def __init__(self, source, stylesheets, destFile,params=None):
        self.source = source
        self.stylesheets = stylesheets
        self.outfile = os.path.join(os.path.dirname(source), destFile)
        self.params = params or {}

class CommandLineApp:
    def __init__(self,
                 appModule,
                 appName,
                 destDir,
                 ):

        self.appModule = appModule
        self.appName = appName
        self.destDir = destDir


# Specification structures for install_web directive

class WebFiles:
    """Installs relative to Apache ServerRoot"""
    def __init__(self, path, files):
        self.path = path
        self.files = files
        self._server = 1

    def serverRoot(self):
        return self._server

class CgiFiles(WebFiles):
    """Installs relative to Apache [ServerRoot]/cgi-bin"""
    def __init__(self, path,files):
        WebFiles.__init__(self, os.path.join('cgi-bin',path), files)

class WebDocuments(WebFiles):
    """Installs relative to DocumentRoot on Apache"""
    def __init__(self, path, files):
        WebFiles.__init__(self, path, files)
        self._server = 0


# Our new Distribution class

class Dist(Distribution):
    def __init__(self,attrs):
        self.py_files = None
        self.l10n = None
        self.gen_docs = None
        self.gen_pydocs = None
        self.gen_cldocs = None
        self.bgen_files = None
        self.idl_files = None
        self.odl_files = None
        self.generatedDirectory = None
        self.corbaNamePath = None
        self.webFiles = None
        Distribution.__init__(self,attrs)

    def has_docs(self):
        return self.gen_docs and len(self.gen_docs) > 0

    def has_pydocs(self):
        return self.gen_pydocs and self.gen_pydocs.pymodules and len(self.gen_pydocs.pymodules) > 0    

    def has_cldocs(self):
        return self.gen_cldocs and len(self.gen_cldocs) > 0    

    def has_idl(self):
        return self.idl_files and len(self.idl_files) > 0

    def has_odl(self):
        return self.odl_files and len(self.odl_files) > 0

    def has_namepath(self):
        return self.corbaNamePath

    def has_webfiles(self):
        return self.webFiles and len(self.webFiles) > 0

    def has_l10n(self):
        return self.l10n and len(self.l10n) > 0

    def get_package_dir(self, package):
        # Copied almost verbatim from distutils.command.build_py
        path = string.split(package, '.')

        if not self.package_dir:
            if path:
                return apply(os.path.join, path)
            else:
                return ''
        else:
            tail = []
            while path:
                try:
                    pdir = self.package_dir[string.join(path, '.')]
                except KeyError:
                    tail.insert(0, path[-1])
                    del path[-1]
                else:
                    tail.insert(0, pdir)
                    return apply(os.path.join, tail)
            else:
                # Oops, got all the way through 'path' without finding a
                # match in package_dir.  If package_dir defines a directory
                # for the root (nameless) package, then fallback on it;
                # otherwise, we might as well have not consulted
                # package_dir at all, as we just use the directory implied
                # by 'tail' (which should be the same as the original value
                # of 'path' at this point).
                pdir = self.package_dir.get('')
                if pdir is not None:
                    tail.insert(0, pdir)

                if tail:
                    return apply(os.path.join, tail)
                else:
                    return ''

class BuildPy(build_py.build_py):
    def initialize_options(self):
        build_py.build_py.initialize_options(self)
        self.py_files = None
        self.outfiles = []

    def finalize_options(self):
        build_py.build_py.finalize_options(self)
        self.py_files = self.distribution.py_files or []

    def run(self):
        for (package, filelist) in self.py_files:
            package = string.split(package, '.')
            package_dir = [self.build_lib] + list(package)
            package_dir = apply(os.path.join, package_dir)
            self.mkpath(package_dir)
            for file in filelist:
                (out, _) = self.copy_file(file, package_dir)
                self.outfiles.append(out)
        build_py.build_py.run(self)

    def get_outputs(self, include_bytecode=1):
        outputs = build_py.build_py.get_outputs(self, include_bytecode)
        outputs.extend(self.outfiles)
        return outputs


class BuildL10n(build_py.build_py):
    def initialize_options(self):
        build_py.build_py.initialize_options(self)
        self.build_temp = None
        self.l10n = None
        self.outfiles = []

    def finalize_options(self):
        build_py.build_py.finalize_options(self)
        self.set_undefined_options('build',
                                   ('build_temp', 'build_temp'))
        self.l10n = self.distribution.l10n or []

    def run(self):
        for loc in self.l10n:
            package_path = string.split(loc.package, '.')
            package_path = apply(os.path.join, package_path)

            lib_dir = os.path.join(self.build_lib, package_path)

            # Make sure the generate directory exists
            temp_dir = os.path.join(self.build_temp, package_path)
            self.mkpath(temp_dir)
            
            src_dir = self.get_package_dir(loc.package)
            po_files = glob.glob(os.path.join(src_dir, '*.po'))
            for po_file in po_files:
                (po_filename, copied) = self.copy_file(po_file, temp_dir)
                if not (copied or self.force):
                    # copy_file() already announced the skip
                    continue

                # Create the locale directory for this .mo file
                locale = os.path.splitext(os.path.basename(po_file))[0]
                locale_dir = os.path.join(lib_dir, locale, 'LC_MESSAGES')
                self.mkpath(locale_dir)

                # What the msgfmt generated file will eventually be called
                locale_mo = os.path.join(locale_dir, '%s.mo' % loc.domain)
                self.outfiles.append(locale_mo)

                if not self.dry_run:
                    # Only generate the .mo if they user really means it
                    msgfmt.MESSAGES = {}
                    msgfmt.make(po_filename)
                    mo_filename = po_filename[:-2] + 'mo'
                    self.copy_file(mo_filename, locale_mo)

    def get_outputs(self, include_bytecode=1):
        outputs = build_py.build_py.get_outputs(self, include_bytecode)
        outputs.extend(self.outfiles)
        return outputs


class BuildOdl(Command):
    description = "compile ODL file pstubs"

    user_options = [
        ('build-lib=', 'd', "directory to \"build\" (copy) to"),
        ('build-temp=', 't', 'directory for temporary files'),
        ('force', 'f', "forcibly build everything (ignore file timestamps)"),
        ]

    boolean_options = ['force']

    def initialize_options (self):
        self.build_lib = None
        self.build_temp = None
        self.force = 0
        self.odl_files = None
        self.outfiles = []
        return

    def finalize_options (self):
        self.set_undefined_options('build', ('build_lib', 'build_lib'),
                                            ('build_temp', 'build_temp'),
                                            ('force', 'force'))
        self.odl_files = self.distribution.odl_files
        self.build_temp = os.path.join(self.build_temp, 'odl')
        return

    def get_outputs (self):
        return self.outfiles

    def run(self):
        if not self.odl_files:
            return

        # Needed for Ft, it might not be installed yet.
        # Make sure to use the (possible) new code first
        sys.path.insert(0, self.build_lib)
        
        # Save old envvar is case this is not run from command-line
        old_ftdb = os.environ.get('FT_DATABASE_DIR')
        # We use a throw away DB so use a throw away directory
        os.environ['FT_DATABASE_DIR'] = tempfile.gettempdir()
        from Ft.Ods.Tools import init

        for (package, odl_files) in self.odl_files:
            package_parts = string.split(package, '.')

            temp_dir = [self.build_temp] + list(package_parts)
            temp_dir = apply(os.path.join, temp_dir)
            self.mkpath(temp_dir)

            for file in odl_files:
                build_file = os.path.join(temp_dir, os.path.basename(file))
                if not (self.force or newer(file, build_file)):
                    self.announce("skipping '%s' ODL pstubs (up-to-date)" % file)
                    continue
                self.announce("generating '%s' ODL pstubs" % file)
                args = {'database':'DUMMY_DB',
                        'file':[file]}
                options = {'init':1,
                           'force':1,
                           'directory':temp_dir}
                init.Run(options,args)
                self.copy_file(file, build_file, preserve_mode=0)
            
            package_dir = [self.build_lib] + list(package_parts)
            package_dir = apply(os.path.join, package_dir)
            self.mkpath(package_dir)
            self.outfiles.extend(self.copy_tree(temp_dir, package_dir))

            # Ensure the package is really a module
            package_init = os.path.join(package_dir, '__init__.py')
            if not os.path.exists(package_init):
                self.announce("package '%s' __init__ not found, creating" % package)
                init = open(package_init, 'w')
                init.write('# Empty file to convince Python this is a module.\n')
                init.close()
                self.outfiles.append(package_init)

        # Removed forced build path and environment vars
        if old_ftdb is not None:
            os.environ['FT_DATABASE_DIR'] = old_ftdb
        else:
            del os.environ['FT_DATABASE_DIR']
        del sys.path[0]

        return


class BuildIdl(Command):
    description = "compile IDL file pstubs"

    user_options = [
        ('build-lib=', 'd', "directory to \"build\" (copy) to"),
        ('build-temp=', 't', 'directory for temporary files'),
        ('force', 'f', "forcibly build everything (ignore file timestamps)"),
        ]

    boolean_options = ['force']

    def initialize_options (self):
        self.build_lib = None
        self.build_temp = None
        self.force = 0
        self.idl_files = None
        self.outfiles = []
        return

    def finalize_options (self):
        self.set_undefined_options('build', ('build_lib', 'build_lib'),
                                            ('build_temp', 'build_temp'),
                                            ('force', 'force'))
        self.idl_files = self.distribution.idl_files
        self.build_temp = os.path.join(self.build_temp, 'idl')
        return

    def get_outputs (self):
        return self.outfiles

    def run(self):
        if not self.idl_files:
            return

        try:
            import omniidl.main
        except ImportError:
            self.warn('unable to import omniidl, skipping IDL pstubs')
            return

        self.outfiles = []
        for (package, idl_files) in self.idl_files:
            package_parts = string.split(package, '.')

            temp_dir = [self.build_temp] + list(package_parts)
            temp_dir = apply(os.path.join, temp_dir)
            self.mkpath(temp_dir)

            for file in idl_files:
                build_file = os.path.join(temp_dir, os.path.basename(file))
                if not (self.force or newer(file, build_file)):
                    self.announce("skipping '%s' IDL pstubs (up-to-date)" % file)
                    continue
                self.announce("generating '%s' IDL pstubs" % file)
                args = ['', '-b', 'python', '-C', temp_dir, '-I', temp_dir, file]
                omniidl.main.main(args)
                # By coping after, we don't get skipped IDL files
                self.copy_file(file, build_file, preserve_mode=0)
            
            package_dir = [self.build_lib] + list(package_parts)
            package_dir = apply(os.path.join, package_dir)
            self.mkpath(package_dir)
            self.outfiles.extend(self.copy_tree(temp_dir, package_dir))

            # Ensure the package is really a module
            package_init = os.path.join(package_dir, '__init__.py')
            if not os.path.exists(package_init):
                self.announce("package '%s' __init__ not found, creating" % package)
                init = open(package_init, 'w')
                init.write('# Empty file to convince Python this is a module.\n')
                init.close()
                self.outfiles.append(package_init)

        return


class Stage(Command):

    description = "shortcut for build/install"

    user_options = [
        ('force', 'f',
         'forcibly do everything (ignore file timestamps)'),
        ]

    boolean_options = ['force']

    def initialize_options(self):
        # Keep stage quiet
        self.distribution.verbose = 0
        self.force = 0

    def finalize_options(self):
        return
    
    def run(self):
        for cmd_name in ['build' ,'install']:
            cmd_dict = self.distribution.get_option_dict(cmd_name)
            cmd_dict['force'] = ('stage', self.force)
            self.run_command(cmd_name)


class InstallLib(install_lib.install_lib):
    # Extended to provide get_outputs() support for our build_odl
    # and build_idl commands.  Needed for --record (RPM builds).
    def get_outputs(self):
        outputs = install_lib.install_lib.get_outputs(self)

        odl_outputs = self._mutate_outputs(self.distribution.has_odl(),
                                           'build_odl', 'build_lib',
                                           self.install_dir)

        idl_outputs = self._mutate_outputs(self.distribution.has_idl(),
                                           'build_idl', 'build_lib',
                                           self.install_dir)

        return outputs + odl_outputs + idl_outputs


class InstallWebFiles(Command):
    description = "install data files for Apache web server"

    user_options = [
        ('server-root=', 's', "Apache server root directory"),
        ('document-root=', 'd', "Apache document root directory"),
        ('force', 'f', "force installation (overwrite existing files)"),
        ]

    def initialize_options (self):
        self.outfiles = []
        self.server_root = None
        self.document_root = None
        self.build_dir = None
        self.force = 0
        
    def finalize_options (self):
      self.set_undefined_options('install',
                                 ('force', 'force'),
                                 )
      if not self.server_root:
          self.server_root = '/usr/local/apache'
      if not self.document_root:
          self.document_root = '/usr/local/apache/htdocs'

    def get_outputs (self):
        return self.outfiles
        
    def run(self):
        webFiles = self.distribution.webFiles or []
        for file_info in webFiles:
            if file_info.serverRoot():
                root = self.server_root
            else:
                root = self.document_root
            dest = os.path.join(root, file_info.path)
            self.mkpath(dest)
            for file in file_info.files:
                (out, _) = self.copy_file(file, dest)
                self.outfiles.append(out)


class InstallNamePath(Command):
    description = "Install an omniORB Nameservice path"

    user_options = [
                    ]

    def initialize_options (self):
        self.corbaNamePath = None
        
    def finalize_options (self):
        self.corbaNamePath = self.distribution.corbaNamePath

    def get_outputs (self):
        return []
        
    def run(self):
        if not self.corbaNamePath:
            return
        names = string.split(self.corbaNamePath,'/')
        fullName = ''
        for n in names:
            if fullName:
                fullName = fullName + '/' + n
            else:
                fullName = n
            if not os.popen('nameclt resolve %s' % fullName).read():
                self.announce('nameclt bind_new_context %s' % fullName)
                os.system('nameclt bind_new_context %s' % fullName)
            else:
                self.announce('not nameclt bind_new_context %s (already there)' % fullName)

        return


class SDist(sdist.sdist):

    def initialize_options(self):
        sdist.sdist.initialize_options(self)

        # This should help reduce number of missing files
        # in the distributions
        self.force_manifest = 1
        return

    def run(self):
        do_dist = not self.manifest_only
        self.manifest_only = 1
        sdist.sdist.run(self)

        if not self._validate():
            self.warn('Please correct missing files and rebuild')
        
        elif do_dist:
            self.manifest_only = 0
            self.make_distribution()
            
        return
        
    def _validate(self):
        all_files = filelist.FileList()
        all_files.include_pattern('*')
        self._prune_files(all_files)

        # Ensure file paths are formatted the same
        # This removes any dot-slashes and converts all slashes to
        # OS-specific separators.
        dist_files = map(os.path.normpath, self.filelist.files)
        src_files = map(os.path.normpath, all_files.files)

        valid = 1
        for file in all_files.files:
            if file not in dist_files and file[-3:] != 'pyc':
                self.warn('Missing from package: %s' % file)
                valid = 0

        if not valid:
            self.warn('Not all source files in distribution')
            prompt = raw_input('Do you want to continue? (y/n)')
            valid = prompt in ['y', 'Y', 'yes']
        return valid
        
    def _prune_files(self, file_list):
        build = self.get_finalized_command('build')
        base_dir = self.distribution.get_fullname()
        
        # Remove complete directory trees
        file_list.exclude_pattern(None, prefix=build.build_base)
        file_list.exclude_pattern(None, prefix=self.dist_dir)
        file_list.exclude_pattern(None, prefix=base_dir)
        file_list.exclude_pattern(None, prefix='CVS')

        # Remove individual files matching pattern
        patterns = ['\\%sCVS\\%s.*' % (os.sep, os.sep),
                    '\.cvsignore$',
                    '\.#.*$',
                    ]
        for pattern in patterns:
            file_list.exclude_pattern(pattern, is_regex=1)
        
        # Remove any duplicates
        file_list.remove_duplicates()
        return

    def add_defaults(self):
        sdist.sdist.add_defaults(self)
        if self.distribution.has_data_files():
            data_files = self.distribution.data_files
            source_files = []
            for f in data_files:
                if type(f) == StringType:
                    source_files.append(f)
                elif type(f) == TupleType:
                    source_files.extend(f[1])
                else:
                    # it's a tuple with path to install to and a list of files
                    for data in f.files:
                        source_files.append(data)

            self.filelist.extend(source_files)


class Validate(Command):
    
    description = "ensure all files would be installed"

    user_options = [
                    ]

    def initialize_options (self):
        self.filelist = filelist.FileList()
        return
    
    def finalize_options (self):
        return

    def run(self):
        # Create the source distribution filelist
        self.distribution.dry_run = 1
        self.distribution.verbose = 0
        self.distribution.get_option_dict('sdist')['manifest_only'] = ('validate', 1)
        self.run_command('sdist')
        sdist = self.get_finalized_command('sdist')
        
        # Make sure the paths are normalized, this is not always the case
        dist_files = map(os.path.normpath, sdist.filelist.files)

        # Get our filelist
        self.filelist.include_pattern('*')
        self.prune_file_list()
        src_files = self.filelist.files

        for file in src_files:
            if file not in dist_files and file[-3:] != 'pyc':
                self.warn('Not in distribution: %s' % file)
        return

    def prune_file_list (self):
        """Prune off branches that might slip into the file list as created
        by 'read_template()', but really don't belong there:
          * the build tree (typically "build")
          * the release tree itself (only an issue if we ran "sdist"
            previously with --keep-temp, or it aborted)
          * any RCS or CVS directories
        """
        build = self.get_finalized_command('build')
        sdist = self.get_finalized_command('sdist')
        base_dir = self.distribution.get_fullname()
        
        # Remove complete directory trees
        self.filelist.exclude_pattern(None, prefix=build.build_base)
        self.filelist.exclude_pattern(None, prefix=sdist.dist_dir)
        self.filelist.exclude_pattern(None, prefix=base_dir)
        self.filelist.exclude_pattern(None, prefix='CVS')

        # Remove individual files matching pattern
        patterns = ['\\%sCVS\\%s.*' % (os.sep, os.sep),
                    '\.cvsignore$',
                    '\.#.*$',
                    ]
        for pattern in patterns:
            self.filelist.exclude_pattern(pattern, is_regex=1)
        
        # Remove any duplicates
        self.filelist.remove_duplicates()
        return


class Uninstall(Command):

    description = "uninstall the package"

    user_options = []

    def initialize_options(self):
        pass

    def finalize_options(self):
        pass
        
    def run(self):

        # Execute build
        self.announce('determining installation files')
        orig_dry_run = self.distribution.dry_run
        orig_verbose = self.distribution.verbose
        self.distribution.dry_run = 0
        self.distribution.verbose = 0
        self.run_command('build')

        # Execute install in dry-run mode
        self.distribution.dry_run = 1
        self.run_command('install')
        self.distribution.dry_run = orig_dry_run
        self.distribution.verbose = orig_verbose
        build = self.get_finalized_command('build')
        install = self.get_finalized_command('install')

        # Directories that should not be removed
        # install_headers is skipped because is uses $dist_name
        root_dirs = [install.install_purelib,
                     install.install_platlib,
                     install.install_scripts,
                     install.install_data,
                     ]

        # Remove all installed files
        self.announce("removing files")
        dirs = {}
        filenames = install.get_outputs()
        for filename in filenames:
            if not os.path.isabs(filename):
                raise DistutilsError,\
                      'filename "%s" from .get_output() not absolute' % \
                      filename

            if os.path.isfile(filename):
                self.announce("removing '%s'" % filename)
                if not self.dry_run:
                    try:
                        os.remove(filename)
                    except OSError, details:
                        self.warn("Could not remove file: %s" % details)
                    dir = os.path.split(filename)[0]
                    if not dirs.has_key(dir):
                        dirs[dir] = 1
                    if os.path.splitext(filename)[1] == '.py':
                        # Try and remove the .pyc if not already in the list
                        if filename+'c' not in filenames:
                            try:
                                os.remove(filename + 'c')
                            except OSError:
                                pass

                        # Try and remove the .pyo if not already in the list
                        if filename+'o' not in filenames:
                            try:
                                os.remove(filename + 'o')
                            except OSError:
                                pass

            elif os.path.isdir(filename):
                if not dirs.has_key(dir):
                    dirs[filename] = 1

            else:
                self.announce("skipping removal of '%s' (not found)" %
                              filename)

        # Remove the installation directories
        self.announce("removing directories")
        dirs = dirs.keys()
        dirs.sort(); dirs.reverse() # sort descending
        for dir in dirs:
            if dir in root_dirs:
                # A base directory that shouldn't be removed
                continue
            self.announce("removing directory '%s'" % dir)
            if not self.dry_run:
                if os.listdir(dir):
                    self.warn("skipping removal of '%s' (not empty)" % dir)
                else:
                    try:
                        os.rmdir(dir)
                    except OSError, details:
                        self.warn("could not remove directory: %s" % details)

#########################
##                     ##
##  Generate Commands  ##
##                     ##
#########################

class Generate(Command):

    description = "generate additional files needed to install"

    user_options = [
        ('force', 'f',
         'forcibly generate everything (ignore file timestamps)'),
        ]

    boolean_options = ['force']

    def initialize_options(self):
        self.force = 0

    def finalize_options(self):
        return

    def run(self):
        for cmd_name in self.get_sub_commands():
            self.run_command(cmd_name)

    def get_outputs(self):
        outputs = []
        for cmd_name in self.get_sub_commands():
            cmd = self.get_finalized_command(cmd_name)
            outputs.extend(cmd.get_outputs())
        return outputs

    def has_docs(self):
        return self.distribution.has_docs()

    def has_pydocs(self):
        return self.distribution.has_pydocs()

    def has_cldocs(self):
        return self.distribution.has_cldocs()

    def has_bisongen(self):
        bgen_files = self.distribution.bgen_files
        return bgen_files and len(bgen_files) > 0

    def has_l10n(self):
        l10n = self.distribution.l10n
        return l10n and len(l10n) > 0

    sub_commands = [('generate_docs', has_docs),
                    ('generate_pydocs', has_pydocs),
                    ('generate_cldocs', has_cldocs),
                    ('generate_bgen', has_bisongen),
                    ]

    if sys.hexversion >= 0x2000000:
        sub_commands.append(('generate_l10n', has_l10n),)


class GeneratePyDocs(Command):

    description = "generate python documentation files"

    user_options = [
        ('force', 'f',
         'force python document generatation (overwrite existing files)'),
        ]

    boolean_options = ['force']

    def initialize_options(self):
        self.gen_pydocs = None
        self.force = 0
        self.outfiles=[]


    def finalize_options(self):
        self.set_undefined_options('generate', ('force', 'force'))
        self.gen_pydocs = self.distribution.gen_pydocs

    def get_outputs(self):
        return self.outfiles

    def run(self):
        if not self.gen_pydocs:
            return

        try:
            from pydoc import writedoc, locate, HTMLDoc, describe
        except ImportError:
            return


        indexBody="""
<HTML>
  <BODY>
    <H1>Core Pydoc documentation:</H1>
    <P><STRONG>Note:</STRONG> Documentation may contain broken links to dependent but undocumented modules</P>
    %s
  </BODY>
</HTML>
        """
        linkList=""
        for pymodule in self.gen_pydocs.pymodules:
            import inspect
            modname = self.gen_pydocs.modulePrefix + inspect.getmodulename(pymodule)
            linkList = linkList + "<LI><A HREF='%s'>%s</A></LI>" % (
                modname + '.html', modname)

            outputFile = os.path.join(self.gen_pydocs.destDir,modname + '.html')
            if not (self.force or newer(pymodule, outputFile)):
                continue

            self.outfiles.append(outputFile)
            
            self.announce("Generating '%s' from %s" % (outputFile, pymodule))            
            html = HTMLDoc()
            try:
                modname=self.gen_pydocs.modulePrefix+inspect.getmodulename(pymodule)
                object = locate(modname, 0)
                page = html.page(describe(object),
                             html.document(object, object.__name__))
                file = open(outputFile, 'w')
                file.write(page)
                file.close()
            except Exception, e:
                import traceback, cStringIO
                stream = cStringIO.StringIO()
                traceback.print_exc(None, stream)
                stream.write(str(e) + '\n')
                self.announce('Unable to generate %s' % outputFile)
                self.announce(stream.getvalue())
                stream.close()
        
        indexBody=indexBody%linkList
        outputFile=os.path.join(self.gen_pydocs.destDir,'index.html')
        f=open(outputFile,'w')
        f.write(indexBody)
        f.close()


class GenerateClDocs(Command):

    description = "generate command line documentation files"

    user_options = [
        ('force', 'f',
         'force document generatation (overwrite existing files)'),
        ]

    boolean_options = ['force']

    def initialize_options(self):
        self.gen_cldocs = None
        self.force = 0
        self.outfiles=[]


    def finalize_options(self):
        self.set_undefined_options('generate', ('force', 'force'))
        self.gen_cldocs = self.distribution.gen_cldocs

    def get_outputs(self):
        return self.outfiles

    def run(self):
        if not self.gen_cldocs:
            return

        for cldoc in self.gen_cldocs:
            appModule = __import__(cldoc.appModule,{},{},"*")
            appClass = getattr(appModule,cldoc.appName)
            appInst = appClass()
            appDocs = appInst.get_help_doc_info()
                               
            for cmdName,cmd in appDocs:
                outfile = os.path.join(cldoc.destDir,cmdName + '-command-line.html')
                cMod = __import__(cmd.__class__.__module__,{},{},'*')
                if not (self.force or newer(cMod.__file__, outfile)):
                    continue
                self.announce("Generating '%s' from %s" % (outfile, cmd.name))            
                cmd.generate_html_help(outfile,cmdName)
            
class GenerateDocs(Command):

    description = "generate documentation files"

    user_options = [
        ('force', 'f',
         'force document generatation (overwrite existing files)'),
        ]

    boolean_options = ['force']

    def initialize_options(self):
        self.outfiles = []
        self.gen_docs = None
        self.force = 0


    def finalize_options(self):
        self.set_undefined_options('generate', ('force', 'force'))
        self.gen_docs = self.distribution.gen_docs

    def get_outputs(self):
        return self.outfiles

    def run(self):
        if not self.gen_docs:
            return

        from xml.xslt.Processor import Processor

        for doc in self.gen_docs:
            if not self.force:
                update = 0
                for file in doc.stylesheets:
                    if os.path.exists(file):
                        update = newer(file, doc.outfile)
                        if update:
                            break
                if not (update or newer(doc.source, doc.outfile)):
                    continue
            
            self.announce("Generating '%s' from %s" % (doc.outfile, doc.source))
            self.outfiles.append(doc.outfile)

            from Ft.Lib import pDomlette
            docReader = pDomlette.PyExpatReader(processIncludes=1)
            processor = Processor(reader = docReader)

            params = {'version' : self.distribution.get_version()}
            params.update(doc.params)
            try:
                ignorePis = 1
                for file in doc.stylesheets:
                    if os.path.exists(file):
                        processor.appendStylesheetFile(file)
                    else:
                        ignorePis = 0
                result = processor.runUri('file:' + doc.source, ignorePis, params)
                stream = open(doc.outfile, 'w')
                stream.write(result + '\n')
                stream.close()
            except Exception, e:
                import traceback, cStringIO
                stream = cStringIO.StringIO()
                traceback.print_exc(None, stream)
                stream.write(str(e) + '\n')
                self.announce('Unable to generate %s' % doc.outfile)
                self.announce(stream.getvalue())
                stream.close()                


class GenerateL10n(Command):

    description = "generate localizations"

    user_options = [
        ('force', 'f',
         'force locale generatation (overwrite existing files)'),
        ]

    boolean_options = ['force']

    def initialize_options(self):
        self.outfiles = []
        self.l10n = None
        self.force = 0

    def finalize_options(self):
        self.set_undefined_options('generate', ('force', 'force'))
        self.l10n = self.distribution.l10n

    def get_outputs(self):
        return self.outfiles

    def run(self):
        if not self.l10n:
            return

        class Options:
            # constants
            GNU = 1
            SOLARIS = 2
            # defaults
            extractall = 0 # FIXME: currently this option has no effect at all.
            escape = 0
            keywords = []
            outpath = ''
            outfile = 'en_US.po'
            writelocations = 1
            locationstyle = GNU
            verbose = 0
            width = 78
            excludefilename = ''

        for loc in self.l10n:
            for source in loc.sources:
                self.announce("scanning for messages in '%s'" % source)
                fp = open(source_file)
                pygettext_options = Options()
                pygettext_options.keywords = ['_']
                pygettext_options.toexclude = []
                pygettext.make_escapes(pygettext_options.escape)
                pygettext_eater = pygettext.TokenEater(pygettext_options)
                pygettext_eater.set_filename(source)
                tokenize.tokenize(fp.readline, pygettext_eater)
                fp.close()
            po_dir = self.distribution.get_package_dir(loc.package)
            po_filename = os.path.join(po_dir, 'en_US' + ".po")
            self.announce("generating '%s'" % po_filename)
            fp = open(po_filename, 'w')
            pygettext_eater.write(fp)
            fp.close()


class GenerateBisonGen(Command):

    description = "regenerate BisonGen parsers"

    user_options = [
        ('force', 'f',
         'force rebuild (ignore timestamps)'),
        ]

    boolean_options = ['force']

    def initialize_options(self):
        self.bgen_files = None
        self.force = 0

    def finalize_options(self):
        self.set_undefined_options('generate', ('force', 'force'))
        self.bgen_files = self.distribution.bgen_files

    def get_outputs(self):
        return []

    def run(self):
        if not self.bgen_files:
            return

        try:
            from BisonGen import Main
        except:
            self.warn('Could not load BisonGen, skipping rebuild')
            return

        outputs = []
        args = ['BisonGen']
        if self.force:
            args.append('--force')
        if not self.distribution.verbose:
            args.append('--quiet')
        for filename in self.bgen_files:
            outputs.extend(Main.Run(args + [filename]))

        return

# Setup functions

def EnsureScripts(*linuxScripts):
    """Creates the proper script names required for each platform"""
    scripts = linuxScripts
    if util.get_platform()[:3] == 'win':
        scripts = map(lambda s: s + '.bat', scripts)
    return scripts

            
def InstallSubCommands():
    """Adds our own sub-commands to build and install"""
    has_idl = lambda self: self.distribution.has_idl()
    has_odl = lambda self: self.distribution.has_odl()
    has_namepath = lambda self: self.distribution.has_namepath()
    has_webfiles = lambda self: self.distribution.has_webfiles()
    is_py2 = lambda self: sys.version[0] == '2'
    has_l10n = lambda self: sys.hexversion >= 0x2000000 and self.distribution.has_l10n()

    installCmds = [('install_namepath', has_namepath),
                   ('install_web', has_webfiles),
                   ]

    build_cmds = [('build_odl', has_odl),
                  ('build_idl', has_idl),
                  ]

    if sys.hexversion >= 0x2000000:
        build_cmds.append(('build_l10n', has_l10n))
    
    install.install.sub_commands.extend(installCmds)
    build.build.sub_commands.extend(build_cmds)

# Command function overrides
Commands = {
    'build_py' : BuildPy,
    'build_odl' : BuildOdl,
    'build_idl' : BuildIdl,
    'build_l10n' : BuildL10n,

    'install_lib' : InstallLib,
    'install_data' : install_Data_Files,
    'install_web' : InstallWebFiles,
    'install_namepath' : InstallNamePath,

    'stage' : Stage,

    'validate' : Validate,

    'uninstall' : Uninstall,

    'generate' : Generate,
    'generate_docs' : GenerateDocs,
    'generate_pydocs' : GeneratePyDocs,    
    'generate_bgen' : GenerateBisonGen,
    'generate_cldocs' : GenerateClDocs,    
    'generate_bgen' : GenerateBisonGen,
    'generate_l10n' : GenerateL10n,

    'sdist' : SDist,
    }
