Flexible Rake task
I would like to use the following Rake task for multiple directories. And each directory would need slightly different constants defined. How can I handle this and stay DRY?
namespace :assets do
EXT = 'js'
OBJDIR = 'public/javascripts'
LIBFILE = "#{OBJDIR}/packaged.#{EXT}"
SRC = FileList["#{OBJDIR}/*.#{EXT}"].select {|file| !file.match(/\.min\.#{EXT}|packaged\.#{EXT}/)}
OBJ = SRC.collect {|fn| File.join(OBJDIR, File.basename(fn).ext("min.#{EXT}"))}
MINE = %w(4sq app fb mbp).collect {|x| x + ".#{EXT}"}
desc "Build #{LIBFILE}"
task :build => LIBFILE
desc "Remove minified files"
task :clean do
rm_f OBJ
end
desc "Remove #{LIBFILE}"
task :clobber do
rm_f LIBFILE
end
file LIBFILE => OBJ do
sh "cat #{OBJ} >> #{LIBFILE}"
end
rule ".m开发者_开发问答in.#{EXT}" => lambda{ |objfile| find_source(objfile) } do |t|
if EXT == 'js'
if MINE.include?(File.basename(t.source))
sh "closure --js #{t.source} --js_output_file #{t.name}"
else
sh "closure --warning_level QUIET --third_party --js #{t.source} --js_output_file #{t.name}"
end
elsif EXT == 'css'
sh "yuicompressor #{t.source} -o #{t.name}"
end
end
def find_source(objfile)
base = File.basename(objfile, ".min.#{EXT}")
SRC.find {|s| File.basename(s, ".#{EXT}") == base}
end
end
First you must replace the constants by variables. The next problem is to set the variables. Task can get variables.
Example:
namespace :assets do |x1,x2|
task :doit, :ext, :objdir do |tsk, args|
puts tsk
p args
end
end
You can call it with:
rake assets:doit[js,objdir]
Result:
assets:doit called with {:ext=>"js", :objdir=>"objdir"}
If you want to avoid to set the variables for each of your task, you may add a 'set' task:
namespace :assets2 do |x1,x2|
task :set, :ext, :objdir do |tsk, args|
@args = args
puts "#{tsk} set: #{@args.inspect}"
end
task :doit do |tsk|
puts "#{tsk} called with #{@args.inspect}"
end
end
Call: rake assets2:set[js,objdir] assets2:doit
Result:
assets2:set set: {:ext=>"js", :objdir=>"objdir"}
assets2:doit called with {:ext=>"js", :objdir=>"objdir"}
Instead of setting all parameters, you may define a configuration file.
There is one disadvantage. The following task would not work:
rake assets:doit[js,objdir] assets:doit[c,objdir2]
assets:doit would be called once. the second call is ignored, the task is already executed. there is no check for different parameters (One solution for this: perhaps you could reset the task)
Edit: I found and tested a 'reset'-method: You just need to add tsk.reenable
namespace :assets do |x1,x2|
task :doit, :ext, :objdir do |tsk, args|
puts "#{tsk} called with #{args.inspect}"
tsk.reenable
end
end
Another problem: If your parameters contains spaces. you may get trouble.
============== Code for generic generation of rule: (see comments)
namespace :assets3 do |x1,x2|
task :set, :ext, :objdir do |tsk, args|
@args = args
@src = FileList["*.rb"]
puts "#{tsk} set: #{@args.inspect}"
#Define rule, when extension is set.
rule ".min.#{@args[:ext]}" => lambda{ |objfile| find_source(objfile) } do |t|
puts "#{t} called with #{@args.inspect}"
end
end
task :doit do |tsk|
puts "#{tsk} called with #{@args.inspect}"
end
def find_source(objfile)
base = File.basename(objfile, ".min.#{@args[:ext]}")
#If nothing is found, rake will abort with 'can't convert nil into String (TypeError)'
#If I return '' in this case, I get 'Don't know how to build task 'test.min.js' (RuntimeError)'
@src.find {|s| File.basename(s, ".#{@args[:ext]}") == base} || ''
end
end
With your help I finally figured it out. Here's what's working for me so far:
namespace :assets do
task :set, [:ext, :objdir] do |t, args|
@ext = args.ext
@objdir = args.objdir
@bundle = "#{@objdir}/bundle.#{@ext}"
@src = FileList["#{@objdir}/*.#{@ext}"].select {|file| !file.match(/\.min\.#{@ext}|#{Regexp.escape(@bundle)}/)}
@min = @src.collect {|fn| File.join(@objdir, File.basename(fn).ext("min.#{@ext}"))}
Rake::Task.define_task 'assets:build' => @bundle
Rake::FileTask.define_task @bundle => @min do
sh "cat #{@min} > #{@bundle}"
end
Rake::Task.create_rule ".min.#{@ext}" => lambda{ |objfile| find_source(objfile) } do |t|
if @ext == 'js'
if @mine.include?(File.basename(t.source))
sh "closure --js #{t.source} --js_output_file #{t.name}"
else
sh "closure --warning_level QUIET --third_party --js #{t.source} --js_output_file #{t.name}"
end
elsif @ext == 'css'
sh "yuicompressor #{t.source} -o #{t.name}"
end
end
end
desc "Remove minified files"
task :clean do
rm_f @min
end
desc "Remove bundle"
task :clobber do
rm_f @bundle
end
def find_source(objfile)
base = File.basename(objfile, ".min.#{@ext}")
@src.find {|s| File.basename(s, ".#{@ext}") == base}
end
end
精彩评论