RVM is a nifty tool for managing
multiple ruby installations. Not only does it make it easy to install
and switch between multiple rubies, but you can also install
gems without sudo access. But, as great as RVM is, I still have to
remember to switch between rubies and often I don't remember to do it
until I see some weird behavior or a test breaks. I'm very lazy and
I want even more.
So I've setup a tiny bit of zsh-fu to automatically switch which ruby
implementation to use based on the directory that I'm in. To do this, one
needs to take advantage of zsh's
hooks,
particular the one invoked when you change directories. By default,
you can simply declare a function named chdir() and it will
automatically be invoked whenever you change directories. Mine looks
like this:
chpwd() {
[[ -t 1 ]] || return
case $TERM in
sun-cmd) print -Pn "\e]l%~\e\\"
;;
*xterm*|rxvt|(dt|k|E)term) print -Pn "\e]2;%3~\a"
;;
esac
}
This function just prints out the name of the directory I've change
into and it's found in a lot of zsh distributions. To keep things clean
I want to have a separate function that just manages the RVM
updating. For the moment, let's ignore the fact that I already have an
existing chdir() function, and look at the hook function I created
to invoke RVM:
chpwd_check_rvm() {
current_version=$(rvm info | grep " version:" | cut -d '"' -f2)
dir=$(pwd)
while [ "${dir}" != "" ]; do
cfg="${dir}/.rvminfo"
if [ -f ${cfg} ]; then
want_version=$(cat ${cfg})
if [ "${want_version}" != "${current_version}" ]; then
rvm use ${want_version}
fi
break
else
dir=${dir%/*}
fi
done
}
This function looks for a file named .rvminfo in the current
directory (this function is invoked after we've changed
directories). If the file isn't found in the new directory, it searches
upward through each parent directory to see if it can find one. If
it's found, the function invokes rvm use with the version string
found in that file.
$ cat .rvminfo
1.8.6
There's also some additional checking to avoid extra rvm invocations
if we're already set to the desired version. This is done more to
reduce the chatter in the shell than for performance reasons.
Now let's get back to invoking multiple hook functions.
If you have more than one function you'd like to have called when
you change directories, you can declare these in an array named
chpwd_functions. After declaring the chpwd_check_rvm() function
above, having two functions for the cd hook is trivial. I just add
this declaration to my ~/.zshrc file:
#--- chpwd_functions
chpwd_functions=( chpwd_check_rvm chpwd )
Now, whenever I change into a directory with a .rvminfo file, my
hooks are automatically executed and I get the right ruby version:
[island] ~/Development: cd alexvollmer.com
<i> Now using ruby 1.8.6 p383 </i>
One final note is that I put all of my specific functions in the
~/.zsh/functions directory, and automatically load all of them in my
~/.zshrc file with the snippet below. I do all of this before I
declare the chpwd_functions array.
#--- Shell Functions ---
#
fpath+=(
${HOME}/.zsh/functions
)
autoload -U ~/.zsh/functions/*(:t)
You may want to organize your functions differently. Just be aware
that zsh needs to know about your functions before you can put them in
the chpwd_functions array.
UPDATE (3/1/2010): Per comments below from RVM's creator, Wayne Seguin, RVM
actually comes with it's own built-in way of handling this. Somehow I
completely missed the rvmrc
documentation for this
feature (how embarassing!). I'm usually inclined to avoid re-inventing
the wheel, but there are two small things I like about the way I
implemented this. First, the solution proposed here doesn't redefine
the cd command, which feels a bit like duck-punching the shell to
me. I don't know if bash has the same kind of hooks that zsh does, so
the implementation is understandable. As for me, I like the hooks
better. Second, the solution I show here works for any sub-directory
within a project that has a specific RVM setting. That said, I'll
probably just use what's already been implemented in RVM.