alloy / kicker

A lean, agnostic, flexible file-change watcher, using OS X FSEvents.

This URL has Read+Write access

kicker /
name age message
file .gitignore Wed Mar 18 09:10:10 -0700 2009 Include the image of the frog in the README. Ig... [Manfred]
file .kick Loading commit data...
file LICENSE Sat Mar 14 10:30:21 -0700 2009 Vendored growlnotifier [alloy]
file README.rdoc
file Rakefile Wed Sep 30 04:49:05 -0700 2009 Updated gem description. [alloy]
file TODO.rdoc
file VERSION.yml
directory bin/ Thu Aug 27 05:21:28 -0700 2009 Prepend path when run directly to make it easie... [evilchelu]
directory html/ Sun Sep 27 15:52:45 -0700 2009 Oops, accidentally removed kikker.jpg during me... [alloy]
file kicker.gemspec
directory lib/
directory test/
directory vendor/ Sat Sep 26 08:23:45 -0700 2009 Instead of checking the mtime of files twice, d... [alloy]
README.rdoc

Kicker

A lean, agnostic, flexible file-change watcher, using OS X FSEvents.

Meet king kikker, kicking stuff in your computers is his dream come true!

Drawing by Manfred Stienstra. The character is purely fictional, so if you feel offended; live with it.

Installation

  $ sudo gem install kicker -s http://gemcutter.org

The short version

  Usage: kicker [options] [paths to watch]
      -e, --execute [COMMAND]          The command to execute.
          --[no-]growl                 Whether or not to use Growl. Default is to use growl.
          --growl-command [COMMAND]    The command to execute when the Growl succeeded message is clicked.
      -l, --latency [FLOAT]            The time to collect file change events before acting on them. Defaults to 1.5 sec.
      -r, --recipe [NAME]              A named recipe to load.

    Available recipes:
      - ignore
      - jstest
      - rails

The long version

Execute a shell command

Show all files, whenever a change occurs in the current work directory:

  $ kicker -e "ls -l" .

Show all files, whenever a change occurs to a specific file:

  $ kicker -e "ls -l" foo.txt

Or use it as a ghetto-autotest, running tests whenever files change:

  $ kicker -e "ruby test/test_case.rb" test/test_case.rb lib/file.rb

Et cetera.

Using recipes

A recipe is a predefined handler. You can use as many as you like, by specifying them with the —recipe (-r) option.

For instance, when in the root of a typical Ruby on Rails application, using the rails recipe will map models, concerns, controllers, helpers, and views to their respective test files. These will then all be ran with Ruby.

A few recipes come shipped with Kicker:

  • Ruby on Rails, as aforementioned.
  • JavaScript tests, needs HeadlessSquirrel to run.
  • Ignore, ignores logs, tmp, and svn and git files.

Add your own shared recipes to ~/.kick.

Project specific handlers

Most of the time, you’ll want to create handlers specific to the project at hand. This can be done by adding your handlers to a .kick file and running Kicker from the directory containing it.

This file is reloaded once saved. No need to stop Kicker.

Writing handlers

Whenever file-change events occur, Kicker will go through a chain of handlers until that the files list is empty, or the end of the chain is reached.

Handlers are objects that respond to #call. These are typically Proc objects. (If you know Rack, you’re familiar with this concept.) Every handler gets passed a list of changed files and can decide whether or not to act on them. Normally when handling a file, you should remove it from the files list, unless you want to let the file fall through to another handler. In the same way, one can add files to handler to the files list.

Time for a simple example

  process do |files|
    execute("rake docs:generate && open -a Safari html/index.html") if files.delete("README.rdoc")
  end

A handler is defined by passing a block to process. Which is one of three possible callback chains to add your handlers to, the others being: pre_process and post_process. See Kernel for more info.

Then README.rdoc is deleted from the files array. If it did exist in the array and was deleted, a shell command is executed which runs a rake task to generate rdoc and open the docs with Safari.

Something more elaborate.

Consider a Rails application with a mailer. Since the naming convention of mailer views tend to be fairly application specific, a specific handler has to be added:

  process do |files|
    test_files = files.take_and_map do |file|
      if path =~ %r{^app/views/mailer/\w+\.erb$}
        'test/unit/mailer_test.rb'

      # elsif ... handle more app specific stuff
      end
    end

    run_ruby_tests test_files
  end

The files list is iterated over with the Array#take_and_map method, which both removes and maps the results. This is an easy way to do a common thing in recipes. See Kicker::ArrayExt for details.

The handler then checks if the file is a mailer view and if so runs the mailers test case. The run_ruby_tests runs them with the following command:

  execute "ruby -r #{test_files.join(' -r ')} -e ''" unless test_files.empty?

See Kernel for more info on the utility methods.

Addendum

The recipes directory that ships with Kicker and ~/.kick are both added to the load path, so any recipes can be required. Once they’re required they are added to the callback chains.

As an example, say you might want to ignore files in ./data:

  require 'ignore'
  ignore(/^data\//)

That’s basically it, just remember that the order of specifying handlers can be important in your decision on where to specify handlers.