Beautiful Ruby in TextMate
TextMate is Allan Odgaard’s popular and powerful text editor for Mac OS X. It has great support for projects involving multiple files and has some very powerful hooks allowing customization. Using language or application-specific “bundles”, TextMate users can add macros, shortcuts, and even run external programs to parse, modify, and replace all or part of a file being edited.
Although TextMate currently has no built-in Ruby code beautifier, Paul Lutus has written a Ruby code beautifier in Ruby. To add it to TextMate, open TextMate’s Bundle Editor window using the Window -> Show Bundle Editor menu command. You’ll then see a window like the one below.

In the pane on the left-hand side, scroll down to the Ruby item and expand it. From the + menu at the bottom left, select the New Command menu item. You can name your command anything you like; I called mine “Beautify”. Click on the text field to the right of Activation: and press the key combination that you want to use to activate your command (I’m using Command-B). Then set the Scope Selector: field to “source.ruby” to ensure that your new command will only be run when you are editing Ruby source code. Finally, paste my slightly-modified version of Paul’s beautifier script into the command window (it’s below). When you’re finished, the Bundle Editor window should look like this:

Now any time you are editing a Ruby file, you can beautify it by pressing Command-B (or your chosen activation key). If something goes wrong or you don’t like the results, just use TextMate’s undo command (Command-Z) to go back to the original version.
The beautification code is below. I’ve made some trivial changes to Paul’s version to make it read from STDIN and write to STDOUT; since his version was released under the GPL, I believe that the GPL license terms still apply, but I waive all rights to my changes. With thanks to Paul and Allan, Happy Rubying!
#!/usr/bin/env ruby
# Ruby beautifier, version 1.3, 04/03/2006
# Copyright (c) 2006, P. Lutus
# TextMate modifications by T. Burks
# Released under the GPL
$tabSize = 2
$tabStr = " "
# indent regexp tests
$indentExp = [
/^module\b/,
/(=\s*|^)if\b/,
/(=\s*|^)until\b/,
/(=\s*|^)for\b/,
/(=\s*|^)unless\b/,
/(=\s*|^)while\b/,
/(=\s*|^)begin\b/,
/(=\s*|^)case\b/,
/\bthen\b/,
/^class\b/,
/^rescue\b/,
/^def\b/,
/\bdo\b/,
/^else\b/,
/^elsif\b/,
/^ensure\b/,
/\bwhen\b/,
/\{[^\}]*$/,
/\[[^\]]*$/
]
# outdent regexp tests
$outdentExp = [
/^rescue\b/,
/^ensure\b/,
/^elsif\b/,
/^end\b/,
/^else\b/,
/\bwhen\b/,
/^[^\{]*\}/,
/^[^\[]*\]/
]
def makeTab(tab)
return (tab < 0)?"":$tabStr * $tabSize * tab
end
def addLine(line,tab)
line.strip!
line = makeTab(tab)+line if line.length > 0
return line + "\n"
end
def beautifyRuby
commentBlock = false
multiLineArray = Array.new
multiLineStr = ""
tab = 0
source = STDIN.read
dest = ""
source.split("\n").each do |line|
# combine continuing lines
if(!(line =~ /^\s*#/) && line =~ /[^\\]\\\s*$/)
multiLineArray.push line
multiLineStr += line.sub(/^(.*)\\\s*$/,"\\1")
next
end
# add final line
if(multiLineStr.length > 0)
multiLineArray.push line
multiLineStr += line.sub(/^(.*)\\\s*$/,"\\1")
end
tline = ((multiLineStr.length > 0)?multiLineStr:line).strip
if(tline =~ /^=begin/)
commentBlock = true
end
if(commentBlock)
# add the line unchanged
dest += line + "\n"
else
commentLine = (tline =~ /^#/)
if(!commentLine)
# throw out sequences that will
# only sow confusion
tline.gsub!(/\/.*?\//,"")
tline.gsub!(/%r\{.*?\}/,"")
tline.gsub!(/%r(.).*?\1/,"")
tline.gsub!(/\\\"/,"'")
tline.gsub!(/".*?"/,"\"\"")
tline.gsub!(/'.*?'/,"''")
tline.gsub!(/#\{.*?\}/,"")
$outdentExp.each do |re|
if(tline =~ re)
tab -= 1
break
end
end
end
if (multiLineArray.length > 0)
multiLineArray.each do |ml|
dest += addLine(ml,tab)
end
multiLineArray.clear
multiLineStr = ""
else
dest += addLine(line,tab)
end
if(!commentLine)
$indentExp.each do |re|
if(tline =~ re && !(tline =~ /\s+end\s*$/))
tab += 1
break
end
end
end
end
if(tline =~ /^=end/)
commentBlock = false
end
end
STDOUT.write(dest)
# uncomment this to complain about mismatched blocks
#if(tab != 0)
# STDERR.puts "Indentation error: #{tab}"
#end
end
beautifyRuby


Comments (4) post a reply
Here is an updated version (2.1), based on the latest release of the original script:
#!/usr/bin/env ruby # Ruby beautifier, version 2.1, 09/11/2006 # Copyright (c) 2006, P. Lutus # Released under the GPL # Modificated to work as a Textmate command $tabSize = 2 $tabStr = " " # indent regexp tests $indentExp = [ /^module\b/, /^if\b/, /(=\s*|^)until\b/, /(=\s*|^)for\b/, /^unless\b/, /(=\s*|^)while\b/, /(=\s*|^)begin\b/, /(^| )case\b/, /\bthen\b/, /^class\b/, /^rescue\b/, /^def\b/, /\bdo\b/, /^else\b/, /^elsif\b/, /^ensure\b/, /\bwhen\b/, /\{[^\}]*$/, /\[[^\]]*$/ ] # outdent regexp tests $outdentExp = [ /^rescue\b/, /^ensure\b/, /^elsif\b/, /^end\b/, /^else\b/, /\bwhen\b/, /^[^\{]*\}/, /^[^\[]*\]/ ] def makeTab(tab) return (tab < 0) ? "" : $tabStr * $tabSize * tab end def addLine(line,tab) line.strip! line = makeTab(tab)+line if line.length > 0 return line + "\n" end def beautifyRuby commentBlock = false programEnd = false multiLineArray = Array.new multiLineStr = "" tab = 0 source = STDIN.read dest = "" source.split("\n").each do |line| if(!programEnd) # detect program end mark if(line =~ /^__END__$/) programEnd = true else # combine continuing lines if(!(line =~ /^\s*#/) && line =~ /[^\\]\\\s*$/) multiLineArray.push line multiLineStr += line.sub(/^(.*)\\\s*$/,"\\1") next end # add final line if(multiLineStr.length > 0) multiLineArray.push line multiLineStr += line.sub(/^(.*)\\\s*$/,"\\1") end tline = ((multiLineStr.length > 0)?multiLineStr:line).strip if(tline =~ /^=begin/) commentBlock = true end end end if(commentBlock || programEnd) # add the line unchanged dest += line + "\n" else commentLine = (tline =~ /^#/) if(!commentLine) # throw out sequences that will # only sow confusion while tline.gsub!(/'.*?'/,"") end while tline.gsub!(/".*?"/,"") end while tline.gsub!(/\`.*?\`/,"") end while tline.gsub!(/\{[^\{]*?\}/,"") end while tline.gsub!(/\([^\(]*?\)/,"") end while tline.gsub!(/\/.*?\//,"") end while tline.gsub!(/%r(.).*?\1/,"") end tline.gsub!(/\\\"/,"'") $outdentExp.each do |re| if(tline =~ re) tab -= 1 break end end end if (multiLineArray.length > 0) multiLineArray.each do |ml| dest += addLine(ml,tab) end multiLineArray.clear multiLineStr = "" else dest += addLine(line,tab) end if(!commentLine) $indentExp.each do |re| if(tline =~ re && !(tline =~ /\s+end\s*$/)) tab += 1 break end end end end if(tline =~ /^=end/) commentBlock = false end end STDOUT.write(dest) # uncomment this to complain about mismatched blocks # if(tab != 0) # STDERR.puts "#{path}: Indentation error: #{tab}" # end end beautifyRubyI tried this many times I and keep getting the same error.
env: ruby: No such file or directory
I have “ruby=/opt/local/bin/ruby” in my environment and if I run ”/usr/bin/env ruby” shows it is responsive.
I tried added a path tag to ~/.MacOSX/environment.plist for a path to ”/opt/local/bin/ruby” with no luck.
I also tried changing ”#!/usr/bin/env ruby” in the script to ”#!/opt/local/bin/ruby” but that did not work either.
I am running Ruby version 1.8.6 and TextMate version 1.5.6 (1405).
I tried this on several macs with the same results.
Just trying this script as well. I have found that the first one works and the second one gives the error you have been describing. Haven’t isolated the cause yet but you could try using the first script and then looking at the difference between them.
Suspect the second script has been modified but not tested.
Thanks for sharing this.
You inspired me to make some further improvements to the beautifier script and put it together as a ready to install Code Beatifier Textmate bundle hosted on Github.