First let’s look at the “Summary” and “Usage” comments.
“Summary” and “Usage” comments
# Summary: List all Ruby versions that contain the given executable
# Usage: rbenv whence [--path] <command>
The whence command “lists all Ruby versions that contain the given executable”, where the executable is specified by <command> above. So for example, I have Ruby version 2.7.5 and 3.0.0 installed via RBENV, but only 2.7.5 contains the rails executable:
$ rbenv whence rails
2.7.5
According to the comments, passing the --path argument is an option as well:
$ rbenv whence --path rails
/Users/myusername/.rbenv/versions/2.7.5/bin/rails
The result is the full path to the executable, rather than just its name.
Next, the tests.
Tests
Creating a mocked executable
After the bats shebang and the loading of test_helper, the first block of code is:
create_executable() {
local bin="${RBENV_ROOT}/versions/${1}/bin"
mkdir -p "$bin"
touch "${bin}/$2"
chmod +x "${bin}/$2"
}
- We create a helper function named
create_executable. - It creates a sub-directory of RBENV’s
versions/directory, whose job is to contain all the executable files for a specific version of Ruby that we specify via argument #1 of the function.- This will also have the effect of mocking out the installation of that version of Ruby, since RBENV considers a version to be “installed” if a directory whose name corresponds to that version exists in
versions/. - For example, RBENV considers Ruby version
2.7.5to be installed if a directory named${RBENV_ROOT}/versions/2.7.5exists.
- This will also have the effect of mocking out the installation of that version of Ruby, since RBENV considers a version to be “installed” if a directory whose name corresponds to that version exists in
- We then create an executable file within our new sub-directory, with a filename corresponding to argument #2 of the function.
Returning a subset of versions which contain the given executable
The next block of code is our first (and only) test for this file:
@test "finds versions where present" {
create_executable "1.8" "ruby"
create_executable "1.8" "rake"
create_executable "2.0" "ruby"
create_executable "2.0" "rspec"
run rbenv-whence ruby
assert_success
assert_output <<OUT
1.8
2.0
OUT
run rbenv-whence rake
assert_success "1.8"
run rbenv-whence rspec
assert_success "2.0"
}
- We create a Ruby version named
1.8containing executables namedrubyandrake. - We create a Ruby version named
2.0containing executables namedrubyandrspec. - We run our
rbenv whencecommand, passing the name of therubyexecutable that we created. - We assert that it outputs both Ruby versions
1.8and2.0, sincerubyis installed inside each of those version directories. - We then run the command again, this time passing
rakeinstead ofruby. Sincerakeis only installed in version directory1.8, we assert the command succeeds and that only1.8is printed to the screen. - Lastly, we run the command a 3rd time with “rspec” as the argument, and assert that
2.0is the only printed output (sincerspecwas only installed in directory2.0).
I notice that we don’t have a spec to cover the --path flag. That seems like something worth adding. In case anyone wants their first RBENV pull request, I’ll leave that as an exercise for the reader.
Now on to the code itself.
Code
Printing Completions
After the calls to set -e and set -x, the first block of code is:
# Provide rbenv completions
if [ "$1" = "--complete" ]; then
echo --path
exec rbenv-shims --short
fi
This block checks whether the first argument is the string --complete. If it is, the user is asking for a list of completions to the rbenv whence command. In this case, we print the string “–path” and then print the list of shims that the user has installed, since each of those shim names is also a valid argument to pass to rbenv whence.
This block of code isn’t covered by a test either. That also seems like something worth adding, especially since we expect to see more than just hard-coded output (rbenv-shims generates dynamic content based on which executables are installed on the user’s machine). Such a test would have to do the following:
- create at least one (ideally a few) executables
- run
rbenv rehash - run the
whencecommand with the--completeflag, and - assert that the output contained
--pathplus the name(s) of any executable(s) created during the test setup.
Again, I leave that as an exercise for the reader.
Preparing to print either the full path, or just the executable name
Next block of code:
if [ "$1" = "--path" ]; then
print_paths="1"
shift
else
print_paths=""
fi
Here we see that we have the option of specifying a flag named --path. If we do this, we set a variable named print_paths equal to 1 and shift it off of our argument stack. Otherwise, we set it equal to the empty string. We’ll use print_paths later on in the code.
Printing the output
Next block of code:
whence() {
local command="$1"
rbenv-versions --bare | while read -r version; do
path="$(rbenv-prefix "$version")/bin/${command}"
if [ -x "$path" ]; then
[ "$print_paths" ] && echo "$path" || echo "$version"
fi
done
}
Here we create a helper function named whence, which does the following:
- We take the first argument provided to this helper, and store it in a local variable named
command. As we saw earlier, the first arg is the name of the command that we passed torbenv whence. - We then run the
rbenv-versions --barecommand, which returns a list of Ruby version numbers. - This list then gets piped to the
read -rcommand, storing each line in a local variable namedversion. - For each installed Ruby version, we then construct a possible filepath to the command within the Ruby version’s directory.
- If that filepath actually exists and corresponds to a file which is executable, we then print one of two things:
- the path itself, if the user passed the
--pathflag, or - just the Ruby version itself, if the user did not pass
--path.
- the path itself, if the user passed the
- When the
readcommand is done reading lines of input fromrbenv-versions, thewhencehelper function terminates.
If the user forgot to specify a command
Next block of code:
RBENV_COMMAND="$1"
if [ -z "$RBENV_COMMAND" ]; then
rbenv-help --usage whence >&2
exit 1
fi
Here we check whether there was an argument passed to rbenv whence. If not, we echo the “Usage” comments for this command and exit with a non-zero return code.
Printing the results
Last block of code:
result="$(whence "$RBENV_COMMAND")"
[ -n "$result" ] && echo "$result"
Here we call our whence helper function, and store the results inside a variable named result. If result is non-empty, we print its contents to the screen.
That’s the end of this file! Next one: