dejw / vip

vip is a simple library that makes your Python aware of existing virtualenv underneath.
3 stars 1 forks source link

Think of a better interface #6

Open dejw opened 11 years ago

dejw commented 11 years ago

Since I used vip only to install (update, or remove) libraries and run something using installed Python, interface can be on even higher level than it is now.

Example: vip install - create a virtualenv and update its state - install, update, remove libraries listed in requirements.txt file vip test [filename] - to run tests, using nose, py.test, unittest2 or simply executing python interpreter on given files vip locate [binary] - to locate binaries inside the virtualenv underneath

juntalis commented 11 years ago

I actually had an idea that sort of fell into this line of thinking, so I figured I'd post it here.

The idea was that by distributing shell scripts as vip's entry point instead of using the current vip.main:main entry point, you could actually handle the activation of the virtualenv in addition to the current functionality. The shell script could be fairly simple, as well. My idea was to have it do the following (replace the pre-existing command arguments here with whatever you decide to change them to in the end):

Unfortunately, for many of the posix-style shells, this would require adding a

vip() { source "$(which vip-script)"; } 

or something similar to their shell profile. (Or so I think) Windows could handle this in bat scripts and PowerShell scripts.

Anyways, I went ahead and wrote a basic bash script to illustrate my point. (Again, sorry if my bash scripting is a bit rusty)

#!/bin/bash

# Four global variables that are unset before completion.
if [ "$_vip_activate_script" ]; then
    # Make sure this starts unset.
    unset -v _vip_activate_script
fi
_vip_do_deactivate=0

# Maybe have this set during installation.
_vip_python="$(which python)"

# TODO: Make this a part of the command-line.
_vip_verbose=0

_vip_error() {
    local errmsg="$1"
    local errcode=${2:-$?}
    echo -en "\033[1m\e[31mError: $(tput sgr0)"
    echo "$errmsg"
    if [ "$_vip_activate_script" ]; then
        unset -v _vip_activate_script
    fi
    unset -v _vip_python _vip_do_deactivate _vip_verbose
    exit $errcode
}

_vip_log() {
    if [ $_vip_verbose -eq 1 ]; then
        echo -en "\033[1m\e[32mLOG: $(tput sgr0)"
        echo "$@"
    fi
}

_vip_maybe_activate() {
    local target=${1:-'.'}
    _vip_log "Checking for virtualenv at $target"
    local vipdir=`$0 -l "$target"`
    if [ $? -ne 0 ]; then
        _vip_error "Not a virtualenv (or any of the parent directories): $target"
    fi

    # I don't remember if bash has scope issues with running
    # environment-changing scripts from within the scope of a
    # function, so instead of executing the here, we'll store
    # it in our _vip_activate_script variable, and catch it
    # on the way out.
    _vip_activate_script="$vipdir/bin/activate"
    # Originally had a check here for [ -x "$_vip_activate_script" ],
    # but that doesn't work on cygwin.
    if [ -e "$_vip_activate_script" ]; then
        export _vip_activate_script
    else
        _vip_error "Found virtualenv, but no activate script at $_vip_activate_script" 1
    fi
}

_vip_maybe_deactivate() {
    type deactivate>/dev/null 2>&1
    # See the comments in _vip_maybe_activate
    if [ $? -eq 0 ]; then
        _vip_log "Deactivating.."
        export _vip_do_deactivate=1
    fi
}

_vip_toggle_activation() {
    _vip_log "Checking for activated virtualenv.."
    if [ "$VIRTUAL_ENV" ]; then
        _vip_log "Existing virtualenv detected at: $VIRTUAL_ENV"
        _vip_maybe_deactivate
    else
        _vip_log "No existing virtualenv active."
        _vip_maybe_activate $@
    fi
}

_vip_process_args() {
    # Check for a command that we handle, and pass
    # anything else along to vip.
    local args=("$_vip_python" "-m" "vip")
    local passthru=1
    local activation=0
    while test $# -gt 0; do
        local arg=$1
        case "$arg" in
            -a|--activate)
                # TODO: Add verbosity to activation process.
                activation=1
                passthru=0
            ;;
            -d|--deactivate)
                # TODO: Add verbosity to deactivation process.
                activation=2
                passthru=0
            ;;
            -i|--init)
                activation=1
                args=(${args[@]} "$arg")
            ;;
            *)
                args=(${args[@]} "$arg")
            ;;
        esac
        builtin shift
    done

    # First we'll run vip if we need to.
    if [ $passthru -eq 1 ]; then
        #_vip_process_args "Executing ${args[@]}"
        eval "${args[@]}"
        if [ $? -ne 0 ]; then
            exit $?
        fi
    fi

    # Could probably use a case structure here, but I don't
    # know if I need to quote numbers or not.
    if [ $activation -eq 1 ]; then
        # We need to cleanup args first.
        if [ ${#args[@]} -gt 3 ]; then
            args=(${args[@]:3})
        else
            args=()
        fi
        declare -a scriptargs=("_vip_maybe_activate" ${args[@]/-*/})
        eval "${scriptargs[@]}"
    elif [ $activation -eq 2 ]; then
        _vip_maybe_deactivate
    fi
}

if [ $# -gt 1 ]; then
    _vip_process_args $*
else
    _vip_toggle_activation
fi

if [ $_vip_do_deactivate -eq 1 ]; then
    _vip_log "Deactivating.."
    deactivate
fi

if [ "$_vip_activate_script" ]; then
    _vip_log "Activating.."
    _vip_log "$_vip_activate_script"
    source "$_vip_activate_script"
    unset -v _vip_activate_script
fi

unset -v _vip_python _vip_do_deactivate _vip_verbose

Anyways, if it sounds like a good idea to you, I can go ahead and write the bat and PowerShell variations. I've also been meaning to give myself an excuse to play around with and learn fish shell, so I could take a shot at writing a shell script for that, as well.

Note - The renaming of the vip script to vip-script for posix shells is only because I wasn't sure what sort of priority functions have on the executable search strategy. If functions take priority over actual executables/scripts, the rename obviously isn't necessary.

Note 2 - This would also require adding a __main__.py file to vip with the following:

# -*- coding: utf-8 -*-
from vip.main import main
if __name__ == "__main__":
    main()

Edit - It might actually save some overhead to handle most of this logic on python side, and add an optional argument, like --shell=bash that, if set, will silence or redirect all output to stderr, and if needed, output only the next command that the shell should execute in the dialect specified. (So on --init, for instance, it would initialize the virutalenv, and then output, source path/to/activate.