import os
from distutils import dir_util
from distutils import filelist
from distutils.command import sdist

class SDist(sdist.sdist):
    """
    Extended 'sdist' command that uses 'manifest_templates' from the
    distribution options instead of manifest files.
    """
    command_name = 'sdist'

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

    user_options = [
        ('use-defaults', None,
         "include the default file set in the manifest "
         "[default; disable with --no-defaults]"),
        ('no-defaults', None,
         "don't include the default file set"),
        ('prune', None,
         "specifically exclude files/directories that should not be "
         "distributed (build tree, RCS/CVS dirs, etc.) "
         "[default; disable with --no-prune]"),
        ('no-prune', None,
         "don't automatically exclude anything"),
        ('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 get_file_list(self):
        """
        Figure out the list of files to include in the source
        distribution, and put it in 'self.filelist'.
        """
        self.filelist.set_allfiles(self.distribution.get_allfiles())

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

        # Process manifest template lines
        if not self.distribution.manifest_templates:
            self.announce("using default file list only", 1)
        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
        if self.prune:
            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!)
        """
        self.filelist.include_pattern('README*')
        self.filelist.include_pattern('COPYRIGHT')

        # Add the files from the distribution
        self.filelist.extend(self.distribution.get_source_files())
        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')
        filelist.exclude_pattern(None, prefix=build.build_base)

        filelist.exclude_pattern(None, prefix=self.dist_dir)

        # Trim the source code tree
        base_dir = self.distribution.get_fullname()
        filelist.exclude_pattern(None, prefix=base_dir)

        # Trim the documentation tree
        base_dir = '%s-docs-%s' % (self.distribution.get_name(),
                                   self.distribution.get_version())
        filelist.exclude_pattern(None, prefix=base_dir)

        return filelist

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

        # Create the source code distribution
        sdist.sdist.make_distribution(self)

        # Create the documentation distribution
        base_dir = '%s-docs-%s' % (self.distribution.get_name(),
                                   self.distribution.get_version())
        base_name = os.path.join(self.dist_dir, base_dir)

        # Make the documentation tree
        install = self.reinitialize_command('install')
        install.install_docs = base_dir
        install.ensure_finalized()
        for command_name in ('install_text', 'install_html'):
            command = self.reinitialize_command(command_name)
            command.ensure_finalized()
            command.run()

        # Archive the documentation tree
        for fmt in self.formats:
            file = self.make_archive(base_name, fmt, base_dir=base_dir)
            self.archive_files.append(file)
            if hasattr(self.distribution, 'dist_files'):
                # Save it for the "upload" command as well.
                self.distribution.dist_files.append(('sdist', '', file))

        if not self.keep_temp:
            dir_util.remove_tree(base_dir, dry_run=self.dry_run)
        return

    def check_distribution(self):
        # Start out with all files included
        allfiles = filelist.FileList()
        allfiles.set_allfiles([])
        allfiles.extend(self.distribution.get_allfiles())

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

        # Exclude files included by other sub-packages
        validate_distributions = [self.distribution]
        if self.distribution.main_distribution:
            main_distribution = self.distribution.main_distribution
            validate_distributions.append(main_distribution)
            for name in main_distribution.package_options:
                dist = main_distribution.get_package_distribution(name)
                if dist is not self.distribution:
                    # Remove the other sub-package source files
                    for filename in dist.get_source_files():
                        allfiles.exclude_pattern(filename)
                    validate_distributions.append(dist)

        # Process the ignorable files for all available distributions
        for distribution in validate_distributions:
            for line in distribution.validate_templates:
                try:
                    allfiles.process_template_line(line)
                except DistutilsTemplateError, msg:
                    self.warn('in %s: %s' % (dist.package_file, 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 source distribution: %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
