tap 0.12.4 → 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
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