Saturday, March 6, 2010

!code

‹prev | My Chain | next›

Tonight I continue to work on adding the !code macro to the couch_docs gem. The !code macro, adopted from couchapp, kicks in when assembling individual Javascript files into a CouchDB design document. When a line with that macro is encountered, it is replaced with the contents of the referenced file (e.g. "foo.js" in "!code foo.js").

First up, I have to decide from where to pull the library files. I'll opt for a sub-directory called "__lib":
    it "should find files with relative paths in __lib" do
File.
should_receive(:read).
with("fixtures/_design/__lib/foo.js")

@it.read_from_lib("foo.js")
end
I can make that pass with:
    def read_from_lib(path)
File.read("#{couch_view_dir}/__lib/#{path}")
end
Now I need to hook this code into the method that maps the directory structure into a code data structure:
    def expand_file(filename)
File.dirname(filename).
gsub(/#{couch_view_dir}\/?/, '').
split(/\//) +
[
File.basename(filename, '.js').gsub(/%2F/, '/'),
File.read(filename)
]
end
The test coverage over this is functional rather than unit. I have fixture that are being assembled so that the result can be compared to expectations. What this means for my work here is that I have a freer reign in refactoring. Specifically, I can move that File.read into a separate method that can ultimately be hooked into the new read_from_lib method:
    def expand_file(filename)
File.dirname(filename).
gsub(/#{couch_view_dir}\/?/, '').
split(/\//) +
[
File.basename(filename, '.js').gsub(/%2F/, '/'),
read_value(filename)
]
end

def read_value(filename)
File.read(filename)
end
Since I am already using fixtures in this area of the code, I might as well continue to do so. I create a fixtures/_design/x/z.js file. This should map into a design document named "x" that points to a data structure like:
{
"z" : "// contents of the z.js file"
}
In the z.js file, I add the following contents, including a !code macro:
// !code foo.js
function bar () { return "bar"; }
To verify the !code macro is working I create a fixtures/_design/__lib/foo.js with the following contents:
function foo () { return "foo"; }
Thus, if the !code macro works, I expect the contents of the design document to be:
function foo () { return "foo"; }
function bar () { return "bar"; }
Or, in RSpec format:
    it "should process code macros when assembling" do
@it.to_hash['x'].
should == {
'z' =>
"function foo () { return \"foo\"; }\n" +
"function bar () { return \"bar\"; }\n"
}
end
Since I have yet to hook the !code processing macro into the read_value method, this example fails:
1)
'CouchDocs::DesignDirectory a valid directory should process code macros when assembling' FAILED
expected: {"z"=>"function foo () { return \"foo\"; }\nfunction bar () { return \"bar\"; }\n"},
got: {"z"=>"// !code foo.js\nfunction bar () { return \"bar\"; }\n"} (using ==)
./spec/couch_docs_spec.rb:309:
First, I switch the read_line method to use read_lines/join instead of read:
    def read_value(filename)
File.
readlines(filename).
join
end
I run my specs to verify that I have not broken functionality (the new !code example still fails). Then I add a map to make sure that the !code macro get evaluated:
    def read_value(filename)
File.
readlines(filename).
map { |line| process_code_macro(line) }.
join
end

def process_code_macro(line)
if line =~ %r{\s*//\s*!code\s*(\S+)\s*}
read_from_lib($1)
else
line
end
end
Running my spec now, I find that indeed, I have got the !code macro working as desired:
cstrom@whitefall:~/repos/couch_docs$ spec spec/couch_docs_spec.rb 
.........................................

Finished in 0.129324 seconds

41 examples, 0 failures
Nice! That will serve well as a stopping point for tonight. I am nearly at the end of my couch_docs 1.1 feature set.

Day #34

No comments:

Post a Comment