#!/usr/bin/env ruby # # gp - "go project" # # I built 'gp' because I have a huge tree of different projects and # opening terminals to use them is a drag. iTerm has bookmarks but # they've never been convenient for me. # # What I wanted was to be able to say "cd ...some project..." and have # the shell "do the right thing." # # What I ended up with was a script that indexes and searches my # project tree and opens, potentially many, terminal sessions in my # choosen directory. # # Usage: # # gp -n 3 mailt # # Opens 3 terminal sessions in ~/Projects/ruby/mailtrap # # but when I am developing a Rails project there is a bunch of stuff # I want like starting mongrel, tail of development.log, etc... so I # added the ability to run a script # # gp --script rails mailt # # Will execute each line of ~/Projects/.gp/rails in ~/Projects/ruby/mailtrap # a new terminal session. When gp is run for the first time it builds a # sample Rails script that you'll want to modify. # # One caveat is not to use "exit" in a script (or only after a sleep for a # few seconds) because the closing session messes with how Appscript numbers # sessions and you will get breakage. # # Since typing gp --script rails is a bit long winded I made a further cheat. # When gp starts up it checks what filename was used to run it. Trims "gp" off # the front and substitutes the rest as a script so that: # # gprails mailt # # becomes equivalent to: # # gp --script rails mailt # # Then you can symlink "gprails" to "gp" for quick access. # require 'rubygems' require 'trollop' require 'appscript' module GoProject class Command def initialize( projects_folder, reindex, index_depth, number ) @folder = projects_folder @gp_folder = File.join( @folder, '.gp' ) @reindex = reindex @depth = index_depth @number = number if !File.directory?( @folder ) raise "Project folder does not exist!" end if !File.directory?( @gp_folder ) Dir.mkdir( @gp_folder ) build_initial_scripts end end def build_initial_scripts # Note, do not use "exit" in your scripts since this will # mess up the session numbering and Appscript will barf File.open( File.join( @gp_folder, "rails" ), "w" ) do |file| file.puts "mate .; diffly .; mongrel_rails start" file.puts "rake log:clear; tail -f log/development.log" file.puts "tail -f log/request.log" file.puts "ruby script/console" file.puts "irb" file.puts "#" end end def build_index index = [] puts "gp: building projects index for '#{@folder}'. Please wait." finder = IO.popen( "find #{@folder} -type d -maxdepth #{@depth} -print" ) while dir = finder.gets path = dir.chomp.sub( /#{@folder}\/?/, "" ) next if path == "" || path.detect { |component| component =~ /\.[^\.]+/ } index << path.split( File::SEPARATOR ) end File.open( File.join( @gp_folder, 'gp.idx' ), 'w' ) do |file| Marshal.dump( index, file ) end index end def load_index if @reindex || !File.exists?( File.join( @gp_folder, 'gp.idx' ) ) build_index else File.open( File.join( @gp_folder, 'gp.idx' ) ) do |file| Marshal.load( file ) end end end def find_matches( index, project ) matches = index.find_all do |folder| folder.last =~ /#{project}$/ end if matches.empty? matches = index.find_all do |folder| folder.last =~ /#{project}/i end end matches end def form_path( path_components ) File.join( @folder, *path_components ) end def create_session( path, command = "" ) @iterm ||= Appscript::app( 'iTerm' ) session = @iterm.current_terminal.sessions.end.make( :new => :session ) session.exec( :command => 'bash -l' ) session.write( :text => "cd #{path}" ) session.write( :text => command ) unless command.nil? end def open_script( path, script ) if !File.exists?( File.join( @gp_folder, script ) ) puts "Error: No gp script #{script} exists" else IO.foreach( File.join( @gp_folder, script ) ) do |command| command = command.chomp.gsub( /^#.*$/, "" ) create_session( path, command.chomp ) end end end def open_terms( path ) @number.times { create_session( path ) } end def open( path, script ) if script open_script( path, script ) else open_terms( path ) end end def go( project, script ) matches = find_matches( load_index, project ) if matches.empty? puts "No matching project found." elsif matches.size == 1 open( form_path( matches.first ), script ) else if matches.size > 30 puts "Too many matches, try a more selective expression." else matches.each_with_index do |match,index| puts "#{index+1}: #{form_path( match )}" end print "1..#{matches.size} or q> " choice = $stdin.gets.chomp if choice.downcase == "q" exit else open( form_path( matches[Integer( choice )-1] ), script ) end end end end def self.main( project, opts ) cmd = Command.new( opts[:projects], opts[:reindex], opts[:depth], opts[:n] ) cmd.go( project, opts[:script] ) end end end # Here is a cheat to allow you to symlink "gpr" to "gp" to get an # automagic "rails" version of the command auto_script = File.split( $0 ).last.sub( /^gp/, "" ) if auto_script != "" ARGV.unshift( "--script", auto_script ) end puts ARGV.inspect opts = Trollop::options do opt :projects, "Projects folder", :default => File.expand_path( "~/Projects" ) opt :script, "Run script", :type => :string opt :reindex, "Build the project index", :default => false opt :depth, "Maximum indexing depth", :default => 4 opt :n, "Number of terminals to open (unless Rails)", :default => 1 end GoProject::Command.main( ARGV.shift, opts )