Saturday, October 07, 2006

Rake: Tasks with parameters

I've written several Rake tasks in the past that could have been improved by accepting parameters, but I never knew how to make it work. It's not that I thought it was impossible to pass parameters to Rake tasks, I just never bothered to figure out how. Lucky for me, a teammate recently took the time to figure it out. Today I'm changing an existing rakefile to take advantage of passing in parameters.

The file now:
require 'writer'
require 'html_writer'
require 'doc_writer'

directory "built_docs"
directory "built_html"

task :doc => [:built_docs, :clean_doc] do
Dir['chapters/*.txt'].each do |chapter|
output = "built_docs/#{File.basename(chapter,'txt')}doc"
File.open(output, 'w') { |file| DocWriter.new(file).write(chapter) }
puts "generated #{output}"
system "chmod 444 #{output}"
end
end

task :html => [:built_html, :clean_html] do
Dir['chapters/*.txt'].each do |chapter|
output = "built_html/#{File.basename(chapter,'txt')}html"
File.open(output, 'w') { |file| HtmlWriter.new(file).write(chapter) }
puts "generated #{output}"
system "chmod 444 #{output}"
end
end

task :clean_doc do
Dir['built_docs/*'].each do |output|
rm output
end
end

task :clean_html do
Dir['built_html/*'].each do |output|
rm output
end
end
Clearly, this file is not near as DRY as it could be.

The first step is to create a new task that will generate output and accept the format as an argument.
task :default do
unless ENV.include?("output") && (ENV['output']=='doc' || ENV['output']=='html')
raise "usage: rake output= # valid formats are [html] or [doc]"
end
format = ENV['output']
puts format
rm_rf format
mkdir format
end
The above code verifies that a valid format is specified. It also removes the need for both the directory and clean tasks. The only work left to do is combine the output generation tasks.
task :default do
unless ENV.include?("output") && (ENV['output']=='doc' || ENV['output']=='html')
raise "usage: rake output= # valid formats are [html] or [doc]"
end
format = ENV['output']
puts format
rm_rf format
mkdir format
Dir['chapters/*.txt'].each do |chapter|
output = "#{format}/#{File.basename(chapter,'txt')}#{format}"
writer = (format=='doc' ? DocWriter : HtmlWriter)
File.open(output, 'w') { |file| writer.new(file).write(chapter) }
puts "generated #{output}"
system "chmod 444 #{output}"
end
end
The resulting task is cleaner and easier to maintain.

Running the new task from the command line: focus:~ jay$ rake output=html

5 comments:

  1. Anonymous1:12 PM

    So how do you use it then? I can see 'rake html' and 'rake doc' from the old version, I'm presuming 'rake output=html' for the second, or do you need to set an environment variable from the shell?

    ReplyDelete
  2. Anonymous1:19 PM

    Thanks, I updated the entry.

    And, you are correct 'rake output=html' is the correct way to use parameters.

    ReplyDelete
  3. Anonymous10:09 PM

    While parameters are often very useful, in this case I'd remove the code duplication using the fact that Rakefiles are just Ruby scripts:

    FORMATS = {
    'html' => HtmlWriter,
    'doc' => DocWriter
    }

    FORMATS.each_pair do |format, writer|
    desc "Build documentation in #{format} format"
    task format do
    # whatever
    end
    end

    As a bonus, 'rake -T' stays usable without extra work (and 'rake html' is easier to type than 'rake output=html' ;))

    ReplyDelete
  4. Anonymous8:34 AM

    @ville, very good point. I guess this proves the point that pairing is much better than working on your own. ;)

    Thanks for the feedback.

    ReplyDelete
  5. Anonymous12:57 PM

    Jay, I would suggest the following variation...

    def generate(writer_class, pathmapping)
    Dir['chapters/*.txt'].each do |chapter|
    output = chapter.pathmap(pathmapping)
    File.open(output, 'w') { |file| writer_class.new(file).write(chapter) }
    puts "generated #{output}"
    system "chmod 444 #{output}"
    end
    end

    task :doc => [:built_docs, :clean_doc] do
    generate(DocWriter, "built_docs/%n.doc")
    end

    task :html => [:built_html, :clean_html] do
    generate(DocWriter, "built_html/%n.html")
    end

    Since indentation is messed up, you might find http://rafb.net/paste/results/wNWexm13.html more readable.

    The next thing I would consider doing is replacing the explicit loop over Dir[...] with a rule.

    ReplyDelete

Note: Only a member of this blog may post a comment.