In Progress
Unit 1, Lesson 1
In Progress

Ascend

Video transcript & code

I was going to show you today's example in the last episode, the one on Enumerator, but there was a lot of info in that episode already and I decided to separate it out.

Suppose we're writing a command-line utility. Its behavior can be customized with per-project configuration files. Given a directory tree like this one, we'd like to be able to execute the tool from any project subdirectory and have it find the configuration file that's in the project root. You may recognize this behavior as similar to how the rake tool behaves. For this example the configuration file is called Tapasfile.

.
├── 059-ascend.org
└── myproj
    ├── lib
    │   └── stuff
    └── Tapasfile

3 directories, 2 files

We need to write our tool in such a way that it will search upwards from the current directory until it finds a Tapasfile. As it happens, Ruby gives us a tool helps with this. The pathname standard library has an #ascend method which will work its way up a directory tree, yielding each path in turn. Here's a little demonstration:

require 'pathname'
p = Pathname.new("/usr/local/bin")
p.ascend do |path|
  puts path
end

If you remember the last episode, you'll recall that an Enumerator can take any method that yields values, and turn it into an Enumerable object. Let's turn #ascend into an Enumerator. We'll use enumerator's #to_a method just to prove that it works:

require 'pathname'
p = Pathname.new("/usr/local/bin")
ascender = p.to_enum(:ascend)
ascender.to_a

Now we have everything we need to ascend a directory heirarchy until we run into a Tapasfile. Instead of turning the enumerator into an array, we'll call the #detect method, provided by Enumerable, to return the first directory which satisfies a given block. In the block, we test to see if the yielded path contains a Tapasfile.

require 'pathname'
p = Pathname.new("myproj/lib/stuff")
ascender = p.to_enum(:ascend)
ascender.detect{|path| (path + 'Tapasfile').exist?}

I think this code reads very nicely; much better than the looping code we'd have had to use without an Enumerator. And because of the way enumerators work, it will only process paths until it finds a Tapasfile. Once that is found, it will simply cease to execute the underlying #ascend method, not wasting cycles ascending all the way to the top of the directory tree.

If the last episode left you hankering for a more concrete demonstration of Enumerator, I hope this has satisfied you. Until next time, happy hacking!

Responses