diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c)2011, Bartosz Ćwikłowski
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Bartosz Ćwikłowski nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,178 @@
+Virtual Haskell Environment
+===========================
+
+What is it?
+-----------
+hsenv is a tool (inspired by Python's virtualenv)
+to create isolated Haskell environments.
+
+
+What does it do?
+----------------
+It creates a sandboxed environment in a .hsenv_<ENVIRONMENT_NAME>/ sub-directory
+of your project, which, when activated, allows you to use regular Haskell tools
+(ghc, ghci, ghc-pkg, cabal) to manage your Haskell code and environment.
+It's possible to create an environment, that uses different GHC version
+than your currently installed. Very simple emacs integration mode is included.
+
+Basic usage
+-----------
+First, choose a directory where you want to keep your
+sandboxed Haskell environment, usually a good choice is a directory containing
+your cabalized project (if you want to work on a few projects
+(perhaps an app and its dependent library), just choose any of them,
+it doesn't really matter). Enter that directory:
+
+> cd ~/projects/foo
+
+Next, create your new isolated Haskell environment
+(this is a one time only (per environment) step):
+
+> hsenv
+
+Now, every time you want to use this environment, you have to activate it:
+
+> source .hsenv_foo/bin/activate
+
+That's it! Now it's possible to use all regular Haskell tools like usual,
+but it won't affect your global/system's Haskell environment, and also
+your per-user environment (from ~/.cabal and ~/.ghc) will stay the same.
+All cabal-installed packages will be private to this environment,
+and also the external environments (global and user) will not affect it
+(this environment will only inherit very basic packages,
+mostly ghc and Cabal and their deps).
+
+When you're done working with this environment, enter command 'deactivate_hsenv',
+or just close the current shell (with exit).
+
+> deactivate_hsenv
+
+Advanced usage
+--------------
+Here's the most advanced usage of hsenv. Let's say you want to:
+
+* hack on json library
+* do so comfortably
+* use your own version of parsec library
+* and do all this using nightly version of GHC
+
+First, download binary distribution of GHC for your platform
+(e.g. ghc-7.3.20111105-i386-unknown-linux.tar.bz2).
+
+Create a directory for you environment:
+
+> mkdir /tmp/test; cd /tmp/test
+
+Then, create a new environment using that GHC:
+
+> hsenv --ghc=/path/to/ghc-7.3.20111105-i386-unknown-linux.tar.bz2
+
+Activate it:
+
+> source .hsenv_test/bin/activate
+
+Download a copy of json library and your private version of parsec:
+
+> darcs get http://patch-tag.com/r/Paczesiowa/parsec; cabal unpack json
+
+Install parsec:
+
+> cd parsec2; cabal install
+
+Install the rest of json deps:
+
+> cd ../json-0.5; cabal install --only-dependencies
+
+Now, let's say you want to hack on Parsec module of json library.
+Open it in emacs:
+
+> emacsclient Text/JSON/Parsec.hs
+
+Activate the virtual environment (hsenv must be required earlier):
+
+> M-x hsenv-activate <RET> /tmp/test/ <RET>
+
+Edit some code and load it in ghci using 'C-c C-l'. If it type checks,
+you can play around with the code using nightly version of ghci running
+in your virtual environment. When you're happy with the code, exit emacs
+and install your edited json library:
+
+> cabal install
+
+And that's it.
+
+Misc
+----
+hsenv has been tested on i386 Linux and FreeBSD systems,
+but it should work on any Posix platform. External (from tarball) GHC feature
+requires binary GHC distribution compiled for your platform,
+that can be extracted with tar and installed with
+"./configure --prefix=PATH; make install".
+
+FAQ
+---
+Q: Can I use it together with tools like cabal-dev or capri?  
+A: No. All these tools work more or less the same (wrapping cabal command,
+   setting GHC_PACKAGE_PATH env variable), so something will probably break.
+
+Q: Using GHC from tarball fails, when using FreeBSD with a bunch of make tool
+   gibberish. What do I do?  
+A: Try '--make-cmd=gmake' switch.
+
+Q: Can I use hsenv inside hsenv?  
+A: No. It may be supported in future versions.
+
+Q: Does it work on x64 systems?  
+A: It hasn't been tested, but there's no reason why it shouldn't.
+
+Q: Will it work on Mac?  
+A: I doubt it. It should be easy to make it work there with system's GHC,
+   Using GHC from tarball will be probably harder. I don't have any mac
+   machines, so you're on your own, but patches/ideas/questions are welcome.
+
+Q: Will it work on Windows?  
+A: I really doubt it would even compile. I don't have access to any windows
+   machines, so you're on your own, but patches/ideas/questions are welcome.
+   Maybe it would work on cygwin.
+
+Q: Does it require bash?  
+A: No, it should work with any POSIX-compliant shell. It's been tested with
+   bash, bash --posix, dash, zsh and ksh.
+
+Q: Can I use it with a different haskell package repository than hackage?  
+A: Yes, just adjust the url in .hsenv_<ENVIRONMENT_NAME>/cabal/config file.
+
+Q: How do I remove the whole virtual environment?  
+A: If it's activated - 'deactivate_hsenv' it. Then, delete
+   the .hsenv_<ENVIRONMENT_NAME>/ directory.
+
+Q: Is every environment completely separate from other environments and
+   the system environment?  
+A: Yes. The only (minor) exception is ghci history - there's only one
+   per user history file. Also, if you alter your system's GHC, then
+   virtual environments using system's GHC copy will probably break.
+   Virtual environments using GHC from a tarball should continue to work.
+
+Q: Can I use multiple environments in the same directory (e.g. to test
+   my project against different ghc versions)?  
+A: Yes, just use different names for all the environments.
+
+Q: Can I share one cabalized project directory among multiple environments
+   (e.g. build cabalized project in the same dir using different environments)?  
+A: Yes. hsenv also overrides cabal with a wrapper, that will force using different
+   builddirs, so there shouldn't be even any recompilation between different environments.
+
+Q: Is it possible to activate an environment upon entering its directory?  
+A: Yes, if you really know what you're doing. Here's a snippet for bash:
+
+    function precmd() {
+        if [[ -z $HSENV ]]; then
+            NUMBER_OF_ENVS=$(find . -maxdepth 1 -type d -name ".hsenv_*" | wc -l)
+            case ${NUMBER_OF_ENVS} in
+                "0") ;;
+                "1") source .hsenv_*/bin/activate;;
+                *) echo multiple environments, manual activaton required;;
+            esac
+        fi
+    }
+    export PROMPT_COMMAND=precmd
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/hsenv.cabal b/hsenv.cabal
new file mode 100644
--- /dev/null
+++ b/hsenv.cabal
@@ -0,0 +1,142 @@
+Name:                hsenv
+
+Version:             0.3
+
+Synopsis:            Virtual Haskell Environment builder
+
+Description:         hsenv is a tool (inspired by Python's virtualenv) to create isolated Haskell environments.
+                     .
+                     It creates a sandboxed environment in a .hsenv_<ENVIRONMENT_NAME>/ directory, which, when activated,
+                     allows you to use regular Haskell tools (ghc, ghci, ghc-pkg, cabal) to manage your Haskell
+                     code and environment. It's possible to create an environment, that uses a different GHC version
+                     than your currently installed system GHC. hsenv is supposed to be easier to learn (and use) than
+                     similar packages (like cabal-dev or capri).
+                     .
+                     Basic usage.
+                     .
+                     First, choose a directory where you want to keep your sandboxed Haskell environment,
+                     usually a good choice is a directory containing your cabalized project (if you want to work
+                     on a few projects (perhaps an app and its dependent library), just choose any of them,
+                     it doesn't really matter). Enter that directory:
+                     .
+                     > cd ~/projects/foo
+                     .
+                     Next, create your new isolated Haskell environment (this is a one time only (per environment) step):
+                     .
+                     > hsenv
+                     .
+                     Now, every time you want to use this enviroment, you have to activate it:
+                     .
+                     > source .hsenv_foo/bin/activate
+                     .
+                     That's it! Now it's possible to use all regular Haskell tools like usual, but it won't affect
+                     your global/system's Haskell environment, and also your per-user environment (from ~/.cabal and
+                     ~/.ghc) will stay the same. All cabal-installed packages will be private to this environment,
+                     and also the external environments (global and user) will not affect it (this environment
+                     will only inherit very basic packages - mostly ghc and Cabal and their deps).
+                     .
+                     When you're done working with this environment, enter command 'deactivate_hsenv',
+                     or just close the current shell (with exit).
+                     .
+                     > deactivate_hsenv
+                     .
+                     Advanced usage.
+                     .
+                     The only advanced usage is using different GHC version. This can be useful to test your code
+                     against different GHC version (even against nightly builds).
+                     .
+                     First, download binary distribution of GHC for your platform
+                     (e.g. ghc-7.0.4-i386-unknown-linux.tar.bz2), then create a new environment using that GHC
+                     .
+                     > hsenv --ghc=/path/to/ghc_something.tar.bz2
+                     .
+                     Then, proceed (with [de]activation) as in basic case.
+                     .
+                     Misc.
+                     .
+                     hsenv has been tested on i386 Linux systems, but it should work on any Posix platform.
+                     External (from tarball) GHC feature requires binary GHC distribution compiled for your platform,
+                     that can be extracted with tar and installed with "./configure --prefix=PATH; make install".
+                     .
+                     For more info please consult "hsenv --help" or the attached README file.
+
+Homepage:            https://github.com/tmhedberg/hsenv
+
+License:             BSD3
+
+License-file:        LICENSE
+
+Author:              Bartosz Ćwikłowski
+
+Maintainer:          Taylor Hedberg <t@tmh.cc>
+
+Copyright:           (c) 2011 Bartosz Ćwikłowski
+
+Category:            Development
+
+Build-type:          Simple
+
+Stability:           provisional
+
+Bug-reports:         https://github.com/tmhedberg/hsenv/issues
+
+Package-url:         http://hackage.haskell.org/package/hsenv
+
+Tested-with:         GHC == 6.12.3, GHC == 7.0.4, GHC == 7.4.2, GHC == 7.6.1
+
+Data-files:          hsenv.el, README.md
+
+Extra-source-files:  skeletons/activate, skeletons/cabal, skeletons/cabal_config, skeletons/ghc, skeletons/ghc-mod,
+                     skeletons/ghc-pkg, skeletons/ghci, skeletons/runghc
+
+Cabal-version:       >=1.6
+
+Executable hsenv
+
+  Main-is: hsenv.hs
+
+  Hs-source-dirs: src
+
+  cpp-options: -Dcabal
+
+  Ghc-options: -threaded -Wall
+
+  Build-depends: base >= 4.2.0.0 && < 4.7
+               , process >= 1.0.1.2 && < 1.2
+               , filepath >= 1.1.0.3 && < 1.4
+               , directory >= 1.0.1.0 && < 1.3
+               , Cabal >= 1.8.0.6 && < 1.17
+               , mtl >= 1.1.0.2 && < 2.2
+               , bytestring >= 0.9.1.7 && < 0.11
+               , file-embed >= 0.0.4.1 && < 0.1
+               , split >= 0.1.4 && < 0.3
+               , safe >= 0.3 && < 0.4
+               , unix >= 2.0 && < 2.7
+
+  Other-modules: Util.Cabal
+               , Util.Args
+               , Util.Args.Args
+               , Util.Args.ArgArrow
+               , Util.Args.ArgDescr
+               , Util.Args.RawArgs
+               , Util.Args.GetOpt
+               , Util.Args.Usage
+               , Util.List
+               , Util.StaticArrowT
+               , Util.String
+               , Util.Template
+               , Util.IO
+               , Util.WordWrap
+               , Skeletons
+               , Types
+               , MyMonad
+               , Args
+               , Paths
+               , SanityCheck
+               , Process
+               , PackageManagement
+               , Actions
+
+Source-repository head
+  Type:     git
+  Location: git://github.com/tmhedberg/hsenv.git
diff --git a/hsenv.el b/hsenv.el
new file mode 100644
--- /dev/null
+++ b/hsenv.el
@@ -0,0 +1,79 @@
+(defvar hsenv-active-environment nil)
+(defconst hsenv-path-prepend-file "path_var_prependix")
+(defconst hsenv-ghc-package-path-file "ghc_package_path_var")
+
+(defun hsenv-valid-dirp (hsenv-dir)
+  (let ((valid (and (file-accessible-directory-p hsenv-dir)
+                    (file-readable-p (concat hsenv-dir hsenv-path-prepend-file))
+                    (file-readable-p (concat hsenv-dir hsenv-ghc-package-path-file)))))
+    (when (not valid)
+      (message "The environment you provided is not a valid hsenv directory (%s)." hsenv-dir))
+    valid))
+
+(defun hsenv-is-not-active ()
+  (let ((is-not-active (not hsenv-active-environment)))
+    (when (not is-not-active)
+      (message "An hsenv is already activated (%s)." (assoc-default 'dir hsenv-active-environment)))
+    is-not-active))
+
+(defun hsenv-is-active ()
+  (let ((is-active hsenv-active-environment))
+    (when (not is-active)
+      (message "No hsenv currently activated."))
+    is-active))
+
+(defun hsenv-read-file-content (hsenv-dir file)
+  (with-temp-buffer
+    (insert-file-contents (concat hsenv-dir file))
+    (buffer-string)))
+
+(defun hsenv-activate-environment (hsenv-dir)
+  "Activate the Virtual Haskell Environment in directory HSENV-DIR"
+  (when (and (hsenv-valid-dirp hsenv-dir)
+             (hsenv-is-not-active))
+    ; Create an hsenv active environment and backup paths
+    (setq hsenv-active-environment (list `(path-backup . ,(getenv "PATH"))
+                                         `(exec-path-backup . ,exec-path)
+                                         `(dir . ,hsenv-dir)))
+    ; Prepend paths
+    (let* ((path-prepend (hsenv-read-file-content hsenv-dir hsenv-path-prepend-file)))
+      (setenv "PATH" (concat  path-prepend ":" (getenv "PATH")))
+      (setq exec-path (append (split-string path-prepend ":") exec-path)))
+    ; Set ghc-package
+    (setenv "GHC_PACKAGE_PATH" (hsenv-read-file-content hsenv-dir hsenv-ghc-package-path-file))))
+
+(defun hsenv-deactivate ()
+  "Deactivate the Virtual Haskell Environment"
+  (interactive)
+  (when (hsenv-is-active)
+    ; Restore paths
+    (setenv "PATH" (assoc-default 'path-backup hsenv-active-environment))
+    (setq exec-path (assoc-default 'exec-path-backup hsenv-active-environment))
+    ; Destroy the hsenv active environment
+    (setq hsenv-active-environment nil)))
+
+(defun hsenv-activate-dir (dir)
+  (let ((environments (hsenv-list-environments dir)))
+    (if (null environments)
+        (message "Directory %s does not contain any hsenv." dir)
+      (let* ((env-name (if (= 1 (length environments))
+                           (car environments)
+                         (completing-read "Environment:" environments)))
+             (hsenv-dir-name (concat dir ".hsenv_" env-name))
+             (hsenv-dir (file-name-as-directory hsenv-dir-name)))
+        (hsenv-activate-environment hsenv-dir)))))
+
+(defun hsenv-list-environments (dir)
+  (let ((hsenv-dirs (file-expand-wildcards (concat dir ".hsenv_*"))))
+    (mapcar (lambda (hsenv-dir) (substring hsenv-dir (+ 7 (length dir)))) hsenv-dirs)))
+
+
+(defun hsenv-activate (&optional select-dir)
+  "Activate a Virtual Haskell Environment"
+  (interactive "P")
+  (if (or select-dir
+          (null (hsenv-list-environments default-directory)))
+      (hsenv-activate-dir (read-directory-name "Directory:"))
+    (hsenv-activate-dir default-directory)))
+
+(provide 'hsenv)
diff --git a/skeletons/activate b/skeletons/activate
new file mode 100644
--- /dev/null
+++ b/skeletons/activate
@@ -0,0 +1,104 @@
+if [ -n "${HSENV}" ]; then
+    if [ "<HSENV>" = "${HSENV}" -a "<HSENV_NAME>" = "${HSENV_NAME}" ]; then
+        echo "${HSENV_NAME} Virtual Haskell Environment is already active."
+    else
+        echo "There is already ${HSENV_NAME} Virtual Haskell Environment activated."
+        echo "(at ${HSENV})"
+        echo "Deactivate it first (using command 'deactivate_hsenv'), to activate"
+        echo "<HSENV_NAME> environment."
+    fi
+    return 1
+fi
+
+export HSENV="<HSENV>"
+export HSENV_NAME="<HSENV_NAME>"
+
+echo "Activating ${HSENV_NAME} Virtual Haskell Environment (at ${HSENV})."
+echo ""
+echo "Use regular Haskell tools (ghc, ghci, ghc-pkg, cabal) to manage your Haskell environment."
+echo ""
+echo "To exit from this virtual environment, enter command 'deactivate_hsenv'."
+
+export "HSENV_PATH_BACKUP"="${PATH}"
+export "HSENV_PS1_BACKUP"="${PS1}"
+
+deactivate_hsenv() {
+    echo "Deactivating ${HSENV_NAME} Virtual Haskell Environment (at ${HSENV})."
+    echo "Restoring previous environment settings."
+
+    export "PATH"="${HSENV_PATH_BACKUP}"
+    unset -v "HSENV_PATH_BACKUP"
+    export "PS1"="${HSENV_PS1_BACKUP}"
+    unset -v "HSENV_PS1_BACKUP"
+
+    unset -v PACKAGE_DB_FOR_CABAL
+    unset -v PACKAGE_DB_FOR_GHC_PKG
+    unset -v PACKAGE_DB_FOR_GHC
+    unset -v HSENV
+    unset -v HSENV_NAME
+    unset -v GHC_PACKAGE_PATH
+    unset -f deactivate_hsenv
+
+    if [ -n "$BASH" -o -n "$ZSH_VERSION" ]; then
+        hash -r
+    fi
+}
+
+PATH_PREPENDIX="$(cat <HSENV_DIR>/path_var_prependix)"
+export PATH="${PATH_PREPENDIX}:${PATH}"
+unset -v PATH_PREPENDIX
+
+unset -v PACKAGE_DB_FOR_GHC
+ghc --version > /dev/null
+if [ "$?" -ne 0 ]; then
+    echo "Failed to get ghc version. Deactivating environment..."
+    deactivate_hsenv
+    exit 1
+fi
+
+# To compare version numbers as ints, split at dots, check them all.
+GHC_VERSION_OUTPUT="$(ghc --version)"
+GHC_VERSION_NUMBER="$(echo $GHC_VERSION_OUTPUT | sed 's/.* //' | tr '.' ' ')"
+
+item=1
+for check_num in 7 6 1
+do
+  cur_num=$(echo $GHC_VERSION_NUMBER | awk "{ print \$$item; }")
+
+  if [ "$cur_num" -lt "$check_num" ]
+  then
+    PKG_DB_OPT_SUFFIX="conf"
+    break
+  fi
+  item=$(expr $item + 1)
+done
+
+# If it's still unset, we're >= 7.6.1
+if [ -z "$PKG_DB_OPT_SUFFIX" ]
+then
+    PKG_DB_OPT_SUFFIX="db"
+fi
+
+GHC_PACKAGE_PATH_REPLACEMENT="$(cat <HSENV_DIR>/ghc_package_path_var | sed -r -e 's/^/:/' -e 's/::*/:/g' -e 's/:\s*$//')"
+
+replace_pkg () {
+  replacement=$1
+
+  echo "$GHC_PACKAGE_PATH_REPLACEMENT" | sed -e "s/:/ $replacement/g"
+}
+
+export PACKAGE_DB_FOR_CABAL="$(replace_pkg '--package-db=')"
+export PACKAGE_DB_FOR_GHC_PKG=" --no-user-package-${PKG_DB_OPT_SUFFIX} $(replace_pkg --package-${PKG_DB_OPT_SUFFIX}=)"
+export PACKAGE_DB_FOR_GHC=" -no-user-package-${PKG_DB_OPT_SUFFIX} $(replace_pkg -package-${PKG_DB_OPT_SUFFIX}=)"
+export PACKAGE_DB_FOR_GHC_MOD=" -g -no-user-package-${PKG_DB_OPT_SUFFIX} $(replace_pkg '-g -package-'${PKG_DB_OPT_SUFFIX}=)"
+
+export PS1="(${HSENV_NAME})${PS1}"
+
+unset -v GHC_VERSION_OUTPUT
+unset -v GHC_VERSION_NUMBER
+unset -v PKG_DB_OPT_SUFFIX
+unset -v GHC_PACKAGE_PATH_REPLACEMENT
+
+if [ -n "$BASH" -o -n "$ZSH_VERSION" ]; then
+    hash -r
+fi
diff --git a/skeletons/cabal b/skeletons/cabal
new file mode 100644
--- /dev/null
+++ b/skeletons/cabal
@@ -0,0 +1,47 @@
+#!/bin/sh
+
+PATH_ELEMS="$(echo ${PATH} | tr -s ':' '\n')"
+
+ORIG_CABAL_BINARY=""
+
+for PATH_ELEM in ${PATH_ELEMS}; do
+    CABAL_CANDIDATE="${PATH_ELEM}/cabal"
+    if command -v "${CABAL_CANDIDATE}" > /dev/null 2> /dev/null; then
+        if [ "${0}" != "${CABAL_CANDIDATE}" ]; then
+            if [ -z "${ORIG_CABAL_BINARY}" ]; then
+                ORIG_CABAL_BINARY="${CABAL_CANDIDATE}"
+            fi
+        fi
+    fi
+done
+
+if [ -z "${ORIG_CABAL_BINARY}" ]; then
+    echo "cabal wrapper: Couldn't find real cabal program"
+    exit 1
+fi
+
+CABAL_CONFIG="<CABAL_CONFIG>"
+
+CABAL_BUILDDIR_ARG=""
+
+CABAL_BUILDABLE_COMMANDS="build clean configure copy haddock hscolour install register sdist test upgrade"
+
+CABAL_COMMAND="$(echo ${@} | tr -s ' ' '\n' | grep -E '[a-z]' | grep -Ev '^-'  | head -n 1)"
+
+for CABAL_BUILDABLE_COMMAND in ${CABAL_BUILDABLE_COMMANDS}; do
+    if [ "${CABAL_BUILDABLE_COMMAND}" = "${CABAL_COMMAND}" ]; then
+        CABAL_BUILDDIR_ARG="--builddir=dist_<HSENV_NAME>"
+    fi
+done
+
+CABAL_CONFIGURE_COMMANDS="configure install upgrade"
+
+for CABAL_CONFIGURE_COMMAND in ${CABAL_CONFIGURE_COMMANDS}; do
+    if [ "${CABAL_CONFIGURE_COMMAND}" = "${CABAL_COMMAND}" ]; then
+        CABAL_BUILDDIR_ARG="${CABAL_BUILDDIR_ARG} ${PACKAGE_DB_FOR_CABAL}"
+    fi
+done
+
+exec "${ORIG_CABAL_BINARY}"            \
+       --config-file="${CABAL_CONFIG}" \
+       ${CABAL_BUILDDIR_ARG} "${@}"
diff --git a/skeletons/cabal_config b/skeletons/cabal_config
new file mode 100644
--- /dev/null
+++ b/skeletons/cabal_config
@@ -0,0 +1,9 @@
+remote-repo: hackage.haskell.org:http://hackage.haskell.org/packages/archive
+remote-repo-cache: <HACKAGE_CACHE>
+logs-dir: <CABAL_DIR>/logs
+world-file: <CABAL_DIR>/world
+package-db: <GHC_PACKAGE_PATH>
+build-summary: <CABAL_DIR>/logs/build.log
+remote-build-reporting: anonymous
+install-dirs user
+  prefix: <CABAL_DIR>
diff --git a/skeletons/ghc b/skeletons/ghc
new file mode 100644
--- /dev/null
+++ b/skeletons/ghc
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+PATH_ELEMS="$(echo ${PATH} | tr -s ':' '\n')"
+
+ORIG_GHC_BINARY=""
+
+for PATH_ELEM in ${PATH_ELEMS}; do
+    GHC_CANDIDATE="${PATH_ELEM}/ghc"
+    if command -v "${GHC_CANDIDATE}" > /dev/null 2> /dev/null; then
+        if [ "${0}" != "${GHC_CANDIDATE}" ]; then
+            if [ -z "${ORIG_GHC_BINARY}" ]; then
+                ORIG_GHC_BINARY="${GHC_CANDIDATE}"
+            fi
+        fi
+    fi
+done
+
+if [ -z "${ORIG_GHC_BINARY}" ]; then
+    echo "ghc wrapper: Couldn't find real ghc program"
+    exit 1
+fi
+
+exec "$ORIG_GHC_BINARY" ${PACKAGE_DB_FOR_GHC} "$@"
diff --git a/skeletons/ghc-mod b/skeletons/ghc-mod
new file mode 100644
--- /dev/null
+++ b/skeletons/ghc-mod
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+PATH_ELEMS="$(echo ${PATH} | tr -s ':' '\n')"
+
+ORIG_GHC_MOD_BINARY=""
+
+for PATH_ELEM in ${PATH_ELEMS}; do
+    GHC_MOD_CANDIDATE="${PATH_ELEM}/ghc-mod"
+    if command -v "${GHC_MOD_CANDIDATE}" > /dev/null 2> /dev/null; then
+        if [ "${0}" != "${GHC_MOD_CANDIDATE}" ]; then
+            if [ -z "${ORIG_GHC_MOD_BINARY}" ]; then
+                ORIG_GHC_MOD_BINARY="${GHC_MOD_CANDIDATE}"
+            fi
+        fi
+    fi
+done
+
+if [ -z "${ORIG_GHC_MOD_BINARY}" ]; then
+    echo "ghc-mod wrapper: Couldn't find real ghc-mod program"
+    exit 1
+fi
+
+exec "$ORIG_GHC_MOD_BINARY" ${PACKAGE_DB_FOR_GHC_MOD} "$@"
diff --git a/skeletons/ghc-pkg b/skeletons/ghc-pkg
new file mode 100644
--- /dev/null
+++ b/skeletons/ghc-pkg
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+PATH_ELEMS="$(echo ${PATH} | tr -s ':' '\n')"
+
+ORIG_GHC_PKG_BINARY=""
+
+for PATH_ELEM in ${PATH_ELEMS}; do
+    GHC_PKG_CANDIDATE="${PATH_ELEM}/ghc-pkg"
+    if command -v "${GHC_PKG_CANDIDATE}" > /dev/null 2> /dev/null; then
+        if [ "${0}" != "${GHC_PKG_CANDIDATE}" ]; then
+            if [ -z "${ORIG_GHC_PKG_BINARY}" ]; then
+                ORIG_GHC_PKG_BINARY="${GHC_PKG_CANDIDATE}"
+            fi
+        fi
+    fi
+done
+
+if [ -z "${ORIG_GHC_PKG_BINARY}" ]; then
+    echo "ghc-pkg wrapper: Couldn't find real ghc-pkg program"
+    exit 1
+fi
+
+exec "$ORIG_GHC_PKG_BINARY" ${PACKAGE_DB_FOR_GHC_PKG} "$@"
diff --git a/skeletons/ghci b/skeletons/ghci
new file mode 100644
--- /dev/null
+++ b/skeletons/ghci
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+PATH_ELEMS="$(echo ${PATH} | tr -s ':' '\n')"
+
+ORIG_GHCI_BINARY=""
+
+for PATH_ELEM in ${PATH_ELEMS}; do
+    GHCI_CANDIDATE="${PATH_ELEM}/ghci"
+    if command -v "${GHCI_CANDIDATE}" > /dev/null 2> /dev/null; then
+        if [ "${0}" != "${GHCI_CANDIDATE}" ]; then
+            if [ -z "${ORIG_GHCI_BINARY}" ]; then
+                ORIG_GHCI_BINARY="${GHCI_CANDIDATE}"
+            fi
+        fi
+    fi
+done
+
+if [ -z "${ORIG_GHCI_BINARY}" ]; then
+    echo "ghci wrapper: Couldn't find real ghci program"
+    exit 1
+fi
+
+exec "$ORIG_GHCI_BINARY" ${PACKAGE_DB_FOR_GHC} "$@"
diff --git a/skeletons/runghc b/skeletons/runghc
new file mode 100644
--- /dev/null
+++ b/skeletons/runghc
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+PATH_ELEMS="$(echo ${PATH} | tr -s ':' '\n')"
+
+ORIG_RUNGHC_BINARY=""
+
+for PATH_ELEM in ${PATH_ELEMS}; do
+    RUNGHC_CANDIDATE="${PATH_ELEM}/runghc"
+    if command -v "${RUNGHC_CANDIDATE}" > /dev/null 2> /dev/null; then
+        if [ "${0}" != "${RUNGHC_CANDIDATE}" ]; then
+            if [ -z "${ORIG_RUNGHC_BINARY}" ]; then
+                ORIG_RUNGHC_BINARY="${RUNGHC_CANDIDATE}"
+            fi
+        fi
+    fi
+done
+
+if [ -z "${ORIG_RUNGHC_BINARY}" ]; then
+    echo "runghc wrapper: Couldn't find real runghc program"
+    exit 1
+fi
+
+exec "$ORIG_RUNGHC_BINARY" ${PACKAGE_DB_FOR_GHC} "$@"
diff --git a/src/Actions.hs b/src/Actions.hs
new file mode 100644
--- /dev/null
+++ b/src/Actions.hs
@@ -0,0 +1,309 @@
+module Actions ( cabalUpdate
+               , installCabalConfig
+               , installCabalWrapper
+               , installActivateScript
+               , installSimpleWrappers
+               , installProgSymlinks
+               , symlinkToSkeleton
+               , copyBaseSystem
+               , initGhcDb
+               , installGhc
+               , createDirStructure
+               ) where
+
+import Control.Monad
+import System.Directory (setCurrentDirectory, getCurrentDirectory, createDirectory, removeDirectoryRecursive, getAppUserDataDirectory, doesFileExist, findExecutable)
+import System.FilePath ((</>))
+import System.Posix hiding (createDirectory, version)
+import Distribution.Version (Version (..))
+import Distribution.Package (PackageName(..))
+import Safe (lastMay)
+import Data.List (intercalate)
+import Data.Maybe
+
+import MyMonad
+import Types
+import Paths
+import PackageManagement
+import Process
+import Util.Template (substs)
+import Util.IO (makeExecutable, createTemporaryDirectory)
+import Skeletons
+
+-- update cabal package info inside Virtual Haskell Environment
+cabalUpdate :: MyMonad ()
+cabalUpdate = do
+  noSharingFlag <- asks noSharing
+  if noSharingFlag then do
+    debug "Sharing user-wide ~/.cabal/packages disabled"
+    cabalUpdate'
+   else do
+    debug "Sharing user-wide ~/.cabal/packages enabled, checking if data is already downloaded"
+    cabalInstallDir <- liftIO $ getAppUserDataDirectory "cabal"
+    let hackageData = foldl (</>) cabalInstallDir [ "packages"
+                                                  , "hackage.haskell.org"
+                                                  , "00-index.tar"
+                                                  ]
+    dataExists <- liftIO $ doesFileExist hackageData
+    if dataExists then do
+      info "Skipping 'cabal update' step, Hackage download cache already downloaded"
+      info "  to ~/.cabal/packages/. You can update it manually with 'cabal update'"
+      info "  (from inside or outside the virtual environment)."
+     else do
+      debug "No user-wide Hackage cache data downloaded"
+      cabalUpdate'
+      where cabalUpdate' = do
+              cabalConfig <- cabalConfigLocation
+              info "Updating cabal package database inside Virtual Haskell Environment."
+              _ <- indentMessages $ insideProcess "cabal" ["--config-file=" ++ cabalConfig, "update"] Nothing
+              return ()
+
+
+-- install cabal wrapper (in bin/ directory) inside virtual environment dir structure
+installCabalWrapper :: MyMonad ()
+installCabalWrapper = do
+  cabalConfig  <- cabalConfigLocation
+  dirStructure <- hseDirStructure
+  hsEnvName'   <- asks hsEnvName
+  let cabalWrapper = hsEnvBinDir dirStructure </> "cabal"
+  info $ concat [ "Installing cabal wrapper using "
+                , cabalConfig
+                , " at "
+                , cabalWrapper
+                ]
+  let cabalWrapperContents = substs [ ("<CABAL_CONFIG>", cabalConfig)
+                                    , ("<HSENV_NAME>", hsEnvName')] cabalWrapperSkel
+  indentMessages $ do
+    trace "cabal wrapper contents:"
+    indentMessages $ mapM_ trace $ lines cabalWrapperContents
+  liftIO $ writeFile cabalWrapper cabalWrapperContents
+  liftIO $ makeExecutable cabalWrapper
+
+installActivateScriptSupportFiles :: MyMonad ()
+installActivateScriptSupportFiles = do
+  debug "installing supporting files"
+  dirStructure <- hseDirStructure
+  ghc          <- asks ghcSource
+  indentMessages $ do
+    let pathVarPrependixLocation = hsEnvDir dirStructure </> "path_var_prependix"
+        pathVarElems =
+            case ghc of
+              System    -> [hsEnvBinDir dirStructure, cabalBinDir dirStructure]
+              Tarball _ -> [ hsEnvBinDir dirStructure
+                          , cabalBinDir dirStructure
+                          , ghcBinDir dirStructure
+                          ]
+        pathVarPrependix = intercalate ":" pathVarElems
+    debug $ "installing path_var_prependix file to " ++ pathVarPrependixLocation
+    indentMessages $ trace $ "path_var_prependix contents: " ++ pathVarPrependix
+    liftIO $ writeFile pathVarPrependixLocation pathVarPrependix
+    ghcPkgDbPath <- indentMessages ghcPkgDbPathLocation
+    let ghcPackagePathVarLocation = hsEnvDir dirStructure </> "ghc_package_path_var"
+        ghcPackagePathVar         = ghcPkgDbPath
+    debug $ "installing ghc_package_path_var file to " ++ ghcPackagePathVarLocation
+    indentMessages $ trace $ "path_var_prependix contents: " ++ ghcPackagePathVar
+    liftIO $ writeFile ghcPackagePathVarLocation ghcPackagePathVar
+
+-- install activate script (in bin/ directory) inside virtual environment dir structure
+installActivateScript :: MyMonad ()
+installActivateScript = do
+  info "Installing activate script"
+  hsEnvName'   <- asks hsEnvName
+  dirStructure <- hseDirStructure
+  ghcPkgDbPath <- indentMessages ghcPkgDbPathLocation
+  let activateScript = hsEnvBinDir dirStructure </> "activate"
+  indentMessages $ debug $ "using location: " ++ activateScript
+  let activateScriptContents = substs [ ("<HSENV_NAME>", hsEnvName')
+                                      , ("<HSENV_DIR>", hsEnvDir dirStructure)
+                                      , ("<HSENV>", hsEnv dirStructure)
+                                      , ("<GHC_PACKAGE_PATH>", ghcPkgDbPath)
+                                      , ("<HSENV_BIN_DIR>", hsEnvBinDir dirStructure)
+                                      , ("<CABAL_BIN_DIR>", cabalBinDir dirStructure)
+                                      , ("<GHC_BIN_DIR>", ghcBinDir dirStructure)
+                                      ] activateSkel
+  indentMessages $ do
+    trace "activate script contents:"
+    indentMessages $ mapM_ trace $ lines activateScriptContents
+  liftIO $ writeFile activateScript activateScriptContents
+  indentMessages installActivateScriptSupportFiles
+
+-- install cabal's config file (in cabal/ directory) inside virtual environment dir structure
+installCabalConfig :: MyMonad ()
+installCabalConfig = do
+  cabalConfig  <- cabalConfigLocation
+  dirStructure <- hseDirStructure
+  noSharingFlag  <- asks noSharing
+  hackageCache <- indentMessages $
+      if noSharingFlag then do
+          info "Using private Hackage download cache directory"
+          return $ cabalDir dirStructure </> "packages"
+      else do
+          info "Using user-wide (~/.cabal/packages) Hackage download cache directory"
+          cabalInstallDir <- liftIO $ getAppUserDataDirectory "cabal"
+          return $ cabalInstallDir </> "packages"
+  info $ "Installing cabal config at " ++ cabalConfig
+  let cabalConfigContents = substs [ ("<GHC_PACKAGE_PATH>", ghcPackagePath dirStructure)
+                                   , ("<CABAL_DIR>", cabalDir dirStructure)
+                                   , ("<HACKAGE_CACHE>", hackageCache)
+                                   ] cabalConfigSkel
+  indentMessages $ do
+    trace "cabal config contents:"
+    indentMessages $ mapM_ trace $ lines cabalConfigContents
+  liftIO $ writeFile cabalConfig cabalConfigContents
+
+installSimpleWrappers :: MyMonad ()
+installSimpleWrappers = mapM_ installSimpleWrapper simpleWrappers
+
+installSimpleWrapper :: (String, String) -> MyMonad ()
+installSimpleWrapper (targetFilename, skeleton) = do
+    ghcPkgDbPath <- indentMessages ghcPkgDbPathLocation
+    dirStructure <- hseDirStructure
+    let ghcWrapperContents =
+            substs [("<GHC_PACKAGE_PATH>", ghcPkgDbPath)] skeleton
+        ghcWrapper = hsEnvBinDir dirStructure </> targetFilename
+    liftIO $ writeFile ghcWrapper ghcWrapperContents
+    liftIO $ makeExecutable ghcWrapper
+
+installProgSymlinks :: MyMonad ()
+installProgSymlinks = mapM_ installSymlink extraProgs
+
+extraProgs :: [String]
+extraProgs = [ "alex"
+             , "ar"
+             , "c2hs"
+             , "cpphs"
+             , "ffihugs"
+             , "gcc"
+             , "greencard"
+             , "haddock"
+             , "happy"
+             , "hmake"
+             , "hpc"
+             , "hsc2hs"
+             , "hscolour"
+             , "hugs"
+             , "jhc"
+             , "ld"
+             , "lhc"
+             , "lhc-pkg"
+             , "nhc98"
+             , "pkg-config"
+             , "ranlib"
+             , "strip"
+             , "tar"
+             , "uhc"
+             ]
+
+installSymlink :: String -> MyMonad ()
+installSymlink prog = do
+    dirStructure <- hseDirStructure
+    ghcSourceOpt <- asks ghcSource
+    mPrivateLoc <- case ghcSourceOpt of
+        System -> return Nothing
+        _      -> liftIO $ findExecutable $ ghcDir dirStructure </> "bin" </> prog
+    mSystemLoc <- liftIO $ findExecutable prog
+    let mProgLoc = mPrivateLoc `mplus` mSystemLoc
+    when (isJust mProgLoc) $ do
+        let Just progLoc = mProgLoc
+        liftIO $ createSymbolicLink progLoc $ hsEnvBinDir dirStructure </> prog
+
+-- | Install a symbolic link to a skeleton script in hsenv's bin directory
+symlinkToSkeleton :: String -- ^ Name of skeleton
+                  -> String -- ^ Name of link
+                  -> MyMonad ()
+symlinkToSkeleton skel link = do
+    dirStructure <- hseDirStructure
+    let prependBinDir = (hsEnvBinDir dirStructure </>)
+    liftIO $ createSymbolicLink (prependBinDir skel) (prependBinDir link)
+
+createDirStructure :: MyMonad ()
+createDirStructure = do
+  dirStructure <- hseDirStructure
+  info "Creating Virtual Haskell directory structure"
+  indentMessages $ do
+    debug $ "hsenv directory: " ++ hsEnvDir dirStructure
+    liftIO $ createDirectory $ hsEnvDir dirStructure
+    debug $ "cabal directory: " ++ cabalDir dirStructure
+    liftIO $ createDirectory $ cabalDir dirStructure
+    debug $ "hsenv bin directory: " ++ hsEnvBinDir dirStructure
+    liftIO $ createDirectory $ hsEnvBinDir dirStructure
+
+-- initialize private GHC package database inside virtual environment
+initGhcDb :: MyMonad ()
+initGhcDb = do
+  dirStructure <- hseDirStructure
+  info $ "Initializing GHC Package database at " ++ ghcPackagePath dirStructure
+  out <- indentMessages $ outsideGhcPkg ["--version"]
+  case lastMay $ words out of
+    Nothing            -> throwError $ MyException $ "Couldn't extract ghc-pkg version number from: " ++ out
+    Just versionString -> do
+      indentMessages $ trace $ "Found version string: " ++ versionString
+      version <- parseVersion versionString
+      let ghc_6_12_1_version = Version [6,12,1] []
+      if version < ghc_6_12_1_version then do
+        indentMessages $ debug "Detected GHC older than 6.12, initializing GHC_PACKAGE_PATH to file with '[]'"
+        liftIO $ writeFile (ghcPackagePath dirStructure) "[]"
+       else do
+        _ <- indentMessages $ outsideGhcPkg ["init", ghcPackagePath dirStructure]
+        return ()
+
+-- copy optional packages and don't fail completely if this copying fails
+-- some packages mail fail to copy and it's not fatal (e.g. older GHCs don't have haskell2010)
+transplantOptionalPackage :: String -> MyMonad ()
+transplantOptionalPackage name = transplantPackage (PackageName name) `catchError` handler
+  where handler e = do
+          warning $ "Failed to copy optional package " ++ name ++ " from system's GHC: "
+          indentMessages $ warning $ getExceptionMessage e
+
+-- copy base system
+-- base - needed for ghci and everything else
+-- Cabal - needed to install non-trivial cabal packages with cabal-install
+-- haskell98 - some packages need it but they don't specify it (seems it's an implicit dependancy)
+-- haskell2010 - maybe it's similar to haskell98?
+-- ghc and ghc-binary - two packages that are provided with GHC and cannot be installed any other way
+-- also include dependant packages of all the above
+-- when using GHC from tarball, just reuse its package database
+-- cannot do the same when using system's GHC, because there might be additional packages installed
+-- then it wouldn't be possible to work on them insie virtual environment
+copyBaseSystem :: MyMonad ()
+copyBaseSystem = do
+  info "Copying necessary packages from original GHC package database"
+  indentMessages $ do
+    ghc <- asks ghcSource
+    case ghc of
+      System -> do
+        transplantPackage $ PackageName "base"
+        transplantPackage $ PackageName "Cabal"
+        mapM_ transplantOptionalPackage ["haskell98", "haskell2010", "ghc", "ghc-binary"]
+      Tarball _ ->
+        debug "Using external GHC - nothing to copy, Virtual environment will reuse GHC package database"
+
+installGhc :: MyMonad ()
+installGhc = do
+  info "Installing GHC"
+  ghc <- asks ghcSource
+  case ghc of
+    System              -> indentMessages $ debug "Using system version of GHC - nothing to install."
+    Tarball tarballPath -> indentMessages $ installExternalGhc tarballPath
+
+installExternalGhc :: FilePath -> MyMonad ()
+installExternalGhc tarballPath = do
+  info $ "Installing GHC from " ++ tarballPath
+  indentMessages $ do
+    dirStructure <- hseDirStructure
+    tmpGhcDir <- liftIO $ createTemporaryDirectory (hsEnv dirStructure) "ghc"
+    debug $ "Unpacking GHC tarball to " ++ tmpGhcDir
+    _ <- indentMessages $ outsideProcess' "tar" ["xf", tarballPath, "-C", tmpGhcDir, "--strip-components", "1"]
+    let configureScript = tmpGhcDir </> "configure"
+    debug $ "Configuring GHC with prefix " ++ ghcDir dirStructure
+    cwd <- liftIO getCurrentDirectory
+    liftIO $ setCurrentDirectory tmpGhcDir
+    make <- asks makeCmd
+    let configureAndInstall = do
+          _ <- indentMessages $ outsideProcess' configureScript ["--prefix=" ++ ghcDir dirStructure]
+          debug $ "Installing GHC with " ++ make ++ " install"
+          _ <- indentMessages $ outsideProcess' make ["install"]
+          return ()
+    configureAndInstall `finally` liftIO (setCurrentDirectory cwd)
+    liftIO $ removeDirectoryRecursive tmpGhcDir
+    return ()
diff --git a/src/Args.hs b/src/Args.hs
new file mode 100644
--- /dev/null
+++ b/src/Args.hs
@@ -0,0 +1,103 @@
+{-# LANGUAGE Arrows, CPP #-}
+
+module Args (getArgs) where
+
+import Control.Arrow
+import Util.Args
+import System.Directory (getCurrentDirectory)
+import System.FilePath (splitPath)
+import Types
+
+#ifdef cabal
+import Util.Cabal (prettyVersion)
+import Paths_hsenv (version)
+
+versionString :: String
+versionString = prettyVersion version
+#else
+versionString :: String
+versionString = "dev"
+#endif
+
+verbosityOpt, veryVerbosityOpt, skipSanityOpt, sharingOpt :: Switch
+
+verbosityOpt = Switch { switchName  = "verbose"
+                      , switchHelp  = "Print some debugging info"
+                      , switchShort = Just 'v'
+                      }
+
+veryVerbosityOpt = Switch { switchName  = "very-verbose"
+                          , switchHelp  = "Print some more debugging info"
+                          , switchShort = Nothing
+                          }
+
+skipSanityOpt = Switch { switchName  = "skip-sanity-check"
+                       , switchHelp  = "Skip all the sanity checks (use at your own risk)"
+                       , switchShort = Nothing
+                       }
+
+sharingOpt = Switch { switchName  = "dont-share-cabal-cache"
+                    , switchHelp  = "Don't share ~/.cabal/packages (hackage download cache)"
+                    , switchShort = Nothing
+                    }
+
+nameOpt, ghcOpt :: DynOpt
+
+nameOpt = DynOpt
+          { dynOptName = "name"
+          , dynOptTemplate = "NAME"
+          , dynOptDescription = "current directory name"
+          , dynOptHelp = "Use NAME as name of the Virtual Haskell Environment"
+          }
+
+ghcOpt = DynOpt
+         { dynOptName = "ghc"
+         , dynOptTemplate = "FILE"
+         , dynOptDescription = "system's copy of GHC"
+         , dynOptHelp =
+             "Use GHC from provided tarball (e.g. ghc-7.0.4-i386-unknown-linux.tar.bz2)"
+         }
+
+makeOpt :: StaticOpt
+makeOpt = StaticOpt
+          { staticOptName = "make-cmd"
+          , staticOptTemplate = "CMD"
+          , staticOptDefault = "make"
+          , staticOptHelp =
+              "Used as make substitute for installing GHC from tarball (e.g. gmake)"
+          }
+
+argParser :: ArgArrow () Options
+argParser = proc () -> do
+  verbosityFlag <- getOpt verbosityOpt -< ()
+  verbosityFlag2 <- getOpt veryVerbosityOpt -< ()
+  let verboseness = case (verbosityFlag, verbosityFlag2) of
+                      (_, True)      -> VeryVerbose
+                      (True, False)  -> Verbose
+                      (False, False) -> Quiet
+  nameFlag <- getOpt nameOpt -< ()
+  name <- case nameFlag of
+           Just name' -> returnA -< name'
+           Nothing -> do
+             cwd <- liftIO' getCurrentDirectory -< ()
+             returnA -< last $ splitPath cwd
+  ghcFlag <- getOpt ghcOpt -< ()
+  let ghc = case ghcFlag of
+              Nothing   -> System
+              Just path -> Tarball path
+  skipSanityCheckFlag <- getOpt skipSanityOpt -< ()
+  noSharingFlag <- getOpt sharingOpt -< ()
+  make <- getOpt makeOpt -< ()
+  returnA -< Options{ verbosity       = verboseness
+                   , skipSanityCheck = skipSanityCheckFlag
+                   , hsEnvName       = name
+                   , ghcSource       = ghc
+                   , makeCmd         = make
+                   , noSharing       = noSharingFlag
+                   }
+    where liftIO' = liftIO . const
+
+getArgs :: IO Options
+getArgs = parseArgs argParser versionString outro
+    where outro = "Creates Virtual Haskell Environment in the current directory.\n"
+                  ++ "All files will be stored in the .hsenv_ENVNAME/ subdirectory."
diff --git a/src/MyMonad.hs b/src/MyMonad.hs
new file mode 100644
--- /dev/null
+++ b/src/MyMonad.hs
@@ -0,0 +1,88 @@
+{-# LANGUAGE GeneralizedNewtypeDeriving, ScopedTypeVariables #-}
+module MyMonad ( MyMonad
+               , runMyMonad
+               , indentMessages
+               , debug
+               , info
+               , trace
+               , warning
+               , finally
+               , throwError
+               , catchError
+               , asks
+               , gets
+               , tell
+               , modify
+               , liftIO
+               ) where
+
+import Types
+
+import Control.Exception as Exception (IOException, catch)
+import Control.Monad.Trans (MonadIO, liftIO)
+import Control.Monad.Reader (ReaderT, MonadReader, runReaderT, asks)
+import Control.Monad.Writer (WriterT, MonadWriter, runWriterT, tell)
+import Control.Monad.State (StateT, MonadState, evalStateT, modify, gets)
+import Control.Monad.Error (ErrorT, MonadError, runErrorT, throwError, catchError)
+import Control.Monad (when)
+import System.IO (stderr, hPutStrLn)
+
+import Prelude hiding (log)
+
+newtype MyMonad a = MyMonad (StateT MyState (ReaderT Options (ErrorT MyException (WriterT [String] IO))) a)
+    deriving (Functor, Monad, MonadReader Options, MonadState MyState, MonadError MyException, MonadWriter [String])
+
+instance MonadIO MyMonad where
+    liftIO m = MyMonad $ do
+                 x <- liftIO $ (Right `fmap` m) `Exception.catch` \(e :: IOException) -> return $ Left e
+                 case x of
+                   Left e  -> throwError $ MyException $ "IO error: " ++ show e
+                   Right y -> return y
+
+runMyMonad :: MyMonad a -> Options -> IO (Either MyException a, [String])
+runMyMonad (MyMonad m) = runWriterT . runErrorT . runReaderT (evalStateT m (MyState 0))
+
+finally :: MyMonad a -> MyMonad b -> MyMonad a
+finally m sequel = do
+  result <- (Right `fmap` m) `catchError` (return . Left)
+  _ <- sequel
+  case result of
+    Left e  -> throwError e
+    Right x -> return x
+
+indentMessages :: MyMonad a -> MyMonad a
+indentMessages m = do
+  modify (\s -> s{logDepth = logDepth s + 2})
+  result <- m
+  modify (\s -> s{logDepth = logDepth s - 2})
+  return result
+
+-- add message to private log and return adjusted message (with log depth)
+-- that can be printed somewhere else
+privateLog :: String -> MyMonad String
+privateLog str = do
+  depth <- gets logDepth
+  let text = replicate (fromInteger depth) ' ' ++ str
+  tell [text]
+  return text
+
+log :: Verbosity -> String -> MyMonad ()
+log minLevel str = do
+  text <- privateLog str
+  flag <- asks verbosity
+  when (flag >= minLevel) $
+    liftIO $ putStrLn text
+
+debug :: String -> MyMonad ()
+debug = log Verbose
+
+info :: String -> MyMonad ()
+info = log Quiet
+
+trace :: String -> MyMonad ()
+trace = log VeryVerbose
+
+warning :: String -> MyMonad ()
+warning str = do
+  text <- privateLog str
+  liftIO $ hPutStrLn stderr text
diff --git a/src/PackageManagement.hs b/src/PackageManagement.hs
new file mode 100644
--- /dev/null
+++ b/src/PackageManagement.hs
@@ -0,0 +1,110 @@
+module PackageManagement ( Transplantable(..)
+                         , parseVersion
+                         , parsePkgInfo
+                         , outsideGhcPkg
+                         ) where
+
+import Distribution.Package (PackageIdentifier(..), PackageName(..))
+import Distribution.Version (Version(..))
+import Control.Monad (unless)
+
+import Types
+import MyMonad
+import Process (outsideProcess', insideProcess)
+import Util.Cabal (prettyPkgInfo, prettyVersion)
+import qualified Util.Cabal (parseVersion, parsePkgInfo)
+
+outsideGhcPkg :: [String] -> MyMonad String
+outsideGhcPkg = outsideProcess' "ghc-pkg"
+
+insideGhcPkg :: [String] -> Maybe String -> MyMonad String
+insideGhcPkg = insideProcess "ghc-pkg"
+
+parseVersion :: String -> MyMonad Version
+parseVersion s = case Util.Cabal.parseVersion s of
+                    Nothing      -> throwError $ MyException $ "Couldn't parse " ++ s ++ " as a package version"
+                    Just version -> return version
+
+parsePkgInfo :: String -> MyMonad PackageIdentifier
+parsePkgInfo s = case Util.Cabal.parsePkgInfo s of
+                   Nothing      -> throwError $ MyException $ "Couldn't parse package identifier " ++ s
+                   Just pkgInfo -> return pkgInfo
+
+getDeps :: PackageIdentifier -> MyMonad [PackageIdentifier]
+getDeps pkgInfo = do
+  let prettyPkg = prettyPkgInfo pkgInfo
+  debug $ "Extracting dependencies of " ++ prettyPkg
+  out <- indentMessages $ outsideGhcPkg ["field", prettyPkg, "depends"]
+  -- example output:
+  -- depends: ghc-prim-0.2.0.0-3fbcc20c802efcd7c82089ec77d92990
+  --          integer-gmp-0.2.0.0-fa82a0df93dc30b4a7c5654dd7c68cf4 builtin_rts
+  case words out of
+    []           -> throwError $ MyException $ "Couldn't parse ghc-pkg output to find dependencies of " ++ prettyPkg
+    _:depStrings -> do -- skip 'depends:'
+      indentMessages $ trace $ "Found dependency strings: " ++ unwords depStrings
+      mapM parsePkgInfo depStrings
+
+-- things that can be copied from system's GHC pkg database
+-- to GHC pkg database inside virtual environment
+class Transplantable a where
+    transplantPackage :: a -> MyMonad ()
+
+-- choose the highest installed version of package with this name
+instance Transplantable PackageName where
+    transplantPackage (PackageName packageName) = do
+      debug $ "Copying package " ++ packageName ++ " to Virtual Haskell Environment."
+      indentMessages $ do
+        debug "Choosing package with highest version number."
+        out <- indentMessages $ outsideGhcPkg ["field", packageName, "version"]
+        -- example output:
+        -- version: 1.1.4
+        -- version: 1.2.0.3
+        let extractVersionString :: String -> MyMonad String
+            extractVersionString line = case words line of
+                                          [_, x] -> return x
+                                          _   -> throwError $ MyException $ "Couldn't extract version string from: " ++ line
+        versionStrings <- mapM extractVersionString $ lines out
+        indentMessages $ trace $ "Found version strings: " ++ unwords versionStrings
+        versions <- mapM parseVersion versionStrings
+        case versions of
+          []     -> throwError $ MyException $ "No versions of package " ++ packageName ++ " found"
+          (v:vs) -> do
+            indentMessages $ debug $ "Found: " ++ unwords (map prettyVersion versions)
+            let highestVersion = foldr max v vs
+            indentMessages $ debug $ "Using version: " ++ prettyVersion highestVersion
+            let pkgInfo = PackageIdentifier (PackageName packageName) highestVersion
+            transplantPackage pkgInfo
+
+-- check if this package is already installed in Virtual Haskell Environment
+checkIfInstalled :: PackageIdentifier -> MyMonad Bool
+checkIfInstalled pkgInfo = do
+  let package = prettyPkgInfo pkgInfo
+  debug $ "Checking if " ++ package ++ " is already installed."
+  (do
+    _ <- indentMessages $ insideGhcPkg ["describe", package] Nothing
+    indentMessages $ debug "It is."
+    return True) `catchError` handler
+   where handler _ = do
+           debug "It's not."
+           return False
+
+instance Transplantable PackageIdentifier where
+    transplantPackage pkgInfo = do
+      let prettyPkg = prettyPkgInfo pkgInfo
+      debug $ "Copying package " ++ prettyPkg ++ " to Virtual Haskell Environment."
+      indentMessages $ do
+        flag <- checkIfInstalled pkgInfo
+        unless flag $ do
+          deps <- getDeps pkgInfo
+          debug $ "Found: " ++ unwords (map prettyPkgInfo deps)
+          mapM_ transplantPackage deps
+          movePackage pkgInfo
+
+-- copy single package that already has all deps satisfied
+movePackage :: PackageIdentifier -> MyMonad ()
+movePackage pkgInfo = do
+  let prettyPkg = prettyPkgInfo pkgInfo
+  debug $ "Moving package " ++ prettyPkg ++ " to Virtual Haskell Environment."
+  out <- outsideGhcPkg ["describe", prettyPkg]
+  _ <- insideGhcPkg ["register", "-"] (Just out)
+  return ()
diff --git a/src/Paths.hs b/src/Paths.hs
new file mode 100644
--- /dev/null
+++ b/src/Paths.hs
@@ -0,0 +1,59 @@
+module Paths ( hseDirStructure
+             , cabalConfigLocation
+             , dotDirName
+             , insidePathVar
+             ) where
+
+import Data.List (intercalate)
+import System.FilePath ((</>))
+import System.Directory (getCurrentDirectory)
+
+import Util.IO (getEnvVar)
+import Types
+import MyMonad
+
+-- returns record containing paths to all important directories
+-- inside virtual environment dir structure
+hseDirStructure :: MyMonad DirStructure
+hseDirStructure = do
+  cwd <- liftIO getCurrentDirectory
+  dirName <- dotDirName
+  let hsEnvLocation    = cwd
+      hsEnvDirLocation = hsEnvLocation </> dirName
+      cabalDirLocation = hsEnvDirLocation </> "cabal"
+      ghcDirLocation   = hsEnvDirLocation </> "ghc"
+  return DirStructure { hsEnv          = hsEnvLocation
+                      , hsEnvDir       = hsEnvDirLocation
+                      , ghcPackagePath = hsEnvDirLocation </> "ghc_pkg_db"
+                      , cabalDir       = cabalDirLocation
+                      , cabalBinDir    = cabalDirLocation </> "bin"
+                      , hsEnvBinDir    = hsEnvDirLocation </> "bin"
+                      , ghcDir         = ghcDirLocation
+                      , ghcBinDir      = ghcDirLocation </> "bin"
+                      }
+
+-- directory name of hsEnvDir
+dotDirName :: MyMonad String
+dotDirName = do
+  name <- asks hsEnvName
+  return $ ".hsenv_" ++ name
+
+-- returns location of cabal's config file inside virtual environment dir structure
+cabalConfigLocation :: MyMonad FilePath
+cabalConfigLocation = do
+  dirStructure <- hseDirStructure
+  return $ cabalDir dirStructure </> "config"
+
+-- returns value of $PATH env variable to be used inside virtual environment
+insidePathVar :: MyMonad String
+insidePathVar = do
+  oldPathVar <- liftIO $ getEnvVar "PATH"
+  let oldPathVarSuffix = case oldPathVar of
+                           Nothing -> ""
+                           Just x  -> ':' : x
+  dirStructure <- hseDirStructure
+  ghc          <- asks ghcSource
+  let extraPathElems = case ghc of
+                         System    -> [cabalBinDir dirStructure]
+                         Tarball _ -> [cabalBinDir dirStructure, ghcBinDir dirStructure]
+  return $ intercalate ":" extraPathElems ++ oldPathVarSuffix
diff --git a/src/Process.hs b/src/Process.hs
new file mode 100644
--- /dev/null
+++ b/src/Process.hs
@@ -0,0 +1,143 @@
+module Process ( outsideProcess
+               , outsideProcess'
+               , insideProcess
+               , ghcPkgDbPathLocation
+               ) where
+
+import           MyMonad
+import           Paths
+import           Types
+
+import           Util.IO            (Environment, readProcessWithExitCodeInEnv, which)
+
+import           Data.Maybe         (fromMaybe)
+import           System.Environment (getEnvironment)
+import           System.Exit        (ExitCode(..))
+import           System.FilePath    ((</>))
+import           System.Process     (readProcessWithExitCode)
+
+runProcess :: Maybe Environment -> FilePath -> [String] -> Maybe String -> MyMonad String
+runProcess env prog args input = do
+  case input of
+    Nothing  -> return ()
+    Just inp -> do
+      trace "Using the following input:"
+      indentMessages $ mapM_ trace $ lines inp
+
+  let execProcess = case env of
+         Nothing   -> readProcessWithExitCode prog args (fromMaybe "" input)
+         Just env' -> readProcessWithExitCodeInEnv env' prog args input
+
+  (exitCode, output, errors) <- liftIO execProcess
+
+  debug $ case exitCode of
+    ExitSuccess         -> "Process exited successfully"
+    ExitFailure errCode -> "Process failed with exit code " ++ show errCode
+
+  case output of
+    "" -> trace "Empty process output"
+    _  -> do
+      trace "Process output:"
+      indentMessages $ mapM_ trace $ lines output
+
+  case errors of
+    "" -> trace "Empty process error output"
+    _  -> do
+      trace "Process error output:"
+      indentMessages $ mapM_ trace $ lines errors
+
+  case exitCode of
+    ExitSuccess         -> return output
+    ExitFailure errCode -> throwError $ MyException $ prog ++ " process failed with status " ++ show errCode
+
+-- run regular process, takes:
+-- * program name, looks for it in $PATH,
+-- * list of arguments
+-- * maybe standard input
+-- returns standard output
+outsideProcess :: String -> [String] -> Maybe String -> MyMonad String
+outsideProcess progName args input = do
+  debug $ unwords $ ["Running outside process:", progName] ++ args
+  indentMessages $ do
+    trace $ unwords ["Looking for", progName, "in $PATH"]
+    program <- liftIO $ which Nothing progName
+    case program of
+      Nothing -> throwError $ MyException $ unwords ["No", progName, "in $PATH"]
+      Just programPath -> do
+        trace $ unwords [progName, "->", programPath]
+        runProcess Nothing programPath args input
+
+outsideProcess' :: String -> [String] -> MyMonad String
+outsideProcess' progName args = outsideProcess progName args Nothing
+
+-- returns path to GHC (installed from tarball) builtin package database
+externalGhcPkgDb :: MyMonad FilePath
+externalGhcPkgDb = do
+  trace "Checking where GHC (installed from tarball) keeps its package database"
+  indentMessages $ do
+    dirStructure <- hseDirStructure
+    let ghcPkg = ghcDir dirStructure </> "bin" </> "ghc-pkg"
+    trace $ unwords ["Running process:", ghcPkg, "list"]
+    ghcPkgOutput <- indentMessages $ runProcess Nothing ghcPkg ["list"] Nothing
+    debug "Trying to parse ghc-pkg's output"
+    case lines ghcPkgOutput of
+      []             -> throwError $ MyException "ghc-pkg returned empty output"
+      lineWithPath:_ ->
+          case lineWithPath of
+            "" -> throwError $ MyException "ghc-pkg's first line of output is empty"
+            _  -> do
+              -- ghc-pkg ends pkg db path with trailing colon
+              -- but only when not run from the terminal
+              let path = init lineWithPath
+              debug $ "Found: " ++ path
+              return path
+
+-- returns value of GHC_PACKAGE_PATH that should be used inside virtual environment
+-- defined in this module, because insideProcess needs it
+ghcPkgDbPathLocation :: MyMonad String
+ghcPkgDbPathLocation = do
+  trace "Determining value of GHC_PACKAGE_PATH to be used inside virtual environment"
+  dirStructure <- hseDirStructure
+  ghc <- asks ghcSource
+  case ghc of
+    System    -> return $ ghcPackagePath dirStructure
+    Tarball _ -> do
+             externalGhcPkgDbPath <- indentMessages externalGhcPkgDb
+             return $ ghcPackagePath dirStructure ++ ":" ++ externalGhcPkgDbPath
+
+virtualEnvironment :: MyMonad Environment
+virtualEnvironment = do
+  debug "Calculating unix env dictionary used inside virtual environment"
+  indentMessages $ do
+    env <- liftIO getEnvironment
+    ghcPkgDb <- ghcPkgDbPathLocation
+    debug $ "$GHC_PACKAGE_PATH=" ++ ghcPkgDb
+    pathVar <- insidePathVar
+    debug $ "$PATH=" ++ pathVar
+    let varToBeOverridden var = var `elem` ["GHC_PACKAGE_PATH", "PATH"]
+        strippedEnv = filter (not . varToBeOverridden . fst) env
+    return $ [("GHC_PACKAGE_PATH", ghcPkgDb), ("PATH", pathVar)] ++ strippedEnv
+
+-- run process from inside the virtual environment, takes:
+-- * program name, looks for it in (in order):
+--    - cabal bin dir (e.g. .hsenv*/cabal/bin)
+--    - ghc bin dir (e.g. .hsenv*/ghc/bin), only when using ghc from tarball
+--    - $PATH
+-- * list of arguments
+-- * maybe standard input
+-- returns standard output
+-- process is run in altered environment (new $GHC_PACKAGE_PATH env var,
+-- adjusted $PATH var)
+insideProcess :: String -> [String] -> Maybe String -> MyMonad String
+insideProcess progName args input = do
+  debug $ unwords $ ["Running inside process:", progName] ++ args
+  indentMessages $ do
+    pathVar <- insidePathVar
+    trace $ unwords ["Looking for", progName, "in", pathVar]
+    program <- liftIO $ which (Just pathVar) progName
+    case program of
+      Nothing -> throwError $ MyException $ unwords ["No", progName, "in", pathVar]
+      Just programPath -> do
+        trace $ unwords [progName, "->", programPath]
+        env <- virtualEnvironment
+        runProcess (Just env) programPath args input
diff --git a/src/SanityCheck.hs b/src/SanityCheck.hs
new file mode 100644
--- /dev/null
+++ b/src/SanityCheck.hs
@@ -0,0 +1,64 @@
+module SanityCheck (sanityCheck) where
+
+import Control.Monad (when)
+import System.Directory (doesDirectoryExist)
+
+import Util.IO (getEnvVar, which)
+import Types
+import MyMonad
+import Paths (hseDirStructure, dotDirName)
+
+-- check if any virtual env is already active
+checkHSE :: MyMonad ()
+checkHSE = do
+    hsEnvVar <- liftIO $ getEnvVar "HSENV"
+    case hsEnvVar of
+        Nothing   -> return ()
+        Just path -> do
+            hsEnvNameVar <- liftIO $ getEnvVar "HSENV_NAME"
+            case hsEnvNameVar of
+                Nothing -> do
+                       debug $ "warning: HSENV environment variable is defined" ++ ", but no HSENV_NAME environment variable defined."
+                       throwError $ MyException $ "There is already active Virtual Haskell Environment (at " ++ path ++ ")."
+                Just name ->
+                    throwError $ MyException $ "There is already active " ++ name ++ " Virtual Haskell Environment (at " ++ path ++ ")."
+
+checkHsenvAlreadyExists :: MyMonad ()
+checkHsenvAlreadyExists = do
+  dirStructure <- hseDirStructure
+  flag         <- liftIO $ doesDirectoryExist $ hsEnvDir dirStructure
+  dotDir       <- dotDirName
+  when flag $ throwError $ MyException $ "There is already " ++ dotDir ++ " directory at " ++ hsEnv dirStructure
+
+-- check if cabal binary exist on PATH
+checkCabalInstall :: MyMonad ()
+checkCabalInstall = do
+  cabalInstallPath <- liftIO $ which Nothing "cabal"
+  case cabalInstallPath of
+    Just _  -> return ()
+    Nothing -> throwError $ MyException "Couldn't find cabal binary (from cabal-install package) in your $PATH."
+
+-- check if GHC tools (ghc, ghc-pkg) exist on PATH
+-- skip the check if using GHC from a tarball
+checkGhc :: MyMonad ()
+checkGhc = do
+  ghcSrc <- asks ghcSource
+  case ghcSrc of
+    Tarball _ -> return ()
+    System    -> do
+      ghcPath <- liftIO $ which Nothing "ghc"
+      case ghcPath of
+        Just _  -> return ()
+        Nothing -> throwError $ MyException "Couldn't find ghc binary in your $PATH."
+      ghc_pkgPath <- liftIO $ which Nothing "ghc-pkg"
+      case ghc_pkgPath of
+        Just _  -> return ()
+        Nothing -> throwError $ MyException "Couldn't find ghc-pkg binary in your $PATH."
+
+-- check if everything is sane
+sanityCheck :: MyMonad ()
+sanityCheck = do
+  checkHSE
+  checkHsenvAlreadyExists
+  checkCabalInstall
+  checkGhc
diff --git a/src/Skeletons.hs b/src/Skeletons.hs
new file mode 100644
--- /dev/null
+++ b/src/Skeletons.hs
@@ -0,0 +1,39 @@
+{-# LANGUAGE TemplateHaskell #-}
+
+module Skeletons where
+
+import Data.FileEmbed (embedFile)
+import Data.ByteString.Char8 (unpack)
+import System.FilePath ((</>))
+
+activateSkel :: String
+activateSkel = unpack $(embedFile $ "skeletons" </> "activate")
+
+cabalWrapperSkel :: String
+cabalWrapperSkel = unpack $(embedFile $ "skeletons" </> "cabal")
+
+cabalConfigSkel :: String
+cabalConfigSkel = unpack $(embedFile $ "skeletons" </> "cabal_config")
+
+simpleWrappers :: [(String, String)]
+simpleWrappers = [ ghcWrapperSkel
+                 , ghciWrapperSkel
+                 , ghcPkgWrapperSkel
+                 , ghcModWrapperSkel
+                 , runghcWrapperSkel
+                 ]
+
+ghcWrapperSkel :: (String, String)
+ghcWrapperSkel = ("ghc", unpack $(embedFile $ "skeletons" </> "ghc"))
+
+ghciWrapperSkel :: (String, String)
+ghciWrapperSkel = ("ghci", unpack $(embedFile $ "skeletons" </> "ghci"))
+
+ghcPkgWrapperSkel :: (String, String)
+ghcPkgWrapperSkel = ("ghc-pkg", unpack $(embedFile $ "skeletons" </> "ghc-pkg"))
+
+ghcModWrapperSkel :: (String, String)
+ghcModWrapperSkel = ("ghc-mod", unpack $(embedFile $ "skeletons" </> "ghc-mod"))
+
+runghcWrapperSkel :: (String, String)
+runghcWrapperSkel = ("runghc", unpack $(embedFile $ "skeletons" </> "runghc"))
diff --git a/src/Types.hs b/src/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/Types.hs
@@ -0,0 +1,44 @@
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+module Types ( GhcSource(..)
+             , Options(..)
+             , MyState(..)
+             , DirStructure(..)
+             , MyException(..)
+             , Verbosity(..)
+             ) where
+
+import Control.Monad.Error (Error)
+
+data GhcSource = System           -- Use System's copy of GHC
+               | Tarball FilePath -- Use GHC from tarball
+
+data Verbosity = Quiet
+               | Verbose
+               | VeryVerbose
+    deriving (Eq, Ord)
+
+data Options = Options { verbosity       :: Verbosity
+                       , skipSanityCheck :: Bool
+                       , hsEnvName       :: String -- Virtual Haskell Environment name
+                       , ghcSource       :: GhcSource
+                       , makeCmd         :: String -- make substitute used for 'make install' of external GHC
+                       , noSharing       :: Bool   -- don't share ~/.cabal/packages
+                       }
+
+data MyState = MyState { logDepth :: Integer -- used for indentation of logging messages
+                       }
+
+newtype MyException = MyException { getExceptionMessage :: String }
+    deriving Error
+
+-- Only absolute paths!
+data DirStructure = DirStructure { hsEnv          :: FilePath -- dir containing .hsenv_ENVNAME dir
+                                                             -- (usually dir with cabal project)
+                                 , hsEnvDir       :: FilePath -- .hsenv_ENVNAME dir
+                                 , ghcPackagePath :: FilePath -- file (<ghc-6.12) or dir (>=ghc-6.12) containing private GHC pkg db
+                                 , cabalDir       :: FilePath -- directory with private cabal dir
+                                 , cabalBinDir    :: FilePath -- cabal's bin/ dir (used in $PATH)
+                                 , hsEnvBinDir    :: FilePath -- dir with haskell tools wrappers and activate script
+                                 , ghcDir         :: FilePath -- directory with private copy of external GHC (only used when using GHC from tarball)
+                                 , ghcBinDir      :: FilePath -- ghc's bin/ dir (with ghc[i|-pkg]) (only used when using GHC from tarball)
+                                 }
diff --git a/src/Util/Args.hs b/src/Util/Args.hs
new file mode 100644
--- /dev/null
+++ b/src/Util/Args.hs
@@ -0,0 +1,12 @@
+module Util.Args ( parseArgs
+                 , GetOpt(..)
+                 , Switch(..)
+                 , DynOpt(..)
+                 , StaticOpt(..)
+                 , ArgArrow
+                 , liftIO
+                 ) where
+
+import Util.Args.Args (parseArgs)
+import Util.Args.GetOpt
+import Util.Args.ArgArrow
diff --git a/src/Util/Args/ArgArrow.hs b/src/Util/Args/ArgArrow.hs
new file mode 100644
--- /dev/null
+++ b/src/Util/Args/ArgArrow.hs
@@ -0,0 +1,39 @@
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+module Util.Args.ArgArrow ( ArgArrow
+                          , runArgArrow
+                          , liftIO
+                          , addKnownArg
+                          , askArgs
+                          , getKnownArgs
+                          ) where
+
+import Util.StaticArrowT (StaticArrowT(..), addStatic, getStatic)
+import Util.Args.RawArgs (Args)
+import Util.Args.ArgDescr (KnownArgs)
+import Data.Monoid (mempty)
+import Control.Monad.Reader (ReaderT(..), ask)
+import qualified Control.Monad.Reader as Reader (liftIO)
+import Control.Arrow (Kleisli(..), Arrow, ArrowChoice)
+import Control.Category (Category)
+
+-- cli options parsing arrow, that exports statically all known args, and their info
+newtype ArgArrow a b = ArgArrow (StaticArrowT KnownArgs (Kleisli (ReaderT Args IO)) a b)
+    deriving (Category, Arrow, ArrowChoice)
+
+runArgArrow :: ArgArrow () a -> Args -> IO a
+runArgArrow (ArgArrow (StaticArrowT _ m)) = runReaderT $ runKleisli m ()
+
+liftIO :: (a -> IO b) -> ArgArrow a b
+liftIO m = ArgArrow $ StaticArrowT mempty $ Kleisli (Reader.liftIO . m)
+
+-- record statically new known argument
+addKnownArg :: KnownArgs -> ArgArrow () ()
+addKnownArg = ArgArrow . addStatic
+
+-- returns raw parsed args
+askArgs :: ArgArrow () Args
+askArgs = ArgArrow $ StaticArrowT mempty $ Kleisli $ const ask
+
+-- returns statically known (by this computation) cli args
+getKnownArgs :: ArgArrow a b -> KnownArgs
+getKnownArgs (ArgArrow arrow) = getStatic arrow
diff --git a/src/Util/Args/ArgDescr.hs b/src/Util/Args/ArgDescr.hs
new file mode 100644
--- /dev/null
+++ b/src/Util/Args/ArgDescr.hs
@@ -0,0 +1,27 @@
+module Util.Args.ArgDescr ( DefaultValue(..)
+                          , ArgDescr(..)
+                          , KnownArgs
+                          ) where
+
+-- default value for cli option
+data DefaultValue = ConstValue String -- explicit default value
+                  | DynValue String   -- human readable description of a process
+                                      -- that will provide default value
+
+-- cli option description
+data ArgDescr =
+      -- switch
+      SwitchDescr { argName  :: String     -- switch name (e.g. 'verbose' for --verbose)
+                  , helpMsg  :: String     -- human readable description of this switch
+                  , shortOpt :: Maybe Char -- optional short version for this switch
+                                          -- (e.g. 'v' for '-v'
+                                          -- as a shortcut for '--verbose')
+                  }
+    -- option with a value
+  | ValArg { argName      :: String -- option name (e.g. 'key' for '--key=value')
+           , valTemplate  :: String -- help template for value (e.g. 'PATH' for --binary=PATH)
+           , defaultValue :: DefaultValue -- default value
+           , helpMsg      :: String -- human readable description of this switch
+           }
+
+type KnownArgs = [ArgDescr]
diff --git a/src/Util/Args/Args.hs b/src/Util/Args/Args.hs
new file mode 100644
--- /dev/null
+++ b/src/Util/Args/Args.hs
@@ -0,0 +1,86 @@
+{-# LANGUAGE Arrows #-}
+module Util.Args.Args (parseArgs) where
+
+import Util.Args.ArgDescr (ArgDescr(..), KnownArgs)
+import Util.Args.ArgArrow (ArgArrow, runArgArrow, askArgs, getKnownArgs, addKnownArg)
+import Util.Args.RawArgs (Args(..), parseArguments)
+import Control.Arrow ((>>>), returnA, arr)
+import System.Environment (getArgs)
+import System.IO (stderr, hPutStrLn)
+import System.Exit (exitFailure, exitSuccess)
+import Data.Maybe (catMaybes)
+import Util.Args.Usage (usage)
+
+-- cli arg parsing result
+data ArgParseResult a = Usage
+                      | Help
+                      | Error String
+                      | Version
+                      | OK a
+
+-- wraps a cli arg parsing computation to add few standard
+-- arguments: --help (and -h) and --version
+-- and allows to distinguish them in the result
+helperArgArrow :: ArgArrow a b -> ArgArrow a (ArgParseResult b)
+helperArgArrow arrow = proc x -> do
+  addKnownArg knargs -< ()
+  args <- askArgs -< ()
+  if "help" `elem` switches args || 'h' `elem` shortSwitches args then
+    returnA -< Help
+   else if "usage" `elem` switches args then
+    returnA -< Usage
+   else if "version" `elem` switches args then
+    returnA -< Version
+   else
+    arrow >>> arr OK -< x
+    where knargs = [versionOpt, helpOpt]
+          helpOpt = SwitchDescr "help" "Show this help message" (Just 'h')
+          versionOpt = SwitchDescr "version" "Show version string" Nothing
+
+-- prints a msg to stderr and exits program with return value 1
+failWith :: String -> IO a
+failWith s = hPutStrLn stderr s >> exitFailure
+
+-- validates provided cli arguments against known argument descriptions
+-- handles unknown arguments, and currently forbids positional arguments
+validateArguments :: Args -> KnownArgs -> IO ()
+validateArguments args knArgs
+    | not $ null $ positionals args = failWith "Positional arguments are not allowed"
+    | otherwise =
+        either failWith return $ do
+          mapM_ (validate "short switch" "-" knShortSwitches (:"")) $ shortSwitches args
+          mapM_ (validate "switch" "--" knSwitches id) $ switches args
+          mapM_ (validate "option" "--" knKeys id . fst) $ valArgs args
+    where knShortSwitches = catMaybes $ flip map knArgs $ \x -> case x of
+                               SwitchDescr _ _ c -> c
+                               _ -> Nothing
+          knSwitches = catMaybes $ flip map knArgs $ \x -> case x of
+                               SwitchDescr name _ _ -> Just name
+                               _ -> Nothing
+          knKeys = catMaybes $ flip map knArgs $ \x -> case x of
+                               ValArg name _ _ _ -> Just name
+                               _ -> Nothing
+          validate xName prefix knownXs showX x =
+              if x `elem` knownXs then
+                  Right ()
+              else
+                  Left $ "Unknown " ++ xName ++ " '" ++ prefix ++ showX x ++ "'"
+
+-- takes a cli arg parsing computation, version string and usage msg footer
+-- runs the computation on program cli arguments
+-- and returns the result. if arguments validation fails,
+-- prints usage message and exits with failure
+parseArgs :: ArgArrow () a -> String -> String -> IO a
+parseArgs arrgArr version outro = do
+  args <- getArgs
+  case parseArguments args of
+    Left s -> failWith s
+    Right parsedArgs -> do
+      validateArguments parsedArgs $ getKnownArgs arrgArr'
+      result <- runArgArrow arrgArr' parsedArgs
+      case result of
+        OK a -> return a
+        Error s -> failWith s
+        Version -> putStrLn version >> exitSuccess
+        _ -> usage arrgArr' outro >>= putStr >> exitSuccess
+  where arrgArr' = helperArgArrow arrgArr
diff --git a/src/Util/Args/GetOpt.hs b/src/Util/Args/GetOpt.hs
new file mode 100644
--- /dev/null
+++ b/src/Util/Args/GetOpt.hs
@@ -0,0 +1,91 @@
+{-# LANGUAGE MultiParamTypeClasses
+  , FunctionalDependencies
+  , TypeSynonymInstances
+  , FlexibleInstances
+  , Arrows
+  #-}
+module Util.Args.GetOpt ( GetOpt(..)
+                        , Switch(..)
+                        , DynOpt(..)
+                        , StaticOpt(..)
+                        ) where
+
+import Util.Args.ArgArrow (ArgArrow, askArgs, addKnownArg)
+import Util.Args.ArgDescr (DefaultValue(..), ArgDescr(..))
+import Util.Args.RawArgs (Args(..))
+import Data.Maybe (fromMaybe)
+import Control.Arrow (returnA)
+
+-- getOpt method takes a cli option description and returns at runtime value for that option.
+-- it also statically records information about such option (and uses it to generate usage)
+class GetOpt a b | a -> b where
+    getOpt :: a -> ArgArrow () b
+
+-- description of a cli switch, getOpt-ing it will return True if it's present
+-- in cli args and False otherwise
+data Switch =
+    Switch { switchName  :: String     -- switch long name (e.g. 'verbose' for '--verbose')
+           , switchHelp  :: String     -- human readable switch description (used for usage msg)
+           , switchShort :: Maybe Char -- optional short version of this switch
+                                      -- (e.g. 'v' for '-v')
+           }
+
+instance GetOpt Switch Bool where
+    getOpt descr = proc () -> do
+      addKnownArg [SwitchDescr (switchName descr)
+                               (switchHelp descr)
+                               (switchShort descr)] -< ()
+      args <- askArgs -< ()
+      let longSwitchStatus = switchName descr `elem` switches args
+          switchStatus = case switchShort descr of
+                           Nothing -> longSwitchStatus
+                           Just c  -> longSwitchStatus || c `elem` shortSwitches args
+      returnA -< switchStatus
+
+-- description of a key,value cli argument, that if not present,
+-- defaults to a value returned by some dynamic process
+-- getOpt-ing it will return Just its value if it's present in cli args,
+-- otherwise Nothing
+data DynOpt =
+    DynOpt { dynOptName        :: String -- key name (e.g. 'foo' for '--foo=bar')
+           , dynOptTemplate    :: String -- help template for value
+                                        -- (e.g. 'PATH' for --binary=PATH)
+           , dynOptDescription :: String -- human readable description of a process
+                                        -- that will provide default value
+                                        -- (e.g. 'current directory')
+           , dynOptHelp        :: String -- human readable switch description
+                                        -- (used for usage msg)
+           }
+
+instance GetOpt DynOpt (Maybe String) where
+    getOpt descr = proc () -> do
+      addKnownArg [ValArg (dynOptName descr)
+                          (dynOptTemplate descr)
+                          (DynValue $ dynOptDescription descr)
+                          (dynOptHelp descr)] -< ()
+      args <- askArgs -< ()
+      returnA -< lookup (dynOptName descr) $ valArgs args
+
+-- description of a key,value cli argument, that if not present,
+-- defaults to an explicit value
+-- getOpt-ing it will return String with its value if it's present in cli args,
+-- otherwise default value is returned
+data StaticOpt =
+    StaticOpt { staticOptName     :: String -- key name (e.g. 'foo' for '--foo=bar')
+              , staticOptTemplate :: String -- help template for value
+                                           -- (e.g. 'PATH' for --binary=PATH)
+              , staticOptDefault  :: String -- default value for this argument
+              , staticOptHelp     :: String -- human readable switch description
+                                           -- (used for usage msg)
+              }
+
+instance GetOpt StaticOpt String where
+    getOpt descr = proc () -> do
+      addKnownArg [ValArg (staticOptName descr)
+                          (staticOptTemplate descr)
+                          (DynValue $ staticOptDefault descr)
+                          (staticOptHelp descr)] -< ()
+      args <- askArgs -< ()
+      returnA -< fromMaybe (staticOptDefault descr)
+                          $ lookup (staticOptName descr)
+                          $ valArgs args
diff --git a/src/Util/Args/RawArgs.hs b/src/Util/Args/RawArgs.hs
new file mode 100644
--- /dev/null
+++ b/src/Util/Args/RawArgs.hs
@@ -0,0 +1,40 @@
+module Util.Args.RawArgs ( Args(..)
+                         , parseArguments
+                         ) where
+
+import Data.Monoid (Monoid(..))
+import Control.Monad (liftM)
+import Control.Monad.Instances ()
+import Util.List (breakOn)
+
+{-# ANN Args "HLint: ignore Use String" #-}
+-- parsed command line options
+data Args = Args { shortSwitches :: [Char]             -- list of enabled short switches
+                 , switches      :: [String]           -- list of enabled switches
+                 , valArgs       :: [(String, String)] -- list of (key,value) cli opts
+                 , positionals   :: [String]           -- positional arguments
+                 }
+
+instance Monoid Args where
+    mempty = Args [] [] [] []
+    Args xs1 ys1 zs1 us1 `mappend` Args xs2 ys2 zs2 us2 =
+        Args (xs1 ++ xs2) (ys1 ++ ys2) (zs1 ++ zs2) (us1 ++ us2)
+
+-- parses a single word or returns an error
+parseArgument :: String -> Either String Args
+parseArgument ('-':'-':arg) =
+    Right $ case breakOn '=' arg of
+      Nothing         -> mempty{switches = [arg]}
+      Just (key, val) -> mempty{valArgs = [(key, val)]}
+parseArgument ['-', c] = Right mempty{shortSwitches = [c]}
+parseArgument param@('-':_) = Left $ "Invalid option: '" ++ param ++ "'"
+parseArgument arg = Right mempty{positionals = [arg]}
+
+-- parses many words or returns an error
+parseArguments :: [String] -> Either String Args
+parseArguments args =
+    case breakOn "--" args of
+      Nothing -> mconcat `liftM` mapM parseArgument args
+      Just (args', rest) -> do
+        parsedArgs <- mapM parseArgument args'
+        return $ mconcat parsedArgs `mappend` mempty{positionals = rest}
diff --git a/src/Util/Args/Usage.hs b/src/Util/Args/Usage.hs
new file mode 100644
--- /dev/null
+++ b/src/Util/Args/Usage.hs
@@ -0,0 +1,46 @@
+module Util.Args.Usage (usage) where
+
+import Util.Args.ArgDescr (ArgDescr(..), DefaultValue(..))
+import Data.Function (on)
+import Util.WordWrap (wordWrap)
+import Data.List(sortBy)
+import Util.Args.ArgArrow (ArgArrow, getKnownArgs)
+import Util.String (padTo)
+import System.Environment (getProgName)
+
+-- pretty prints cli arg description for usage
+-- left column contains cli arg header (e.g. '--verbose', or '--binary=PATH')
+-- right column contains info messages for that argument
+-- returns list of lines
+showFlagDescr :: ArgDescr -> [String]
+showFlagDescr argDescr = zipWith makeLine lefts helpLines
+    where lefts    = argLine : repeat ""
+          argLine  = case argDescr of
+                       SwitchDescr name _ Nothing -> "--" ++ name
+                       SwitchDescr name _ (Just c) ->
+                           concat ["-", [c], " ", "--", name]
+                       ValArg name tmpl _ _ -> concat ["--", name, "=", tmpl]
+          msgLines = wordWrap 60 $ case argDescr of
+                                     SwitchDescr _ hlp _ -> hlp
+                                     ValArg _ _ default' help ->
+                                         concat [help, "\n", defaultsLine default']
+          helpLines = if length argLine < 18 then
+                          msgLines
+                      else
+                          "" : msgLines -- line with argument is too long
+                                        -- make more room for it
+          defaultsLine (ConstValue s) = concat ["(defaults to '", s, "')"]
+          defaultsLine (DynValue s)   = concat ["(defaults to ", s, ")"]
+          makeLine infoLine descrLine = (infoLine `padTo` 20) ++ descrLine
+
+-- returns string with formatted cli arg usage help message
+-- argument descriptions are extracted from arg parsing computation
+-- adds simple header and appends provided footer
+usage :: ArgArrow a b -> String -> IO String
+usage arrow outro = do
+  self <- getProgName
+  let intro = "usage: " ++ self ++ " [FLAGS]"
+  return $ unlines $ [intro, "", "Flags:"] ++ flagsDescr ++ [""] ++  outro'
+      where flagsDescr = concatMap showFlagDescr $ argDescrSort $ getKnownArgs arrow
+            argDescrSort = sortBy (compare `on` argName)
+            outro' = wordWrap 80 outro
diff --git a/src/Util/Cabal.hs b/src/Util/Cabal.hs
new file mode 100644
--- /dev/null
+++ b/src/Util/Cabal.hs
@@ -0,0 +1,39 @@
+module Util.Cabal ( prettyVersion
+                  , prettyPkgInfo
+                  , parseVersion
+                  , parsePkgInfo
+                  ) where
+
+import Distribution.Version (Version(..))
+import Distribution.Package (PackageIdentifier(..), PackageName(..))
+import Distribution.Compat.ReadP (readP_to_S)
+import Distribution.Text (parse, Text)
+
+import Data.Char (isSpace)
+import Data.List (isPrefixOf, intercalate)
+
+-- render Version to human and ghc-pkg readable string
+prettyVersion :: Version -> String
+prettyVersion (Version [] _) = ""
+prettyVersion (Version numbers _) = intercalate "." $ map show numbers
+
+-- render PackageIdentifier to human and ghc-pkg readable string
+prettyPkgInfo :: PackageIdentifier -> String
+prettyPkgInfo (PackageIdentifier (PackageName name) (Version [] _)) = name
+prettyPkgInfo (PackageIdentifier (PackageName name) version) =
+  name ++ "-" ++ prettyVersion version
+
+parseVersion :: String -> Maybe Version
+parseVersion = parseCheck
+
+parseCheck :: Text a => String -> Maybe a
+parseCheck str =
+  case [ x | (x,ys) <- readP_to_S parse str, all isSpace ys ] of
+    [x] -> Just x
+    _   -> Nothing
+
+parsePkgInfo :: String -> Maybe PackageIdentifier
+parsePkgInfo str | "builtin_" `isPrefixOf` str =
+                     let name = drop (length "builtin_") str -- ghc-pkg doesn't like builtin_ prefix
+                     in Just $ PackageIdentifier (PackageName name) $ Version [] []
+                 | otherwise = parseCheck str
diff --git a/src/Util/IO.hs b/src/Util/IO.hs
new file mode 100644
--- /dev/null
+++ b/src/Util/IO.hs
@@ -0,0 +1,83 @@
+module Util.IO ( getEnvVar
+               , makeExecutable
+               , readProcessWithExitCodeInEnv
+               , Environment
+               , createTemporaryDirectory
+               , which
+               ) where
+
+import System.Environment (getEnv)
+import System.IO.Error (isDoesNotExistError)
+import System.Directory (getPermissions, setPermissions, executable, removeFile, createDirectory, doesFileExist)
+import Control.Concurrent (forkIO, putMVar, takeMVar, newEmptyMVar)
+import Control.Exception as Exception (catch, evaluate)
+import System.Process (runInteractiveProcess, waitForProcess)
+import System.IO (hGetContents, hPutStr, hFlush, hClose, openTempFile)
+import System.Exit (ExitCode)
+import Data.List.Split (splitOn)
+import Control.Monad (foldM)
+import System.FilePath ((</>))
+
+-- Computation getEnvVar var returns Just the value of the environment variable var,
+-- or Nothing if the environment variable does not exist
+getEnvVar :: String -> IO (Maybe String)
+getEnvVar var = Just `fmap` getEnv var `Exception.catch` noValueHandler
+    where noValueHandler e | isDoesNotExistError e = return Nothing
+                           | otherwise             = ioError e
+
+makeExecutable :: FilePath -> IO ()
+makeExecutable path = do
+  perms <- getPermissions path
+  setPermissions path perms{executable = True}
+
+type Environment = [(String, String)]
+
+-- like readProcessWithExitCode, but takes additional environment argument
+readProcessWithExitCodeInEnv :: Environment -> FilePath -> [String] -> Maybe String -> IO (ExitCode, String, String)
+readProcessWithExitCodeInEnv env progName args input = do
+  (inh, outh, errh, pid) <- runInteractiveProcess progName args Nothing (Just env)
+  out <- hGetContents outh
+  outMVar <- newEmptyMVar
+  _ <- forkIO $ evaluate (length out) >> putMVar outMVar ()
+  err <- hGetContents errh
+  errMVar <- newEmptyMVar
+  _ <- forkIO $ evaluate (length err) >> putMVar errMVar ()
+  case input of
+    Just inp | not (null inp) -> hPutStr inh inp >> hFlush inh
+    _ -> return ()
+  hClose inh
+  takeMVar outMVar
+  hClose outh
+  takeMVar errMVar
+  hClose errh
+  ex <- waitForProcess pid
+  return (ex, out, err)
+
+-- similar to openTempFile, but creates a temporary directory
+-- and returns its path
+createTemporaryDirectory :: FilePath -> String -> IO FilePath
+createTemporaryDirectory parentDir templateName = do
+  (path, handle) <- openTempFile parentDir templateName
+  hClose handle
+  removeFile path
+  createDirectory path
+  return path
+
+which :: Maybe String -> String -> IO (Maybe FilePath)
+which pathVar name = do
+  path <- case pathVar of
+           Nothing   -> getEnvVar "PATH"
+           Just path -> return $ Just path
+  case path of
+    Nothing    -> return Nothing
+    Just path' -> do
+      let pathElems = splitOn ":" path'
+          aux x@(Just _) _ = return x
+          aux Nothing pathDir = do
+            let programPath = pathDir </> name
+            flag <- doesFileExist programPath
+            if flag then
+                return $ Just programPath
+             else
+                return Nothing
+      foldM aux Nothing pathElems
diff --git a/src/Util/List.hs b/src/Util/List.hs
new file mode 100644
--- /dev/null
+++ b/src/Util/List.hs
@@ -0,0 +1,7 @@
+module Util.List (breakOn) where
+
+breakOn :: Eq a => a -> [a] -> Maybe ([a], [a])
+breakOn sep = aux []
+  where aux _ [] = Nothing
+        aux prevs (x:xs) | x == sep   = Just (reverse prevs, xs)
+                         | otherwise = aux (x:prevs) xs
diff --git a/src/Util/StaticArrowT.hs b/src/Util/StaticArrowT.hs
new file mode 100644
--- /dev/null
+++ b/src/Util/StaticArrowT.hs
@@ -0,0 +1,33 @@
+module Util.StaticArrowT ( StaticArrowT(..)
+                         , addStatic
+                         , getStatic
+                         ) where
+
+import Data.Monoid (Monoid(..))
+import Control.Arrow (Arrow(..), ArrowChoice(..))
+import qualified Control.Category as C
+
+-- arrow transformer, that adds static information
+-- to underlying computation
+data StaticArrowT m arr a b = StaticArrowT m (arr a b)
+
+instance (C.Category arr, Monoid m) => C.Category (StaticArrowT m arr) where
+  id = StaticArrowT mempty C.id
+  StaticArrowT m2 arr2 . StaticArrowT m1 arr1 =
+      StaticArrowT (m2 `mappend` m1) $ arr2 C.. arr1
+
+instance (Arrow arr, Monoid m) => Arrow (StaticArrowT m arr) where
+  arr f = StaticArrowT mempty $ arr f
+  first (StaticArrowT m arrow) = StaticArrowT m $ first arrow
+
+instance (ArrowChoice arr, Monoid m) => ArrowChoice (StaticArrowT m arr) where
+    left (StaticArrowT m arrow) = StaticArrowT m $ left arrow
+
+-- simplest computation with specified static information
+addStatic :: (Monoid m, Arrow arr) => m -> StaticArrowT m arr a a
+addStatic m = StaticArrowT m C.id
+
+-- returns static information from the whole computation
+-- (without running it)
+getStatic :: (Monoid m, Arrow arr) => StaticArrowT m arr a b -> m
+getStatic (StaticArrowT m _) = m
diff --git a/src/Util/String.hs b/src/Util/String.hs
new file mode 100644
--- /dev/null
+++ b/src/Util/String.hs
@@ -0,0 +1,5 @@
+module Util.String (padTo) where
+
+padTo :: String -> Int -> String
+padTo s n | length s < n = take n $ s ++ repeat ' '
+          | otherwise    = s
diff --git a/src/Util/Template.hs b/src/Util/Template.hs
new file mode 100644
--- /dev/null
+++ b/src/Util/Template.hs
@@ -0,0 +1,22 @@
+module Util.Template ( substs
+                     ) where
+
+import Data.List (isPrefixOf)
+
+-- Simple templating system
+-- most of the existing tools are either too complicated
+-- or use $ (or even ${}) chars for variables, which is unfortunate
+-- for use with [ba]sh templates.
+
+type Substitution = (String, String)
+
+-- substitute all  occurences of FROM to TO
+subst :: Substitution -> String -> String
+subst _ [] = []
+subst phi@(from, to) input@(x:xs)
+    | from `isPrefixOf` input = to ++ subst phi (drop (length from) input)
+    | otherwise               = x:subst phi xs
+
+-- multi version of subst
+substs :: [Substitution] -> String -> String
+substs phis str = foldr subst str phis
diff --git a/src/Util/WordWrap.hs b/src/Util/WordWrap.hs
new file mode 100644
--- /dev/null
+++ b/src/Util/WordWrap.hs
@@ -0,0 +1,27 @@
+module Util.WordWrap (wordWrap) where
+
+import Data.Char (isSpace)
+
+trim :: String -> String
+trim = trimAndReverse . trimAndReverse
+  where trimAndReverse = reverse . dropWhile isSpace
+
+reverseBreak :: (a -> Bool) -> [a] -> ([a], [a])
+reverseBreak f xs = (reverse before, reverse after)
+  where (after, before) = break f $ reverse xs
+
+wordWrap :: Int -> String -> [String]
+wordWrap maxLen line =
+    case break (== '\n') chunk of
+      (beginning, '\n':rest) -> beginning : wordWrap maxLen (rest ++ chunks)
+      _ -> wordWrap' maxLen line
+    where (chunk, chunks) = splitAt maxLen line
+
+wordWrap' :: Int -> String -> [String]
+wordWrap' maxLen line
+  | length line <= maxLen = [trim line]
+  | any isSpace beforeMax = trim beforeSpace : wordWrap maxLen (afterSpace ++ afterMax)
+  | otherwise = firstBigWord : wordWrap maxLen rest
+    where (beforeMax, afterMax) = splitAt maxLen line
+          (beforeSpace, afterSpace) = reverseBreak isSpace beforeMax
+          (firstBigWord, rest) = break isSpace line
diff --git a/src/hsenv.hs b/src/hsenv.hs
new file mode 100644
--- /dev/null
+++ b/src/hsenv.hs
@@ -0,0 +1,48 @@
+import System.IO (stderr, hPutStrLn)
+import System.Exit (exitFailure)
+import System.FilePath ((</>))
+
+import Types
+import MyMonad
+import Actions
+import SanityCheck (sanityCheck)
+import Args (getArgs)
+import Paths (dotDirName)
+
+main :: IO ()
+main = do
+  options <- getArgs
+  (result, messageLog) <- runMyMonad realMain options
+  case result of
+    Left err -> do
+                hPutStrLn stderr $ getExceptionMessage err
+                hPutStrLn stderr ""
+                hPutStrLn stderr "hsenv.log file contains detailed description of the process."
+                let errorLog = unlines $ messageLog ++ ["", getExceptionMessage err]
+                writeFile "hsenv.log" errorLog
+                exitFailure
+    Right ()  -> do
+                let dotDir = ".hsenv_" ++ hsEnvName options
+                writeFile (dotDir </> "hsenv.log") $ unlines messageLog
+
+realMain :: MyMonad ()
+realMain = do
+  skipSanityCheckFlag <- asks skipSanityCheck
+  if skipSanityCheckFlag then
+      info "WARNING: sanity checks are disabled."
+   else
+      sanityCheck
+  createDirStructure
+  installGhc
+  initGhcDb
+  copyBaseSystem
+  installCabalConfig
+  installActivateScript
+  installCabalWrapper
+  installSimpleWrappers
+  installProgSymlinks
+  symlinkToSkeleton "runghc" "runhaskell"
+  cabalUpdate
+  info ""
+  dotDir <- dotDirName
+  info $ "To activate the new environment use 'source " ++ dotDir ++ "/bin/activate'"
