tap 0.12.4 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. data/History +34 -0
  2. data/README +62 -41
  3. data/bin/tap +36 -40
  4. data/cmd/console.rb +14 -6
  5. data/cmd/manifest.rb +62 -58
  6. data/cmd/run.rb +49 -31
  7. data/doc/API +84 -0
  8. data/doc/Class Reference +83 -115
  9. data/doc/Examples/Command Line +36 -0
  10. data/doc/Examples/Workflow +40 -0
  11. data/lib/tap/app.rb +293 -214
  12. data/lib/tap/app/node.rb +43 -0
  13. data/lib/tap/app/queue.rb +77 -0
  14. data/lib/tap/app/stack.rb +16 -0
  15. data/lib/tap/app/state.rb +22 -0
  16. data/lib/tap/constants.rb +2 -2
  17. data/lib/tap/env.rb +400 -314
  18. data/lib/tap/env/constant.rb +227 -0
  19. data/lib/tap/env/gems.rb +63 -0
  20. data/lib/tap/env/manifest.rb +89 -0
  21. data/lib/tap/env/minimap.rb +292 -0
  22. data/lib/tap/{support → env}/string_ext.rb +2 -2
  23. data/lib/tap/exe.rb +113 -125
  24. data/lib/tap/join.rb +175 -0
  25. data/lib/tap/joins.rb +9 -0
  26. data/lib/tap/joins/switch.rb +44 -0
  27. data/lib/tap/joins/sync.rb +99 -0
  28. data/lib/tap/root.rb +100 -491
  29. data/lib/tap/root/utils.rb +220 -0
  30. data/lib/tap/{support → root}/versions.rb +31 -29
  31. data/lib/tap/schema.rb +248 -0
  32. data/lib/tap/schema/parser.rb +413 -0
  33. data/lib/tap/schema/utils.rb +82 -0
  34. data/lib/tap/support/intern.rb +19 -6
  35. data/lib/tap/support/templater.rb +8 -3
  36. data/lib/tap/task.rb +175 -171
  37. data/lib/tap/tasks/dump.rb +58 -0
  38. data/lib/tap/tasks/load.rb +62 -0
  39. metadata +30 -73
  40. data/cmd/destroy.rb +0 -27
  41. data/cmd/generate.rb +0 -27
  42. data/doc/Command Reference +0 -105
  43. data/doc/Syntax Reference +0 -234
  44. data/doc/Tutorial +0 -348
  45. data/lib/tap/dump.rb +0 -142
  46. data/lib/tap/file_task.rb +0 -384
  47. data/lib/tap/generator/arguments.rb +0 -13
  48. data/lib/tap/generator/base.rb +0 -176
  49. data/lib/tap/generator/destroy.rb +0 -60
  50. data/lib/tap/generator/generate.rb +0 -93
  51. data/lib/tap/generator/generators/command/command_generator.rb +0 -21
  52. data/lib/tap/generator/generators/command/templates/command.erb +0 -32
  53. data/lib/tap/generator/generators/config/config_generator.rb +0 -98
  54. data/lib/tap/generator/generators/generator/generator_generator.rb +0 -37
  55. data/lib/tap/generator/generators/generator/templates/task.erb +0 -27
  56. data/lib/tap/generator/generators/generator/templates/test.erb +0 -26
  57. data/lib/tap/generator/generators/root/root_generator.rb +0 -84
  58. data/lib/tap/generator/generators/root/templates/MIT-LICENSE +0 -22
  59. data/lib/tap/generator/generators/root/templates/README +0 -14
  60. data/lib/tap/generator/generators/root/templates/Rakefile +0 -84
  61. data/lib/tap/generator/generators/root/templates/Rapfile +0 -11
  62. data/lib/tap/generator/generators/root/templates/gemspec +0 -27
  63. data/lib/tap/generator/generators/root/templates/test/tap_test_helper.rb +0 -3
  64. data/lib/tap/generator/generators/task/task_generator.rb +0 -25
  65. data/lib/tap/generator/generators/task/templates/task.erb +0 -14
  66. data/lib/tap/generator/generators/task/templates/test.erb +0 -19
  67. data/lib/tap/generator/manifest.rb +0 -20
  68. data/lib/tap/generator/preview.rb +0 -69
  69. data/lib/tap/load.rb +0 -64
  70. data/lib/tap/spec.rb +0 -41
  71. data/lib/tap/support/aggregator.rb +0 -65
  72. data/lib/tap/support/audit.rb +0 -333
  73. data/lib/tap/support/constant.rb +0 -143
  74. data/lib/tap/support/constant_manifest.rb +0 -126
  75. data/lib/tap/support/dependencies.rb +0 -54
  76. data/lib/tap/support/dependency.rb +0 -44
  77. data/lib/tap/support/executable.rb +0 -198
  78. data/lib/tap/support/executable_queue.rb +0 -125
  79. data/lib/tap/support/gems.rb +0 -43
  80. data/lib/tap/support/join.rb +0 -144
  81. data/lib/tap/support/joins.rb +0 -12
  82. data/lib/tap/support/joins/switch.rb +0 -27
  83. data/lib/tap/support/joins/sync_merge.rb +0 -38
  84. data/lib/tap/support/manifest.rb +0 -171
  85. data/lib/tap/support/minimap.rb +0 -90
  86. data/lib/tap/support/node.rb +0 -176
  87. data/lib/tap/support/parser.rb +0 -450
  88. data/lib/tap/support/schema.rb +0 -385
  89. data/lib/tap/support/shell_utils.rb +0 -67
  90. data/lib/tap/test.rb +0 -77
  91. data/lib/tap/test/assertions.rb +0 -38
  92. data/lib/tap/test/env_vars.rb +0 -29
  93. data/lib/tap/test/extensions.rb +0 -73
  94. data/lib/tap/test/file_test.rb +0 -362
  95. data/lib/tap/test/file_test_class.rb +0 -15
  96. data/lib/tap/test/regexp_escape.rb +0 -87
  97. data/lib/tap/test/script_test.rb +0 -46
  98. data/lib/tap/test/script_tester.rb +0 -115
  99. data/lib/tap/test/subset_test.rb +0 -260
  100. data/lib/tap/test/subset_test_class.rb +0 -99
  101. data/lib/tap/test/tap_test.rb +0 -109
  102. data/lib/tap/test/utils.rb +0 -231
@@ -0,0 +1,36 @@
1
+ = Command Line Examples
2
+
3
+ == Basic Input/Output
4
+
5
+ === Read data from $stdin
6
+ # [goodnight.txt]
7
+ # goodnight moon
8
+
9
+ % tap run -- load --: dump < goodnight.txt
10
+ goodnight moon
11
+
12
+ === Pipe data from $stdin
13
+ % echo goodnight moon | tap run -- load --: dump
14
+ goodnight moon
15
+
16
+ === Load data from argument
17
+ % tap run -- load 'goodnight moon' --: dump
18
+ goodnight moon
19
+
20
+ === Dump data to $stdout
21
+ % tap run -- load 'goodnight moon' --: dump > goodnight.txt
22
+ % more goodnight.txt
23
+ goodnight moon
24
+
25
+ === Pipe data via $stdout
26
+ % tap run -- load 'goodnight moon' --: dump | more
27
+ goodnight moon
28
+
29
+ == Setup
30
+
31
+ === Use an ENV variable to setup the Tap::Env
32
+
33
+ % TAP_GEMS= tap run -T
34
+ % TAP_GEMS=:all tap run -T
35
+ % TAP_GEMS=:latest tap run -T
36
+ % TAP_GEMS="[rap, tap-tasks]" tap run -T
@@ -0,0 +1,40 @@
1
+ = Workflow Examples
2
+
3
+ === Sequence
4
+
5
+ A simple sequence using the --: syntax that joins the previous to next task.
6
+
7
+ % tap run -- load 'goodnight moon' --: dump
8
+ goodnight moon
9
+
10
+ === Sequence (canonical)
11
+
12
+ The more canonical way of specifying a sequence.
13
+
14
+ % tap run -- load 'goodnight moon' -- dump --[0][1]
15
+ goodnight moon
16
+
17
+ === Fork
18
+
19
+ Multiple outputs for a single input.
20
+
21
+ % tap run -- load 'goodnight moon' -- dump -- dump --[0][1,2]
22
+ goodnight moon
23
+ goodnight moon
24
+
25
+ === Merge
26
+
27
+ Multiple inputs for a single output; note that as seen in the output, dump
28
+ receives the inputs in serial, whenever they happen to be ready.
29
+
30
+ % tap run -- load goodnight -- load moon -- dump --[0,1][2]
31
+ goodnight
32
+ moon
33
+
34
+ === Synchronized Merge
35
+
36
+ Similar to a merge, but the results of each input are collected into an array
37
+ before being passed to dump. The printout is: ['goodnight', 'moon'].to_s
38
+
39
+ % tap run -- load goodnight -- load moon -- dump --[0,1][2].sync
40
+ goodnightmoon
data/lib/tap/app.rb CHANGED
@@ -1,207 +1,181 @@
1
1
  require 'logger'
2
-
3
- require 'tap/support/aggregator'
4
- require 'tap/support/dependencies'
5
- require 'tap/support/executable_queue'
6
- require 'tap/task'
2
+ require 'configurable'
3
+ require 'tap/app/node'
4
+ require 'tap/app/state'
5
+ require 'tap/app/stack'
6
+ require 'tap/app/queue'
7
7
 
8
8
  module Tap
9
9
 
10
- # App coordinates the setup and running of tasks, and provides an interface
11
- # to the application directory structure. All tasks have an app (by default
12
- # App.instance) through which they access application-wide resources like
13
- # the logger, executable queue, and dependencies.
14
- #
15
- # === Running Tasks
10
+ # App coordinates the setup and execution of workflows.
16
11
  #
17
- # Tasks may be enqued and run by an App:
12
+ # === Workflows
18
13
  #
19
- # app = App.instance
14
+ # Workflows are composed of nodes and joins such as instances of Tap::Task
15
+ # and Tap::Join. The actual workflow exists between nodes; each node can
16
+ # specify a join to receive it's output and enque or execute other nodes.
17
+ # When a node does not have a join, apps allow the specification of a
18
+ # default join to, for instance, aggregate results.
20
19
  #
21
- # t = Task.intern {|task, *inputs| inputs }
22
- # t.enq('a', 'b', 'c')
23
- # t.enq(1)
24
- # t.enq(2)
25
- # t.enq(3)
26
- #
27
- # app.run
28
- # app.results(t) # => [['a', 'b', 'c'], [1], [2], [3]]
20
+ # Any object satisfying the correct API[link:files/doc/API.html] can be used
21
+ # as a node or join. Apps have helpers to make nodes out of blocks.
29
22
  #
30
- # By default apps simply run tasks and collect the results. To construct
31
- # a workflow, set an on_complete block to receive the audited result and
32
- # enque or execute the next series of tasks. Here is a simple sequence:
23
+ # app = Tap::App.new
24
+ # n = app.node {|*inputs| inputs }
25
+ # app.enq(n, 'a', 'b', 'c')
26
+ # app.enq(n, 1)
27
+ # app.enq(n, 2)
28
+ # app.enq(n, 3)
33
29
  #
34
- # t0 = Task.intern {|task| "0" }
35
- # t1 = Task.intern {|task, input| "#{input}:1" }
36
- # t2 = Task.intern {|task, input| "#{input}:2"}
30
+ # results = []
31
+ # app.on_complete {|result| results << result }
37
32
  #
38
- # t0.on_complete {|_result| t1.enq(_result) }
39
- # t1.on_complete {|_result| t2.enq(_result) }
40
- #
41
- # t0.enq
42
33
  # app.run
43
- # app.results(t0, t1) # => []
44
- # app.results(t2) # => ["0:1:2"]
34
+ # results # => [['a', 'b', 'c'], [1], [2], [3]]
45
35
  #
46
- # Apps may be assigned an on_complete block as well; the app on_complete
47
- # block is called when a task has no on_complete block set. If neither the
48
- # task nor the app has an on_complete block, the app stores the audit in
49
- # app.aggregator and makes it available through app.results. Note how after
50
- # the sequence, the t0 and t1 results are not in the app (they were handled
51
- # by the on_complete block).
36
+ # To construct a workflow, set joins for individual nodes. Here is a simple
37
+ # sequence:
52
38
  #
53
- # Tracking how inputs evolve through a workflow can be onerous. To help,
54
- # Tap audits changes to the inputs. Audit values are by convention prefixed
55
- # by an underscore; all on_complete block receive the audited values and not
56
- # the actual result of a task. Aggregated audits are available through
57
- # app._results.
39
+ # n0 = app.node { "a" }
40
+ # n1 = app.node {|input| "#{input}.b" }
41
+ # n2 = app.node {|input| "#{input}.c"}
58
42
  #
59
- # t2.enq("a")
60
- # t1.enq("b")
43
+ # n0.on_complete {|result| app.execute(n1, result) }
44
+ # n1.on_complete {|result| app.execute(n2, result) }
45
+ # app.enq(n0)
46
+ #
47
+ # results.clear
61
48
  # app.run
62
- # app.results(t2) # => ["0:1:2", "a:2", "b:1:2"]
63
- #
64
- # t0.name = "zero"
65
- # t1.name = "one"
66
- # t2.name = "two"
49
+ # results # => ["a.b.c"]
67
50
  #
68
- # trails = app._results(t2).collect do |_result|
69
- # _result.dump
70
- # end
51
+ # Tasks have helpers to simplify the manual constructon of workflows, but
52
+ # even with these methods large workflows are cumbersome to build. More
53
+ # typically, a Tap::Schema is used in such cases.
71
54
  #
72
- # "\n" + trails.join("\n")
73
- # # => %q{
74
- # # o-[zero] "0"
75
- # # o-[one] "0:1"
76
- # # o-[two] "0:1:2"
77
- # #
78
- # # o-[] "a"
79
- # # o-[two] "a:2"
80
- # #
81
- # # o-[] "b"
82
- # # o-[one] "b:1"
83
- # # o-[two] "b:1:2"
84
- # # }
55
+ # === Middleware
85
56
  #
86
- # See Audit for more details.
57
+ # Apps allow middleware to wrap the execution of each node. This can be
58
+ # particularly useful to track the progress of a workflow. Middleware is
59
+ # initialized with the application stack and uses the call method to
60
+ # wrap the execution of the stack.
87
61
  #
88
- # === Dependencies
62
+ # Using middleware, an auditor looks like this:
89
63
  #
90
- # Tasks allow the construction of dependency-based workflows. A dependent
91
- # task only executes after its dependencies have been resolved.
64
+ # class AuditMiddleware
65
+ # attr_reader :stack, :audit
92
66
  #
93
- # runlist = []
94
- # t0 = Task.intern {|task| runlist << task }
95
- # t1 = Task.intern {|task| runlist << task }
67
+ # def initialize(stack)
68
+ # @stack = stack
69
+ # @audit = []
70
+ # end
96
71
  #
97
- # t0.depends_on(t1)
98
- # t0.enq
72
+ # def call(node, inputs=[])
73
+ # audit << node
74
+ # stack.call(node, inputs)
75
+ # end
76
+ # end
99
77
  #
100
- # app.run
101
- # runlist # => [t1, t0]
78
+ # auditor = app.use AuditMiddleware
102
79
  #
103
- # Once a dependency is resolved, it will not execute again:
80
+ # app.enq(n0)
81
+ # app.enq(n2, "x")
82
+ # app.enq(n1, "y")
104
83
  #
105
- # t0.enq
84
+ # results.clear
106
85
  # app.run
107
- # runlist # => [t1, t0, t0]
86
+ # results # => ["a.b.c", "x.c", "y.b.c"]
87
+ # auditor.audit
88
+ # # => [
89
+ # # n0, n1, n2,
90
+ # # n2,
91
+ # # n1, n2
92
+ # # ]
93
+ #
94
+ # Middleware can be nested with multiple calls to use.
108
95
  #
109
- # === Executables
96
+ # === Dependencies
110
97
  #
111
- # App can enque and run any Executable object. Arbitrary methods may be
112
- # made into Executables using Object#_method.
98
+ # Nodes allow the construction of dependency-based workflows. A node only
99
+ # executes after its dependencies have been resolved (ie executed).
113
100
  #
114
- # array = []
101
+ # runlist = []
102
+ # n0 = app.node { runlist << 0 }
103
+ # n1 = app.node { runlist << 1 }
115
104
  #
116
- # m = array._method(:push)
117
- # m.enq(1)
118
- # m.enq(2)
119
- # m.enq(3)
105
+ # n0.depends_on(n1)
106
+ # app.enq(n0)
120
107
  #
121
- # array.empty? # => true
122
108
  # app.run
123
- # array # => [1, 2, 3]
109
+ # runlist # => [1, 0]
124
110
  #
125
- class App < Monitor
111
+ # Dependencies are resolved <em>every time</em> a node executes; individual
112
+ # dependencies can implement single-execution if desired. Dependencies are
113
+ # not resolved with arguments, ie dependency nodes must be able to execute
114
+ # without inputs.
115
+ #
116
+ class App
126
117
  class << self
127
118
  # Sets the current app instance
128
119
  attr_writer :instance
129
120
 
130
121
  # Returns the current instance of App. If no instance has been set,
131
- # then instance initializes a new App with the default configuration.
132
- def instance
133
- @instance ||= App.new
122
+ # then instance initializes a new App with the default configuration.
123
+ #
124
+ # Instance is used to initialize tasks when no app is specified. Aside
125
+ # from that, there is nothing magical about instance.
126
+ def instance(auto_initialize=true)
127
+ @instance ||= (auto_initialize ? new : nil)
134
128
  end
135
129
  end
136
130
 
137
131
  include Configurable
132
+ include MonitorMixin
138
133
 
139
- # The application logger
140
- attr_reader :logger
141
-
142
- # The application queue
143
- attr_reader :queue
134
+ # The default App logger writes to $stderr at level INFO.
135
+ DEFAULT_LOGGER = Logger.new($stderr)
136
+ DEFAULT_LOGGER.level = Logger::INFO
137
+ DEFAULT_LOGGER.formatter = lambda do |severity, time, progname, msg|
138
+ " %s[%s] %18s %s\n" % [severity[0,1], time.strftime('%H:%M:%S') , progname || '--' , msg]
139
+ end
144
140
 
145
141
  # The state of the application (see App::State)
146
142
  attr_reader :state
147
143
 
148
- # A Tap::Support::Aggregator that collects the results of
149
- # methods that have no on_complete block
150
- attr_reader :aggregator
144
+ # The application call stack for executing nodes
145
+ attr_reader :stack
151
146
 
152
- # A Tap::Support::Dependencies to track dependencies.
153
- attr_reader :dependencies
147
+ # The application queue
148
+ attr_reader :queue
154
149
 
155
- # The block called when an executable completes and has no
156
- # on_complete_block set. An on_complete_block effectively
157
- # takes the place of aggregation (ie when set, no results
158
- # will be collected by self).
159
- attr_reader :on_complete_block
150
+ # A cache of application-specific data. Internally used to store class
151
+ # instances of tasks. Not recommended for casual use.
152
+ attr_reader :cache
153
+
154
+ # The default joins for nodes that have no joins set
155
+ attr_accessor :default_joins
156
+
157
+ # The application logger
158
+ attr_reader :logger
160
159
 
161
- config :audit, true, &c.switch # Signal auditing
162
160
  config :debug, false, &c.flag # Flag debugging
163
161
  config :force, false, &c.flag # Force execution at checkpoints
164
162
  config :quiet, false, &c.flag # Suppress logging
165
163
  config :verbose, false, &c.flag # Enables extra logging (overrides quiet)
166
164
 
167
- # The constants defining the possible App states.
168
- module State
169
- READY = 0
170
- RUN = 1
171
- STOP = 2
172
- TERMINATE = 3
173
-
174
- module_function
175
-
176
- # Returns a string corresponding to the input state value.
177
- # Returns nil for unknown states.
178
- #
179
- # State.state_str(0) # => 'READY'
180
- # State.state_str(12) # => nil
181
- def state_str(state)
182
- constants.inject(nil) {|str, s| const_get(s) == state ? s.to_s : str}
183
- end
184
- end
185
-
186
165
  # Creates a new App with the given configuration.
187
- def initialize(config={}, logger=DEFAULT_LOGGER, &block)
166
+ def initialize(config={}, options={}, &block)
188
167
  super() # monitor
189
168
 
190
169
  @state = State::READY
191
- @queue = Support::ExecutableQueue.new
192
- @aggregator = Support::Aggregator.new
193
- @dependencies = Support::Dependencies.new
194
- @on_complete_block = block
170
+ @stack = options[:stack] || Stack.new
171
+ @queue = options[:queue] || Queue.new
172
+ @cache = options[:cache] || {}
173
+ @trace = []
174
+ @default_joins = []
175
+ on_complete(&block)
195
176
 
196
177
  initialize_config(config)
197
- self.logger = logger
198
- end
199
-
200
- # The default App logger writes to $stderr at level INFO.
201
- DEFAULT_LOGGER = Logger.new($stderr)
202
- DEFAULT_LOGGER.level = Logger::INFO
203
- DEFAULT_LOGGER.formatter = lambda do |severity, time, progname, msg|
204
- " %s[%s] %18s %s\n" % [severity[0,1], time.strftime('%H:%M:%S') , progname || '--' , msg]
178
+ self.logger = options[:logger] || DEFAULT_LOGGER
205
179
  end
206
180
 
207
181
  # True if debug or the global variable $DEBUG is true.
@@ -225,19 +199,111 @@ module Tap
225
199
  logger.add(level, msg, action.to_s) if !quiet || verbose
226
200
  end
227
201
 
228
- # Sequentially calls execute with the (executable, inputs) pairs in
229
- # queue; run continues until the queue is empty and then returns self.
202
+ # Returns a new node that executes block on call.
203
+ def node(&block) # :yields: *inputs
204
+ Node.intern(&block)
205
+ end
206
+
207
+ # Enques the node with the inputs. Returns the node.
208
+ def enq(node, *inputs)
209
+ queue.enq(node, inputs)
210
+ node
211
+ end
212
+
213
+ # Generates a node from the block and enques. Returns the new node.
214
+ def bq(*inputs, &block) # :yields: *inputs
215
+ node = self.node(&block)
216
+ queue.enq(node, inputs)
217
+ node
218
+ end
219
+
220
+ # Adds the specified middleware to the stack.
221
+ def use(middleware)
222
+ @stack = middleware.new(@stack)
223
+ end
224
+
225
+ # Clears the cache, the queue, and resets the stack so that no middleware
226
+ # is used. Reset raises an error unless state == State::READY.
227
+ def reset
228
+ synchronize do
229
+ unless state == State::READY
230
+ raise "cannot reset unless READY"
231
+ end
232
+
233
+ @stack = Stack.new
234
+ cache.clear
235
+ queue.clear
236
+ end
237
+ end
238
+
239
+ # Dispatches each dependency of node. A block can be given to do something
240
+ # else with the nodes (ex: reset single-execution dependencies). Resolve
241
+ # will recursively yield dependencies if specified.
242
+ #
243
+ # Resolve raises an error for circular dependencies.
244
+ def resolve(node, recursive=false, &block)
245
+ node.dependencies.each do |dependency|
246
+ if @trace.include?(dependency)
247
+ @trace.push dependency
248
+ raise DependencyError.new(@trace)
249
+ end
250
+
251
+ # mark the results at the index to prevent
252
+ # infinite loops with circular dependencies
253
+ @trace.push dependency
254
+
255
+ if recursive
256
+ resolve(dependency, recursive, &block)
257
+ end
258
+
259
+ if block_given?
260
+ yield(dependency)
261
+ else
262
+ dispatch(dependency)
263
+ end
264
+
265
+ @trace.pop
266
+ end
267
+ end
268
+
269
+ # Dispatches node to the application stack with the inputs.
270
+ def execute(node, *inputs)
271
+ dispatch(node, inputs)
272
+ end
273
+
274
+ # Dispatch sends the node into the application stack with the inputs.
275
+ # Dispatch does the following in order:
276
+ #
277
+ # - resolve node dependencies using resolve_dependencies
278
+ # - call stack with the node and inputs
279
+ # - call the node joins, if set, or the default_joins with the results
280
+ #
281
+ # Dispatch returns the node result.
282
+ def dispatch(node, inputs=[])
283
+ resolve(node)
284
+ result = stack.call(node, inputs)
285
+
286
+ joins = node.joins.empty? ? default_joins : node.joins
287
+ joins.each do |join|
288
+ join.call(result)
289
+ end
290
+ result
291
+ end
292
+
293
+ # Sequentially dispatches each enqued (node, inputs) pair to the
294
+ # application stack. A run continues until the queue is empty. Returns
295
+ # self.
230
296
  #
231
297
  # ==== Run State
232
298
  #
233
- # Run checks the state of self before executing a method. If state
299
+ # Run checks the state of self before dispatching a node. If the state
234
300
  # changes from State::RUN, the following behaviors result:
235
301
  #
236
- # State::STOP:: No more executables will be executed; the current
237
- # executable will continute to completion.
238
- # State::TERMINATE:: No more executables will be executed and the
239
- # currently running executable will be
240
- # discontinued as described in terminate.
302
+ # State::STOP:: No more nodes will be dispatched; the current node will
303
+ # continute to completion.
304
+ # State::TERMINATE:: No more nodes will be dispatched and the currently
305
+ # running node will be discontinued as described in
306
+ # terminate.
241
307
  #
242
308
  # Calls to run when the state is not State::READY do nothing and
243
309
  # return immediately.
@@ -250,8 +316,7 @@ module Tap
250
316
  # TODO: log starting run
251
317
  begin
252
318
  until queue.empty? || state != State::RUN
253
- executable, inputs = queue.deq
254
- executable._execute(*inputs)
319
+ dispatch(*queue.deq)
255
320
  end
256
321
  rescue(TerminateError)
257
322
  # gracefully fail for termination errors
@@ -267,9 +332,9 @@ module Tap
267
332
  self
268
333
  end
269
334
 
270
- # Signals a running application to stop executing tasks in the
271
- # queue by setting state = State::STOP. The task currently
272
- # executing will continue uninterrupted to completion.
335
+ # Signals a running app to stop dispatching nodes to the application stack
336
+ # by setting state = State::STOP. The node currently in the stack will
337
+ # will continue to completion.
273
338
  #
274
339
  # Does nothing unless state is State::RUN.
275
340
  def stop
@@ -278,10 +343,14 @@ module Tap
278
343
  end
279
344
 
280
345
  # Signals a running application to terminate execution by setting
281
- # state = State::TERMINATE. In this state, an executing task
282
- # will then raise a TerminateError upon check_terminate, thus
283
- # allowing the invocation of task-specific termination, perhaps
284
- # performing rollbacks. (see Tap::Support::Executable#check_terminate).
346
+ # state = State::TERMINATE. In this state, calls to check_terminate
347
+ # will raise a TerminateError. Run considers TerminateErrors a normal
348
+ # exit and rescues them quietly.
349
+ #
350
+ # Nodes can set breakpoints that call check_terminate to invoke
351
+ # node-specific termination. If a node never calls check_terminate, then
352
+ # it will continue to completion and terminate is functionally the same
353
+ # as stop.
285
354
  #
286
355
  # Does nothing if state == State::READY.
287
356
  def terminate
@@ -289,76 +358,86 @@ module Tap
289
358
  self
290
359
  end
291
360
 
361
+ # Raises a TerminateError if state == State::TERMINATE. Nodes should call
362
+ # check_terminate to provide breakpoints in long-running processes.
363
+ def check_terminate
364
+ if state == App::State::TERMINATE
365
+ raise App::TerminateError.new
366
+ end
367
+ end
368
+
292
369
  # Returns an information string for the App.
293
370
  #
294
- # App.instance.info # => 'state: 0 (READY) queue: 0 results: 0'
371
+ # App.instance.info # => 'state: 0 (READY) queue: 0'
295
372
  #
296
373
  def info
297
- "state: #{state} (#{State.state_str(state)}) queue: #{queue.size} results: #{aggregator.size}"
374
+ "state: #{state} (#{State.state_str(state)}) queue: #{queue.size}"
298
375
  end
299
376
 
300
- # Enques the task (or Executable) with the inputs. Raises an error if the
301
- # input is not an Executable, or is not assigned to self. Returns task.
302
- def enq(task, *inputs)
303
- unless task.kind_of?(Support::Executable)
304
- raise ArgumentError, "not an Executable: #{task.inspect}"
305
- end
306
-
307
- unless task.app == self
308
- raise ArgumentError, "not assigned to enqueing app: #{task.inspect}"
309
- end
310
-
311
- queue.enq(task, inputs)
312
- task
313
- end
314
-
315
- # Method enque. Enques the specified method from object with the inputs.
316
- # Returns the enqued method.
317
- def mq(object, method_name, *inputs)
318
- m = object._method(method_name)
319
- enq(m, *inputs)
320
- end
321
-
322
- # Returns all aggregated, audited results for the specified tasks.
323
- # Results are joined into a single array. Arrays of tasks are
324
- # allowed as inputs. See results.
325
- def _results(*tasks)
326
- aggregator.retrieve_all(*tasks.flatten)
327
- end
328
-
329
- # Returns all aggregated results for the specified tasks. Results are
330
- # joined into a single array. Arrays of tasks are allowed as inputs.
377
+ # Dumps self to the target as YAML.
331
378
  #
332
- # t0 = Task.intern {|task, input| "#{input}.0" }
333
- # t1 = Task.intern {|task, input| "#{input}.1" }
379
+ # ==== Notes
334
380
  #
335
- # t0.enq(0)
336
- # t1.enq(1)
381
+ # Platforms that use {Syck}[http://whytheluckystiff.net/syck/] (ex MRI)
382
+ # require a fix because Syck misformats certain dumps, such that they
383
+ # cannot be reloaded (even by Syck). Specifically:
337
384
  #
338
- # app.run
339
- # app.results(t1, t0) # => ["1.1", "0.0"]
385
+ # &id001 !ruby/object:Tap::Task ?
340
386
  #
341
- def results(*tasks)
342
- _results(tasks).collect {|_result| _result.value }
343
- end
344
-
345
- # Sets a block to receive the results tasks with no on_complete_block
346
- # set. Raises an error if an on_complete_block is already set.
347
- # Override the existing on_complete_block by specifying override = true.
387
+ # should be:
388
+ #
389
+ # ? &id001 !ruby/object:Tap::Task
348
390
  #
349
- # Note: the block recieves an audited result and not the result
350
- # itself (see Audit for more information).
351
- def on_complete(override=false, &block) # :yields: _result
352
- unless on_complete_block == nil || override
353
- raise "on_complete_block already set: #{self}"
391
+ # Dump fixes this error and, in addition, removes Thread and Proc dumps
392
+ # because they can't be allocated on load.
393
+ def dump(target=$stdout, options={})
394
+ synchronize do
395
+ options = {
396
+ :date_format => '%Y-%m-%d %H:%M:%S',
397
+ :date => true,
398
+ :info => true
399
+ }.merge(options)
400
+
401
+ # print basic headers
402
+ target.puts "# date: #{Time.now.strftime(options[:date_format])}" if options[:date]
403
+ target.puts "# info: #{info}" if options[:info]
404
+
405
+ # # print load paths and requires
406
+ # target.puts "# load paths"
407
+ # target.puts $:.to_yaml
408
+ #
409
+ # target.puts "# requires"
410
+ # target.puts $".to_yaml
411
+
412
+ # dump yaml, fixing as necessary
413
+ yaml = YAML.dump(self)
414
+ yaml.gsub!(/\&(.*!ruby\/object:.*?)\s*\?/) {"? &#{$1} " } if YAML.const_defined?(:Syck)
415
+ yaml.gsub!(/!ruby\/object:(Thread|Proc) \{\}/, '')
416
+ target << yaml
354
417
  end
355
- @on_complete_block = block
418
+
419
+ target
420
+ end
421
+
422
+ # Sets the block to receive the audited result of nodes with no join
423
+ # (ie the block is set as default_join).
424
+ def on_complete(&block) # :yields: _result
425
+ self.default_joins << block if block
356
426
  self
357
427
  end
358
428
 
359
- # TerminateErrors are raised to kill executing tasks when terminate is
429
+ protected
430
+
431
+ # TerminateErrors are raised to kill executing nodes when terminate is
360
432
  # called on an running App. They are handled by the run rescue code.
361
- class TerminateError < RuntimeError
433
+ class TerminateError < RuntimeError
434
+ end
435
+
436
+ # Raised when Tap::App#resolve detects a circular dependency.
437
+ class DependencyError < StandardError
438
+ def initialize(trace)
439
+ super "circular dependency: [#{trace.join(', ')}]"
440
+ end
362
441
  end
363
442
  end
364
443
  end