Commits

Michael Elsdörfer committed 506da41 Merge

Merged upstream.

Comments (0)

Files changed (19)

 =======================
- virtualenvwrapper 2.8
+ virtualenvwrapper 2.9
 =======================
 
 What is virtualenvwrapper
 making it easier to work on more than one project at a time without
 introducing conflicts in their dependencies.
 
-What's New in 2.8
+What's New in 2.9
 =================
 
-This release includes a fix to make ``cpvirtualenv`` use the copy of
-``virtualenv`` specified by the ``VIRTUALENVWRAPPER_VIRTUALENV``
-variable. It also adds support for running the wrapper commands under
-the MSYS environment on Microsoft Windows systems (contributed by
-noirbizarre).
+This release merges in the project directory management features
+previously delivered separately as ``virtualenvwrapper.project``.  The
+new command ``mkproject`` creates a working directory associated with
+a virtualenv, and can apply templates to populate the directory (for
+example, to create a new Django site).
+
+This release also adds a ``-r`` option to ``mkvirtualenv`` to specify
+a pip requirements file for packages that should be installed into the
+new environment after is is created.
+
+Upgrading to 2.9
+================
+
+Version 2.9 includes the features previously delivered separately by
+``virtualenvwrapper.project``.  If you have an older verison of the
+project extensions installed, remove them before upgrading.
 
 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
 

docs/en/command_ref.rst

 
 Syntax::
 
-    mkvirtualenv [options] ENVNAME
+    mkvirtualenv [-r requirements_file] [virtualenv options] ENVNAME
 
-All command line options are passed directly to ``virtualenv``.  The
-new environment is automatically activated after being initialized.
+All command line options except ``-r`` and ``-h`` are passed directly
+to ``virtualenv``.  The new environment is automatically activated
+after being initialized.
 
 ::
 
     mynewenv
     (mynewenv)$ 
 
+The ``-r`` option can be used to specify a text file listing packages
+to be installed. The argument value is passed to ``pip -r`` to be
+installed.
+
 .. seealso::
 
    * :ref:`scripts-premkvirtualenv`
    * :ref:`scripts-postmkvirtualenv`
+   * `requirements file format`_
+
+.. _requirements file format: http://www.pip-installer.org/en/latest/requirement-format.html
 
 .. _command-lsvirtualenv:
 
     Enabled global site-packages
     (env1)$ toggleglobalsitepackages -q
     (env1)$
+
+============================
+Project Directory Management
+============================
+
+.. seealso::
+
+   :ref:`project-management`
+
+.. _command-mkproject:
+
+mkproject
+---------
+
+Create a new virtualenv in the WORKON_HOME and project directory in
+PROJECT_HOME.
+
+Syntax::
+
+    mkproject [-t template] [virtualenv_options] ENVNAME
+
+The template option may be repeated to have several templates used to
+create a new project.  The templates are applied in the order named on
+the command line.  All other options are passed to ``mkvirtualenv`` to
+create a virtual environment with the same name as the project.
+
+::
+
+    $ mkproject myproj
+    New python executable in myproj/bin/python
+    Installing distribute.............................................
+    ..................................................................
+    ..................................................................
+    done.
+    Creating /Users/dhellmann/Devel/myproj
+    (myproj)$ pwd
+    /Users/dhellmann/Devel/myproj
+    (myproj)$ echo $VIRTUAL_ENV
+    /Users/dhellmann/Envs/myproj
+    (myproj)$ 
+
+.. seealso::
+
+  * :ref:`scripts-premkproject`
+  * :ref:`scripts-postmkproject`
+
+setvirtualenvproject
+--------------------
+
+Bind an existing virtualenv to an existing project.
+
+Syntax::
+
+  setvirtualenvproject [virtualenv_path project_path]
+
+The arguments to ``setvirtualenvproject`` are the full paths to the
+virtualenv and project directory.  An association is made so that when
+``workon`` activates the virtualenv the project is also activated.
+
+::
+
+    $ mkproject myproj
+    New python executable in myproj/bin/python
+    Installing distribute.............................................
+    ..................................................................
+    ..................................................................
+    done.
+    Creating /Users/dhellmann/Devel/myproj
+    (myproj)$ mkvirtualenv myproj_new_libs
+    New python executable in myproj/bin/python
+    Installing distribute.............................................
+    ..................................................................
+    ..................................................................
+    done.
+    Creating /Users/dhellmann/Devel/myproj
+    (myproj_new_libs)$ setvirtualenvproject $VIRTUAL_ENV $(pwd)
+
+When no arguments are given, the current virtualenv and current
+directory are assumed.
+
+Any number of virtualenvs can refer to the same project directory,
+making it easy to switch between versions of Python or other
+dependencies for testing.
+
+.. _command-cdproject:
+
+cdproject
+---------
+
+Change the current working directory to the one specified as the
+project directory for the active virtualenv.
+
+Syntax::
+
+  cdproject
+

docs/en/developers.rst

 .. _shunit2: http://shunit2.googlecode.com/
 
 .. _tox: http://codespeak.net/tox
+
+.. _developer-templates:
+
+Creating a New Template
+=======================
+
+virtualenvwrapper.project templates work like `virtualenvwrapper
+plugins
+<http://www.doughellmann.com/docs/virtualenvwrapper/plugins.html>`__.
+The *entry point* group name is
+``virtualenvwrapper.project.template``.  Configure your entry point to
+refer to a function that will **run** (source hooks are not supported
+for templates).
+
+The argument to the template function is the name of the project being
+created.  The current working directory is the directory created to
+hold the project files (``$PROJECT_HOME/$envname``).
+
+Help Text
+---------
+
+One difference between project templates and other virtualenvwrapper
+extensions is that only the templates specified by the user are run.
+The ``mkproject`` command has a help option to give the user a list of
+the available templates.  The names are taken from the registered
+entry point names, and the descriptions are taken from the docstrings
+for the template functions.

docs/en/extensions.rst

 Below is a list of some of the extensions available for use with
 virtualenvwrapper.
 
-.. _extensions-user_scripts:
-
-project
-=======
-
-The project_ extension adds development directory management with
-templates to virtualenvwrapper.
-
-bitbucket
----------
-
-The bitbucket_ project template creates a working directory and
-automatically clones the repository from BitBucket.  Requires
-project_.
-
-.. _project: http://www.doughellmann.com/projects/virtualenvwrapper.project/
-
-.. _bitbucket: http://www.doughellmann.com/projects/virtualenvwrapper.bitbucket/
-
 emacs-desktop
 =============
 
 
 .. _emacs-desktop: http://www.doughellmann.com/projects/virtualenvwrapper-emacs-desktop/
 
+.. _extensions-user_scripts:
+
 user_scripts
 ============
 
 activate based on the name of the file being edited.
 
 .. _vim-virtualenv: https://github.com/jmcantrell/vim-virtualenv
+
+.. _extensions-templates:
+
+Templates
+=========
+
+Below is a list of some of the templates available for use with
+:ref:`command-mkproject`.
+
+.. _templates-bitbucket:
+
+bitbucket
+---------
+
+The bitbucket_ extension automatically clones a mercurial repository
+from the specified bitbucket project.
+
+.. _bitbucket: http://www.doughellmann.com/projects/virtualenvwrapper.bitbucket/
+
+.. _templates-django:
+
+django
+------
+
+The django_ extension automatically creates a new Django project.
+
+.. _django: http://www.doughellmann.com/projects/virtualenvwrapper.django/
+
+.. seealso::
+
+   * :ref:`developer-templates`

docs/en/history.rst

 Release History
 ===============
 
+2.9
+
+  - Change the shell function shell definition syntax so that ksh will
+    treat typeset-declared variables as local. No kidding.
+  - Merge the "project directory" features of the
+    ``virtualenvwrapper.project`` plugin into the main project, adding
+    :ref:`command-mkproject`, :ref:`command-cdproject`, and
+    :ref:`command-setvirtualenvproject` commands.
+  - Add ``-r`` option to :ref:`command-mkvirtualenv` to install
+    dependencies using a pip requirements file.
+
 2.8
 
   - Use VIRTUALENVWRAPPER_VIRTUALENV in `cpvirtualenv` (:bbissue:`104`).

docs/en/index.rst

    install
    command_ref
    hooks
+   projects
    tips
    developers
    extensions

docs/en/install.rst

 Shell Startup File
 ==================
 
-Add two lines to your shell startup file (``.bashrc``, ``.profile``,
-etc.) to set the location where the virtual environments should live
-and the location of the script installed with this package::
+Add three lines to your shell startup file (``.bashrc``, ``.profile``,
+etc.) to set the location where the virtual environments should live,
+the location of your development project directorkes, and the location
+of the script installed with this package::
 
     export WORKON_HOME=$HOME/.virtualenvs
+    export PROJECT_HOME=$HOME/Devel
     source /usr/local/bin/virtualenvwrapper.sh
 
 After editing it, reload the startup file (e.g., run ``source
 variables. Set the variables in your shell startup file *before*
 loading ``virtualenvwrapper.sh``.
 
+.. _variable-WORKON_HOME:
+
 Location of Environments
 ------------------------
 
 the directory does not exist when virtualenvwrapper is loaded, it will
 be created automatically.
 
+.. _variable-PROJECT_HOME:
+
+Location of Project Directories
+-------------------------------
+
+The variable ``PROJECT_HOME`` tells virtualenvwrapper where to place
+your project working directories.  The variable must be set and the
+directory created before :ref:`command-mkproject` is used.
+
+.. seealso::
+
+   * :ref:`project-management`
+
 .. _variable-VIRTUALENVWRAPPER_HOOK_DIR:
 
 Location of Hook Scripts
 where the :ref:`user-defined hooks <scripts>` should be placed. The
 default is ``$WORKON_HOME``.
 
+.. seealso::
+
+   * :ref:`scripts`
+
 .. _variable-VIRTUALENVWRAPPER_LOG_DIR:
 
 Location of Hook Logs
 cannot disable it. Refer to the documentation for the shell to
 identify the appropriate file to edit.
 
+Upgrading to 2.9
+================
+
+Version 2.9 includes the features previously delivered separately by
+``virtualenvwrapper.project``.  If you have an older verison of the
+project extensions installed, remove them before upgrading.
+
 Upgrading from 1.x
 ==================
 

docs/en/projects.rst

+.. _project-management:
+
+====================
+ Project Management
+====================
+
+A :term:`project directory` is associated with a virtualenv, but
+usually contains the source code under active development rather than
+the installed components needed to support the development. For
+example, the project directory may contain the source code checked out
+from a version control system, temporary artifacts created by testing,
+experimental files not committed to version control, etc.
+
+A project directory is created and bound to a virtualenv when
+:ref:`command-mkproject` is run instead of
+:ref:`command-mkvirtualenv`. To bind an existing project directory to
+a virtualenv, use :ref:`command-setvirtualenvproject`.
+
+Using Templates
+===============
+
+A new project directory can be created empty, or populated using one
+or more :term:`template` plugins. Templates should be specified as
+arguments to :ref:`command-mkproject`. Multiple values can be provided
+to apply more than one template. For example, to check out a Mercurial
+repository from on a project on bitbucket and create a new Django
+site, combine the :ref:`templates-bitbucket` and
+:ref:`templates-django` templates.
+
+::
+
+    $ mkproject -t bitbucket -t django my_site
+
+.. seealso::
+
+   * :ref:`extensions-templates`

docs/en/scripts.rst

 The ``$VIRTUALENVWRAPPER_HOOK_DIR/postrmvirtualenv`` script is run as an external
 program after the environment is removed. The full path to the
 environment directory is passed as an argument to the script.
+
+.. _scripts-premkproject:
+
+premkproject
+===============
+
+  :Global/Local: global
+  :Argument(s): name of new project
+  :Sourced/Run: run
+
+``$WORKON_HOME/premkproject`` is run as an external program after the
+virtual environment is created and after the current environment is
+switched to point to the new env, but before the new project directory
+is created. The current working directory for the script is
+``$PROJECT_HOME`` and the name of the new project is passed as an
+argument to the script.
+
+.. _scripts-postmkproject:
+
+postmkproject
+================
+
+  :Global/Local: global
+  :Argument(s): none
+  :Sourced/Run: sourced
+
+``$WORKON_HOME/postmkproject`` is sourced after the new environment
+and project directories are created and the virtualenv is activated.
+The current working directory is the project directory.

docs/sphinx/conf.py

 # built documents.
 #
 # The short X.Y version.
-version = '2.8'
+version = '2.9'
 # The full version, including alpha/beta/rc tags.
 release = version
 
 PROJECT = 'virtualenvwrapper'
 
 # Change docs/sphinx/conf.py too!
-VERSION = '2.8'
+VERSION = '2.9'
 
 # Bootstrap installation of Distribute
 import distribute_setup
 
     provides=['virtualenvwrapper',
               'virtualenvwrapper.user_scripts',
+              'virtualenvwrapper.project',
               ],
     install_requires=['virtualenv'],
 
         #'console_scripts': [ 'venvw_hook = virtualenvwrapper.hook_loader:main' ],
         'virtualenvwrapper.initialize': [
             'user_scripts = virtualenvwrapper.user_scripts:initialize',
+            'project = virtualenvwrapper.project:initialize',
             ],
         'virtualenvwrapper.initialize_source': [
             'user_scripts = virtualenvwrapper.user_scripts:initialize_source',
             'user_scripts = virtualenvwrapper.user_scripts:post_rmvirtualenv',
             ],
 
+        'virtualenvwrapper.project.pre_mkproject': [
+            'project = virtualenvwrapper.project:pre_mkproject',
+            ],
+        'virtualenvwrapper.project.post_mkproject_source': [
+            'project = virtualenvwrapper.project:post_mkproject_source',
+            ],
+
         'virtualenvwrapper.pre_activate': [
             'user_scripts = virtualenvwrapper.user_scripts:pre_activate',
             ],
         'virtualenvwrapper.post_activate_source': [
             'user_scripts = virtualenvwrapper.user_scripts:post_activate_source',
+            'project = virtualenvwrapper.project:post_activate_source',
             ],
 
         'virtualenvwrapper.pre_deactivate_source': [

tests/test_mkvirtualenv_requirements.sh

+#!/bin/sh
+
+#set -x
+
+test_dir=$(cd $(dirname $0) && pwd)
+
+export WORKON_HOME="$(echo ${TMPDIR:-/tmp}/WORKON_HOME | sed 's|//|/|g')"
+
+oneTimeSetUp() {
+    rm -rf "$WORKON_HOME"
+    mkdir -p "$WORKON_HOME"
+    source "$test_dir/../virtualenvwrapper.sh"
+}
+
+oneTimeTearDown() {
+    rm -rf "$WORKON_HOME"
+    rm -f "$test_dir/requirements.txt"
+}
+
+setUp () {
+    echo
+    rm -f "$test_dir/catch_output"
+}
+
+test_requirements_file () {
+    echo "commandlineapp" > "$test_dir/requirements.txt"
+    mkvirtualenv -r "$test_dir/requirements.txt" "env3" >/dev/null 2>&1
+    installed=$(pip freeze)
+    assertTrue "CommandLineApp not found in $installed" "echo $installed | grep CommandLineApp"
+}
+
+. "$test_dir/shunit2"

tests/test_project.sh

+#!/bin/sh
+
+#set -x
+
+test_dir=$(dirname $0)
+
+export WORKON_HOME="$(echo ${TMPDIR:-/tmp}/WORKON_HOME | sed 's|//|/|g')"
+export PROJECT_HOME="$(echo ${TMPDIR:-/tmp}/PROJECT_HOME | sed 's|//|/|g')"
+
+oneTimeSetUp() {
+    rm -rf "$WORKON_HOME"
+    mkdir -p "$WORKON_HOME"
+    rm -rf "$PROJECT_HOME"
+    mkdir -p "$PROJECT_HOME"
+}
+
+oneTimeTearDown() {
+    rm -rf "$WORKON_HOME"
+    rm -rf "$PROJECT_HOME"
+}
+
+setUp () {
+    echo
+    rm -f "$test_dir/catch_output"
+}
+
+test_initialize() {
+    source "$test_dir/../virtualenvwrapper.sh"
+    for hook in  premkproject postmkproject prermproject postrmproject
+    do
+        assertTrue "Global $hook was not created" "[ -f $WORKON_HOME/$hook ]"
+        assertTrue "Global $hook is not executable" "[ -x $WORKON_HOME/$hook ]"
+    done
+}
+
+test_initialize_hook_dir() {
+    export VIRTUALENVWRAPPER_HOOK_DIR="$WORKON_HOME/hooks"
+    mkdir -p "$VIRTUALENVWRAPPER_HOOK_DIR"
+    source "$test_dir/../virtualenvwrapper.sh"
+    for hook in  premkproject postmkproject prermproject postrmproject
+    do
+        assertTrue "Global $hook was not created" "[ -f $VIRTUALENVWRAPPER_HOOK_DIR/$hook ]"
+        assertTrue "Global $hook is not executable" "[ -x $VIRTUALENVWRAPPER_HOOK_DIR/$hook ]"
+    done
+    VIRTUALENVWRAPPER_HOOK_DIR="$WORKON_HOME"
+}
+
+test_virtualenvwrapper_verify_project_home() {
+    assertTrue "PROJECT_HOME not verified" virtualenvwrapper_verify_project_home
+}
+
+test_virtualenvwrapper_verify_project_home_missing_dir() {
+    old_home="$PROJECT_HOME"
+    PROJECT_HOME="$PROJECT_HOME/not_there"
+    assertFalse "PROJECT_HOME verified unexpectedly" virtualenvwrapper_verify_project_home
+    PROJECT_HOME="$old_home"
+}
+
+. "$test_dir/shunit2"

tests/test_project_activate.sh

+#!/bin/sh
+
+#set -x
+
+test_dir=$(dirname $0)
+
+export WORKON_HOME="$(echo ${TMPDIR:-/tmp}/WORKON_HOME | sed 's|//|/|g')"
+export PROJECT_HOME="$(echo ${TMPDIR:-/tmp}/PROJECT_HOME | sed 's|//|/|g')"
+
+oneTimeSetUp() {
+    rm -rf "$WORKON_HOME"
+    mkdir -p "$WORKON_HOME"
+    rm -rf "$PROJECT_HOME"
+    mkdir -p "$PROJECT_HOME"
+    source "$test_dir/../virtualenvwrapper.sh"
+}
+
+# oneTimeTearDown() {
+#     rm -rf "$WORKON_HOME"
+#     rm -rf "$PROJECT_HOME"
+# }
+
+setUp () {
+    echo
+    rm -f "$TMPDIR/catch_output"
+}
+
+test_activate () {
+    mkproject myproject
+    deactivate
+    cd $TMPDIR
+    assertSame "" "$VIRTUAL_ENV"
+    workon myproject
+    assertSame "myproject" "$(basename $VIRTUAL_ENV)"
+    assertSame "$PROJECT_HOME/myproject" "$(pwd)"
+    deactivate
+}
+
+test_space_in_path () {
+    old_project_home="$PROJECT_HOME"
+    PROJECT_HOME="$PROJECT_HOME/with spaces"
+    mkdir -p "$PROJECT_HOME"
+    mkproject "myproject" >/dev/null 2>&1
+    deactivate
+    cd $TMPDIR
+    workon "myproject"
+    assertSame "myproject" "$(basename $VIRTUAL_ENV)"
+    assertSame "$PROJECT_HOME/myproject" "$(pwd)"
+    deactivate
+    PROJECT_HOME="$old_project_home"
+}
+
+
+. "$test_dir/shunit2"

tests/test_project_cd.sh

+#!/bin/sh
+
+#set -x
+
+test_dir=$(dirname $0)
+
+export WORKON_HOME="$(echo ${TMPDIR:-/tmp}/WORKON_HOME | sed 's|//|/|g')"
+export PROJECT_HOME="$(echo ${TMPDIR:-/tmp}/PROJECT_HOME | sed 's|//|/|g')"
+
+oneTimeSetUp() {
+    rm -rf "$WORKON_HOME"
+    mkdir -p "$WORKON_HOME"
+    rm -rf "$PROJECT_HOME"
+    mkdir -p "$PROJECT_HOME"
+    source "$test_dir/../virtualenvwrapper.sh"
+}
+
+oneTimeTearDown() {
+    rm -rf "$WORKON_HOME"
+    rm -rf "$PROJECT_HOME"
+}
+
+setUp () {
+    echo
+    rm -f "$TMPDIR/catch_output"
+}
+
+test_with_project () {
+    mkproject myproject >/dev/null 2>&1
+    cd $TMPDIR
+    cdproject
+    assertSame "$PROJECT_HOME/myproject" "$(pwd)"
+    deactivate
+}
+
+test_without_project () {
+    mkvirtualenv myproject >/dev/null 2>&1
+    cd $TMPDIR
+    output=$(cdproject 2>&1)
+    echo "$output" | grep -q "No project set"
+    RC=$?
+    assertSame "1" "$RC"
+    deactivate
+}
+
+test_space_in_path () {
+    old_project_home="$PROJECT_HOME"
+    PROJECT_HOME="$PROJECT_HOME/with spaces"
+    mkdir -p "$PROJECT_HOME"
+    mkproject "myproject" >/dev/null 2>&1
+    cd $TMPDIR
+    cdproject
+    assertSame "$PROJECT_HOME/myproject" "$(pwd)"
+    deactivate
+    PROJECT_HOME="$old_project_home"
+}
+
+
+. "$test_dir/shunit2"

tests/test_project_mk.sh

+#!/bin/sh
+
+#set -x
+
+test_dir=$(dirname $0)
+
+export WORKON_HOME="$(echo ${TMPDIR:-/tmp}/WORKON_HOME | sed 's|//|/|g')"
+export PROJECT_HOME="$(echo ${TMPDIR:-/tmp}/PROJECT_HOME | sed 's|//|/|g')"
+
+oneTimeSetUp() {
+    rm -rf "$WORKON_HOME"
+    mkdir -p "$WORKON_HOME"
+    rm -rf "$PROJECT_HOME"
+    mkdir -p "$PROJECT_HOME"
+    source "$test_dir/../virtualenvwrapper.sh"
+}
+
+oneTimeTearDown() {
+    rm -rf "$WORKON_HOME"
+    rm -rf "$PROJECT_HOME"
+}
+
+setUp () {
+    echo
+    rm -f "$WORKON_HOME/catch_output"
+}
+
+tearDown () {
+    type deactivate >/dev/null 2>&1 && deactivate
+}
+
+test_create_directories () {
+    mkproject myproject1 >/dev/null 2>&1
+    assertTrue "env directory not created" "[ -d $WORKON_HOME/myproject1 ]"
+    assertTrue "project directory not created" "[ -d $PROJECT_HOME/myproject1 ]"
+}
+
+test_create_virtualenv () {
+    mkproject myproject2 >/dev/null 2>&1
+    assertSame "myproject2" $(basename "$VIRTUAL_ENV")
+    assertSame "$PROJECT_HOME/myproject2" "$(cat $VIRTUAL_ENV/.project)"
+}
+
+test_hooks () {
+    echo "echo GLOBAL premkproject \`pwd\` \"\$@\" >> \"$WORKON_HOME/catch_output\"" >> "$VIRTUALENVWRAPPER_HOOK_DIR/premkproject"
+    chmod +x "$VIRTUALENVWRAPPER_HOOK_DIR/premkproject"
+    echo "echo GLOBAL postmkproject \`pwd\` >> $WORKON_HOME/catch_output" > "$VIRTUALENVWRAPPER_HOOK_DIR/postmkproject"
+
+    mkproject myproject3 >/dev/null 2>&1
+
+    output=$(cat "$WORKON_HOME/catch_output")
+
+    expected="GLOBAL premkproject $PROJECT_HOME myproject3
+GLOBAL postmkproject $PROJECT_HOME/myproject3"
+    assertSame "$expected" "$output"
+
+    rm -f "$VIRTUALENVWRAPPER_HOOK_DIR/premkproject"
+    rm -f "$VIRTUALENVWRAPPER_HOOK_DIR/postmkproject"
+}
+
+test_no_project_home () {
+    old_home="$PROJECT_HOME"
+    export PROJECT_HOME="$PROJECT_HOME/not_there"
+    output=`mkproject should_not_be_created 2>&1`
+    assertTrue "Did not see expected message" "echo $output | grep 'does not exist'"
+    PROJECT_HOME="$old_home"
+}
+
+test_project_exists () {
+    mkproject myproject4 >/dev/null 2>&1
+    output=`mkproject myproject4 2>&1`
+    assertTrue "Did not see expected message 'already exists' in: $output" "echo $output | grep 'already exists'"
+}
+
+test_same_workon_and_project_home () {
+    old_project_home="$PROJECT_HOME"
+    export PROJECT_HOME="$WORKON_HOME"
+    mkproject myproject5 >/dev/null 2>&1
+    assertTrue "env directory not created" "[ -d $WORKON_HOME/myproject1 ]"
+    assertTrue "project directory was created" "[ -d $old_project_home/myproject1 ]"
+    PROJECT_HOME="$old_project_home"
+}
+
+. "$test_dir/shunit2"

tests/test_project_templates.sh

+#!/bin/sh
+
+#set -x
+
+test_dir=$(dirname $0)
+
+export WORKON_HOME="$(echo ${TMPDIR:-/tmp}/WORKON_HOME | sed 's|//|/|g')"
+export PROJECT_HOME="$(echo ${TMPDIR:-/tmp}/PROJECT_HOME | sed 's|//|/|g')"
+
+oneTimeSetUp() {
+    rm -rf "$WORKON_HOME"
+    mkdir -p "$WORKON_HOME"
+    rm -rf "$PROJECT_HOME"
+    mkdir -p "$PROJECT_HOME"
+    source "$test_dir/../virtualenvwrapper.sh"
+}
+
+oneTimeTearDown() {
+    rm -rf "$WORKON_HOME"
+    rm -rf "$PROJECT_HOME"
+}
+
+setUp () {
+    echo
+    rm -f "$TMPDIR/catch_output"
+}
+
+test_list_templates () {
+    mkproject myproject >/dev/null 2>&1
+    output=`mkproject myproject 2>&1`
+    assertTrue "Did not see expected message" "echo $output | grep 'already exists'"
+    deactivate
+}
+
+
+. "$test_dir/shunit2"

virtualenvwrapper.sh

 	VIRTUALENVWRAPPER_ENV_BIN_DIR="Scripts"
 fi
 
-virtualenvwrapper_derive_workon_home() {
+function virtualenvwrapper_derive_workon_home {
     typeset workon_home_dir="$WORKON_HOME"
 
     # Make sure there is a default value for WORKON_HOME.
 # create it if it does not
 # seperate from creating the files in it because this used to just error
 # and maybe other things rely on the dir existing before that happens.
-virtualenvwrapper_verify_workon_home () {
+function virtualenvwrapper_verify_workon_home {
     RC=0
     if [ ! -d "$WORKON_HOME/" ]
     then
 #HOOK_VERBOSE_OPTION="-q"
 
 # Expects 1 argument, the suffix for the new file.
-virtualenvwrapper_tempfile () {
+function virtualenvwrapper_tempfile {
     # Note: the 'X's must come last
     typeset suffix=${1:-hook}
     typeset file="`\mktemp -t virtualenvwrapper-$suffix-XXXXXXXXXX`"
 }
 
 # Run the hooks
-virtualenvwrapper_run_hook () {
+function virtualenvwrapper_run_hook {
     typeset hook_script="$(virtualenvwrapper_tempfile ${1}-hook)"
     if [ -z "$hook_script" ]
     then
             echo "ERROR: virtualenvwrapper_run_hook could not find temporary file $hook_script" 1>&2
             return 2
         fi
+        # cat "$hook_script"
         source "$hook_script"
     fi
     \rm -f "$hook_script" >/dev/null 2>&1
 
 # Set up tab completion.  (Adapted from Arthur Koziel's version at 
 # http://arthurkoziel.com/2008/10/11/virtualenvwrapper-bash-completion/)
-virtualenvwrapper_setup_tab_completion () {
+function virtualenvwrapper_setup_tab_completion {
     if [ -n "$BASH" ] ; then
         _virtualenvs () {
             local cur="${COMP_WORDS[COMP_CWORD]}"
 }
 
 # Set up virtualenvwrapper properly
-virtualenvwrapper_initialize () {
+function virtualenvwrapper_initialize {
     export WORKON_HOME="$(virtualenvwrapper_derive_workon_home)"
 
     virtualenvwrapper_verify_workon_home -q || return 1
 
 
 # Verify that virtualenv is installed and visible
-virtualenvwrapper_verify_virtualenv () {
+function virtualenvwrapper_verify_virtualenv {
     typeset venv=$(\which "$VIRTUALENVWRAPPER_VIRTUALENV" | (unset GREP_OPTIONS; \grep -v "not found"))
     if [ "$venv" = "" ]
     then
 }
 
 # Verify that the requested environment exists
-virtualenvwrapper_verify_workon_environment () {
+function virtualenvwrapper_verify_workon_environment {
     typeset env_name="$1"
     if [ ! -d "$WORKON_HOME/$env_name" ]
     then
 }
 
 # Verify that the active environment exists
-virtualenvwrapper_verify_active_environment () {
+function virtualenvwrapper_verify_active_environment {
     if [ ! -n "${VIRTUAL_ENV}" ] || [ ! -d "${VIRTUAL_ENV}" ]
     then
         echo "ERROR: no virtualenv active, or active virtualenv is missing" >&2
     return 0
 }
 
+# Help text for mkvirtualenv
+function mkvirtualenv_help {
+    echo "Usage: mkvirtualenv [-r requirements_file] [virtualenv options] env_name"
+    echo
+    echo " -r requirements_file"
+    echo
+    echo "    Provide a pip requirements file to install a base set of packages"
+    echo "    into the new environment."
+    echo;
+    echo 'virtualenv help:';
+    echo;
+    virtualenv -h;
+}
+
 # Create a new environment, in the WORKON_HOME.
 #
 # Usage: mkvirtualenv [options] ENVNAME
 # (where the options are passed directly to virtualenv)
 #
-mkvirtualenv () {
+function mkvirtualenv {
+    typeset -a in_args
+    typeset -a out_args
+    typeset -i i
+    typeset tst
+    typeset a
+    typeset envname
+    typeset requirements
+
+    in_args=( "$@" )
+
+    if [ -n "$ZSH_VERSION" ]
+    then
+        i=1
+        tst="-le"
+    else
+        i=0
+        tst="-lt"
+    fi
+    while [ $i $tst $# ]
+    do
+        a="${in_args[$i]}"
+        # echo "arg $i : $a"
+        case "$a" in
+            -h)
+                mkvirtualenv_help;
+                return;;
+            -r)
+                i=$(( $i + 1 ));
+                requirements="${in_args[$i]}";;
+            *)
+                if [ ${#out_args} -gt 0 ]
+                then
+                    out_args=( "${out_args[@]-}" "$a" )
+                else
+                    out_args=( "$a" )
+                fi;;
+        esac
+        i=$(( $i + 1 ))
+    done
+
+    set -- "${out_args[@]}"
+
     eval "envname=\$$#"
     virtualenvwrapper_verify_workon_home || return 1
     virtualenvwrapper_verify_virtualenv || return 1
     [ ! -d "$WORKON_HOME/$envname" ] && return 0
     # Now activate the new environment
     workon "$envname"
+
+    if [ ! -z "$requirements" ]
+    then
+        pip install -r "$requirements"
+    fi
+
     virtualenvwrapper_run_hook "post_mkvirtualenv"
 }
 
 # Remove an environment, in the WORKON_HOME.
-rmvirtualenv () {
+function rmvirtualenv {
     typeset env_name="$1"
     virtualenvwrapper_verify_workon_home || return 1
     if [ "$env_name" = "" ]
 }
 
 # List the available environments.
-virtualenvwrapper_show_workon_options () {
+function virtualenvwrapper_show_workon_options {
     virtualenvwrapper_verify_workon_home || return 1
     # NOTE: DO NOT use ls here because colorized versions spew control characters
     #       into the output list.
 #    (\cd "$WORKON_HOME"; find -L . -depth 3 -path '*/bin/activate') | sed 's|^\./||' | sed 's|/bin/activate||' | sort
 }
 
-_lsvirtualenv_usage () {
+function _lsvirtualenv_usage {
     echo "lsvirtualenv [-blh]"
     echo "  -b -- brief mode"
     echo "  -l -- long mode"
 # List virtual environments
 #
 # Usage: lsvirtualenv [-l]
-lsvirtualenv () {
+function lsvirtualenv {
     
     typeset long_mode=true
     if command -v "getopts" &> /dev/null 
 # Show details of a virtualenv
 #
 # Usage: showvirtualenv [env]
-showvirtualenv () {
+function showvirtualenv {
     typeset env_name="$1"
     if [ -z "$env_name" ]
     then
 #
 # Usage: workon [environment_name]
 #
-workon () {
+function workon {
 	typeset env_name="$1"
 	if [ "$env_name" = "" ]
     then
 
 
 # Prints the Python version string for the current interpreter.
-virtualenvwrapper_get_python_version () {
+function virtualenvwrapper_get_python_version {
     # Uses the Python from the virtualenv because we're trying to
     # determine the version installed there so we can build
     # up the path to the site-packages directory.
 }
 
 # Prints the path to the site-packages directory for the current environment.
-virtualenvwrapper_get_site_packages_dir () {
+function virtualenvwrapper_get_site_packages_dir {
     echo "$VIRTUAL_ENV/lib/python`virtualenvwrapper_get_python_version`/site-packages"    
 }
 
 # "virtualenv_path_extensions.pth" inside the virtualenv's
 # site-packages directory; if this file does not exist, it will be
 # created first.
-add2virtualenv () {
+function add2virtualenv {
 
     virtualenvwrapper_verify_workon_home || return 1
     virtualenvwrapper_verify_active_environment || return 1
 
 # Does a ``cd`` to the site-packages directory of the currently-active
 # virtualenv.
-cdsitepackages () {
+function cdsitepackages {
     virtualenvwrapper_verify_workon_home || return 1
     virtualenvwrapper_verify_active_environment || return 1
     typeset site_packages="`virtualenvwrapper_get_site_packages_dir`"
 }
 
 # Does a ``cd`` to the root of the currently-active virtualenv.
-cdvirtualenv () {
+function cdvirtualenv {
     virtualenvwrapper_verify_workon_home || return 1
     virtualenvwrapper_verify_active_environment || return 1
     \cd $VIRTUAL_ENV/$1
 
 # Shows the content of the site-packages directory of the currently-active
 # virtualenv
-lssitepackages () {
+function lssitepackages {
     virtualenvwrapper_verify_workon_home || return 1
     virtualenvwrapper_verify_active_environment || return 1
     typeset site_packages="`virtualenvwrapper_get_site_packages_dir`"
 
 # Toggles the currently-active virtualenv between having and not having
 # access to the global site-packages.
-toggleglobalsitepackages () {
+function toggleglobalsitepackages {
     virtualenvwrapper_verify_workon_home || return 1
     virtualenvwrapper_verify_active_environment || return 1
     typeset no_global_site_packages_file="`virtualenvwrapper_get_site_packages_dir`/../no-global-site-packages.txt"
 }
 
 # Duplicate the named virtualenv to make a new one.
-cpvirtualenv() {
+function cpvirtualenv {
     typeset env_name="$1"
     if [ "$env_name" = "" ]
     then
 }
 
 #
+# virtualenvwrapper project functions
+#
+
+# Verify that the PROJECT_HOME directory exists
+function virtualenvwrapper_verify_project_home {
+    if [ -z "$PROJECT_HOME" ]
+    then
+        echo "ERROR: Set the PROJECT_HOME shell variable to the name of the directory where projects should be created." >&2
+        return 1
+    fi
+    if [ ! -d "$PROJECT_HOME" ]
+    then
+        [ "$1" != "-q" ] && echo "ERROR: Project directory '$PROJECT_HOME' does not exist.  Create it or set PROJECT_HOME to an existing directory." >&2
+        return 1
+    fi
+    return 0
+}
+
+# Given a virtualenv directory and a project directory,
+# set the virtualenv up to be associated with the 
+# project
+function setvirtualenvproject {
+    typeset venv="$1"
+    typeset prj="$2"
+    if [ -z "$venv" ]
+    then
+        venv="$VIRTUAL_ENV"
+    fi
+    if [ -z "$prj" ]
+    then
+        prj="$(pwd)"
+    fi
+    echo "Setting project for $(basename $venv) to $prj"
+    echo "$prj" > "$venv/.project"
+}
+
+# Show help for mkproject
+function mkproject_help {
+    echo "Usage: mkproject [-t template] [virtualenv options] project_name"
+    echo ""
+    echo "Multiple templates may be selected.  They are applied in the order"
+    echo "specified on the command line."
+    echo;
+    echo "mkvirtualenv help:"
+    echo
+    mkvirtualenv -h;
+    echo
+    echo "Available project templates:"
+    echo
+    "$VIRTUALENVWRAPPER_PYTHON" -m virtualenvwrapper.hook_loader -l project.template
+}
+
+# Create a new project directory and its associated virtualenv.
+function mkproject {
+    typeset -a in_args
+    typeset -a out_args
+    typeset -i i
+    typeset tst
+    typeset a
+    typeset t
+    typeset templates
+
+    in_args=( "$@" )
+
+    if [ -n "$ZSH_VERSION" ]
+    then
+        i=1
+        tst="-le"
+    else
+        i=0
+        tst="-lt"
+    fi
+    while [ $i $tst $# ]
+    do
+        a="${in_args[$i]}"
+        # echo "arg $i : $a"
+        case "$a" in
+            -h)
+                mkproject_help;
+                return;;
+            -t)
+                i=$(( $i + 1 ));
+                templates="$templates ${in_args[$i]}";;
+            *)
+                if [ ${#out_args} -gt 0 ]
+                then
+                    out_args=( "${out_args[@]-}" "$a" )
+                else
+                    out_args=( "$a" )
+                fi;;
+        esac
+        i=$(( $i + 1 ))
+    done
+
+    set -- "${out_args[@]}"
+
+    # echo "templates $templates"
+    # echo "remainder $@"
+    # return 0
+
+    eval "typeset envname=\$$#"
+    virtualenvwrapper_verify_project_home || return 1
+
+    if [ -d "$PROJECT_HOME/$envname" ]
+    then
+        echo "Project $envname already exists." >&2
+        return 1
+    fi
+
+    mkvirtualenv "$@" || return 1
+
+    cd "$PROJECT_HOME"
+
+    virtualenvwrapper_run_hook project.pre_mkproject $envname
+
+    echo "Creating $PROJECT_HOME/$envname"
+    mkdir -p "$PROJECT_HOME/$envname"
+    setvirtualenvproject "$VIRTUAL_ENV" "$PROJECT_HOME/$envname"
+
+    cd "$PROJECT_HOME/$envname"
+
+    for t in $templates
+    do
+        echo
+        echo "Applying template $t"
+        virtualenvwrapper_run_hook --name $t project.template $envname
+    done
+
+    virtualenvwrapper_run_hook project.post_mkproject
+}
+
+# Change directory to the active project
+function cdproject {
+    virtualenvwrapper_verify_workon_home || return 1
+    virtualenvwrapper_verify_active_environment || return 1
+    if [ -f "$VIRTUAL_ENV/.project" ]
+    then
+        project_dir=$(cat "$VIRTUAL_ENV/.project")
+        if [ ! -z "$project_dir" ]
+        then
+            cd "$project_dir"
+        else
+            echo "Project directory $project_dir does not exist" 1>&2
+            return 1
+        fi
+    else
+        echo "No project set in $VIRTUAL_ENV/.project" 1>&2
+        return 1
+    fi
+    return 0
+}
+
+
+#
 # Invoke the initialization hooks
 #
 virtualenvwrapper_initialize

virtualenvwrapper/project.py

+#!/usr/bin/env python
+# encoding: utf-8
+#
+# Copyright (c) 2010 Doug Hellmann.  All rights reserved.
+#
+"""virtualenvwrapper.project
+"""
+
+import logging
+import os
+
+import pkg_resources
+
+from virtualenvwrapper.user_scripts import make_hook, run_global
+
+log = logging.getLogger(__name__)
+
+GLOBAL_HOOKS = [
+    # mkproject
+    ("premkproject",
+     "This hook is run after a new project is created and before it is activated."),
+    ("postmkproject",
+     "This hook is run after a new project is activated."),
+
+    # rmproject
+    ("prermproject",
+     "This hook is run before a project is deleted."),
+    ("postrmproject",
+     "This hook is run after a project is deleted."),
+    ]
+
+def initialize(args):
+    """Set up user hooks
+    """
+    for filename, comment in GLOBAL_HOOKS:
+        make_hook(os.path.join('$VIRTUALENVWRAPPER_HOOK_DIR', filename), comment)
+    return
+
+
+def pre_mkproject(args):
+    log.debug('pre_mkproject %s', str(args))
+    envname=args[0]
+    run_global('premkproject', *args)
+    return
+
+def post_mkproject_source(args):
+    return """
+#
+# Run user-provided scripts
+#
+[ -f "$WORKON_HOME/postmkproject" ] && source "$WORKON_HOME/postmkproject"
+"""
+
+def post_activate_source(args):
+    return """
+#
+# Change to the project directory
+#
+[ -f "$VIRTUAL_ENV/.project" ] && cd "$(cat \"$VIRTUAL_ENV/.project\")"
+"""