Search code examples
rubyrake

Pass lambda as an argument to rake task


I'm building a cli to bootstrap my gem projects, setting up minitest, guard, travis, git etc...

I was planning on using Rake because I like how it handles dependencies, shell commands, and cleanup.

First I'm creating the directory structure of a "type" of project (gem, gli, methadone in this case), then rendering all the erb files in the template directory for project.

As shown methadone => gem and gli => gem but methadone !=> gli. Which means methadone needs all the files from gem but may add new files/dirs and also may modify existing files/dirs.

I like how easy it is for me to express these dependencies with Rake tasks.

But I find it weird that I have to write the same code in 3 different places as shown below:

code:

require 'rake'
require 'rake/clean'

desc "Bootstrap gem"
task :gem, [:project_name] do |t, args|
  Project.new(t.name, args.project_name).bootstrap
end

desc "Bootstrap methadone"
task :methadone, [:project_name] => :gem do |t, args|
  Project.new(t.name, args.project_name).bootstrap
end

desc "Bootstrap gli"
task :gli, [:project_name] => :gem do |t, args|
  Project.new(t.name, args.project_name).bootstrap
end

class Project
  attr_reader :project_name, :template_name
  def initialize template_name, project_name
    @project_name = project_name
    @template_name = template_name
  end

  def bootstrap 
    create_project_dirs 
    render 
  end

  def create_project_dirs   
    puts "Copying dirs from #{template_dir} to #{project_dir}"
  end

  def render   
    puts "Rendering #{project_name} templates..."
  end

  def template_dir
    File.expand_path(File.join(__dir__, "templates", template_name))
  end

  def project_dir
    File.expand_path(File.join(__dir__, project_name))
  end
end



CLEAN.include('templates')

template_files = %w( templates/gem/foo.erb templates/gem/bar.erb templates/methadone/foo.erb templates/gli/foo.erb )
template_files.each do |f|
  file f do |t|
    path = t.name
    mkdir_p File.dirname path
    touch path
  end
end

desc "Setup tmp project"
task :setup => template_files

file structure:

$ tree .
.
├── Rakefile
├── some.rake
└── templates
    ├── gem
    │   ├── bar.erb
    │   └── foo.erb
    ├── gli
    │   └── foo.erb
    └── methadone
        └── foo.erb

4 directories, 6 files

Is it possible to make a Rake task that can run in the context of the task that called it and take args?

Then I think I could have a task like:

desc "Bootstrap gem"
task :gem, [:project_name] => :bootstrap ??

desc "Bootstrap methadone"
task :methadone, [:project_name] => [:gem, :bootstrap]

desc "Bootstrap gli"
task :gli, [:project_name] =>[:gem, :bootstrap]

task :bootstrap ??
  Project.new(t.name, args.project_name).bootstrap
end

output:

$ rake methadone['some_gem']
Copying dirs from /Users/max/Dropbox/work/tmp/devify_dependency/templates/gem to /Users/max/Dropbox/work/tmp/devify_dependency/some_gem
Rendering some_gem templates...
Copying dirs from /Users/max/Dropbox/work/tmp/devify_dependency/templates/methadone to /Users/max/Dropbox/work/tmp/devify_dependency/some_gem
Rendering some_gem templates...

But as it stands I don't know how to tell :bootstrap about the tasks that called it or how to pass it the args from the task that called it.


Solution

  • If I properly understood what you want to achieve, there is a very simple refactoring:

    BOOTSTRAP = lambda do |t, args|
      Project.new(t.name, args.project_name).bootstrap
    end
    
    desc "Bootstrap gem"
    task :gem, [:project_name], &BOOTSTRAP
    
    desc "Bootstrap methadone"
    task :methadone, [:project_name], &BOOTSTRAP
    
    desc "Bootstrap gli"
    task :gli, [:project_name] => :gem, &BOOTSTRAP