

import os
import StringIO

from fabric.api import run, put, get, cd
from fabric.contrib.files import exists


# THE MODULE VERSION
__version__ = '0.2.0'


class Venv(object):

    def __init__(self, venv, requirements=None, virtualenv='virtualenv'):
        '''
        venv: The (remote) path to a virtual environment.
        requirements: The (local) path to a requirements.txt file, for use with
        pip.
        virtualenv: The (remote) path or name of the virtualenv executable that
        is used to create the venv.  Defaults to 'virtualenv'.
        '''
        self.venv = venv
        self.requirements = requirements
        self.virtualenv = virtualenv

    def bin(self):
        '''
        return: path to the venv bin dir.
        '''
        return os.path.join(self.venv, 'bin')

    def python(self):
        '''
        return: path to the venv python executable.
        '''
        return os.path.join(self.venv, 'bin', 'python')

    def pip(self):
        '''
        return: path to the venv pip executable.
        '''
        return os.path.join(self.venv, 'bin', 'pip')

    def exists(self):
        '''
        Return True if the venv has already been created.  This is implemented
        simply by testing if the venv directory exists and the python executable
        with it exists too.
        '''
        return exists(self.venv) and exists(self.python())

    def create(self):
        '''
        On the remote host, use an installed virtualenv executable (e.g. the
        globally installed virtualenv on the path) to create a virutal
        environment.  The venv directory MUST NOT already exist.
        '''
        # require that the venv does not yet exist.
        if self.exists():
            raise Exception('Path already exists. Abort creation. venv={}'.format(self.venv))

        # create the venv dir
        run('mkdir -p {}'.format(self.venv))

        # create the virtual environment
        run('{virtualenv} {venv}'.format(virtualenv=self.virtualenv, venv=self.venv))

    def install(self):
        '''
        Use pip to install the requirements file.
        '''
        remote_path = os.path.join(self.venv, 'requirements.txt')
        put(self.requirements, remote_path)
        run('{pip} install -r {requirements}'.format(
            pip=self.pip(), requirements=remote_path))

    def upgrade(self):
        '''
        Use pip to upgrade all packages in the requirements file "to the newest
        available version. This process is recursive irregardless of whether a
        dependency is already satisfied."
        '''
        remote_path = os.path.join(self.venv, 'requirements.txt')
        put(self.requirements, remote_path)
        run('{pip} install --upgrade -r {requirements}'.format(
            pip=self.pip(), requirements=remote_path))

    def freeze(self):
        '''
        Use pip to freeze the requirements and save them to the local
        requirements.txt file.
        '''
        remote_path = os.path.join(self.venv, 'requirements.txt')
        run('{} freeze > {}'.format(self.pip(), remote_path))
        get(remote_path, self.requirements)

    def remove(self):
        '''
        Remove the virtual environment completely
        '''
        print 'remove'
        if self.exists():
            print 'cleaning', self.venv
            run('rm -rf {}'.format(self.venv))

    def site_packages_dir(self):
        return os.path.join(self.venv, 'lib', 'python2.7', 'site-packages')

    def venv_pth(self, dirs):
        '''
        Add the directories in `dirs` to the `sys.path`.  A venv.pth file
        will be written in the site-packages dir of this virtualenv to add
        dirs to sys.path.

        dirs: a list of directories.
        '''
        # Create venv.pth to add dirs to sys.path when using the virtualenv.
        text = StringIO.StringIO()
        text.write("# Autogenerated file.  Do not modify.\n")
        for path in dirs:
            text.write('{}\n'.format(path))
        put(text, os.path.join(self.site_packages_dir(), 'venv.pth'), mode=0664)


