rubyist / guard-rake

guard-rake runs a rake task when files change
MIT License
94 stars 32 forks source link

It should read task dependencies automatically #45

Closed edusantana closed 4 years ago

edusantana commented 4 years ago

It should read tasks from Rake tasks variable and watch which file changed and call the task that has it as a dependency.

When I run rake -P I get tasks and dependencies:

rake test
    test/teste1/teste1.pdf
rake test/teste1/teste1.pdf
    test/teste1/teste1.tex

We can then create a Guardfile with this content:

guard 'rake', :task => 'test/teste1/teste1.pdf' do
  watch(%r{^test/teste1/teste1.tex$})
end

I have created a simple task file to generate this code:

desc 'Update Guardfile from Rakefile tasks'
task :upguard do
  # https://ruby.github.io/rake/Rake/Task.html#method-c-tasks
  Rake::Task.tasks.each do |t|

    # ignore tasks that aren't files names
    next unless t.name.include? "/"

    # guard 'rake', :task => 'doit' do
    #   watch(%r{^some_files/.+$})
    # end

    puts "guard 'rake', :task => '#{t.name}' do"

    t.prerequisites.each do |p|
      puts "  watch(%r{^#{p}$})"
    end

    puts "end"

  end
end

Here's this upguard task execution:

$ rake -T
rake test     # gera os testes
rake upguard  # Update Guardfile from Rakefile tasks
$ rake -P
rake default
rake test
    test/teste1/teste1.pdf
rake test/teste1/teste1.pdf
    test/teste1/teste1.tex
rake upguard
$ rake upguard > Guardfile
$ cat Guardfile 
guard 'rake', :task => 'test/teste1/teste1.pdf' do
  watch(%r{^test/teste1/teste1.tex$})
end

I would like this guard-rake plugin to do it on the fly. When Rakefile changes it should load new watches.

edusantana commented 4 years ago

And here's my Rakefile for this small project:

require 'rake'

desc 'gera os testes'
task :test

rule '.pdf' => '.tex' do |t|
  sh "latexmk -pdflatex=lualatex -f -pdf #{t.name.ext('.tex')}"
end

FileList['test/**/*.tex'].each do |f|
  task :test => f.ext('.pdf')
  file f.ext('.pdf') => f
end

desc 'Update Guardfile from Rakefile tasks'
task :upguard do
  # https://ruby.github.io/rake/Rake/Task.html#method-c-tasks
  Rake::Task.tasks.each do |t|

    # ignore tasks that aren't files names
    next unless t.name.include? "/"

    # guard 'rake', :task => 'doit' do
    #   watch(%r{^some_files/.+$})
    # end

    puts "guard 'rake', :task => '#{t.name}' do"

    t.prerequisites.each do |p|
      puts "  watch(%r{^#{p}$})"
    end

    puts "end"

  end
end
edusantana commented 4 years ago

I finally got a solution

Description of the solution

The solution would be get all task that has file dependencies and create a watch and a action (rake task).

Rakefile

Let's keep a simple example, with this Rakefile:

files = Rake::FileList.new('*.md')

desc "Create a book"
task 'book' => files do
  sh "cat #{files.join(" ")} > book.txt"
end

Here we can see it's tasks dependencies:

$ rake -P
rake book
    CHANGELOG.md
    CODE_OF_CONDUCT.md
    CONTRIBUTING.md
    ISSUE_TEMPLATE.md
    README.md

These file are dynamically listed from the FileList.

Expected Guardfile

This Guardfile would solve my problem:

guard :shell do
    watch(%r{^(CHANGELOG.md|CODE_OF_CONDUCT.md|CONTRIBUTING.md|ISSUE_TEMPLATE.md|README.md)$}) do |m|
      system("rake book")
    end  
end

Task to generate the Guardfile

To make solution easy, lets create a file called rakelib/guard.rake with this content:

ERB_TEMPLATE = <<~HEREDOC
guard :shell do
  <% all_tasks.each do |t, files| %>
    watch(%r{^(<%= files.join('|') %>)$}) do |m|
      system("rake <%= t %>")
    end
  <% end %>
end
HEREDOC

desc "Generates a Guardfile from Rake tasks"
task :guard do
  all_tasks = {}
  current_task = nil
  `rake -P`.each_line do |line|
    if line.start_with? "rake" then
      current_task = line[5..-2]
    else
      #its a dependency
      filename = line.strip
      if File.file?(filename) then
        all_tasks[current_task] = [] unless all_tasks[current_task]
        all_tasks[current_task] << filename
      end
    end
  end
  template = ERB.new(ERB_TEMPLATE)
  File.write('Guardfile', template.result(binding))
end

Now we can see it:

$ rake -T
rake book               # Create a book
rake guard              # Generates a Guardfile from Rake tasks

If you call rake guard it will generate Guardfile with the expected content.

NOTE: If someone find a better solution, please let me know.

edusantana commented 4 years ago

I have updated the rakelib/guard.rake file:

ERB_TEMPLATE = <<~HEREDOC
guard :shell do
  <% all_tasks.each do |t, files| %>
    watch(%r{^(<%= files.join('|') %>)$}) do |m|
      system("rake <%= t %>")
    end
  <% end %>
end
HEREDOC

desc "Generates a Guardfile from Rake tasks"
task :guard do
  app = Rake::application
  app.init
  app.load_rakefile

  all_tasks = {}
  app.tasks.each do |t|
    t.sources.each do |src|
      if File.file?(src) then
        all_tasks[t.name] = [] unless all_tasks[t.name]
        all_tasks[t.name] << src
      end
    end
  end
  template = ERB.new(ERB_TEMPLATE)
  File.write('Guardfile', template.result(binding))
end

We could make this inside this lib, so one could require it inside the Rakefile.