command setup

Continuous Integration is pretty awesome and there’s a few options out there in the Ruby world. Most of them are more than what I want for my personal projects though, so I decided to hack together my own. I mainly use RSpec and all I wanted was something that would run my spec every time I save it or it’s corresponding class/module. Enter, Speccer.

Speccer really has two parts, a TextMate command that replaces the normal Apple + S shortcut and the command line script that “receives” these saves. Set up your TextMate command like the image above, making sure to duplicate the settings exactly. Then copy in the code below.

  1   #!/usr/bin/ruby
  2   require 'fileutils'
  3   require 'ftools'
  4   
  5   logpath = File.expand_path( "~/log" )
  6   
  7   unless File.directory?( logpath )
  8       FileUtils.mkdir( logpath )
  9   end
 10  
 11  log = File.open( File.join( logpath, "/spec.log" ), "w" )
 12  log.write "#{ Time.now }\n"
 13  log.write "#{ ENV['TM_FILEPATH'] }\n"
 14  log.write ENV['TM_PROJECT_DIRECTORY']
 15  log.close

Basically all this does is creates ~/log if it doesn’t exist, opens ~/log/spec.log and writes the time, file saved and the project directory to it, every time you hit Apple + S. This acts as a sort of Queue, but in reality the file only ever has one record in it at a time. This is how we let the script in the next part know about when we saved the file.

Create /usr/local/bin/speccer (or in whichever bin you want) and add the following:

  1   #!/usr/bin/ruby
  2   require 'ftools'
  3   
  4   log = File.open( File.expand_path( "~/log/spec.log"), "r" )
  5   
  6   loop do
  7   
  8       trap("INT") { |this| puts ""; exit this }
  9   
 10      last_time = @time
 11  
 12      log.seek(0)	
 13      record = log.read.split("\n")
 14  
 15      @time         = record.first
 16      @saved_file  = record[1]
 17      @proj_path  = record.last
 18  
 19      begin
 20  	   
 21          unless @saved_file.include?( "_spec.rb" )
 22              saved_file_array = @saved_file.split( "/" )
 23              lib_position       = saved_file_array.index( "lib" ) 
 24              spec_filename   = File.basename( @saved_file, ".rb" ) << "_spec.rb"
 25              spec_dir            = saved_file_array[ lib_position+1..saved_file_array.length-2 ].join( "/" )     
 26              @spec               = "#{@proj_path}/spec/#{spec_dir}/#{spec_filename}"
 27          else
 28              @spec = @saved_file
 29          end
 30  
 31          if last_time != @time
 32              system("clear")
 33              puts @time
 34              system("spec #{@spec} -c -f specdoc")
 35          end
 36  	
 37      rescue
 38      end
 39  
 40      sleep 1
 41  	
 42  end

This actual speccer script first opens the log file then just continuously checks to see if the Date is the same as the previous run through. If it is, it won’t do anything, but if it’s new it’ll run the spec. The if-statement in the middle just figures out which spec is associated with the Class/Module file being saved by breaking apart the file’s path and reconstructing it within the spec/ folder. Speccer assumes you set up your spec/ directory in the same structure as your lib/ folder and name your specs classname_spec.rb. Save the script and chmod 755 speccer and you’re set.

Now just run speccer then go writing your tests/specs and the file needed to make them pass. Every time you save one of them it’ll automatically run. Quick and easy continuous integration for the solo developer.