doc/Tutorial in bahuvrihi-tap-0.10.7 vs doc/Tutorial in bahuvrihi-tap-0.10.8

- old
+ new

@@ -1,237 +1,307 @@ -= Quick Start Tutorial - -=== Basic Usage - -Begin by creating a tap directory structure: - - % tap generate root sample - % cd sample - -Comes with a task: - - % tap run -T - sample: - goodnight # your basic goodnight moon task - tap: - dump # the default dump task - rake # run rake tasks - -Test the task: - - % rake test - -Get help for the task: - - % tap run -- goodnight --help - Goodnight -- your basic goodnight moon task - -------------------------------------------------------------------------------- - Prints the input with a configurable message. - -------------------------------------------------------------------------------- - usage: tap run -- goodnight INPUT - - configurations: - --message MESSAGE - - options: - -h, --help Print this help - --name NAME Specify a name - --use FILE Loads inputs from file - -Run the task: - - % tap run -- goodnight moon - I[23:22:19] goodnight moon - -Run the task, setting the 'message' configuration: - - % tap run -- goodnight moon --message hello - I[23:22:46] hello moon - -Run multiple tasks, or in this case the same task twice: - - % tap run -- goodnight moon -- goodnight opus - I[23:23:06] goodnight moon - I[23:23:06] goodnight opus - -Same as above, but now dump the results to a file: - - % tap run -- goodnight moon -- goodnight opus --+ dump output.yml - I[23:23:26] goodnight moon - I[23:23:26] goodnight opus - I[23:23:26] dump output.yml - -The dump file contents look like this: - - # audit: - # o-[] "opus" - # o-[goodnight] "goodnight opus" - # - # o-[] "moon" - # o-[goodnight] "goodnight moon" - # - # date: 2008-08-05 23:23:26 - --- - goodnight (2769410): - - goodnight opus - goodnight (2780180): - - goodnight moon - -The comments at the beginning are an audit trace of the run. In this case two -separate tasks were run sequentially, hence you can see each task, the task inputs, -and the task results as separate units. A YAML hash follows the audit with the -aggregated task results, keyed by the task name and object id. Since the results -are represented as a hash, the order of the tasks sometimes gets scrambled, as in -this case. - -=== Task Declaration - -Tap provides a declaration syntax a-la rake, accessible through the Tap module to -prevent conflicts with rake. Declarations can get put in any <tt>.rb</tt> file -under the lib directory or in <tt>tapfile.rb</tt>. - - [tapfile.rb] - # Goodnight::manifest your basic goodnight moon task - # Prints the input with a configurable message. - - Tap.task('goodnight', :message => 'goodnight') do |task, name| - task.log task.message, name - "#{task.message} #{name}" - end - -The declaration makes a task class based on the name (ie namespaces are naturally -supported by names like <tt>'nested/task'</tt>). The classes are ready for use in -scripts: - - require 'tapfile' - Goodnight.new.process('moon') # => 'goodnight moon' - -And from the command line, as above. - -=== Task Definition - -Sometimes you need more than a block to define a task. Generate a task: - - % tap generate task hello - -Navigate to and open the <tt>lib/hello.rb</tt> file. Inside you can see the class -definition. Notice configurations are mapped to methods, and the task documentation -is located in the comments. Let's change it up a bit: - - [lib/hello.rb] - # Hello::manifest your basic hello world task - # - # Prints hello to a number of things with a configurable, - # reversible message. - # - class Hello < Tap::Task - - config :message, 'hello' # a greeting - config :reverse, false, &c.flag # reverses the message - - def process(*names) - names.collect do |name| - log(reverse ? message.reverse : message, name) - "#{message} #{name}" - end - end - end - -The new configurations and documentation are immediately available: - - % tap run -- hello --help - Hello -- your basic hello world task - -------------------------------------------------------------------------------- - Prints hello to a number of things with a configurable, reversible message. - -------------------------------------------------------------------------------- - usage: tap run -- hello NAMES... - - configurations: - --message MESSAGE a greeting - --reverse reverses the message - - options: - -h, --help Print this help - --name NAME Specify a name - --use FILE Loads inputs from file - -And the task is ready to go: - - % tap run -- hello moon lamp 'little toy boat' - I[23:29:26] hello moon - I[23:29:26] hello lamp - I[23:29:26] hello little toy boat - - % tap run -- hello mittens --reverse - I[23:29:53] olleh mittens - -Now lets use the previous results; they get loaded and added to the end of the inputs: - - % tap run -- hello --use output.yml - I[23:31:32] hello goodnight moon - I[23:31:32] hello goodnight opus - -=== Config Files - -So say you wanted static configs for a task. Make a configuration file: - - % tap generate config goodnight - -Set the configurations here and they get used by the task: - - [config/goodnight.yml] - ############################################################################### - # Goodnight configuration - ############################################################################### - - message: good evening - -As can be seen here: - - % tap run -- goodnight moon - I[23:40:39] good evening moon - -If you need to run a task with multiple sets of configurations, simply define an -array of configurations in the config file: - - [config/goodnight.yml] - - message: good afternoon - - message: good evening - - message: goodnight - - % tap run -- goodnight moon - I[23:42:46] good afternoon moon - I[23:42:46] good evening moon - I[23:42:46] goodnight moon - -The --name option sets the config file used: - - % tap run -- goodnight moon --name no_config_file - I[23:43:20] goodnight moon - -=== Tap Configuration - -Tap itself is highly configurable. Say you think the run syntax is unnecessarily -verbose; you can make command aliases to shorten it. Open the <tt>tap.yml</tt> -file in your root directory and set the following: - - [tap.yml] - alias: - --: [run, --] - -T: [run, -T] - -Now: - - % tap -- hello world - I[23:43:59] hello world - - % tap -T - sample: - goodnight # your basic goodnight moon task - hello # your basic hello world task - tap: - dump # the default dump task - rake # run rake tasks - -Global configurations can go in the <tt>~/.tap.yml</tt> file. Using configurations, -you can specify directory aliases, options, gems, and even additional paths to load -as if they were gems. += 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: tap run -- 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) + extend 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: + + [Tapfile] + + # 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.configurations.to_hash # => {:message => 'goodnight'} + + 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 Tap::Support::Validation module. + + [Tapfile] + + # 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: tap run -- 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 can also 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] (surprise, tap uses OptionParser). + +For example 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 an you find something like this: + + [lib/goodnight.rb] + + # Goodnight::manifest <replace with manifest summary> + # <replace with command line description> + + # Goodnight Documentation + class Goodnight < Tap::Task + + # <config file documentation> + 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 ExpAudit[[nil, "moon"], [task, "goodnight moon"]], app._results(task)[0] + end + end + +Run the test: + + % rap test + +Run the task: + + % tap run -- 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 + 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.10.8") + 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. \ No newline at end of file