Strange Symphonies The best way to predict the future is to invent it

19Mar/080

Malaysia.rb March 2008 Meetup

Spammed on several places:

We have two presentations lined up for the night:

1. Gavin will provide a survey of different methods of handling missing references in various programming languages with emphasis on Scala. This stuff has been popping up a lot on Reddit. For a good backgrounder (in Ruby), see http://blog.thoughtfolder.com/2008-03-16-navigating-nil-method-chaining-in-ruby.html

2. Aizat will live build a Jabber bot that integrates with Twitter, provides system monitoring and makes coffee in the morning. Expect lots of BDD love with Autotest and RSpec.

Also, I have one last FREE PeepCode Episode Coupons to give away! 

When: Thursday, March 27th 2008,  7:30PM 

Agenda:
7:30 - 8:00pm Socializing
8:00 - 8:15pm Opening by the Organizer
8:15 - 8:45pm "Survey of different methods of handling missing Preferences" by Gavin Bong
8:45 - 9:15pm "Developing a Jabber bot ... LIVE!" by Aizat Faiz
9:15 - 9:30pm PeepCode Episode Coupon Lucky Draw and Close 

Cost: 
Free. No registration required, just come right in, have a seat, and join the crowd. Invite your friends. 

Contact:
kamal.fariz@gmail.com
+60123099143 
aizat.faiz@gmail.com
+60176908783

Where:
Open University Malaysia Angkasa Raya
Floor Tingkat 3, Bangunan Angkasa Raya, 
Jalan Ampang
Kuala Lumpur

Located near Kuala Lumpur City Centre (KLCC)
15 minute walk from the KLCC LRT station.
Praying facilities are available on the campus.

11Dec/070

Automatically Restart script/server For Easier Plugin Development with FSEvents

When developing a Rails plugin, you are required to restart the server so that your plugin will be reloaded. You'll notice that after a while this becomes a rather tedious process, especially if you are working on a hot-off-the-press new plugin.

Following suit on my autotest with FSEvents, I opted to listen for any changes to the vendor/plugins and lib directories to restart the server as required. Thus freeing me of the horrible grunt work of restarting the server.

Note: This is Mac OS X 10.5 Leopard specific.

The Incantation

Create the required file script/autorestart_server, and put this beauty in it.

For an easy to copy dump, click "view plain".

Note: Don't forget to make it executable! chmod u+x script/autorestart_server Terminal

#!/usr/bin/env ruby

PATHS_TO_OBSERVE = /^(lib|vendor\/plugins)/

require 'osx/foundation'
OSX.require_framework '/System/Library/Frameworks/CoreServices.framework/Frameworks/CarbonCore.framework'
include OSX

def start_server
  IO.popen("script/server #{ARGV.join(' ')}")
end

def stop_server(io)
  return if io.nil?
  Process.kill("INT", io.pid)
end

io = start_server

callback = proc do |stream, ctx, numEvents, paths, marks, eventIDs|
  paths.regard_as('*')
  rpaths = []
  length = Dir.pwd.length + 1

  numEvents.times { |i| rpaths << paths[i][length..-1] }

  next if rpaths.select { |path| path =~ PATHS_TO_OBSERVE }.empty?
  stop_server(io)
  puts "Restarting server"
  io = start_server
end

stream = FSEventStreamCreate(KCFAllocatorDefault, callback, nil, [Dir.pwd], KFSEventStreamEventIdSinceNow, 1.0, 0)
unless stream
  puts "Failed to create stream"
  exit
end

FSEventStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), KCFRunLoopDefaultMode)
unless FSEventStreamStart(stream)
  puts "Failed to start stream"
  exit
end

begin
  CFRunLoopRun()
rescue Interrupt
  stop_server(io)
  FSEventStreamStop(stream)
  FSEventStreamInvalidate(stream)
  FSEventStreamRelease(stream)
end

Automatically Restarting script/server

Now feel free to execute script/autorestart_server

Wondering where else I can inject FSEvents into, its quite a handy little tool...

28Nov/070

Taming the autotest Beast with FSEvents

autotest is a great tool and all, but it is simply resource intensive. Due to autotest's implementation, it eats up CPU resources, not because the tests are always running (only after you modify your file), but because autotest continually polls each file in your directory, and sub directories, and checks to see if it has been modified.

This continual polling isn't good for CPU resources. Now I may have some spare cycles to let autotest do its thang, but it also doesn't sound too healthy for my hard drive.

Note: This is Mac OS X 10.5 Leopard specific.

Along Comes a Leopard

Whilst reading the Arstechnica review on Leopard, I came upon the section on the File System Events (FSEvents) that was introduced in 10.4 actually (for Spotlight), but used once again for Time Machine. In Mac OS X 10.5 Leopard, the API was opened up for the public to consume.

File System Events (FSEvents)

In its simplest level, your application will notify FSEvents, that it wants to listen to a particular directory, and when that directory (or its sub directories) are modified, FSEvents basically triggers a callback in your application. This allows you to hook to, listen to file system changes, and react to accordingly.

This is exactly what I needed to calm the fury of the autotest beast.

Install RubyCocoa

Note: It looks like the code works on the stock Ruby and RubyCocoa out of the box actually. You don't need to install the latest version.

First of all, you need to install RubyCocoa, as this provides us with the bindings required to communicate with FSEvents. As I installed Ruby via MacPorts, I opted to do a source install (the MacPorts at present, is one version behind). I did run into trouble, encountering this error "file is not of required architecture".

If you ever needed a reason to actually use RubyCocoa, let this be your reason!

Taming the beast

Once installed, dump this little gem into your ~/.autotest file:

class Autotest
  def run
    hook :run
    reset

    run_tests

    require 'osx/foundation'
    OSX.require_framework '/System/Library/Frameworks/CoreServices.framework/Frameworks/CarbonCore.framework'

    callback = proc do |stream, ctx, numEvents, paths, marks, eventIDs|
      paths.regard_as('*')
      rpaths = []

      numEvents.times { |i| rpaths << paths[i] }

      run_tests_in_paths(*rpaths)
    end

    allocator = OSX::KCFAllocatorDefault
    context   = nil
    path      = [Dir.pwd]
    sinceWhen = OSX::KFSEventStreamEventIdSinceNow
    latency   = 1.0
    flags     = 0

    stream   = OSX::FSEventStreamCreate(allocator, callback, context, path, sinceWhen, latency, flags)
    unless stream
      puts "Failed to create stream"
      exit
    end

    OSX::FSEventStreamScheduleWithRunLoop(stream, OSX::CFRunLoopGetCurrent(), OSX::KCFRunLoopDefaultMode)
    unless OSX::FSEventStreamStart(stream)
      puts "Failed to start stream"
      exit
    end

    OSX::CFRunLoopRun()
  rescue Interrupt
    OSX::FSEventStreamStop(stream)
    OSX::FSEventStreamInvalidate(stream)
    OSX::FSEventStreamRelease(stream)
  end

  def find_files_in_paths(*paths)
    current_dir = Dir.pwd.length + 1
    result = {}
    paths.each do |path|
      # Largely copied from autotest
      Find.find path do |f|
        Find.prune if @exceptions and f =~ @exceptions and test ?d, f
        next if test ?d, f
        next if f =~ /(swp|~|rej|orig)$/
        next if f =~ /\/\.?#/

        filename = f[current_dir..-1]
        result[filename] = File.stat(filename).mtime rescue next
      end
    end
    return result
  end

  def run_tests_in_paths(*paths)
    find_files_to_test(find_files_in_paths(*paths))
    return if @files_to_test.empty?
    # Copied from autotest
    cmd = make_test_cmd @files_to_test

    hook :run_command
    puts cmd

    old_sync = $stdout.sync
    $stdout.sync = true
    @results = []
    line = []
    begin
      open("| #{cmd}", "r") do |f|
        until f.eof? do
          c = f.getc
          putc c
          line << c
          if c == ?\n then
            @results << line.pack("c*")
            line.clear
          end
        end
      end
    ensure
      $stdout.sync = old_sync
    end
    hook :ran_command
    @results = @results.join

    handle_results(@results)
  end
end

Now when you run autotest, you'll run into something like this:

/Users/aizat/.autotest:4: warning: method redefined; discarding old run

Don't worry about it, and feel free to ignore it.

With this code, you can also tame the beast. There you have it, a much saner autotest, only for Mac OS X 10.5 Leopard.