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 global 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('COPYRIGHT', anchor=0)
        
        # Add the files from the selected package and its dependencies
        for name in self.distribution.sub_packages:
            dist = self.distribution.get_package_distribution(name)
            self.filelist.extend(dist.get_source_files(self.filelist.allfiles))
        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)

        for name in self.distribution.package_info:
            dist = self.distribution.get_package_distribution(name)

            # Exclude those files eliminated by the 'package' option.
            if name not in self.distribution.sub_packages:
                # Remove package-local source files
                for filename in dist.get_source_files(allfiles.files):
                    allfiles.files.remove(filename)

                # Process package-local ignorable files
                if dist.validate_templates:
                    for line in dist.validate_templates:
                        try:
                            allfiles.process_template_line(line)
                        except DistutilsTemplateError, msg:
                            self.warn(str(msg))

        # Process global ignorable files
        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
