Thursday, March 11, 2010

Watching, For Keeps

‹prev | My Chain | next›

Time to implement my directory watcher feature in couch_docs for real. First things first—my work from last night:
cstrom@whitefall:~/repos/couch_docs$ git st
# On branch master
# Changed but not updated:
# (use "git add ..." to update what will be committed)
# (use "git checkout -- ..." to discard changes in working directory)
#
# modified: Rakefile
# modified: couch_docs.gemspec
# modified: lib/couch_docs.rb
# modified: lib/couch_docs/command_line.rb
#
no changes added to commit (use "git add" and/or "git commit -a")
Gone:
cstrom@whitefall:~/repos/couch_docs$ git checkout -- Rakefile couch_docs.gemspec lib/couch_docs.rb lib/couch_docs/command_line.rb
cstrom@whitefall:~/repos/couch_docs$ git st
# On branch master
nothing to commit (working directory clean)
I am fairly obsessive about deleting prototype code. The code that I produced might be fine, it might not. It is more than likely that it is not great because I was learning something new and making mistakes along the way. I do not want to be in a position to be building on crap code. I alway build on the simplest thing that can possibly work. The only way to be sure of that (that I know of) is BDD.

Soooo...
    it "should know watch" do
@it = CommandLine.new(%w(load dir uri -w))
@it.options[:watch].should be_true
end
This fails because there is no OptParse code supporting the -w switch:
cstrom@whitefall:~/repos/couch_docs$ spec ./spec/couch_docs_spec.rb 
..........................*................F

Pending:

CouchDocs::DesignDirectory a valid directory should work with absolute !code paths (Not Yet Implemented)
./spec/couch_docs_spec.rb:314

1)
OptionParser::InvalidOption in 'CouchDocs::CommandLine an instance that uploads to a CouchDB database should know watch'
invalid option: -w
./spec/couch_docs_spec.rb:483:in `new'
./spec/couch_docs_spec.rb:483:

Finished in 0.225247 seconds

44 examples, 1 failure, 1 pending
(that pending spec is a note for version 1.2)

To make the example pass, I add:
        opts.on("-w", "--watch", "Watch the directory for changes, uploading when detected") do
@options[:watch] = true
end
Do I want to add a time to that? I think not. I will stick with 2 seconds and await pull requests if anyone wants something different. Keep it small...

Now that I have the command line options being set in response to the -w switch, I need to actually do something with it. Currently, the push code looks like:
#...
when "push"
if @options[:destructive]
CouchDocs.destructive_database_create(options[:couchdb_url])
end
CouchDocs.put_dir(@options[:couchdb_url],
@options[:target_dir])
#...
If the -w switch is present, the put_dir should be run as long as the directory watcher run. If the switch is not present, then put_dir should be run once. Happily, the directory watcher gem can run one or indefinitely, so I can exploit that. First, I add directory_watcher as a dependency in my Bones 2.5 Rakefile:
depend_on 'rest-client'
depend_on 'json'
depend_on 'directory_watcher'
And regenerate my gemspec with rake gem:spec.

To describe normal usage, I use this example:
    before(:each) do
CouchDocs.stub!(:put_dir)

@dw = mock("Directory Watcher").as_null_object
DirectoryWatcher.stub!(:new).and_return(@dw)
end

it "should run once normally" do
@dw.should_receive(:run_once)

@it = CommandLine.new(%w(push uri dir))
@it.run
end
In other words, I want the DirectoryWatcher object to be told to run once. That fails because I am not using a directory watcher yet, so I wrap the current put_dir call inside one:
        dw = DirectoryWatcher.new @options[:target_dir]
dw.add_observer do |*args|
CouchDocs.put_dir(@options[:couchdb_url],
@options[:target_dir])
end

dw.run_once
I do not have a full stack test of this at the moment, so before moving on, I verify that the command line still pushes directories to CouchDB (it does).

Now I need to start the watcher if the :watch option is set:
    it "should start a watcher with -w" do
@dw.should_receive(:start)

@it = CommandLine.new(%w(push uri dir -w))
@it.run
end
To make that pass, I add a simple conditional:
        dw = DirectoryWatcher.new @options[:target_dir]

dw.add_observer do |*args|
CouchDocs.put_dir(@options[:couchdb_url],
@options[:target_dir])
end

if @options[:watch]
dw.start
else
dw.run_once
end
That should just about do it. I need to add some settings to directory watched (glob all changes in sub-directories, set the watch interval for 2 seconds:
        dw = DirectoryWatcher.new @options[:target_dir]
dw.glob = '**/*'
dw.interval = 2.0


dw.add_observer do |*args|
CouchDocs.put_dir(@options[:couchdb_url],
@options[:target_dir])
end

if @options[:watch]
dw.start

begin
sleep 30 while true
rescue Interrupt
dw.stop
puts
end

else
dw.run_once
end
I also add an endless loop that can be Ctrl-C'd top stop the watcher. That behavior seems like more trouble than it is worth to BDD so I simply add it. The other two options are not new behaviors, just settings, so no new specs there either.

That about does it. I do not know if the resulting code is any simpler that last night's prototype. I do know that it is as simple as I can make it. That is what matters most to me.

Tomorrow: document and release couch_docs 1.1.

Day #39

No comments:

Post a Comment