Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Tap --[not] a talk about replacing Rake

Avatar for thinkerbot thinkerbot
October 10, 2011

Tap --[not] a talk about replacing Rake

Slides from my talk at RubyConf 2008

Avatar for thinkerbot

thinkerbot

October 10, 2011
Tweet

More Decks by thinkerbot

Other Decks in Programming

Transcript

  1. Design Goals • Each task is independent, configurable • Tasks

    must have an immediate command line interface • Ability to link tasks together into workflows
  2. Originally, Rake desc “a simple goodnight task” task :goodnight, :obj

    do |task, args| puts “#{ENV[‘msg’] || ‘goodnight’} #{args.obj}” end [Rakefile] % rake -T % rake goodnight[moon] % rake goodnight[world] msg=hello
  3. The nature of Rake • Insufficient command line support •

    No imperative workflows • Hard to test/reuse/distribute • Not a knock on rake! • Domain specific solution
  4. Tap (Task Application) • A command line framework • Imperative

    and dependency workflows • Distribution of tasks as gems a b1 b c1 c2 c ? x x1 x2 y y1 z
  5. Rake Task desc “a simple goodnight task” task :goodnight, :obj

    do |task, args| puts “#{ENV[‘msg’] || ‘goodnight’} #{args.obj}” end % rake -T % rake goodnight[moon] % rake goodnight[world] msg=hello [Rakefile, rakefile, rakefile.rb]
  6. Rap Task # ::desc a simple goodnight task # Extended

    documentation for the goodnight task... task :goodnight, :obj, :msg => ‘goodnight’ do |task,args| puts “#{task.msg} #{args.obj}” end % rap -T % rap goodnight moon % rap goodnight world --msg hello % rap goodnight --help [Rapfile, rapfile, rapfile.rb]
  7. Quite Compatible • Declarations must be included • No FileList,

    Rake::TestTask, etc. require ‘tap’ include Tap::Declartions namespace :name do desc ‘one line description’ task({:name => [:deps]}, :inputs, {:key => ‘value’}) do sh “...” end end
  8. Rap runs Rake require ‘rake’ desc “says hello” task :say_hello

    do puts “hello” end require ‘tap’ include Tap::Declarations desc “says goodbye” task :say_goodbye do puts “goodbye” end % rap say_hello % rap say_goodbye % rap -T [Rakefile] [Rapfile]
  9. Class vs Instance • Rake makes instances • Rap makes

    singleton subclasses t = task :name t.class # => Rake::Task t = task :name t.class # => Name Name.superclass # => Tap::Task Name.instance # => t
  10. Beyond Rap # ::desc a simple goodnight task task :goodnight,

    :obj, :msg => ‘goodnight’ do |task,args| puts “#{task.msg} #{args.obj}” end # ::manifest a simple goodnight task class Goodnight < Tap::Task config :msg, ‘goodnight’ def process(obj) puts “#{msg} #{obj}” end end
  11. Process class Goodnight < Tap::Task def process(obj=‘opus’, *objects) objects.unshift(obj) puts

    “goodnight #{objects.join(‘,’)}” end end % rap goodnight % rap goodnight moon lamp “little toy boat” % rap goodnight --help
  12. Configurable class Goodnight < Tap::Task config :key, ‘value’ do |input|

    input.downcase end end t = Goodnight.new t.key # => ‘value’ t.key = ‘New Value’ t.config[:key] # => ‘new value’
  13. Standard Blocks class Goodnight < Tap::Task config :message, ‘goodnight’ config

    :reverse, false, &c.switch config :repeat, 1, &c.integer def process str = “#{message} moon” str = str.reverse if reverse == true repeat.times { puts str } end end % rap goodnight --reverse --repeat 3 % rap goodnight --help
  14. Lazydoc # SomeClass::key this is the value # Extended value...

    class SomeClass extend Tap::Support::LazyAttributes lazy_attr :key end SomeClass.source_file = __FILE__ SomeClass.key.value # => “this is the value” SomeClass.key.to_s # => “Extended value...”
  15. Lazydoc # ::manifest a fancy goodnight task # Says goodnight

    to lots of objects. class Goodnight < Tap::Task config :message, ‘goodnight’ # a goodnight message config :reverse, false, &c.switch # reverses output config :repeat, 1, &c.integer # repeats output end Goodnight.manifest.value # => “a fancy goodnight task” Goodnight.manifest.to_s # => “Says goodnight to lots of objects.” [lib/goodnight.rb]
  16. Off the command line class Goodnight < Tap::Task config :msg,

    ‘goodnight’ def process(obj) “#{msg} #{obj}” end end g = Goodnight.new g.process(‘moon’) # => ‘goodnight moon’ g.msg = ‘hello’ g.process(‘world’) # => ‘hello world’
  17. Subclasses class ReverseGoodnight < Goodnight config :reverse, true, &c.switch def

    process(obj) super(reverse ? obj.reverse : obj) end end g = ReverseGoodnight.new(:message => ‘hello’) g.process(‘world’) # => ‘dlrow olleh’ g.reverse = false g.process(‘world’) # => ‘hello world’
  18. Pass Results g1 = Goodnight.new(:message => ‘yo’) g2 = Goodnight.new(:message

    => ‘yo’) result = g1.process(‘moon’) g2.process(result) # => ‘yo yo moon’
  19. Executable • Audits execution of the method g = Goodnight.new

    _result = g._execute(‘moon’) _result.class # => Tap::Support::Audit _result._current # => ‘goodnight moon’ _result._source # => g
  20. Executable • Adds an on_complete block current = nil g

    = Goodnight.new g.on_complete do |_result| current = _result._current end g._execute(‘moon’) current # => ‘goodnight moon’
  21. Executable • Allows specification of dependencies dep_ran = false dep

    = Tap::Task.intern {|task| dep_ran = true } g = Goodnight.new g.depends_on(dep) g._execute dep_ran # => true
  22. Example • Simple Trace task, adds name to str class

    Trace < Tap::Task def process(str) str + name end end t = Trace.new({}, ‘a’) t.name # => ‘a’ t.process(‘’) # => ‘a’ t.process(‘xyz’) # => ‘xyza’
  23. Standard Joins a, b, b1, c, c1, c2 = %w{a

    b b. c c. c..}.map do |name| Tracer.new({}, name) end a.switch(b, b1) do |_result| _result._current == ‘a’ ? 0 : 1 end b.fork(c, c1) c.sequence(a) c2.merge(b1, c1) a b1 b c1 c2 c ?
  24. Enque and Run # (could just do this) # a.execute(‘’)

    app = Tap::App.instance app.enq(a, ‘’) app.run app.results(c2) # => ["abcab.c..", "abc.c.."] a b1 b c1 c2 c ?
  25. Audit Trail puts app._results(c2)[0]._to_s # o-[] "" # o-[a] "a"

    # o-[b] "ab" # o-[c] "abc" # o-[a] "abca" # o-[b.] "abcab." # o-[c..] "abcab.c.." a b1 b c1 c2 c ?
  26. Workflow Syntax --- - [task, a, b, c, --key, value]

    - [task, x, y, z, --key, value] - 0:1 task a b c --key value --: task x y z --key value
  27. Delimiters break meaning -- enque next to round 0 --+

    enque next to round 1 --++ enque next to round 2 --+3 enque next to round 3 --0:1 sequence 0 -> 1 --0[1,2] fork 0 -> [1,2] --2{0,1} merge [0,1] -> 2 --2(0,1) sync_merge [0,1] -> 2 --* enque a ‘global’ dependency instance
  28. tap: (/Users/simonchiang/Documents/Gems/tap) commands console (cmd/console.rb) destroy (cmd/destroy.rb) generate (cmd/generate.rb) manifest

    (cmd/manifest.rb) run (cmd/run.rb) generators command (lib/tap/generator/generators/command/command_generator.rb) config (lib/tap/generator/generators/config/config_generator.rb) file_task (lib/tap/generator/generators/file_task/file_task_generator.rb) generator (lib/tap/generator/generators/generator/generator_generator.rb) root (lib/tap/generator/generators/root/root_generator.rb) task (lib/tap/generator/generators/task/task_generator.rb) tasks dump (lib/tap/tasks/dump.rb) load (lib/tap/tasks/load.rb) rake (lib/tap/tasks/rake.rb) Env, Manifests
  29. Manifest via Lazydoc module Nested # ::manifest class Sample <

    Tap::Task end end # Another::manifest class Another < Tap::Task end class Hidden < Tap::Task end [lib/nested/sample.rb] implicit, by path explicitly named overlooked
  30. RubyGems • Package normally (Rapfile doesn’t work) • Add a

    tap.yml file (can be empty) • Tasks will be discovered automatically
  31. Scripts #!/usr/bin/env ruby require ‘rubygems’ require ‘tap’ # Goodnight::manifest a

    fancy goodnight task # Says goodnight to lots of objects. class Goodnight < Tap::Task config :message, ‘goodnight’ def process(*objects) puts “#{message} #{objects.join(‘,’)}” end end Goodnight.execute(ARGV)