"""Bootstrap for package loaders.

This allows automatic downloading and installation of pure-Python
packages that have a setup.py file that depends on distutils only.  It
supports zip and tar files, the latter uncompressed or compressed with
gzip or bz2.
"""

import os
import sys
import glob
import getopt
import shutil
import urllib
import tarfile
import zipfile
import tempfile

def main(args=None):
    """Main function invoked by python -m bootstrap ...

    Command line flags:
      (None)

    Positional command line arguments:
      url: the URL to download
    """
    if args is None:
        args = sys.argv[1:]

    try:
        opts, args = getopt.gnu_getopt(args, "")
    except getopt.GetoptError, err:
        print >>sys.stderr, err
        sys.exit(2)

    if not args:
        print >>sys.stderr, "Please specify a URL on the command line."
        sys.exit(2)
    url = args[0]
    if args[1:]:
        print >>sys.stderr, "Too many arguments.  Only one URL is allowed."
        sys.exit(2)

    zip = False
    tar = False
    compression = None
    if url.endswith(".zip"):
        zip = True
    elif url.endswith(".tgz") or url.endswith(".tar.gz"):
        tar = True
        compression = "gz"
    elif url.endswith(".tar.bz2"):
        tar = True
        compression = "bz2"
    elif url.endswith(".tar"):
        tar = True
    else:
        print >>sys.stderr, "Can't figure out download type; expected .zip, .tar, .tar.gz, .tar.bz2, or .tgz"
        sys.exit(2)

    try:
        tempstream = tempfile.TemporaryFile()
    except Exception, err:
        print >>sys.stderr, "Can't create temporary file; %s: %s" % (err.__class__.__name__, err)
        sys.exit(1)

    try:
        try:
            tempdir = tempfile.mkdtemp()
        except Exception, err:
            print >>sys.stderr, "Can't create temporary directory; %s: %s" % (err.__class__.__name__, err)
            sys.exit(1)

        try:
            try:
                netstream = urllib.urlopen(url)
                code = 200
                if hasattr(netstream, "getcode"):
                    code = netstream.getcode()
                if not 200 <= code < 300:
                    raise ValueError("HTTP Error code %s" % code)
            except Exception, err:
                print >>sys.stderr, "Can't open URL; %s: %s" % (err.__class__.__name__, err)
                sys.exit(1)

            BUFSIZE = 2**13  # 8KB
            size = 0
            while True:
                data = netstream.read(BUFSIZE)
                if not data:
                    break
                tempstream.write(data)
                size += len(data)
            netstream.close()
            print "Downloaded %d bytes." % size

            tempstream.seek(0)
            if zip:
                unpack_zip(tempstream, tempdir)
            elif tar:
                unpack_tar(tempstream, compression, tempdir)
            else:
                assert False, "Neither zip nor tar (should have been caught earlier)."

            print "Extracted all into", tempdir
            run_setup(tempdir)

        finally:
            shutil.rmtree(tempdir)

    finally:
        tempstream.close()

def unpack_tar(stream, compression, destdir):
    mode = "r|"
    if compression:
        mode += compression
    tarstream = tarfile.open("", mode=mode, fileobj=stream)
    try:
        for tarinfo in tarstream:
            name = tarinfo.name
            if bad_name(name):
                print "%s: skipping" % name
                continue
            print "%s: extracting" % name
            filestream = tarstream.extractfile(tarinfo)
            data = filestream.read()
            filestream.close()
            save_file(data, destdir, name)
    finally:
        tarstream.close()

def unpack_zip(stream, destdir):
    zipstream = zipfile.ZipFile(stream, mode="r")
    try:
        for zipinfo in zipstream.infolist():
            name = zipinfo.filename
            if bad_name(name):
                print "%s: skipping" % name
                continue
            print "%s: extracting" % name
            data = zipstream.read(name)
            save_file(data, destdir, name)
    finally:
        zipstream.close()

def bad_name(name):
    return ("\\" in name or name.startswith("/") or name.endswith("/") or
            ".." in name or name.endswith("/."))

def save_file(data, destdir, name):
    target = os.path.join(destdir, name)
    dirname = os.path.dirname(target)
    if not os.path.isdir(dirname):
        try:
            os.makedirs(dirname)
        except os.error, err:
            print >>sys.stderr, "Can't create extraction directory: %s" % str(err)
            return
    try:
        wstream = open(target, "wb")
    except IOError, err:
        print >>sys.stderr, "Can't create extraction target file: %s" % str(err)
        return
    try:
        wstream.write(data)
    finally:
        wstream.close()

def run_setup(destdir):
    setup_py = os.path.join(destdir, "setup.py")
    if not os.path.exists(setup_py):
        setups = glob.glob(os.path.join(destdir, "*/setup.py"))
        if not setups:
            print >>sys.stderr, "Can't find a setup.py file in there"
            sys.exit(1)
        if len(setups) > 1:
            print >>sys.stderr, "Too many setup.py files found"
            sys.exit(1)
        setup_py = setups[0]
        destdir = os.path.dirname(setup_py)
    namespace = {"__name__": "__main__", "__file__": setup_py}
    save_sys_argv = sys.argv
    save_pwd = os.getcwd()
    try:
        sys.argv = [setup_py, "install"]
        os.chdir(destdir)
        print "Running setup_py install"
        execfile(setup_py, namespace)
    finally:
        sys.argv = save_sys_argv
        os.chdir(save_pwd)
    print "Running setup.py completed"

if __name__ == "__main__":
    main()
