<commit_before>#!python
"""\
Easy Install
============

Easy Install is a python module (easy_install) that lets you automatically
download, build, install, and manage Python packages.

.. contents:: **Table of Contents**


Downloading and Installing a Package
------------------------------------

For basic use of ``easy_install``, you need only supply the filename or URL of
a source distribution or .egg file (`Python Egg`__).

__ http://peak.telecommunity.com/DevCenter/PythonEggs

**Example 1**. Download a source distribution, automatically building and
installing it::

    easy_install http://example.com/path/to/MyPackage-1.2.3.tgz

**Example 2**. Install an already-downloaded .egg file::

    easy_install /my_downloads/OtherPackage-3.2.1-py2.3.egg

Easy Install recognizes distutils *source* (not binary) distribution files with
extensions of .tgz, .tar, .tar.gz, .tar.bz2, or .zip. And of course it handles
already-built .egg distributions.

By default, packages are installed to the running Python installation's
``site-packages`` directory, unless you provide the ``-d`` or ``--install-dir``
option to specify an alternative directory.

Packages installed to ``site-packages`` are added to an ``easy-install.pth``
file, so that Python will be able to import the package by default. If you do
not want this to happen, you should use the ``-m`` or ``--multi`` option, which
allows multiple versions of the same package to be selected at runtime.

Note that installing to a directory other than ``site-packages`` already
implies the ``-m`` option, so if you cannot install to ``site-packages``,
please see the `Options`_ section below (under ``--multi``) to find out how to
select packages at runtime.


Upgrading a Package
-------------------

You don't need to do anything special to upgrade a package: just install the
new version. If you're using ``-m`` or ``--multi`` (or installing outside of
``site-packages``), the runtime system automatically selects the newest
available version of a package. If you're installing to ``site-packages`` and
not using ``-m``, installing a package automatically replaces its older version
in the ``easy-install.pth`` file, so that Python will import the latest version
by default.

``easy_install`` never actually deletes packages (unless you're installing a
package with the same name and version number as an existing package), so if
you want to get rid of older versions of a package, please see `Uninstalling
Packages`_, below.


Changing the Active Version (``site-packages`` installs only)
-------------------------------------------------------------

If you've upgraded a package, but need to revert to a previously-installed
version, you can do so like this::

    easy_install PackageName==1.2.3

Where ``1.2.3`` is replaced by the exact version number you wish to switch to.
Note that the named package and version must already have been installed to
``site-packages``.

If you'd like to switch to the latest version of ``PackageName``, you can do so
like this::

    easy_install PackageName

This will activate the latest installed version.


Uninstalling Packages
---------------------

If you have replaced a package with another version, then you can just delete
the package(s) you don't need by deleting the PackageName-versioninfo.egg file
or directory (found in the installation directory).

If you want to delete the currently installed version of a package (or all
versions of a package), you should first run::

    easy_install -m PackageName

This will ensure that Python doesn't continue to search for a package you're
planning to remove. After you've done this, you can safely delete the .egg
files or directories.


Options
-------

``--zip, -z``
    Enable installing the package as a zip file. This can significantly
    increase Python's overall import performance if you're installing to
    ``site-packages`` and not using the ``--multi`` option, because Python
    process zipfile entries on ``sys.path`` much faster than it does
    directories. So, if you don't use this option, and you install a lot of
    packages, some of them may be slower to import.

    But, this option is disabled by default, unless you're installing from an
    already-built binary zipfile (``.egg`` file). This is to avoid problems
    when using packages that dosn't support running from a zip file. Such
    packages usually access data files in their package directories using the
    Python ``__file__`` or ``__path__`` attribute, instead of the
    ``pkg_resources`` API. So, if you find that a package doesn't work properly
    when used with this option, you may want to suggest to the author that they
    switch to using the ``pkg_resources`` resource API, which will allow their
    package to work whether it's installed as a zipfile or not.

    (Note: this option only affects the installation of newly-built packages
    that are not already installed in the target directory; if you want to
    convert an existing installed version from zipped to unzipped or vice
    versa, you'll need to delete the existing version first.)

``--multi-version, -m``
    "Multi-version" mode. Specifying this option prevents ``easy_install`` from
    adding an ``easy-install.pth`` entry for the package being installed, and
    if an entry for any version the package already exists, it will be removed
    upon successful installation. In multi-version mode, no specific version of
    the package is available for importing, unless you use
    ``pkg_resources.require()`` to put it on ``sys.path``. This can be as
    simple as::

        from pkg_resources import require
        require("SomePackage", "OtherPackage", "MyPackage")

    which will put the latest installed version of the specified packages on
    ``sys.path`` for you. (For more advanced uses, like selecting specific
    versions and enabling optional dependencies, see the ``pkg_resources`` API
    doc.) Note that if you install to a directory other than ``site-packages``,
    this option is automatically in effect, because ``.pth`` files can only be
    used in ``site-packages`` (at least in Python 2.3 and 2.4). So, if you use
    the ``--install-dir`` or ``-i`` options, you must also use ``require()`` to
    enable packages at runtime

``--install-dir=DIR, -d DIR``
    Set the installation directory. It is up to you to ensure that this
    directory is on ``sys.path`` at runtime, and to use
    ``pkg_resources.require()`` to enable the installed package(s) that you
    need.
"""

import sys, os.path, pkg_resources, re, zipimport
from pkg_resources import *







class Installer:
    """Manage a download/build/install process"""

    pth_file = None

    def __init__(self, instdir=None, zip_ok=False, multi=None, tmpdir=None):
        from distutils.sysconfig import get_python_lib
        site_packages = get_python_lib()

        if tmpdir is None:
            from tempfile import mkdtemp
            tmpdir = mkdtemp(prefix="easy_install-")

        self.tmpdir = tmpdir

        if instdir is None or self.samefile(site_packages,instdir):
            instdir = site_packages
            self.pth_file = PthDistributions(
                os.path.join(instdir,'easy-install.pth')
            )
        elif multi is None:
            multi = True

        elif not multi:
            # explicit false, raise an error
            raise RuntimeError(
                "Can't do single-version installs outside site-packages"
            )

        self.instdir = instdir
        self.zip_ok = zip_ok
        self.multi = multi

    def close(self):
        if os.path.isdir(self.tmpdir):
            from shutil import rmtree
            rmtree(self.tmpdir,True)

    def __del__(self):
        self.close()

    def samefile(self,p1,p2):
        try:
            os.path.samefile
        except AttributeError:
            return (
                os.path.normpath(os.path.normcase(p1)) ==
                os.path.normpath(os.path.normcase(p2))
            )
        else:
            return os.path.samefile(p1,p2)

    def download(self, spec):
        """Locate and/or download or `spec`, returning a local filename"""
        if isinstance(spec,Requirement):
            pass
        else:
            scheme = URL_SCHEME(spec)
            if scheme:
                # It's a url, download it to self.tmpdir
                return self._download_url(scheme, spec)

            elif os.path.exists(spec):
                # Existing file or directory, just return it
                return spec
            else:
                try:
                    spec = Requirement.parse(spec)
                except ValueError:
                    raise RuntimeError(
                        "Not a URL, existing file, or requirement spec: %r" %
                        (spec,)
                    )
        # process a Requirement
        dist = AvailableDistributions().best_match(spec,[])
        if dist is not None and dist.path.endswith('.egg'):
            return dist.path

        # TODO: search here for a distro to download

        raise DistributionNotFound(spec)

    def install_eggs(self, dist_filename):
        # .egg dirs or files are already built, so just return them
        if dist_filename.lower().endswith('.egg'):
            return self.install_egg(dist_filename,True)

        # Anything else, try to extract and build
        import zipfile, tarfile
        if zipfile.is_zipfile(dist_filename):
            self._extract_zip(dist_filename, self.tmpdir)
        else:
            import tarfile
            try:
                tar = tarfile.open(dist_filename)
            except tarfile.TarError:
                raise RuntimeError(
                    "Not a valid tar or zip archive: %s" % dist_filename
                )
            else:
                self._extract_tar(tar)

        # Find the setup.py file
        from glob import glob
        setup_script = os.path.join(self.tmpdir, 'setup.py')
        if not os.path.exists(setup_script):
            setups = glob(os.path.join(self.tmpdir, '*', 'setup.py'))
            if not setups:
                raise RuntimeError(
                    "Couldn't find a setup script in %s" % dist_filename
                )
            if len(setups)>1:
                raise RuntimeError(
                    "Multiple setup scripts in %s" % dist_filename
                )
            setup_script = setups[0]

        self._run_setup(setup_script)
        for egg in glob(
            os.path.join(os.path.dirname(setup_script),'dist','*.egg')
        ):
            self.install_egg(egg, self.zip_ok)

    def _extract_zip(self,zipname,extract_dir):
        import zipfile
        z = zipfile.ZipFile(zipname)

        try:
            for info in z.infolist():
                name = info.filename

                # don't extract absolute paths or ones with .. in them
                if name.startswith('/') or '..' in name:
                    continue

                target = os.path.join(extract_dir,name)
                if name.endswith('/'):
                    # directory
                    ensure_directory(target)
                else:
                    # file
                    ensure_directory(target)
                    data = z.read(info.filename)
                    f = open(target,'wb')
                    try:
                        f.write(data)
                    finally:
                        f.close()
                        del data
        finally:
            z.close()

    def _extract_tar(self,tarobj):
        try:
            tarobj.chown = lambda *args: None   # don't do any chowning!
            for member in tarobj:
                if member.isfile() or member.isdir():
                    name = member.name
                    # don't extract absolute paths or ones with .. in them
                    if not name.startswith('/') and '..' not in name:
                        tarobj.extract(member,self.tmpdir)
        finally:
            tarobj.close()

    def _run_setup(self, setup_script):
        from setuptools.command import bdist_egg
        sys.modules.setdefault('distutils.command.bdist_egg', bdist_egg)
        old_dir = os.getcwd()
        save_argv = sys.argv[:]
        save_path = sys.path[:]
        try:
            os.chdir(os.path.dirname(setup_script))
            try:
                sys.argv[:] = [setup_script, '-q', 'bdist_egg']
                sys.path.insert(0,os.getcwd())
                execfile(setup_script,
                    {'__file__':setup_script, '__name__':'__main__'}
                )
            except SystemExit, v:
                if v.args and v.args[0]:
                    raise RuntimeError(
                        "Setup script exited with %s" % (v.args[0],)
                    )
        finally:
            os.chdir(old_dir)
            sys.path[:] = save_path
            sys.argv[:] = save_argv


















    def install_egg(self, egg_path, zip_ok):
        import shutil
        destination = os.path.join(self.instdir, os.path.basename(egg_path))
        ensure_directory(destination)

        if not self.samefile(egg_path, destination):
            if os.path.isdir(destination):
                shutil.rmtree(destination)
            elif os.path.isfile(destination):
                os.unlink(destination)

            if zip_ok:
                if egg_path.startswith(self.tmpdir):
                    shutil.move(egg_path, destination)
                else:
                    shutil.copy2(egg_path, destination)

            elif os.path.isdir(egg_path):
                shutil.move(egg_path, destination)

            else:
                os.mkdir(destination)
                self._extract_zip(egg_path, destination)

        if self.pth_file is not None:
            if os.path.isdir(destination):
                dist = Distribution.from_filename(
                    destination, metadata=PathMetadata(
                        destination, os.path.join(destination,'EGG-INFO')
                    )
                )
            else:
                metadata = EggMetadata(zipimport.zipimporter(destination))
                dist = Distribution.from_filename(destination,metadata=metadata)

            # remove old
            map(self.pth_file.remove, self.pth_file.get(dist.key,()))
            if not self.multi:
                self.pth_file.add(dist) # add new
            self.pth_file.save()

    def _download_url(self, scheme, url):
        # Determine download filename
        from urlparse import urlparse
        name = filter(None,urlparse(url)[2].split('/'))[-1]
        while '..' in name:
            name = name.replace('..','.').replace('\\','_')

        # Download the file
        from urllib import FancyURLopener, URLopener
        class opener(FancyURLopener):
            http_error_default = URLopener.http_error_default
        try:
            filename,headers = opener().retrieve(
                url,os.path.join(self.tmpdir,name)
            )
        except IOError,v:
            if v.args and v.args[0]=='http error':
                raise RuntimeError(
                    "Download error: %s %s" % v.args[1:3]
                )
            else:
                raise
        # and return its filename
        return filename

















class PthDistributions(AvailableDistributions):
    """A .pth file with Distribution paths in it"""

    dirty = False

    def __init__(self, filename):
        self.filename = filename; self._load()
        AvailableDistributions.__init__(
            self, list(yield_lines(self.paths)), None, None
        )

    def _load(self):
        self.paths = []
        if os.path.isfile(self.filename):
            self.paths = [line.rstrip() for line in open(self.filename,'rt')]
            while self.paths and not self.paths[-1].strip(): self.paths.pop()

    def save(self):
        """Write changed .pth file back to disk"""
        if self.dirty:
            data = '\n'.join(self.paths+[''])
            f = open(self.filename,'wt')
            f.write(data)
            f.close()
            self.dirty = False

    def add(self,dist):
        """Add `dist` to the distribution map"""
        if dist.path not in self.paths:
            self.paths.append(dist.path); self.dirty = True
        AvailableDistributions.add(self,dist)

    def remove(self,dist):
        """Remove `dist` from the distribution map"""
        while dist.path in self.paths:
            self.paths.remove(dist.path); self.dirty = True
        AvailableDistributions.remove(self,dist)




URL_SCHEME = re.compile('([-+.a-z0-9]{2,}):',re.I).match

def main(argv):
    from optparse import OptionParser

    parser = OptionParser(usage = "usage: %prog [options] url [url...]")
    parser.add_option("-d", "--install-dir", dest="instdir", default=None,
                      help="install package to DIR", metavar="DIR")

    parser.add_option("-z", "--zip",
                      action="store_true", dest="zip_ok", default=False,
                      help="install package as a zipfile")

    parser.add_option("-m", "--multi-version",
                      action="store_true", dest="multi", default=None,
                      help="make apps have to require() a version")

    (options, args) = parser.parse_args()

    try:
        if not args:
            raise RuntimeError("No urls, filenames, or requirements specified")

        for spec in args:
            inst = Installer(options.instdir, options.zip_ok, options.multi)
            try:
                print "Downloading", spec
                downloaded = inst.download(spec)
                print "Installing", os.path.basename(downloaded)<commit_msg>Add support for using Installer subclasses, especially ones with support for searching a package database.<commit_after>#!python
"""\
Easy Install
============

Easy Install is a python module (easy_install) that lets you automatically
download, build, install, and manage Python packages.

.. contents:: **Table of Contents**


Downloading and Installing a Package
------------------------------------

For basic use of ``easy_install``, you need only supply the filename or URL of
a source distribution or .egg file (`Python Egg`__).

__ http://peak.telecommunity.com/DevCenter/PythonEggs

**Example 1**. Download a source distribution, automatically building and
installing it::

    easy_install http://example.com/path/to/MyPackage-1.2.3.tgz

**Example 2**. Install an already-downloaded .egg file::

    easy_install /my_downloads/OtherPackage-3.2.1-py2.3.egg

Easy Install recognizes distutils *source* (not binary) distribution files with
extensions of .tgz, .tar, .tar.gz, .tar.bz2, or .zip. And of course it handles
already-built .egg distributions.

By default, packages are installed to the running Python installation's
``site-packages`` directory, unless you provide the ``-d`` or ``--install-dir``
option to specify an alternative directory.

Packages installed to ``site-packages`` are added to an ``easy-install.pth``
file, so that Python will be able to import the package by default. If you do
not want this to happen, you should use the ``-m`` or ``--multi`` option, which
allows multiple versions of the same package to be selected at runtime.

Note that installing to a directory other than ``site-packages`` already
implies the ``-m`` option, so if you cannot install to ``site-packages``,
please see the `Options`_ section below (under ``--multi``) to find out how to
select packages at runtime.


Upgrading a Package
-------------------

You don't need to do anything special to upgrade a package: just install the
new version. If you're using ``-m`` or ``--multi`` (or installing outside of
``site-packages``), the runtime system automatically selects the newest
available version of a package. If you're installing to ``site-packages`` and
not using ``-m``, installing a package automatically replaces its older version
in the ``easy-install.pth`` file, so that Python will import the latest version
by default.

``easy_install`` never actually deletes packages (unless you're installing a
package with the same name and version number as an existing package), so if
you want to get rid of older versions of a package, please see `Uninstalling
Packages`_, below.


Changing the Active Version (``site-packages`` installs only)
-------------------------------------------------------------

If you've upgraded a package, but need to revert to a previously-installed
version, you can do so like this::

    easy_install PackageName==1.2.3

Where ``1.2.3`` is replaced by the exact version number you wish to switch to.
Note that the named package and version must already have been installed to
``site-packages``.

If you'd like to switch to the latest version of ``PackageName``, you can do so
like this::

    easy_install PackageName

This will activate the latest installed version.


Uninstalling Packages
---------------------

If you have replaced a package with another version, then you can just delete
the package(s) you don't need by deleting the PackageName-versioninfo.egg file
or directory (found in the installation directory).

If you want to delete the currently installed version of a package (or all
versions of a package), you should first run::

    easy_install -m PackageName

This will ensure that Python doesn't continue to search for a package you're
planning to remove. After you've done this, you can safely delete the .egg
files or directories.


Options
-------

``--zip, -z``
    Enable installing the package as a zip file. This can significantly
    increase Python's overall import performance if you're installing to
    ``site-packages`` and not using the ``--multi`` option, because Python
    process zipfile entries on ``sys.path`` much faster than it does
    directories. So, if you don't use this option, and you install a lot of
    packages, some of them may be slower to import.

    But, this option is disabled by default, unless you're installing from an
    already-built binary zipfile (``.egg`` file). This is to avoid problems
    when using packages that dosn't support running from a zip file. Such
    packages usually access data files in their package directories using the
    Python ``__file__`` or ``__path__`` attribute, instead of the
    ``pkg_resources`` API. So, if you find that a package doesn't work properly
    when used with this option, you may want to suggest to the author that they
    switch to using the ``pkg_resources`` resource API, which will allow their
    package to work whether it's installed as a zipfile or not.

    (Note: this option only affects the installation of newly-built packages
    that are not already installed in the target directory; if you want to
    convert an existing installed version from zipped to unzipped or vice
    versa, you'll need to delete the existing version first.)

``--multi-version, -m``
    "Multi-version" mode. Specifying this option prevents ``easy_install`` from
    adding an ``easy-install.pth`` entry for the package being installed, and
    if an entry for any version the package already exists, it will be removed
    upon successful installation. In multi-version mode, no specific version of
    the package is available for importing, unless you use
    ``pkg_resources.require()`` to put it on ``sys.path``. This can be as
    simple as::

        from pkg_resources import require
        require("SomePackage", "OtherPackage", "MyPackage")

    which will put the latest installed version of the specified packages on
    ``sys.path`` for you. (For more advanced uses, like selecting specific
    versions and enabling optional dependencies, see the ``pkg_resources`` API
    doc.) Note that if you install to a directory other than ``site-packages``,
    this option is automatically in effect, because ``.pth`` files can only be
    used in ``site-packages`` (at least in Python 2.3 and 2.4). So, if you use
    the ``--install-dir`` or ``-i`` options, you must also use ``require()`` to
    enable packages at runtime

``--install-dir=DIR, -d DIR``
    Set the installation directory. It is up to you to ensure that this
    directory is on ``sys.path`` at runtime, and to use
    ``pkg_resources.require()`` to enable the installed package(s) that you
    need.
"""

import sys, os.path, pkg_resources, re, zipimport
from pkg_resources import *







class Installer:
    """Manage a download/build/install process"""

    pth_file = None

    def __init__(self, instdir=None, zip_ok=False, multi=None, tmpdir=None):
        from distutils.sysconfig import get_python_lib
        site_packages = get_python_lib()

        if tmpdir is None:
            from tempfile import mkdtemp
            tmpdir = mkdtemp(prefix="easy_install-")

        self.tmpdir = tmpdir

        if instdir is None or self.samefile(site_packages,instdir):
            instdir = site_packages
            self.pth_file = PthDistributions(
                os.path.join(instdir,'easy-install.pth')
            )
        elif multi is None:
            multi = True

        elif not multi:
            # explicit false, raise an error
            raise RuntimeError(
                "Can't do single-version installs outside site-packages"
            )

        self.instdir = instdir
        self.zip_ok = zip_ok
        self.multi = multi

    def close(self):
        if os.path.isdir(self.tmpdir):
            from shutil import rmtree
            rmtree(self.tmpdir,True)

    def __del__(self):
        self.close()

    def samefile(self,p1,p2):
        try:
            os.path.samefile
        except AttributeError:
            return (
                os.path.normpath(os.path.normcase(p1)) ==
                os.path.normpath(os.path.normcase(p2))
            )
        else:
            return os.path.samefile(p1,p2)

    def download(self, spec):
        """Locate and/or download or `spec`, returning a local filename"""
        if isinstance(spec,Requirement):
            pass
        else:
            scheme = URL_SCHEME(spec)
            if scheme:
                # It's a url, download it to self.tmpdir
                return self._download_url(scheme, spec)

            elif os.path.exists(spec):
                # Existing file or directory, just return it
                return spec
            else:
                try:
                    spec = Requirement.parse(spec)
                except ValueError:
                    raise RuntimeError(
                        "Not a URL, existing file, or requirement spec: %r" %
                        (spec,)
                    )

        # process a Requirement
        dist = AvailableDistributions().best_match(spec,[])
        if dist is not None and dist.path.endswith('.egg'):
            return dist.path

        return self.download(self._find_package(spec))


    def install_eggs(self, dist_filename):
        # .egg dirs or files are already built, so just return them
        if dist_filename.lower().endswith('.egg'):
            return self.install_egg(dist_filename,True)

        # Anything else, try to extract and build
        import zipfile, tarfile
        if zipfile.is_zipfile(dist_filename):
            self._extract_zip(dist_filename, self.tmpdir)
        else:
            import tarfile
            try:
                tar = tarfile.open(dist_filename)
            except tarfile.TarError:
                raise RuntimeError(
                    "Not a valid tar or zip archive: %s" % dist_filename
                )
            else:
                self._extract_tar(tar)

        # Find the setup.py file
        from glob import glob
        setup_script = os.path.join(self.tmpdir, 'setup.py')
        if not os.path.exists(setup_script):
            setups = glob(os.path.join(self.tmpdir, '*', 'setup.py'))
            if not setups:
                raise RuntimeError(
                    "Couldn't find a setup script in %s" % dist_filename
                )
            if len(setups)>1:
                raise RuntimeError(
                    "Multiple setup scripts in %s" % dist_filename
                )
            setup_script = setups[0]

        self._run_setup(setup_script)
        for egg in glob(
            os.path.join(os.path.dirname(setup_script),'dist','*.egg')
        ):
            self.install_egg(egg, self.zip_ok)

    def _extract_zip(self,zipname,extract_dir):
        import zipfile
        z = zipfile.ZipFile(zipname)

        try:
            for info in z.infolist():
                name = info.filename

                # don't extract absolute paths or ones with .. in them
                if name.startswith('/') or '..' in name:
                    continue

                target = os.path.join(extract_dir,name)
                if name.endswith('/'):
                    # directory
                    ensure_directory(target)
                else:
                    # file
                    ensure_directory(target)
                    data = z.read(info.filename)
                    f = open(target,'wb')
                    try:
                        f.write(data)
                    finally:
                        f.close()
                        del data
        finally:
            z.close()

    def _extract_tar(self,tarobj):
        try:
            tarobj.chown = lambda *args: None   # don't do any chowning!
            for member in tarobj:
                if member.isfile() or member.isdir():
                    name = member.name
                    # don't extract absolute paths or ones with .. in them
                    if not name.startswith('/') and '..' not in name:
                        tarobj.extract(member,self.tmpdir)
        finally:
            tarobj.close()

    def _run_setup(self, setup_script):
        from setuptools.command import bdist_egg
        sys.modules.setdefault('distutils.command.bdist_egg', bdist_egg)
        old_dir = os.getcwd()
        save_argv = sys.argv[:]
        save_path = sys.path[:]
        try:
            os.chdir(os.path.dirname(setup_script))
            try:
                sys.argv[:] = [setup_script, '-q', 'bdist_egg']
                sys.path.insert(0,os.getcwd())
                execfile(setup_script,
                    {'__file__':setup_script, '__name__':'__main__'}
                )
            except SystemExit, v:
                if v.args and v.args[0]:
                    raise RuntimeError(
                        "Setup script exited with %s" % (v.args[0],)
                    )
        finally:
            os.chdir(old_dir)
            sys.path[:] = save_path
            sys.argv[:] = save_argv


    def _find_package(self, req):       
        # TODO: search here for a distro to download, matching Requirement
        # 'req' and return the package URL or filename
        raise DistributionNotFound(spec)












    def install_egg(self, egg_path, zip_ok):
        import shutil
        destination = os.path.join(self.instdir, os.path.basename(egg_path))
        ensure_directory(destination)

        if not self.samefile(egg_path, destination):
            if os.path.isdir(destination):
                shutil.rmtree(destination)
            elif os.path.isfile(destination):
                os.unlink(destination)

            if zip_ok:
                if egg_path.startswith(self.tmpdir):
                    shutil.move(egg_path, destination)
                else:
                    shutil.copy2(egg_path, destination)

            elif os.path.isdir(egg_path):
                shutil.move(egg_path, destination)

            else:
                os.mkdir(destination)
                self._extract_zip(egg_path, destination)

        if self.pth_file is not None:
            if os.path.isdir(destination):
                dist = Distribution.from_filename(
                    destination, metadata=PathMetadata(
                        destination, os.path.join(destination,'EGG-INFO')
                    )
                )
            else:
                metadata = EggMetadata(zipimport.zipimporter(destination))
                dist = Distribution.from_filename(destination,metadata=metadata)

            # remove old
            map(self.pth_file.remove, self.pth_file.get(dist.key,()))
            if not self.multi:
                self.pth_file.add(dist) # add new
            self.pth_file.save()

    def _download_url(self, scheme, url):
        # Determine download filename
        from urlparse import urlparse
        name = filter(None,urlparse(url)[2].split('/'))[-1]
        while '..' in name:
            name = name.replace('..','.').replace('\\','_')

        # Download the file
        from urllib import FancyURLopener, URLopener
        class opener(FancyURLopener):
            http_error_default = URLopener.http_error_default
        try:
            filename,headers = opener().retrieve(
                url,os.path.join(self.tmpdir,name)
            )
        except IOError,v:
            if v.args and v.args[0]=='http error':
                raise RuntimeError(
                    "Download error: %s %s" % v.args[1:3]
                )
            else:
                raise
        # and return its filename
        return filename

















class PthDistributions(AvailableDistributions):
    """A .pth file with Distribution paths in it"""

    dirty = False

    def __init__(self, filename):
        self.filename = filename; self._load()
        AvailableDistributions.__init__(
            self, list(yield_lines(self.paths)), None, None
        )

    def _load(self):
        self.paths = []
        if os.path.isfile(self.filename):
            self.paths = [line.rstrip() for line in open(self.filename,'rt')]
            while self.paths and not self.paths[-1].strip(): self.paths.pop()

    def save(self):
        """Write changed .pth file back to disk"""
        if self.dirty:
            data = '\n'.join(self.paths+[''])
            f = open(self.filename,'wt')
            f.write(data)
            f.close()
            self.dirty = False

    def add(self,dist):
        """Add `dist` to the distribution map"""
        if dist.path not in self.paths:
            self.paths.append(dist.path); self.dirty = True
        AvailableDistributions.add(self,dist)

    def remove(self,dist):
        """Remove `dist` from the distribution map"""
        while dist.path in self.paths:
            self.paths.remove(dist.path); self.dirty = True
        AvailableDistributions.remove(self,dist)




URL_SCHEME = re.compile('([-+.a-z0-9]{2,}):',re.I).match

def main(argv, factory=Installer):
    from optparse import OptionParser

    parser = OptionParser(usage = "usage: %prog [options] url [url...]")
    parser.add_option("-d", "--install-dir", dest="instdir", default=None,
                      help="install package to DIR", metavar="DIR")

    parser.add_option("-z", "--zip",
                      action="store_true", dest="zip_ok", default=False,
                      help="install package as a zipfile")

    parser.add_option("-m", "--multi-version",
                      action="store_true", dest="multi", default=None,
                      help="make apps have to require() a version")

    (options, args) = parser.parse_args()

    try:
        if not args:
            raise RuntimeError("No urls, filenames, or requirements specified")

        for spec in args:
            inst = factory(options.instdir, options.zip_ok, options.multi)
            try:
                print "Downloading", spec
                downloaded = inst.download(spec)
                print "Installing", os.path.basename(downloaded)
{'fraction_non_alphanumeric': 0.1005586592178771, 'fraction_numerical': 0.0023463687150837987, 'mean_word_length': 2.0502683820397034, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 39, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3658480', 'n_tokens_mistral': 9784, 'n_tokens_neox': 9243, 'n_words': 3257}