= Tap (Task Application) Tap is a framework for creating configurable, distributable tasks and workflows. Although scalable for complex workflows, at it simplest tap works like a supercharged rake. Using the rap executable, you can declare tasks using a syntax almost identical to rake, but with added support for configurations and documentation. == Quickstart If you've used rake, tap will be easy to pick up. To get started, make a Tapfile with a simple task declaration: [Tapfile] # ::desc your basic goodnight moon task # Says goodnight with a configurable message. Tap.task(:goodnight, :obj, :message => 'goodnight') do |task, args| puts "#{task.message} #{args.obj}\n" end Now from the command line: % rap goodnight moon goodnight moon % rap goodnight world --message hello hello world % rap goodnight --help Goodnight -- your basic goodnight moon task -------------------------------------------------------------------------------- Says goodnight with a configurable message. -------------------------------------------------------------------------------- usage: rap goodnight OBJ configurations: --message MESSAGE options: -h, --help Print this help --name NAME Specify a name --use FILE Loads inputs from file Just like that you have a command-line application with inputs, configuration, and documentation. The declaration syntax is obviously similar to rake; configurations are new but the task-block style is the same, as are inputs. Other rake constructs are available. Here is a similar goodnight task using dependencies, rake-style. [Tapfile] # make the declarations available everywhere # (normally they're accessed via Tap, as above) include Tap::Declarations namespace :example do task(:say, :message) do |task, args| print(args.message || 'goodnight') end desc "your basic goodnight moon task" task({:goodnight => :say}, :obj) do |task, args| puts " #{args.obj}\n" end end And now from the command line: % rap goodnight moon goodnight moon % rap goodnight world --* say hello hello world Unlike rake, rap inputs are written out individually and tasks are delimited by a modified double-dash. Aside from that, you can see rap is basically a supercharged rake. Furthermore, rap runs rake. Directly substitute rap for rake on the command line and your tasks should run as normal (see the {Syntax Reference}[link:files/doc/Syntax%20Reference.html] for more details). However, supercharging rake isn't the point of Tap. Declarations bridge the gap between rake and tap, but tap itself is a more general framework. To get at other features like imperative workflows, testing, and distribution, we have to go beyond rap and take a look at what declarations do. Spoiler: declarations make subclasses of Tap::Task. == Beyond Rap Going back to the first example, lets take a look at how a task declaration maps to a class definition: [Tapfile] # ::desc your basic goodnight moon task # Says goodnight with a configurable message. Tap.task(:goodnight, :obj, :message => 'goodnight') do puts "#{task.message} #{args.obj}\n" end Here is a corresponding class: # Goodnight::manifest your basic goodnight moon task # Says goodnight with a configurable message. class Goodnight < Tap::Task config :message, 'goodnight' def process(obj) "#{message} #{obj}" end end Simple enough; the name corresponds to the class, configurations (and dependencies, although they aren't show) are written out individually, and the block corresponds to process. There are a few differences, especially relating to process, but for the moment lets gloss over them and see how Goodnight works. goodnight = Goodnight.new goodnight.message # => 'goodnight' goodnight.process('moon') # => 'goodnight moon' hello = Goodnight.new(:message => 'hello') hello.message # => 'hello' hello.process('world') # => 'hello world' Totally straightforward. Goodnight stores the default configurations, each instance has accessors to the configurations, and the defaults may be overridden during initialization, or later. Class definitions allow validation/transformation blocks to be specified for configurations. These blocks process inputs (ex the string inputs from the command line), quite literally defining the writer for a configuration accessor. A set of standard blocks are available through +c+, an alias for the {Configurable::Validation}[http://tap.rubyforge.org/configurable/classes/Configurable/Validation.html] module. [lib/goodnight.rb] # Goodnight::manifest a fancy goodnight moon task # Says goodnight with a configurable message. class Goodnight < Tap::Task config :message, 'goodnight' # a goodnight message config :reverse, false, &c.switch # reverses the message config :n, 1, &c.integer # repeats message n times def process(*objects) print "#{reverse == true ? message.reverse : message} " * n puts objects.join(', ') puts end end A few examples show a validation block in action: goodnight = Goodnight.new goodnight.n = 10 goodnight.n # => 10 goodnight.n = "100" goodnight.n # => 100 goodnight.n = "not an integer" # !> ValidationError Now from the command line: % rap goodnight moon goodnight moon % rap goodnight moon mittens "little toy boat" goodnight moon, mittens, little toy boat % rap goodnight world --message hello --reverse --n 3 olleh olleh olleh world % rap goodnight --help Goodnight -- a fancy goodnight moon task -------------------------------------------------------------------------------- Says goodnight with a configurable message. -------------------------------------------------------------------------------- usage: rap goodnight OBJECTS... configurations: --message MESSAGE a goodnight message --[no-]reverse reverses the message --n N repeats message n times options: -h, --help Print this help --name NAME Specify a name --use FILE Loads inputs from file Take a quick look at the documentation. Class definitions map documentation and, in some cases, metadata to the command line; the configurations now have comments and reverse is a switch! Rich mapping allows tasks to act as an script interface, not unlike {OptionParser}[http://www.ruby-doc.org/stdlib/libdoc/optparse/rdoc/classes/OptionParser.html] (check out the {Configurable}[http://tap.rubyforge.org/configurable/] gem and specifically {ConfigParser}[http://tap.rubyforge.org/configurable/classes/ConfigParser.html] for more details). This is a stand-alone goodnight script: [goodnight] #!/usr/bin/env ruby require 'rubygems' require 'tap' # Goodnight::manifest a goodnight moon script # Says goodnight with a configurable message. class Goodnight < Tap::Task config :message, 'goodnight' def process(obj) puts "#{message} #{obj}\n" end end instance, args = Goodnight.parse!(ARGV) instance.execute(*args) Now, from the command line: % ./goodnight moon goodnight moon % ./goodnight --help ... As simple as it is to take a task to the command line, it's nice to note that tasks may be subclassed, tested, and distributed as usual. No magic, just convenience. == Tap Tap comes with two executables, rap and tap. The tap executable is more verbose than rap for running tasks, but it is more configurable, scalable, and logically pure. Tap comes with a number of {commands}[link:files/doc/Command%20Reference.html] but we'll focus on generate to make, test, and package a task library. Begin by creating a tap directory structure and a task: % tap generate root sample % cd sample % tap generate task goodnight Take a look at the task files and you find something like this: [lib/goodnight.rb] # Goodnight::manifest # # Goodnight Documentation class Goodnight < Tap::Task # config :message, 'goodnight' # a sample config def process(name) log message, name "#{message} #{name}" end end [test/goodnight_test.rb] require File.join(File.dirname(__FILE__), 'tap_test_helper.rb') require 'goodnight' class GoodnightTest < Test::Unit::TestCase acts_as_tap_test def test_goodnight task = Goodnight.new :message => "goodnight" # a simple test assert_equal({:message => 'goodnight'}, task.config) assert_equal "goodnight moon", task.process("moon") # a more complex test task.enq("moon") app.run assert_equal ["goodnight moon"], app.results(task) assert_audit_equal [[nil, "moon"], [task, "goodnight moon"]], app._results(task)[0] end end Run the test: % rap test Run the task: % rap goodnight moon I[23:22:19] goodnight moon Ok, lets share it. Print the current gemspec manifest: % rap print_manifest true README Rakefile lib/goodnight.rb sample.gemspec true tap.yml test/goodnight_test.rb true test/tap_test_helper.rb true test/tap_test_suite.rb As you can see, this needs an update to include the task file. Open up sample.gemspec and fix the manifest. [sample.gemspec] Gem::Specification.new do |s| s.name = "sample" s.version = "0.0.1" s.platform = Gem::Platform::RUBY s.summary = "sample" s.require_path = "lib" s.add_dependency("tap", ">= 0.11") s.files = %W{ lib/goodnight.rb tap.yml } end Now package the gem and install it (gem may require sudo): % rap gem % gem install pkg/sample-0.0.1.gem Now you can say goodnight anywhere, using 'tap run' or rap: % cd ~/Desktop % tap run -- goodnight moon goodnight moon % rap goodnight opus goodnight opus And that is that.