API

Path: doc/API
Last Update: Tue May 04 23:26:16 -0600 2010

Application Programming Interfaces

All of the objects used by tap have two APIs — one regarding the specific type of object (ex a task, a join, or middleware), and another regarding how applications find and use the objects. These APIs allow users to make custom objects.

Object Interface

Applications require the following methods for tasks, joins, and middleware. Tap provides base classes that implement these APIs. Objects satisfying the task, join, or middleware API are referred to as workflow objects.

Task

  call(input)                    # any return is allowed
  joins()                        # returns an array of joins, or nil (optional)

The call method takes an object and returns an object. There are no formal constraints for the input/output objects. The optional joins method returns an array of joins to be called when the task completes, or nil if no joins are to be called. Each join is called in order.

Join

  call(output)                   # any return is allowed

The call method receives the output of a task. The result of call is not used; call internally performs the join actions.

Middleware

  Middleware.new(stack, *args)   # returns an instance of middleware
  call(node, input)              # return is the task output
  stack()                        # returns the original stack

Middleware wraps the execution of tasks. Tasks and inputs are passed to the middleware during execution. Joins are performed with the call output.

Application Interface

Tap defines an interface allowing applications to create and manage objects using signals. The application interface is distinct from the workflow APIs, although typically one will be implemented on top of the other. Objects satisfying the application interface are referred to as application objects.

The application interface allows instantiation of a class from a hash and serialization of an instance back into a hash. The hash is referred to as a specification and must be serializable as JSON (meaning the hash must consist of simple object types: numbers, strings, hashes, and arrays).

The application interface consists of two methods:

  Const.build(spec, app)         # returns an instance of self
  to_spec                        # returns a spec

As an example:

  class Example
    class << self
      # Build takes a specification and returns an instance of self.
      # The spec must be serializable as JSON.
      def build(spec={}, app=Tap::App.current)
      end
    end

    # Takes no inputs and returns a specification that, when built,
    # returns an object like self.
    #
    #   obj.class.build(obj.to_spec)      # => returns an object like obj
    #
    # Users can determine for themselves what constitutes 'likeness'.
    def to_spec
    end
  end

The application interface reserves several additional methods that do not need to be implemented but add functionality for specific, common use cases. These are:

  Const.parse(argv, app)         # returns an instance of self, cannot modify ARGV
  Const.parse!(argv, app)        # same as parse but can modify ARGV
  signal(sig)                    # returns an object responding to call
  associations                   # returns an array like [refs, brefs]

If present they must do the following:

  class Example
    class << self
      # Takes an argument vector (an array, usually from the command line)
      # and returns an instance of self.  If a block is given, parse
      # yields the instance and remaining args and returns block result.
      #
      # Parse should not modify argv.
      def parse(argv=ARGV, app=Tap::App.current)
      end

      # Same as parse, but able to modify argv.
      def parse!(argv=ARGV, app=Tap::App.current)
      end
    end

    # Takes a signal name and returns an object that responds to call; the
    # call method invokes the signal actions.
    def signal(sig)
    end

    # Returns a nested array of application objects associated with self.
    # The array should be structured like [refs, brefs], where refs are
    # references to objects that must be built BEFORE self and brefs are
    # back-references to objects that must be built AFTER self.
    #
    # For example, tasks must be built before joins.  As such, the associations
    # method for a task returns a brefs for each of its joins. Similarly, joins
    # must be built after tasks and hence the associations method for a join
    # returns refs to their input and output tasks:
    #
    #   task.associations       # => [nil, join]
    #   join.associations       # => [inputs + outputs, nil]
    #
    # Nil is a valid return for associations, indicating no associations.
    def associations
    end
  end

The parse methods are used for building objects from interfaces that provide an array of inputs rather a hash (ex the command line); without them objects are effectively excluded from use within these interfaces.

Signals can be used to interact with specific objects from a user interface much as signals can interact with an app. Objects without a signal method cannot receive signals.

The associations method is used to order complex builds and is described in more detail below.

Object References

Specifications often require references to other application objects, as when a join refers to input and output tasks. These references are normally specified as variables that, unlike the object itself, are serializable as JSON. Apps manage variables via the obj and var methods.

As an example:

  class A
    def initialize(b)
      @b = b
    end

    def to_spec
      {'b' => app.var(@b)}       # store a variable into the spec
    end

    def associations
      [[@b], nil]                # establish a build order
    end

    class << self
      def build(spec={}, app=Tap::App.current)
        b = app.obj(spec['b'])   # retrieve an object referenced by the spec
        new(b)
      end
    end
  end

The to_spec method records a variable identifying @b, rather than @b itself, which allows the spec to be properly serialized. Likewise the build method de-references the variable to retrieve b when initializing a new instance; the associations array is used to ensure that b is built by the time A.build tries to de-reference the variable.

Note that only references to objects implementing the application interface should be stored this way.

[Validate]