Source

xenv / xenv / models.py

Full commit
import os
from paver.easy import path
from subprocess import Popen, call, check_output
from urllib2 import urlopen
from urlparse import urljoin


class XenvFS(object):
    '''The xenv filesystem object provides the specification for the
    xenv filesystem. This will allow finding directories, establishing
    the PATH when calling a command along with locations for specific
    files.'''
    def __init__(self, root=None):
        self.root = path(root or '.')

    def create_base_env(self):
        (self.root / 'local').mkdir()
    
    def create_venv(self):
        venv = self.root.abspath() / 'usr'
        cmd = 'virtualenv --no-site-packages %s' % venv
        call(cmd.split())

    def path(self):
        '''Return the path hiearchy'''
        return ':'.join([
                (self.root / 'usr' / 'bin').abspath(),
                (self.root / 'local' / 'bin').abspath(),
                '/bin', '/usr/bin', '/usr/local/bin',
                ])


class Xenv(object):
    def __init__(self, root=None):
        self.xenv_fs = XenvFS(root)
        self.root = self.xenv_fs.root.abspath()

    def run(self, cmd):
        env = {
            'PATH': self.xenv_fs.path()
        }
        try:
            print cmd
            call(cmd, env=env)
        except KeyboardInterrupt:
            pass

    def create(self):
        self.xenv_fs.root.mkdir()
        self.xenv_fs.create_base_env()
        self.xenv_fs.create_venv()


    def install(self, pkg):
        pkgfile = pkg.fetch(self)
        call(['tar', 'xf', pkgfile], cwd=self.root)
        install_hook = self.root / 'packages' / 'install'

        history = self.root / 'packages' / pkg.name / pkg.version
        history.makedirs()

        if install_hook.isfile():
            call(install_hook.abspath(), cwd=self.root)
            install_hook.move(history)
        (self.root/ pkg.fname).move(history)

    def uninstall(self, pkg):
        filelist = check_output(['tar', 'tf', self.root/'packages'/pkg.name/pkg.version/pkg.fname])
        for line in filelist.split('\n'):
            name = path(line)
            if name.isfile():
                name.remove()
                
        

               
class Package(object):
    '''
    A package is a single file that contains a directory structure to
    untar on top a xenv. It may place files anywhere in the xenv and
    is responsible for not corrupting the environment.

    The only restriction is that it may not add any hidden (.) files
    to the root directory.

    Package Name
    ============

    The package must be a tarball (.tar) or gzipped tar (.tar.gz). It
    must be named using the following pattern: ::
    
      {package-name}-{version}.tar[.gz]

    The version must comply with the semver.org specification.

    File Hiearchy
    =============

    The file hierarchy can be whatever the package creator likes. The
    only restrictions are the following.


    The /usr Directory
    ------------------

    The usr directory is where an xenv installs its core utilities and
    executables. For example, we install a virtualenv in /usr for
    using Python.

    
    The /local Directory
    --------------------

    If you create a /local/bin, it will be added to the PATH when
    running something via xe.


    Installation Hooks
    ==================

    A xenv package does not aim to be a "real" package format and as
    such, it may wrap a more complex package such as an RPM. For this
    reason we provide a package hook in order to allow installing
    these sorts of packages.

    When a package is installed (untarred) in the xenv, an install
    script can be placed at /packages/install. This file must be
    executable. It may use any language that is available on the host
    system via a shebang.

    After it is installed, the install script will be moved to: ::
      /packages/{package-name}/{version}/install
    '''

    def __init__(self, name, version, base='file:'):
        self.name = name
        self.version = version
        self.base = base
        self.pkg_tmpl = '{self.name}-{self.version}.tar'

    @property
    def fname(self):
        return path(self.pkg_tmpl.format(**vars()))

    def fetch(self, xenv):
        if self.base.startswith('file:'):
            fname = self.fname.abspath()
        url = urljoin(self.base, fname)
        pkgfile = urlopen(url)
        return self.save(pkgfile, xenv)

    def save(self, fh, xenv):
        fname = self.pkg_tmpl.format(**vars())
        pkgfile = xenv.root.abspath() / fname
        with open(pkgfile, 'w+') as pkg:
            for line in fh:
                pkg.write(line)
        return pkgfile