Tap (Task Application)
A framework for creating configurable, distributable tasks and workflows. Tap allows tasks to act like command line executables, but with the ability to pass any object as an input/output (not just stdin/stdout). Moreover any join logic may be used in workflows (not just pipe) so forks, merges, and synchronization are all possible. Tap supports signaling objects in the workflow, allowing interrogation and manipulation of long-running workflows.
All that said, Tap is often used as a really advanced Rake for providing toolkits to developers.
A quick look
Tasks are defined with simple declarations:
[tapfile] # Constructs a configurable message for the input.
desc "make a goodnight message"
task :goodnight, :msg => 'goodnight' do |config, input|
"#{config.msg} #{input}"
end
Tasks may be linked into workflows and configured individually:
% tap goodnight moon -: dump goodnight moon
% tap goodnight world --msg hello -: dump hello world
Workflows support the use of middleware to wrap the execution of each task, most commonly for logging and/or debugging.
% tap goodnight moon -: dump --/use debugger
21:06:53 0 << ["moon"] (Goodnight)
21:06:53 0 >> "goodnight moon" (Goodnight)
21:06:53 1 << "goodnight moon" (Tap::Tasks::Dump)
goodnight moon
21:06:53 1 >> "goodnight moon" (Tap::Tasks::Dump)
Tap also generates copious help.
% tap goodnight --help
Tapfile::Goodnight -- make a goodnight message
--------------------------------------------------------------------------------
Constructs a configurable message for the input.
--------------------------------------------------------------------------------
usage: tap tapfile/goodnight arg
configurations:
--msg MSG
options:
--help Print this help
--config FILE Specifies a config file
As workflows grow more complex, they can be declared and re-used much like tasks. Both task and workflow declarations define classes that can be instantiated and tested like any other plain-old-ruby object. In fact, oftentimes tasks are defined directly as classes, without the declaration sugar.
[lib/goodnight.rb] require 'tap/task'
# ::task your basic goodnight moon task
# Says goodnight with a configurable message.
class Goodnight < Tap::Task
config :msg, 'goodnight' # a goodnight message
def process(name)
"#{msg} #{name}"
end
end
[test/goodnight_test.rb] require 'tap/test/unit' require 'goodnight'
class GoodnightTest < Test::Unit::TestCase
acts_as_tap_test
def test_goodnight_makes_the_configurable_message
task = Goodnight.new
assert_equal 'goodnight moon', task.process('moon')
task = Goodnight.new :msg => 'hello'
assert_equal 'hello world', task.process('world')
end
end
In this form tasks can be distributed in a gem as normal. Tap discovers tasks in all gems that contain a ‘tap.yml’ file, although this behavior is customizable to limit the gems or individual tasks available in a workflow. Now lets have some fun.
[tapfile]
desc "Iteration of inputs (copy-paste command line syntax)"
work :iterate, %{
- load/yaml
-:i goodnight
-: dump
}
desc "Forking and merging (literal definition of joins)"
work :fork_and_merge, %{
- load
- goodnight --msg hi
- goodnight --msg bye
- dump
- join 0 1,2
- join 1,2 3
}
desc "Out of order, synchronized Merge (showcases full syntax)"
work :ooo_sync, %{
- dump
- goodnight
- goodnight
- load
- join 3 1,2
- sync 1,2 0
},{
:one => 'hi',
:two => 'bye'
} do |config|
node(1).msg = config.one
node(2).msg = config.two
node(3) # the return is the entry point for the workflow, ie 'load'
end
And here we go…
% tap iterate "[moon, mittens, 'little toy boat']" goodnight moon goodnight mittens goodnight little toy boat
% tap fork_and_merge moon hi moon bye moon
% tap ooo_sync moon ["hi moon", "bye moon"]
% tap ooo_sync moon --one hello --two goodnight ["hello moon", "goodnight moon"]
See the Tutorial and Documentation for more information.