TextMate filetype detection for script/runner Rails scripts
So you’re building some righteous automation for your killer web 2.0 app, placing scripts in RAILS_ROOT/script that you can call from cron for nightly maintenance, etc. To bootstrap your rails environment, you decide to use the shebang feature of script/runner, available since changeset 5189. When you start to edit the script in TextMate (you are using TextMate, aren’t you?) there is no syntax highlighting to be found! It’s all plain text with no colors, and none of your ever-so-helpful keyboard macros work! Frightful. Well, take a deep breath, because together, we’re going to get the filetype detection magic working for you.
Before we get started, it’s helpful to know how filetype detection works. TextMate does a couple of different types of filetype detection — the first is based off of the extension, so if you named your script with a .rb extension, you are probably wondering what in the world I’m rambling about. Dude. It just works.
However, if you followed the rails convention for scripts, and did not use an extension with your filename, keep reading. The second type of detection works by scanning the so called “shebang” line at the top of the script which tells the shell (and in this case TextMate) which interpreter to use to evaluate your script — this is how we will tell TextMate that script/runner really means ruby.
First of all, you’ll need to fire up the Bundle Editor and select “Languages” from the drop-down filter. Expand the “Rails” node, and then select the “Ruby on Rails” language. On the right side, you should see the definition being used by TextMate to detect the Ruby on Rails scope. If you have not modified your bundle, you’ll probably see that it is using a fileTypes to look for .rxml files. This is where we want to insert the following line:
@ firstLineMatch = ‘^#!.*(script/runner)’;@
Here’s a screenshot of what it should look like when you are done:

Now go back to your script and enjoy all the colorized, scope-aware editing goodness that is TextMate!
Rake Command Completion Using Rake
This is an update of a previous post.
I just watched Jim Weirich's talk on Rake at the Rails Edge Studio and decided to update the command completion script to use Rake itself for caching the tasks. Err the Blog did an update on my script last month to include caching for a nice speedup, so I took that and made sure the cache is regenerated when the Rakefile or any rake tasks found in your rails app have been updated. I added this with just some straight ruby code, but Jim's talk made me realize that this type of thing is exactly is what rake is for. So, here is the updated version:
#!/usr/bin/env ruby # Complete rake tasks script for bash # Save it somewhere and then add # complete -C path/to/script -o default rake # to your ~/.bashrc # Nicholas Seckar <nseckar@gmail.com> # Saimon Moore <saimon@webtypes.com> # http://www.webtypes.com/2006/03/31/rake-completion-script-that-handles-namespaces # http://errtheblog.com/post/33 exit 0 unless File.file?(File.join(Dir.pwd, 'Rakefile')) require 'rubygems' require 'rake' DOTCACHE = File.join(File.expand_path('~'), ".rake_task_cache" , Dir.pwd.hash.to_s) RAKE_FILES = FileList[ __FILE__, 'Rakefile', 'lib/tasks/**/*.rake', 'vendor/plugins/*/tasks/**/*.rake'] file DOTCACHE => RAKE_FILES do tasks = `rake --silent --tasks`.split("\n").map { |line| line.split[1] } require 'fileutils' dirname = File.dirname(DOTCACHE) FileUtils.mkdir_p(dirname) unless File.exists?(dirname) File.open(DOTCACHE, 'w') { |f| f.puts tasks } end Rake::Task[DOTCACHE].invoke tasks = File.read(DOTCACHE) exit 0 unless /\brake\b/ =~ ENV["COMP_LINE"] after_match = $' task_match = (after_match.empty? || after_match =~ /\s$/) ? nil : after_match.split.last tasks = tasks.select { |t| /^#{Regexp.escape task_match}/ =~ t } if task_match # handle namespaces if task_match =~ /^([-\w:]+:)/ upto_last_colon = $1 after_match = $' tasks = tasks.map { |t| (t =~ /^#{Regexp.escape upto_last_colon}([-\w:]+)$/) ? "#{$1}" : t } end puts tasks exit 0
Update: I fixed the bug that janfri pointed out. The bug caused the first task to be missed. I also changed it so it won't abort if rake isn't the first command on the command line. This will allow stringing multiple commands together. For instance:
rake db:migrate VERSION=0 && rake db:migrate
Namespaces and Rake Command Completion
I got some basic rake command line completion working today using Jon Baer’s comment. Very simple, very easy:
complete -W '`rake—silent—tasks | cut -d ” ” -f 2`' -o default rake
However this didn’t work for the namespaced tasks in a rails app like rake test:units. Searching a little further I found a reference to some code Nicholas Seckar wrote on project.ioni.st. This used ruby to find the possible tasks for command completion. This looked promising, but it still didn’t work for namespaced tasks. A little more googling led me to what looked like the perfect link: Rake-completion script that handles namespaces. Alas, it only handled one level of namespacing. It worked nicely for rake test:units, but rake tmp:ses<TAB> would complete to rake tmp:clear instead of rake tmp:sessions:clear. Also, rake test:units <TAB> would complete to rake test:units units instead of giving me all the tasks again, just in case you want to run multiple tasks form the command line.
So, now what? Stand on the shoulders of others, naturally. Here is what I’m using now that handles multiple namespaces and multiple tasks per command line:
#!/usr/bin/env ruby # Complete rake tasks script for bash # Save it somewhere and then add # complete -C path/to/script -o default rake # to your ~/.bashrc # Nicholas Seckar <nseckar@gmail.com> # Saimon Moore <saimon@webtypes.com> # http://www.webtypes.com/2006/03/31/rake-completion-script-that-handles-namespaces exit 0 unless File.file?(File.join(Dir.pwd, 'Rakefile')) exit 0 unless /^rake\b/ =~ ENV["COMP_LINE"] after_match = $' task_match = (after_match.empty? || after_match =~ /\s$/) ? nil : after_match.split.last tasks = `rake --silent --tasks`.split("\n")[1..-1].collect {|line| line.split[1]} tasks = tasks.select {|t| /^#{Regexp.escape task_match}/ =~ t} if task_match # handle namespaces if task_match =~ /^([-\w:]+:)/ upto_last_colon = $1 after_match = $' tasks = tasks.collect { |t| (t =~ /^#{Regexp.escape upto_last_colon}([-\w:]+)$/) ? "#{$1}" : t } end puts tasks exit 0
Managing Rails Plugins dependencies
Rails has a nice plugin system allowing to add common code to a project. A plugin should really be independent from any other plugins. But we also use plugins to share code among different projects we are working on and our code depends on existing plugins. The Rails development team want’s to keep the plugin system simple and didn’t provide an explicit way to handle these dependencies, which I believe is a good decision. There is a solution. Simply name the plugins in order off the dependencies you have. Let’s assume you want to add “my_very_own_plugin” plugin that depends on the enumation_mixin, then simply organize the /vendor/plugins folder as follows, et Voila!:.
/myrailsapp /vendor /plugins /01_acts_as_taggable /01_enumations_mixin /01_acts_as_versioned /02_my_very_own_plugin
If we look at the Rails::Initializer we can see why this works. Note, this is only an extract of the full class that Rails provides to bootstrap your rails applicaton. The sort on line 4 here after allows this trick.
1 2 3 4 5 6 7 8
|