import os, glob
from types import *
from distutils.command import build_py

class BuildTests(build_py.build_py):

    command_name = 'build_tests'

    description = "build test files"

    user_options = [
        ('build-dir=', 'd', "directory to \"build\" (copy) to"),
        ('force', 'f',
         "forcibly build everything (ignore file timestamps)"),
        ('compile', 'c', "compile .py to .pyc"),
        ('no-compile', None, "don't compile .py files [default]"),
        ('optimize=', 'O',
         "also compile with optimization: -O1 for \"python -O\", "
         "-O2 for \"python -OO\", and -O0 to disable [default: -O0]"),
        ]

    boolean_options = ['force', 'compile']
    negative_opt = {'no-compile' : 'compile'}
        
    def initialize_options(self):
        self.force = None
        self.build_dir = None
        self.compile = 0
        self.optimize = 0
        self.outfiles = []
        return

    def finalize_options(self):
        self.tests = self.distribution.tests or []
        self.tests_basedir = 'test'

        self.set_undefined_options('build',
                                   ('build_tests', 'build_dir'),
                                   ('force', 'force'))

        if type(self.optimize) is not IntType:
            try:
                self.optimize = int(self.optimize)
                assert 0 <= self.optimize <= 2
            except (ValueError, AssertionError):
                raise DistutilsOptionError, "optimize must be 0, 1, or 2"
        return

    def run(self):
        for package in self.tests:
            # Get list of (package, module, module_file) tuples based on
            # scanning the package directory.  'package' is only included
            # in the tuple so that 'find_modules()' and
            # 'find_package_tuples()' have a consistent interface; it's
            # ignored here (apart from a sanity check).  Also, 'module' is
            # the *unqualified* module name (ie. no dots, no package -- we
            # already know its package!), and 'module_file' is the path to
            # the .py file, relative to the current directory
            # (ie. including 'package_dir').
            package_dir = self.get_package_dir(package)
            tests = self.find_package_tests(package, package_dir)

            # Now loop over the modules we found, "building" each one (just
            # copy it to self.build_lib).
            for (package_, test, test_file) in tests:
                assert package == package_
                self.build_test(package, test, test_file)

        self.byte_compile(self.get_outputs(include_bytecode=0))
        return

    def find_package_tests(self, package, package_dir):
        readme = os.path.join(package_dir, 'README')
        if os.path.exists(readme):
            test_files = [readme]
        else:
            test_files = []

        glob_spec = os.path.join(package_dir, '*.*')
        test_files.extend(glob.glob(glob_spec))
        tests = []
        setup_script = os.path.abspath(self.distribution.script_name)

        for f in test_files:
            abs_f = os.path.abspath(f)
            if abs_f != setup_script:
                test = os.path.basename(f)
                tests.append((package, test, f))
            else:
                self.debug_print("excluding %s" % setup_script)
        return tests
        
    def get_test_outfile(self, build_dir, package, test):
        if type(package) is StringType:
            package = package.split('.')
        elif type(package) not in (ListType, TupleType):
            raise TypeError, \
                  "'package' must be a string (dot-separated), list, or tuple"
        outfile_path = [build_dir] + list(package) + [test]
        return apply(os.path.join, outfile_path)

    def build_test(self, package, test, test_file):
        outfile = self.get_test_outfile(self.build_dir, package, test)
        dir = os.path.dirname(outfile)
        self.mkpath(dir)
        return self.copy_file(test_file, outfile, preserve_mode=0)

    def get_outputs (self, include_bytecode=1):
        modules = self.find_all_tests()
        outputs = []
        for (package, test, test_file) in modules:
            filename = self.get_test_outfile(self.build_dir, package, test)
            outputs.append(filename)
            if include_bytecode and filename.endswith('.py'):
                if self.compile:
                    outputs.append(filename + "c")
                if self.optimize > 0:
                    outputs.append(filename + "o")
        return outputs

    def find_all_tests(self):
        tests = []
        for package in self.tests:
            package_dir = self.get_package_dir(package)
            t = self.find_package_tests(package, package_dir)
            tests.extend(t)
        return tests

    def get_source_files(self):
        tests = self.find_all_tests()
        filenames = []
        for package, test, source in tests:
            filenames.append(source)
        return filenames

    def get_package_dir (self, package):
        """Return the directory, relative to the top of the source
           distribution, where package 'package' should be found."""
        return os.path.join(self.tests_basedir, *package.split('.'))

    def byte_compile(self, files):
        from distutils.util import byte_compile
        prefix = self.build_dir
        if prefix[-1] != os.sep:
            prefix = prefix + os.sep

        if self.compile:
            byte_compile(files, optimize=0,
                         force=self.force,
                         prefix=prefix,
                         verbose=self.verbose, dry_run=self.dry_run)
        if self.optimize > 0:
            byte_compile(files, optimize=self.optimize,
                         force=self.force,
                         prefix=prefix,
                         verbose=self.verbose, dry_run=self.dry_run)
