28 October 2006

My first Ruby program

I decided to learn Ruby and Rails — now I need to get books on both (damn that DHH and his contagious enthusiasm)

Below are some snippets from my first Ruby program: convert.rb. It is a CGI program, that converts PostScript, TeX, CWEB and a couple of other formats to PDF. It also converts .tar.gz, .tar.bz2 and other compressed files to .zip.

[Note: I wrote this program flipping back and forth between the PixAxe1 and a bunch of other online resources — so forgive me if it isn't in the spirit of Ruby.]

Ruby blocks are cool!!!

def ignore_errors &block
  begin
    block.call
  rescue Exception => e
    e
  end
end

def with_tmpdir &block
  result = nil
  original = Dir.pwd
  tmp = tmpname # creates a temporary name (not safe)
  begin
    Dir.mkdir tmp
    Dir.chdir tmp
    result = block.call original
  ensure
    Dir.chdir original
  end
  FileUtils.rm_rf tmp
  result
end
and it is quiet easy to create DSLs:
# converters operate like so:
# 3 args: first, a symbol naming the converter;
# second, a regexp that if matched the third arg
# is executed: which is a block that takes one
# argument -- a local filename
# the converter should return true or nil:
# true to continue and try other converters;
# nil to return -- as this is the final one.

defconverter(:ps, /\.ps$/) { | x |
  sh "ps2pdf #{x}"
  nil
}

defconverter(:cweb, /\.w$/) { | x |
  sh "cweave #{x} - #{x}.tex"
  sh "tex #{x}.tex"
  sh "dvips #{x}.dvi"
  sh "ps2pdf #{x}.ps"
  nil
}
Finally, CGI.rb's HTML output facility make the code elegant (I wish that, optionally, you could output to a stream...)
  cgi.out {
    cgi.html("PRETTY" => " ") {
      cgi.head { cgi.title { 'convert.rb' } } +
      cgi.body {
        cgi.h3 { 'convert.rb — convert file to PDF or ZIP' } +
        cgi.form('get', $0) {
        cgi.b { 'url:' } +
        cgi.text_field('url', '', 150) + ' ' +
        cgi.submit + cgi.br +
        cgi.checkbox('zipitup') + ' zip it up? ' +
        cgi.small { '[with this turned on, you can convert
                     unix archive formats (like .tar.gz, .tar.bz2,
                     ...) to .zip format]' }

      ...
      ...
      ...
      [snipped]
Here are some initial thoughts:
  • I was surprised at how easily and quickly I was able to develop the program
  • calling methods (those that don't take args and those that take a single arg) without parenthesis is quiet refreshing:
    (Before this, looking at Ruby from the "outside", this convention was disturbing — "How do I pass a function to another function" — but I didn't encounter that problem with this program because it seems to me that you pass in closures (blocks) to other functions...)

    compare method.to_a.to_s to method.to_a().to_s()
    sh "rpm2cpio #{x} | cpio -dimv --no-absolute-filenames"
  • love the short, nicely named methods: FileUtils.rm_rf tmp
  • ability to specify integer as 100_000_000 is nice