import os
from types import *
from distutils import filelist
from distutils.command import sdist

class SDist(sdist.sdist):

    command_name = 'sdist'

    description = "create a source distribution (tarball, zip file, etc.)"

    user_options = [
        ('manifest-only', 'o',
         "just regenerate the manifest and then stop "
         "(implies --force-manifest)"),
        ('formats=', None,
         "formats for source distribution (comma-separated list)"),
        ('keep-temp', 'k',
         "keep the distribution tree around after creating " +
         "archive file(s)"),
        ('dist-dir=', 'd',
         "directory to put the source distribution archive(s) in "
         "[default: dist]"),
        ]

    # sdist.sdist defines this, so we must undefine it
    negative_opt = {}

    def run(self):
        # 'filelist' contains the list of files that will make up the
        # manifest
        self.filelist = filelist.FileList()

        # Ensure that all required meta-data is given; warn if not (but
        # don't die, it's not *that* serious!)
        self.check_metadata()

        # Do whatever it takes to get the list of files to process
        # (process the manifest template, read an existing manifest,
        # whatever).  File list is accumulated in 'self.filelist'.
        self.get_file_list()

        # Ensure that all files in the source tree are included in the
        # would be distribution
        if not self.validate():
            return

        # If user just wanted us to regenerate the manifest, stop now.
        if self.manifest_only:
            return

        # Otherwise, go ahead and create the source distribution tarball,
        # or zipfile, or whatever.
        self.make_distribution()
        return

    def get_file_list(self):
        """
        Figure out the list of files to include in the source
        distribution, and put it in 'self.filelist'.
        """

        self.filelist.findall()

        # Add default file set to 'files'
        self.add_defaults()

        # Process the manifest template lines
        if not self.distribution.manifest_templates:
            self.warn("using default file list only")
        else:
            for line in self.distribution.manifest_templates:
                try:
                    self.filelist.process_template_line(line)
                except DistutilsTemplateError, msg:
                    self.warn(str(msg))
        
        # Prune away any directories that don't belong in the source
        # distribution
        self.prune_file_list(self.filelist)

        # File list now complete -- sort it so that higher-level files
        # come first
        self.filelist.sort()

        # Remove duplicates from the file list
        self.filelist.remove_duplicates()
        return

    def add_defaults(self):
        """Add all the default files to self.filelist:
          - setup.py
          - README or README.txt (in all directories)
          - all pure Python modules mentioned in setup script
          - all C sources listed as part of extensions or C libraries
            in the setup script (doesn't catch C headers!)
        """
        script_name = os.path.basename(self.distribution.script_name)
        self.filelist.append(script_name)

        self.filelist.include_pattern('README', anchor=0)
        self.filelist.include_pattern('README.txt', anchor=0)
        
        if self.distribution.has_pure_modules():
            build_py = self.get_finalized_command('build_py')
            self.filelist.extend(build_py.get_source_files())

        if self.distribution.has_ext_modules():
            build_ext = self.get_finalized_command('build_ext')
            self.filelist.extend(build_ext.get_source_files())

        if self.distribution.has_c_libraries():
            build_clib = self.get_finalized_command('build_clib')
            self.filelist.extend(build_clib.get_source_files())

        if self.distribution.has_l10n():
            build_l10n = self.get_finalized_command('build_l10n')
            self.filelist.extend(build_l10n.get_source_files())

        if self.distribution.has_scripts():
            build_scripts = self.get_finalized_command('build_scripts')
            self.filelist.extend(build_scripts.get_source_files())

        if self.distribution.has_docs():
            build_docs = self.get_finalized_command('build_docs')
            self.filelist.extend(build_docs.get_source_files())

        if self.distribution.has_tests():
            build_tests = self.get_finalized_command('build_tests')
            self.filelist.extend(build_tests.get_source_files())

        if self.distribution.has_bgen():
            generate_bgen = self.get_finalized_command('generate_bgen')
            self.filelist.extend(generate_bgen.get_source_files())

        if self.distribution.has_sysconf():
            cmd = self.get_finalized_command('install_sysconf')
            self.filelist.extend(cmd.get_inputs())

        if self.distribution.has_localstate():
            cmd = self.get_finalized_command('install_localstate')
            self.filelist.extend(cmd.get_inputs())

        if self.distribution.has_data_files():
            install_data = self.get_finalized_command('install_data')
            self.filelist.extend(install_data.get_inputs())
        return

    def prune_file_list(self, filelist):
        """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
        """
        config = self.get_finalized_command('config')
        filelist.exclude_pattern(config.cache_filename, anchor=1)

        build = self.get_finalized_command('build')
        base_dir = self.distribution.get_fullname()

        filelist.exclude_pattern(None, prefix=build.build_base)
        filelist.exclude_pattern(None, prefix=self.dist_dir)
        filelist.exclude_pattern(None, prefix=base_dir)
        filelist.exclude_pattern(None, prefix='CVS')
        filelist.exclude_pattern(r'\%sCVS\%s.*' % (os.sep, os.sep),
                                 is_regex=1)
        filelist.exclude_pattern('CVS.sandboxinfo')
        return filelist

    def validate(self):
        allfiles = filelist.FileList()
        allfiles.findall()

        # Start out with all files included
        allfiles.include_pattern(None)

        # Prune the same files as the distribution filelist
        self.prune_file_list(allfiles)

        # Remove additional CVS files used for bookkeeping
        allfiles.exclude_pattern(r'\.cvsignore$', is_regex=1)
        allfiles.exclude_pattern(r'\.#[^\%s]*$' % os.sep, is_regex=1)
        allfiles.exclude_pattern(r'\.(pyc|pyo)$', is_regex=1)
        allfiles.exclude_pattern(r'#[^\%s]*$' % os.sep, is_regex=1)

        if self.distribution.validate_templates:
            for line in self.distribution.validate_templates:
                try:
                    allfiles.process_template_line(line)
                except DistutilsTemplateError, msg:
                    self.warn(str(msg))
            
        # File list now complete -- sort it so that higher-level files
        # come first
        allfiles.sort()

        # Remove duplicates from the file list
        allfiles.remove_duplicates()
        
        # 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, allfiles.files)

        valid = 1
        for file in src_files:
            if file not in dist_files:
                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? (yes/no)')
            valid = prompt.lower() in ['y', 'yes']
        return valid
        

Commands = {
    'sdist' : SDist,
    }
