lib/tap/app.rb in bahuvrihi-tap-0.10.8 vs lib/tap/app.rb in bahuvrihi-tap-0.11.0

- old
+ new

@@ -4,135 +4,135 @@ require 'tap/support/executable_queue' module Tap # App coordinates the setup and running of tasks, and provides an interface - # to the application directory structure. App is convenient for use within - # scripts and, with Env, provides the basis for the 'tap' command line - # application. + # to the application directory structure. All tasks have an App (by default + # App.instance) through which tasks access access application-wide resources + # like the logger, executable queue, aggregator, and dependencies. # # === Running Tasks # - # All tasks have an App (by default App.instance) through which tasks access - # access application-wide resources like the logger. Additionally, task - # enque command are forwarded to App#enq: + # Task enque command are forwarded to App#enq: # - # t1 = Task.intern {|task, input| input += 1 } - # t1.enq(0) - # app.enq(t1, 1) + # t0 = Task.intern {|task, input| "#{input}.0" } + # t0.enq('a') + # app.enq(t0, 'b') # # app.run - # app.results(t1) # => [1, 2] + # app.results(t0) # => ['a.0', 'b.0'] # # When a task completes, the results will be passed to the task on_complete # block, if set, or be collected into an Aggregator (aggregated results may - # be accessed per-task, as shown above); on_complete blocks typically enque - # other tasks, allowing the construction of imperative workflows: + # be accessed per-task, as shown above); on_complete blocks typically + # execute or enque other tasks, allowing the construction of imperative + # workflows: # # # clear the previous results # app.aggregator.clear # - # t2 = Task.intern {|task, input| input += 10 } - # t1.on_complete {|_result| t2.enq(_result) } + # t1 = Task.intern {|task, input| "#{input}.1" } + # t0.on_complete {|_result| t1.enq(_result) } + # t0.enq 'c' # - # t1.enq 0 - # t1.enq 10 - # # app.run - # app.results(t1) # => [] - # app.results(t2) # => [11, 21] + # app.results(t0) # => [] + # app.results(t1) # => ['c.0.1'] # - # Here t1 has no results because the on_complete block passed them to t2 in + # Here t0 has no results because the on_complete block passed them to t1 in # a simple sequence. # - # ==== Dependencies + # === Dependencies # - # Tasks allow the construction of dependency-based workflows as well; tasks - # may be set to depend on other tasks such that the dependent task only - # executes after the dependencies have been resolved. + # Tasks allow the construction of dependency-based workflows such that a + # dependent task only executes after its dependencies have been resolved. # - # array = [] - # t1 = Task.intern {|task, *inputs| array << inputs } - # t2 = Task.intern {|task| array << self } + # runlist = [] + # t0 = Task.intern {|task| runlist << task } + # t1 = Task.intern {|task| runlist << task } # - # t1.depends_on(t2) - # t1.enq(4,5,6) + # t0.depends_on(t1) + # t0.enq # # app.run - # array # => [t2, [4,5,6]] + # runlist # => [t1, t0] # # Once a dependency is resolved, it will not execute again: # - # t1.enq(7,8) + # t0.enq # app.run - # array # => [t2, [4,5,6], [7,8]] + # runlist # => [t1, t0, t0] # - # ==== Batching + # === Batching # # Tasks can be batched, allowing the same input to be enqued to multiple # tasks at once. # - # t1 = Task.intern {|task, input| input += 1 } - # t2 = Task.intern {|task, input| input += 10 } + # t0 = Task.intern {|task, input| "#{input}.0" } + # t1 = Task.intern {|task, input| "#{input}.1" } # - # t1.batch_with(t2) - # t1.enq 0 + # t0.batch_with(t1) + # t0.enq 'a' + # t1.enq 'b' # # app.run - # app.results(t1) # => [1] - # app.results(t2) # => [10] + # app.results(t0) # => ['a.0', 'b.0'] + # app.results(t1) # => ['a.1', 'b.1'] # - # ==== Executables + # === Executables # - # App can enque and run any Executable object. One way to initialize an - # Executable for a method is to use the Object#_method added by Tap. - # The mq (method enq) method generates and enques the method in one step. + # App can enque and run any Executable object. Arbitrary methods may be + # made into Executables using Object#_method. The mq (method enq) method + # generates and enques methods in one step. # # array = [] + # + # # longhand # m = array._method(:push) - # - # app.enq(m, 1) + # m.enq(1) + # + # # shorthand # app.mq(array, :push, 2) # # array.empty? # => true # app.run # array # => [1, 2] # # === Auditing # # All results are audited to track how a given input evolves during a workflow. - # To illustrate auditing, consider a workflow that uses the 'add_one' method - # to add one to an input until the result is 3, then adds five more with the - # 'add_five' method. The final result should always be 8. + # To illustrate auditing, consider and addition workflow that ends in eights. # - # t1 = Tap::Task.intern {|task, input| input += 1 } - # t1.name = "add_one" + # add_one = Tap::Task.intern({}, 'add_one') {|task, input| input += 1 } + # add_five = Tap::Task.intern({}, 'add_five') {|task, input| input += 5 } # - # t2 = Tap::Task.intern {|task, input| input += 5 } - # t2.name = "add_five" - # - # t1.on_complete do |_result| + # add_one.on_complete do |_result| # # _result is the audit; use the _current method # # to get the current value in the audit trail + # current_value = _result._current # - # _result._current < 3 ? t1.enq(_result) : t2.enq(_result) + # if current_value < 3 + # add_one.enq(_result) + # else + # add_five.enq(_result) + # end # end # - # t1.enq(0) - # t1.enq(1) - # t1.enq(2) + # add_one.enq(0) + # add_one.enq(1) + # add_one.enq(2) # # app.run - # app.results(t2) # => [8,8,8] + # app.results(add_five) # => [8,8,8] # # Although the results are indistinguishable, each achieved the final value # through a different series of tasks. With auditing you can see how each # input came to the final value of 8: # # # app.results returns the actual result values # # app._results returns the audits for these values - # app._results(t2).each do |_result| + # app._results(add_five).each do |_result| # puts "How #{_result._original} became #{_result._current}:" # puts _result._to_s # puts # end # @@ -255,14 +255,10 @@ # task_name is nil. def config_filepath(name) name == nil ? nil : filepath('config', "#{name}.yml") end - # - # Execution methods - # - # Sets state = State::READY unless the app is running. Returns self. def ready @state = State::READY unless state == State::RUN self end @@ -335,14 +331,12 @@ # def info "state: #{state} (#{State.state_str(state)}) queue: #{queue.size} results: #{aggregator.size}" end - # Enques the task with the inputs. If the task is batched, then each - # task in task.batch will be enqued with the inputs. Returns task. - # - # An Executable may provided instead of a task. + # Enques the task (or Executable) with the inputs. If the task is batched, + # then each task in task.batch will be enqued with the inputs. Returns task. def enq(task, *inputs) case task when Tap::Task raise ArgumentError, "not assigned to enqueing app: #{task}" unless task.app == self task.enq(*inputs) @@ -369,19 +363,20 @@ end # Returns all aggregated results for the specified tasks. Results are # joined into a single array. Arrays of tasks are allowed as inputs. # - # t1 = Task.intern {|task, input| input += 1 } - # t2 = Task.intern {|task, input| input += 10 } - # t3 = t2.initialize_batch_obj + # t0 = Task.intern {|task, input| "#{input}.0" } + # t1 = Task.intern {|task, input| "#{input}.1" } + # t2 = Task.intern {|task, input| "#{input}.2" } + # t1.batch_with(t2) # - # t1.enq(0) - # t2.enq(1) + # t0.enq(0) + # t1.enq(1) # # app.run - # app.results(t1, t2.batch) # => [1, 11, 11] - # app.results(t2, t1) # => [11, 1] + # app.results(t0, t1.batch) # => ["0.0", "1.1", "1.2"] + # app.results(t1, t0) # => ["1.1", "0.0"] # def results(*tasks) _results(tasks).collect {|_result| _result._current} end \ No newline at end of file