Module Tap
In: lib/tap.rb
lib/tap/app.rb
lib/tap/app/api.rb
lib/tap/app/queue.rb
lib/tap/app/stack.rb
lib/tap/app/state.rb
lib/tap/declarations.rb
lib/tap/declarations/context.rb
lib/tap/declarations/description.rb
lib/tap/env.rb
lib/tap/env/cache.rb
lib/tap/env/constant.rb
lib/tap/env/path.rb
lib/tap/env/string_ext.rb
lib/tap/join.rb
lib/tap/joins/gate.rb
lib/tap/joins/switch.rb
lib/tap/joins/sync.rb
lib/tap/middleware.rb
lib/tap/middlewares/debugger.rb
lib/tap/parser.rb
lib/tap/root.rb
lib/tap/signal.rb
lib/tap/signals.rb
lib/tap/signals/class_methods.rb
lib/tap/signals/configure.rb
lib/tap/signals/help.rb
lib/tap/signals/load.rb
lib/tap/signals/module_methods.rb
lib/tap/task.rb
lib/tap/tasks/dump.rb
lib/tap/tasks/load.rb
lib/tap/tasks/list.rb
lib/tap/tasks/prompt.rb
lib/tap/tasks/singleton.rb
lib/tap/tasks/signal.rb
lib/tap/tasks/stream.rb
lib/tap/templater.rb
lib/tap/utils.rb
lib/tap/version.rb
lib/tap/workflow.rb

Methods

build   call   check_terminate   enq   exe   gc   get   info   init   inspect   log   middleware   new   obj   options   pq   reset   route   run   scope   serialize   set   setup   signal   stack=   stop   terminate   to_spec   use   var  

Classes and Modules

Module Tap::Declarations
Module Tap::Joins
Module Tap::Middlewares
Module Tap::Signals
Module Tap::Tasks
Module Tap::Utils
Class Tap::App
Class Tap::Env
Class Tap::Join
Class Tap::Middleware
Class Tap::Parser
Class Tap::Root
Class Tap::Signal
Class Tap::Task
Class Tap::Templater
Class Tap::TerminateError
Class Tap::Workflow

Constants

MAJOR = 1
MINOR = 3
TINY = 0
VERSION = "#{MAJOR}.#{MINOR}.#{TINY}"
WEBSITE = "http://tap.rubyforge.org"

Attributes

env  [RW]  The application environment
logger  [RW]  The application logger
objects  [R]  A cache of application objects
queue  [R]  The application queue
stack  [R]  The application call stack for executing tasks
state  [R]  The state of the application (see App::State)

Configurations

debug  [RW]  Flag debugging (false)
force  [RW]  Force execution at checkpoints (false)
quiet  [RW]  Suppress logging (false)
verbose  [RW]  Enables extra logging (overrides quiet) (false)

Public Class methods

Creates a new App with the given configuration. Options can be used to specify objects that are normally initialized for every new app:

  :stack      the application stack; an App::Stack
  :queue      the application queue; an App::Queue
  :objects    application objects; a hash of (var, object) pairs
  :logger     the application logger
  :env        the application environment

A block may also be provided; it will be set as a default join.

[Source]

     # File lib/tap/app.rb, line 213
213:     def initialize(config={}, options={})
214:       super() # monitor

215:       
216:       @state = State::READY
217:       @stack = options[:stack] || Stack.new(self)
218:       @queue = options[:queue] || Queue.new
219:       @objects = options[:objects] || {}
220:       @logger = options[:logger] || begin
221:         logger = Logger.new($stderr)
222:         logger.level = Logger::INFO
223:         logger.formatter = LOG_FORMATTER
224:         logger
225:       end
226:       @env = options[:env] || Env.new
227:       
228:       initialize_config(config)
229:     end

Public Instance methods

[Source]

     # File lib/tap/app.rb, line 387
387:     def build(spec, &block)
388:       var  = spec['var']
389:       clas = spec['class']
390:       spec = spec['spec'] || spec
391:       
392:       obj = nil
393:       unless clas.nil?
394:         method_name = spec.kind_of?(Array) ? :parse : :build
395:         obj = env.constant(clas).send(method_name, spec, self, &block)
396:       end
397:       
398:       unless var.nil?
399:         if var.respond_to?(:each)
400:           var.each {|v| set(v, obj) }
401:         else
402:           set(var, obj)
403:         end
404:       end
405:       
406:       obj
407:     end

Sends a signal to an application object. The input should be a hash defining these fields:

  obj      # a variable identifying an object, or nil for self
  sig      # the signal name
  args     # arguments to the signal (typically a Hash)

Call does the following:

  object = app.get(obj)        # lookup an application object by obj
  signal = object.signal(sig)  # lookup a signal by sig
  signal.call(args)            # call the signal with args

Call returns the result of the signal call.

[Source]

     # File lib/tap/app.rb, line 353
353:     def call(args, &block)
354:       log(:call, nil, Logger::DEBUG) { args.inspect } if debug
355:       
356:       obj  = args['obj']
357:       sig  = args['sig']
358:       args = args['args'] || args
359:       
360:       # nil obj routes back to app, so optimize by evaluating signal directly

361:       (obj.nil? ? signal(sig, &block) : route(obj, sig, &block)).call(args)
362:     end

Raises a TerminateError if state is TERMINATE. Nodes should call check_terminate to provide breakpoints in long-running processes.

A block may be provided to check_terminate to execute code before raising the TerminateError.

[Source]

     # File lib/tap/app.rb, line 551
551:     def check_terminate
552:       if state == State::TERMINATE
553:         yield() if block_given?
554:         raise TerminateError.new
555:       end
556:     end

Enques the task with the input. Returns the task.

[Source]

     # File lib/tap/app.rb, line 268
268:     def enq(task, input=[])
269:       queue.enq(task, input)
270:       task
271:     end

Executes tasks by doing the following.

  • call stack with the task and input
  • call the task joins (task.joins)

Returns the stack result.

[Source]

     # File lib/tap/app.rb, line 468
468:     def exe(task, input=[])
469:       log("#{var(task)} <<", "#{input.inspect} (#{task.class})", Logger::DEBUG) if debug
470:       output = stack.call(task, input)
471:       log("#{var(task)} >>", "#{output.inspect} (#{task.class})", Logger::DEBUG) if debug
472:       
473:       if task.respond_to?(:joins)
474:         if joins = task.joins
475:           joins.each do |join|
476:             join.call(output)
477:           end
478:         end
479:       end
480:       
481:       output
482:     end

Removes objects keyed by integers. If all is specified, gc will clear all objects.

[Source]

     # File lib/tap/app.rb, line 329
329:     def gc(all=false)
330:       if all
331:         objects.clear
332:       else
333:         objects.delete_if {|var, obj| var.kind_of?(Integer) }
334:       end
335:       
336:       self
337:     end

Returns the object set to var, or self if var is nil.

[Source]

     # File lib/tap/app.rb, line 295
295:     def get(var)
296:       var.nil? ? self : objects[var]
297:     end

Returns an information string for the App.

  App.new.info   # => 'state: 0 (READY) queue: 0'

[Source]

     # File lib/tap/app.rb, line 562
562:     def info
563:       "state: #{state} (#{State.state_str(state)}) queue: #{queue.size}"
564:     end

Resolves the class in env and initializes a new instance with the args and block. Note that the app is not appended to args by default.

[Source]

     # File lib/tap/app.rb, line 383
383:     def init(clas, *args, &block)
384:       env.constant(clas).new(*args, &block)
385:     end

[Source]

     # File lib/tap/app.rb, line 676
676:     def inspect
677:       "#<#{self.class}:#{object_id} #{info}>"
678:     end

Logs the action and message at the input level (default INFO). The message may be generated by a block; in that case leave the message unspecified as nil.

Logging is suppressed if quiet is true.

Performance Considerations

Using a block to generate a message is quicker if logging is off, but slower when logging is on. However, when messages use a lot of interpolation the log time is dominated by the interpolation; at some point the penalty for using a block is outweighed by the benefit of being able to skip the interpolation.

For example:

  log(:action, "this is fast")
  log(:action) { "and there's not much benefit to the block" }

  log(:action, "but a message with #{a}, #{b}, #{c}, and #{d}")
  log(:action) { "may be #{best} in a block because you can #{turn} #{it} #{off}" }

[Source]

     # File lib/tap/app.rb, line 260
260:     def log(action='', msg=nil, level=Logger::INFO)
261:       if !quiet || verbose
262:         msg = yield if msg.nil? && block_given?
263:         logger.add(level, msg.to_s, action.to_s)
264:       end
265:     end

Returns an array of middlware in use by self.

[Source]

     # File lib/tap/app.rb, line 418
418:     def middleware
419:       middleware = []
420:       
421:       # collect middleware by walking up the stack

422:       synchronize do
423:         current = stack
424:         visited = [current]
425:         
426:         while current.respond_to?(:stack)
427:           middleware << current
428:           current = current.stack
429:           
430:           circular_stack = visited.include?(current)
431:           visited << current
432:           
433:           if circular_stack
434:             visited.collect! {|m| m.class.to_s }.join(', ')
435:             raise "circular stack detected:\n[#{visited}]"
436:           end
437:         end
438:       end
439:       
440:       middleware
441:     end

Same as get, but raises an error if no object is set to the variable.

[Source]

     # File lib/tap/app.rb, line 300
300:     def obj(var)
301:       get(var) or raise "no object set to: #{var.inspect}"
302:     end

[Source]

    # File lib/tap.rb, line 7
 7:   def options
 8:     options = {
 9:       :tapfile   => ENV['TAPFILE'],
10:       :gems      => ENV['TAP_GEMS'],
11:       :path      => ENV['TAP_PATH'],
12:       :tapenv    => ENV['TAPENV'],
13:       :taprc     => ENV['TAPRC'],
14:       :tap_cache => ENV['TAP_CACHE'],
15:       :debug     => ENV['TAP_DEBUG']
16:     }
17:     options
18:   end

Priority-enques (unshifts) the task with the input. Returns the task.

[Source]

     # File lib/tap/app.rb, line 274
274:     def pq(task, input=[])
275:       queue.unshift(task, input)
276:       task
277:     end

Clears objects, the queue, and resets the stack so that no middleware is used. Reset raises an error unless state is READY.

[Source]

     # File lib/tap/app.rb, line 445
445:     def reset
446:       synchronize do
447:         unless state == State::READY
448:           raise "cannot reset unless READY"
449:         end
450:         
451:         # walk up middleware to find the base of the stack

452:         while @stack.respond_to?(:stack)
453:           @stack = @stack.stack
454:         end
455:         
456:         objects.clear
457:         queue.clear
458:       end
459:       self
460:     end

[Source]

     # File lib/tap/app.rb, line 369
369:     def route(obj, sig, &block)
370:       unless object = get(obj)
371:         raise "unknown object: #{obj.inspect}"
372:       end
373:       
374:       unless object.respond_to?(:signal)
375:         raise "cannot signal: #{object.inspect}"
376:       end
377:       
378:       object.signal(sig, &block)
379:     end

Sequentially executes each enqued job (a [task, input] pair). A run continues until the queue is empty.

Run checks the state of self before executing a task. If the state changes from RUN, the following behaviors result:

  STOP        No more tasks will be executed; the current task
              will continute to completion.
  TERMINATE   No more tasks will be executed and the currently
              running task will be discontinued as described in
              terminate.

Calls to run when the state is not READY do nothing and return immediately.

Returns self.

[Source]

     # File lib/tap/app.rb, line 500
500:     def run
501:       synchronize do
502:         return self unless state == State::READY
503:         @state = State::RUN
504:       end
505:       
506:       begin
507:         while state == State::RUN
508:           break unless job = queue.deq
509:           exe(*job)
510:         end
511:       rescue(TerminateError)
512:         # gracefully fail for termination errors

513:         queue.unshift(*job)
514:       ensure
515:         synchronize { @state = State::READY }
516:       end
517:       
518:       self
519:     end

Sets self as instance in the current context, for the duration of the block (see App.with_context).

[Source]

     # File lib/tap/app.rb, line 568
568:     def scope
569:       App.with_context(CURRENT => self) { yield }
570:     end

Converts the self to a schema that can be used to build a new app with equivalent application objects, queue, and middleware. Schema are a collection of signal hashes such that this will rebuild the state of a on b:

  a, b = App.new, App.new
  a.to_schema.each {|spec| b.call(spec) }

Application objects that do not satisfy the application object API are quietly ignored; enable debugging to be warned of their existance.

[Source]

     # File lib/tap/app.rb, line 583
583:     def serialize(bare=true)
584:       # setup variables

585:       specs = {}
586:       order = []
587:       
588:       # collect enque signals to setup queue

589:       signals = queue.to_a.collect do |(task, input)|
590:         {'sig' => 'enq', 'args' => {'var' => var(task), 'input' => input}}
591:       end
592:       
593:       # collect and trace application objects

594:       objects.keys.sort_by do |var|
595:         var.to_s
596:       end.each do |var|
597:         obj = objects[var]
598:         order.concat trace(obj, specs)
599:       end
600:       
601:       middleware.each do |obj|
602:         order.concat trace(obj, specs)
603:       end
604:       
605:       if bare
606:         order.delete(self)
607:         specs.delete(self)
608:       else
609:         order.unshift(self)
610:         trace(self, specs)
611:       end
612:       order.uniq!
613:       
614:       # assemble specs

615:       variables = {}
616:       objects.each_pair do |var, obj|
617:         (variables[obj] ||= []) << var
618:       end
619:       
620:       specs.keys.each do |obj|
621:         spec = {'sig' => 'set'}
622:         
623:         # assign variables

624:         if vars = variables[obj]
625:           if vars.length == 1
626:             spec['var'] = vars[0]
627:           else
628:             spec['var'] = vars
629:           end
630:         end
631: 
632:         # assign the class

633:         spec['class'] = obj.class.to_s
634: 
635:         # merge obj_spec if possible

636:         obj_spec = specs[obj]
637:         if (obj_spec.keys & RESERVED_KEYS).empty?
638:           spec.merge!(obj_spec)
639:         else
640:           spec['spec'] = obj_spec
641:         end
642:         
643:         specs[obj] = spec
644:       end
645:       
646:       middleware.each do |obj|
647:         spec = specs[obj]
648:         spec['sig'] = 'use'
649:       end
650:       
651:       order.collect! {|obj| specs[obj] }.concat(signals)
652:     end

Sets the object to the specified variable and returns obj. Provide nil as obj to un-set a variable (in which case the existing object is returned).

Nil is reserved as a variable name and cannot be used by set.

[Source]

     # File lib/tap/app.rb, line 284
284:     def set(var, obj)
285:       raise "no var specified" if var.nil?
286:       
287:       if obj
288:         objects[var] = obj
289:       else
290:         objects.delete(var)
291:       end
292:     end

[Source]

    # File lib/tap.rb, line 20
20:   def setup(options=self.options)
21:     env = Env.new
22:     app = App.new({}, :env => env)
23:     app.set('app', app)
24:     app.set('env', env)
25:     App.current = app
26:     
27:     def options.process(key, default=nil)
28:       value = self[key] || default
29:       if self[:debug] == 'true'
30:         $stderr.puts(App::LOG_FORMAT % [' ', nil, key, value])
31:       end
32:       value && block_given? ? yield(value) : nil
33:     end
34:     
35:     if options[:debug] == 'true'
36:       options.process(:ruby, "#{RbConfig::CONFIG['RUBY_INSTALL_NAME']}-#{RUBY_VERSION} (#{RUBY_RELEASE_DATE})")
37:       options.process(:tap, VERSION)
38:       app.debug = true
39:       app.verbose = true
40:       app.logger.level = Logger::DEBUG
41:     end
42:     
43:     options.process(:gems) do |gems|
44:       cache_dir = options[:tap_cache]
45:       
46:       if cache_dir.to_s.strip.empty?
47:         require 'tmpdir'
48:         cache_dir = Dir.tmpdir
49:       end
50:       
51:       env.signal(:load).call Env::Cache.new(cache_dir, options[:debug]).select(gems)
52:     end
53:     
54:     options.process(:path) do |path|
55:       Env::Path.split(path).each {|dir| env.auto(:dir => dir) }
56:     end
57:     
58:     options.process(:tapenv) do |tapenv_path|
59:       env.signal(:load).call Env::Path.split(tapenv_path)
60:     end
61:     
62:     options.process(:taprc) do |taprc_path|
63:       app.signal(:load).call Env::Path.split(taprc_path)
64:     end
65:     
66:     options.process(:tapfile) do |tapfile_path|
67:       Env::Path.split(tapfile_path).each do |tapfile|
68:         next unless File.file?(tapfile)
69:         Declarations::Context.new(app, File.basename(tapfile)).instance_eval(File.read(tapfile), tapfile, 1)
70:       end
71:     end
72:     
73:     app
74:   end

[Source]

     # File lib/tap/app.rb, line 364
364:     def signal(sig, &block)
365:       sig = sig.to_s
366:       sig =~ OBJECT ? route($1, $2, &block) : super(sig, &block)
367:     end

Sets the application stack.

[Source]

     # File lib/tap/app.rb, line 232
232:     def stack=(stack)
233:       synchronize do
234:         @stack = stack
235:       end
236:     end

Signals a running app to stop executing tasks to the application stack by setting state to STOP. The task currently in the stack will continue to completion.

Does nothing unless state is RUN.

[Source]

     # File lib/tap/app.rb, line 526
526:     def stop
527:       synchronize { @state = State::STOP if state == State::RUN }
528:       self
529:     end

Signals a running application to terminate execution by setting state to TERMINATE. In this state, calls to check_terminate will raise a TerminateError. Run considers TerminateErrors a normal exit and rescues them quietly.

Nodes can set breakpoints that call check_terminate to invoke task-specific termination. If a task never calls check_terminate, then it will continue to completion.

Does nothing if state is READY.

[Source]

     # File lib/tap/app.rb, line 541
541:     def terminate
542:       synchronize { @state = State::TERMINATE unless state == State::READY }
543:       self
544:     end

[Source]

     # File lib/tap/app.rb, line 654
654:     def to_spec
655:       signals = serialize(false)
656:       spec = signals.shift
657:       
658:       spec.delete('self')
659:       spec.delete('sig')
660:       
661:       var = spec.delete('var')
662:       klass = spec.delete('class')
663:       spec = spec.delete('spec') || spec
664:       
665:       signals.unshift(
666:         'sig' => 'set',
667:         'var' => var, 
668:         'class' => klass, 
669:         'self' => true
670:       ) if var
671:       spec['signals'] = signals
672:       
673:       spec
674:     end

Adds the specified middleware to the stack. The argv will be used as extra arguments to initialize the middleware.

[Source]

     # File lib/tap/app.rb, line 411
411:     def use(clas, *argv)
412:       synchronize do
413:         @stack = init(clas, @stack, *argv)
414:       end
415:     end

Returns the variable for the object. If the object is not assigned to a variable and auto_assign is true, then the object is set to an unused variable and the new variable is returned.

The new variable will be an integer and will be removed upon gc.

[Source]

     # File lib/tap/app.rb, line 309
309:     def var(obj, auto_assign=true)
310:       objects.each_pair do |var, object|
311:         return var if obj == object
312:       end
313:       
314:       return nil unless auto_assign
315:       
316:       var = objects.length
317:       loop do 
318:         if objects.has_key?(var)
319:           var += 1
320:         else
321:           set(var, obj)
322:           return var
323:         end
324:       end
325:     end

[Validate]