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.5
to be installed if a directory named${RBENV_ROOT}/versions/2.7.5
exists.
- 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.8
containing executables namedruby
andrake
. - We create a Ruby version named
2.0
containing executables namedruby
andrspec
. - We run our
rbenv whence
command, passing the name of theruby
executable that we created. - We assert that it outputs both Ruby versions
1.8
and2.0
, sinceruby
is installed inside each of those version directories. - We then run the command again, this time passing
rake
instead ofruby
. Sincerake
is only installed in version directory1.8
, we assert the command succeeds and that only1.8
is printed to the screen. - Lastly, we run the command a 3rd time with “rspec” as the argument, and assert that
2.0
is the only printed output (sincerspec
was 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
whence
command with the--complete
flag, and - assert that the output contained
--path
plus 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 --bare
command, which returns a list of Ruby version numbers. - This list then gets piped to the
read -r
command, 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
--path
flag, or - just the Ruby version itself, if the user did not pass
--path
.
- the path itself, if the user passed the
- When the
read
command is done reading lines of input fromrbenv-versions
, thewhence
helper 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: