tap 0.17.0 → 0.17.1

Sign up to get free protection for your applications and to get access to all the features.
data/History CHANGED
@@ -1,3 +1,12 @@
1
+ == 0.17.1 / 2009-06-06
2
+
3
+ * documentation and interface updates
4
+ * reworked load to allow recurrent loads
5
+ * modified stack to check_terminate before calling a node
6
+ * fixed parsing of empty breaks in command line schema
7
+ * simplified/standardized YAML schema syntax
8
+ * added support for middleware to parser
9
+
1
10
  == 0.17.0 / 2009-05-25
2
11
 
3
12
  Significant reorganization and update to Tap internals.
data/README CHANGED
@@ -20,7 +20,7 @@ and a
20
20
  {server}[http://tap.rubyforge.org/tap-server/index.html]
21
21
  to execute workflows via HTTP.
22
22
 
23
- * {Tutorial}[link:files/doc/Tutorial.html], {API}[link:files/doc/API.html], {Structure}[link:files/doc/Class%20Reference.html]
23
+ * {Tutorial}[http://tap.rubyforge.org/tap-suite/files/doc/Tutorial.html], {API}[link:files/doc/API.html], {Structure}[link:files/doc/Class%20Reference.html]
24
24
  * Website[http://tap.rubyforge.org]
25
25
  * Lighthouse[http://bahuvrihi.lighthouseapp.com/projects/9908-tap-task-application/tickets]
26
26
  * Github[http://github.com/bahuvrihi/tap/tree/master]
data/cmd/run.rb CHANGED
@@ -2,8 +2,8 @@
2
2
  #
3
3
  # examples:
4
4
  # tap run --help Prints this help
5
- # tap run -w workflow.yml Build and run a workflow
6
- # tap run -w workflow.yml a b c Same with [a, b, c] ARGV
5
+ # tap run -s schema.yml Build and run a workflow
6
+ # tap run -s schema.yml a b c Same with [a, b, c] ARGV
7
7
  #
8
8
  # schema:
9
9
  # tap run -- task --help Prints help for task
@@ -19,13 +19,11 @@ app = Tap::App.new
19
19
 
20
20
  # separate out argv schema
21
21
  argv = []
22
- break_regexp = Tap::Schema::Parser::BREAK
23
- while !ARGV.empty? && ARGV[0] !~ break_regexp
22
+ while !ARGV.empty? && ARGV[0] !~ Tap::Schema::Parser::BREAK
24
23
  argv << ARGV.shift
25
24
  end
26
25
 
27
26
  # parse options
28
- schemas = []
29
27
  ConfigParser.new(app.config) do |opts|
30
28
  opts.separator ""
31
29
  opts.separator "configurations:"
@@ -66,20 +64,6 @@ ConfigParser.new(app.config) do |opts|
66
64
  exit(0)
67
65
  end
68
66
 
69
- opts.on('-m', '--middleware MIDDLEWARE', 'Specify app middleware') do |key|
70
- middleware = env[:middleware][key] or raise("unknown middleware: #{key}")
71
- app.use(middleware)
72
- end
73
-
74
- opts.on("-s", "--schema FILE", "Build the schema file") do |path|
75
- unless File.exists?(path)
76
- puts "No such schema file - #{path}"
77
- exit(1)
78
- end
79
-
80
- schemas << Tap::Schema.load_file(path)
81
- end
82
-
83
67
  end.parse!(argv, :clear_config => false, :add_defaults => false)
84
68
 
85
69
  #
@@ -87,17 +71,29 @@ end.parse!(argv, :clear_config => false, :add_defaults => false)
87
71
  #
88
72
 
89
73
  begin
74
+ if ARGV.empty?
75
+ msg = "No schema specified"
76
+
77
+ unless argv.empty?
78
+ args = argv[0, 3].join(' ') + (argv.length > 3 ? ' ...' : '')
79
+ msg = "#{msg} (did you mean 'tap run -- #{args}'?)"
80
+ end
81
+
82
+ puts msg
83
+ exit(0)
84
+ end
85
+
90
86
  # parse argv schema
91
- schemas << Tap::Schema.parse(ARGV)
87
+ schema = Tap::Schema.parse(ARGV)
88
+ app.build(schema, :resources => env)
89
+
92
90
  ARGV.replace(argv)
91
+ Tap::Exe.set_signals(app)
93
92
 
94
- env.run(schemas, app)
93
+ app.run
95
94
  rescue
96
95
  raise if $DEBUG
97
96
  puts $!.message
98
- if $!.message == "no nodes specified" && !ARGV.empty?
99
- puts "(did you mean 'tap run -- #{ARGV.join(' ')}'?)"
100
- end
101
97
  exit(1)
102
98
  end
103
99
 
data/doc/API CHANGED
@@ -34,12 +34,12 @@ Note the middleware API is essentially the same as for {Rack}[http://rack.rubyfo
34
34
 
35
35
  == Tap::Schema
36
36
 
37
- Schema describe workflows as data. To build a workflow from a schema, workflow classes need to instantiate themselves using the schema data. The <tt>parse</tt> and <tt>instantiate</tt> methods are provided to do so.
37
+ Schema describe workflows as data. To build a workflow from a schema, workflow classes need to instantiate themselves using the schema data. The <tt>parse!</tt> and <tt>instantiate</tt> methods are provided to do so.
38
38
 
39
- WorkflowClass.parse(argv=ARGV, app=App.instance)
39
+ WorkflowClass.parse!(argv=ARGV, app=App.instance)
40
40
  WorkflowClass.instantiate(argh, app=App.instance)
41
41
 
42
- As implied in by the inputs, <tt>parse</tt> instantiates from an array, while <tt>instantiate</tt> instantiates from a hash with symbol keys. If <tt>parse</tt> receives a string, it must be able to convert it to an array (ex using Shellwords).
42
+ As implied in by the inputs, <tt>parse!</tt> instantiates from an array, while <tt>instantiate</tt> instantiates from a hash with symbol keys. If <tt>parse!</tt> receives a string, it must be able to convert it to an array (ex using Shellwords).
43
43
 
44
44
  How the class actually performs the instantiation is up to the class but typically parse creates a hash and calls instantiate.
45
45
 
@@ -4,6 +4,7 @@ require 'tap/app/node'
4
4
  require 'tap/app/state'
5
5
  require 'tap/app/stack'
6
6
  require 'tap/app/queue'
7
+ require 'tap/schema'
7
8
 
8
9
  module Tap
9
10
 
@@ -167,7 +168,7 @@ module Tap
167
168
  super() # monitor
168
169
 
169
170
  @state = State::READY
170
- @stack = options[:stack] || Stack.new
171
+ @stack = options[:stack] || Stack.new(self)
171
172
  @queue = options[:queue] || Queue.new
172
173
  @cache = options[:cache] || {}
173
174
  @trace = []
@@ -218,8 +219,28 @@ module Tap
218
219
  end
219
220
 
220
221
  # Adds the specified middleware to the stack.
221
- def use(middleware)
222
- @stack = middleware.new(@stack)
222
+ def use(middleware, *argv)
223
+ @stack = middleware.new(@stack, *argv)
224
+ end
225
+
226
+ def build(schema, options={})
227
+ unless schema.kind_of?(Schema)
228
+ schema = Schema.new(schema)
229
+ end
230
+
231
+ if resources = options[:resources]
232
+ schema.resolve! do |type, id|
233
+ resources[type][id]
234
+ end
235
+ end
236
+
237
+ schema.validate!
238
+
239
+ if options[:clean]
240
+ reset
241
+ end
242
+
243
+ schema.build!(self)
223
244
  end
224
245
 
225
246
  # Clears the cache, the queue, and resets the stack so that no middleware
@@ -230,7 +251,7 @@ module Tap
230
251
  raise "cannot reset unless READY"
231
252
  end
232
253
 
233
- @stack = Stack.new
254
+ @stack = Stack.new(self)
234
255
  cache.clear
235
256
  queue.clear
236
257
  end
@@ -312,23 +333,19 @@ module Tap
312
333
  return self unless state == State::READY
313
334
  @state = State::RUN
314
335
  end
315
-
316
- # TODO: log starting run
336
+
317
337
  begin
318
- until queue.empty? || state != State::RUN
319
- dispatch(*queue.deq)
338
+ while state == State::RUN
339
+ break unless entry = queue.deq
340
+ dispatch(*entry)
320
341
  end
321
342
  rescue(TerminateError)
322
343
  # gracefully fail for termination errors
323
- rescue(Exception)
324
- # handle other errors accordingly
325
- raise if debug?
326
- log($!.class, $!.message)
344
+ queue.unshift(*entry)
327
345
  ensure
328
346
  synchronize { @state = State::READY }
329
347
  end
330
348
 
331
- # TODO: log run complete
332
349
  self
333
350
  end
334
351
 
@@ -362,6 +379,7 @@ module Tap
362
379
  # check_terminate to provide breakpoints in long-running processes.
363
380
  def check_terminate
364
381
  if state == App::State::TERMINATE
382
+ yield if block_given?
365
383
  raise App::TerminateError.new
366
384
  end
367
385
  end
@@ -402,13 +420,6 @@ module Tap
402
420
  target.puts "# date: #{Time.now.strftime(options[:date_format])}" if options[:date]
403
421
  target.puts "# info: #{info}" if options[:info]
404
422
 
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
423
  # dump yaml, fixing as necessary
413
424
  yaml = YAML.dump(self)
414
425
  yaml.gsub!(/\&(.*!ruby\/object:.*?)\s*\?/) {"? &#{$1} " } if YAML.const_defined?(:Syck)
@@ -4,11 +4,19 @@ module Tap
4
4
  # The base of the application call stack.
5
5
  class Stack
6
6
 
7
- # Calls the node with the inputs:
7
+ # The application using this stack.
8
+ attr_reader :app
9
+
10
+ def initialize(app)
11
+ @app = app
12
+ end
13
+
14
+ # Checks app for termination and then calls the node with the inputs:
8
15
  #
9
16
  # node.call(*inputs)
10
17
  #
11
18
  def call(node, inputs)
19
+ app.check_terminate
12
20
  node.call(*inputs)
13
21
  end
14
22
  end
@@ -1,7 +1,7 @@
1
1
  module Tap
2
2
  MAJOR = 0
3
3
  MINOR = 17
4
- TINY = 0
4
+ TINY = 1
5
5
 
6
6
  VERSION="#{MAJOR}.#{MINOR}.#{TINY}"
7
7
  WEBSITE="http://tap.rubyforge.org"
@@ -94,20 +94,7 @@ module Tap
94
94
  end
95
95
  end
96
96
 
97
- def build(schema, app=Tap::App.instance)
98
- schema.resolve! do |type, id, data|
99
- klass = self[type][id]
100
- if !klass && block_given?
101
- klass = yield(type, id, data)
102
- end
103
-
104
- klass || id
105
- end
106
- schema.validate!
107
- schema.build(app)
108
- end
109
-
110
- def set_signals(app)
97
+ def self.set_signals(app)
111
98
  # info signal -- Note: some systems do
112
99
  # not support the INFO signal
113
100
  # (windows, fedora, at least)
@@ -139,20 +126,5 @@ module Tap
139
126
  end
140
127
  end if signals.include?("INT")
141
128
  end
142
-
143
- def run(schemas, app=Tap::App.instance, &block)
144
- schemas = [schemas] unless schemas.kind_of?(Array)
145
- schemas.each do |schema|
146
- build(schema, app, &block)
147
- end
148
-
149
- if app.queue.empty?
150
- raise "no nodes specified"
151
- end
152
-
153
- set_signals(app)
154
- app.run
155
- end
156
-
157
129
  end
158
130
  end
@@ -38,11 +38,12 @@ module Tap
38
38
  attr_reader :joins
39
39
 
40
40
  # An array of [key, [args]] data that indicates the tasks and arguments
41
- # to be added to an application during build. If args are not specified,
42
- # then the arguments specified in the task schema are used.
41
+ # to be added to an application during build. A key may be specified
42
+ # alone if tasks[key] is an array; in that case, the arguments remaining
43
+ # in tasks[key] after instantiation will be used.
43
44
  #
44
45
  # queue:
45
- # - key # uses tasks[key] arguments
46
+ # - key # uses tasks[key]
46
47
  # - [key, [1, 2, 3]] # enques tasks[key] with [1, 2, 3]
47
48
  #
48
49
  attr_reader :queue
@@ -51,76 +52,76 @@ module Tap
51
52
  attr_reader :middleware
52
53
 
53
54
  def initialize(schema={})
54
- @tasks = hashify(schema['tasks'] || {})
55
-
56
- @joins = dehashify(schema['joins'] || []).collect do |join|
57
- inputs, outputs, join = dehashify(join)
58
- [inputs, outputs, join]
59
- end
60
-
61
- @queue = dehashify(schema['queue'] || []).collect do |queue|
62
- dehashify(queue)
63
- end
64
-
65
- @middleware = dehashify(schema['middleware'] || [])
55
+ @tasks = schema['tasks'] || {}
56
+ @joins = schema['joins'] || []
57
+ @queue = schema['queue'] || []
58
+ @middleware = schema['middleware'] || []
66
59
  end
67
60
 
68
61
  def resolve!
69
- tasks.dup.each_pair do |key, task|
62
+ tasks.each_pair do |key, task|
70
63
  task ||= {}
71
- tasks[key] = resolve(task) do |id, data|
72
- yield(:task, id || key, data)
64
+ tasks[key] = resolve(task) do |id|
65
+ yield(:task, id || key)
73
66
  end
74
67
  end
75
68
 
76
69
  joins.collect! do |inputs, outputs, join|
77
70
  join ||= {}
78
- join = resolve(join) do |id, data|
79
- yield(:join, id || 'join', data)
71
+ join = resolve(join) do |id|
72
+ yield(:join, id || 'join')
80
73
  end
81
74
  [inputs, outputs, join]
82
75
  end
83
76
 
84
77
  middleware.collect! do |m|
85
- resolve(m) do |id, data|
86
- yield(:middleware, id, data)
78
+ resolve(m) do |id|
79
+ yield(:middleware, id)
87
80
  end
88
81
  end
89
82
 
83
+ queue.collect! do |(key, inputs)|
84
+ [key, inputs || tasks[key]]
85
+ end
86
+
90
87
  self
91
88
  end
92
89
 
93
90
  def validate!
94
91
  errors = []
95
- tasks.each do |key, task|
92
+ tasks.each_value do |task|
96
93
  unless resolved?(task)
97
- errors << "unknown task: #{task}"
94
+ errors << "unknown task: #{task.inspect}"
98
95
  end
99
96
  end
100
97
 
101
98
  joins.each do |inputs, outputs, join|
102
99
  unless resolved?(join)
103
- errors << "unknown join: #{join}"
100
+ errors << "unknown join: #{join.inspect}"
104
101
  end
105
102
 
106
103
  inputs.each do |key|
107
104
  unless tasks.has_key?(key)
108
- errors << "missing join input: #{key}"
105
+ errors << "missing join input: #{key.inspect}"
109
106
  end
110
107
  end
111
108
 
112
109
  outputs.each do |key|
113
110
  unless tasks.has_key?(key)
114
- errors << "missing join output: #{key}"
111
+ errors << "missing join output: #{key.inspect}"
115
112
  end
116
113
  end
117
114
  end
118
115
 
119
- # queue.each do |(key, args)|
120
- # unless tasks.has_key?(key)
121
- # errors << "missing task: #{key}"
122
- # end
123
- # end
116
+ queue.each do |(key, args)|
117
+ if tasks.has_key?(key)
118
+ unless args.kind_of?(Array)
119
+ errors << "non-array args: #{args.inspect}"
120
+ end
121
+ else
122
+ errors << "missing task: #{key}"
123
+ end
124
+ end
124
125
 
125
126
  middleware.each do |m|
126
127
  unless resolved?(m)
@@ -130,7 +131,7 @@ module Tap
130
131
 
131
132
  unless errors.empty?
132
133
  prefix = if errors.length > 1
133
- "#{errors.length} build errors\n"
134
+ "#{errors.length} schema errors\n"
134
135
  else
135
136
  ""
136
137
  end
@@ -162,75 +163,32 @@ module Tap
162
163
  self
163
164
  end
164
165
 
165
- def scrub!
166
- tasks.each do |key, task|
167
- yield(task)
168
- end
169
-
170
- joins.each do |inputs, outputs, join|
171
- yield(join)
172
- end
173
- end
174
-
175
- def build(app)
166
+ def build!(app)
176
167
  # instantiate tasks
177
- tasks = {}
178
- arguments = {}
179
- self.tasks.each_pair do |key, task|
180
- instance, args = instantiate(task, app)
181
-
182
- tasks[key] = instance
183
- arguments[key] = args
168
+ tasks.each_pair do |key, task|
169
+ tasks[key] = instantiate(task, app)
184
170
  end
185
171
 
186
172
  # build the workflow
187
- self.joins.each do |inputs, outputs, join|
173
+ joins.collect! do |inputs, outputs, join|
188
174
  inputs = inputs.collect {|key| tasks[key] }
189
175
  outputs = outputs.collect {|key| tasks[key] }
190
176
  instantiate(join, app).join(inputs, outputs)
191
177
  end
192
178
 
193
179
  # utilize middleware
194
- self.middleware.each do |middleware|
180
+ middleware.collect! do |middleware|
195
181
  instantiate(middleware, app)
196
182
  end
197
183
 
198
184
  # enque tasks
199
185
  queue.each do |(key, inputs)|
200
- unless inputs
201
- inputs = arguments[key]
202
- end
203
-
204
- app.enq(tasks[key], *inputs) if inputs
186
+ app.enq(tasks[key], *inputs)
205
187
  end
206
188
 
207
189
  tasks
208
190
  end
209
191
 
210
- def traverse
211
- map = {}
212
- self.tasks.each_pair do |key, task|
213
- map[key] = [[],[]]
214
- end
215
-
216
- index = 0
217
- self.joins.each do |inputs, outputs, join|
218
- inputs.each do |key|
219
- map[key][1] << index
220
- end
221
-
222
- outputs.each do |key|
223
- map[key][0] << index
224
- end
225
-
226
- index += 1
227
- end
228
-
229
- map.keys.sort.collect do |key|
230
- [key, *map[key]]
231
- end
232
- end
233
-
234
192
  # Creates an hash dump of self.
235
193
  def to_hash
236
194
  { 'tasks' => tasks,
@@ -30,12 +30,11 @@ module Tap
30
30
  # schema.joins # => [['join', [1],[2]]]
31
31
  #
32
32
  # In the example, the indicies of the tasks participating in the sequence
33
- # are inferred as the last and next tasks in the schema, and obviously the
34
- # location of the sequence break is significant. This isn't the case when
35
- # the tasks in a join are explicitly specified. These both sequence a to
36
- # b, and b to c.
33
+ # are inferred as the last and next tasks in the schema. Alternatively
34
+ # the tasks participating in the sequence may be written out directly;
35
+ # these also sequence b to c.
37
36
  #
38
- # schema = Parser.new("a -- b -- c --0:1 --1:2").schema
37
+ # schema = Parser.new("a -- b -- c --1:2").schema
39
38
  # schema.tasks
40
39
  # # => {
41
40
  # # 0 => ["a"],
@@ -44,11 +43,10 @@ module Tap
44
43
  # # }
45
44
  # schema.joins
46
45
  # # => [
47
- # # [[0],[1]],
48
- # # [[1],[2]],
46
+ # # [[1],[2]]
49
47
  # # ]
50
48
  #
51
- # schema = Parser.new("a --1:2 --0:1 b -- c").schema
49
+ # schema = Parser.new("a --1:2 b -- c").schema
52
50
  # schema.tasks
53
51
  # # => {
54
52
  # # 0 => ["a"],
@@ -57,8 +55,7 @@ module Tap
57
55
  # # }
58
56
  # schema.joins
59
57
  # # => [
60
- # # [[1],[2]],
61
- # # [[0],[1]],
58
+ # # [[1],[2]]
62
59
  # # ]
63
60
  #
64
61
  # ==== Multi-Join Syntax
@@ -149,17 +146,17 @@ module Tap
149
146
  # Matches any breaking arg. Examples:
150
147
  #
151
148
  # --
152
- # --+
153
149
  # --1:2
154
150
  # --[1][2]
155
151
  # --[1,2,3][4,5,6]is.join
152
+ # --.middleware
156
153
  #
157
154
  # After the match:
158
155
  #
159
156
  # $1:: The string after the break
160
157
  # (ex: '--' => '', '--:' => ':', '--[1,2][3,4]is.join' => '[1,2][3,4]is.join')
161
158
  #
162
- BREAK = /\A--(\z|[\d\:\[].*\z)/
159
+ BREAK = /\A--(\z|[\d\:\[\.].*\z)/
163
160
 
164
161
  # Matches a sequence break. Examples:
165
162
  #
@@ -204,6 +201,18 @@ module Tap
204
201
  #
205
202
  JOIN_MODIFIER = /\A([A-z]*)(?:\.(.*))?\z/
206
203
 
204
+ # Matches a generic middleware break. Examples:
205
+ #
206
+ # ". middleware --flag"
207
+ # .middleware
208
+ #
209
+ # After the match:
210
+ #
211
+ # $1:: The modifier string.
212
+ # (ex: '.middleware' => 'middleware')
213
+ #
214
+ MIDDLEWARE = /\A\.(.*)\z/
215
+
207
216
  # Parses an indicies str along commas, and collects the indicies
208
217
  # as integers. Ex:
209
218
  #
@@ -258,8 +267,8 @@ module Tap
258
267
 
259
268
  # Parses the match of a JOIN regexp into a [input_indicies,
260
269
  # output_indicies, metadata] array. The inputs corresponds to $1, $2,
261
- # and $3 for a match to a JOIN regexp. A join type of 'join' is
262
- # assumed unless otherwise specified.
270
+ # and $3 for the match. A join type of 'join' is assumed unless
271
+ # otherwise specified.
263
272
  #
264
273
  # parse_join("1", "2,3", "") # => [[1], [2,3]]
265
274
  # parse_join("", "", "is.type") # => [[], [], ['type', '-i', '-s']]
@@ -288,6 +297,13 @@ module Tap
288
297
  Shellwords.shellwords(modifier)
289
298
  end
290
299
  end
300
+
301
+ # Parses the match of a MIDDLEWARE regexp into metadata array.
302
+ # The input corresponds to $1 for the match. Currently this
303
+ # method is an alias for Shellwords.shellwords.
304
+ def parse_middleware(one)
305
+ Shellwords.shellwords(one)
306
+ end
291
307
  end
292
308
 
293
309
  include Utils
@@ -309,17 +325,17 @@ module Tap
309
325
 
310
326
  # Same as parse, but removes parsed args from argv.
311
327
  def parse!(argv)
312
- @current_index = 0
313
328
  @schema = Schema.new
314
329
 
315
330
  # prevent the addition of an empty task to schema
316
- return if argv.empty?
331
+ return schema if argv.empty?
317
332
 
318
333
  argv = Shellwords.shellwords(argv) if argv.kind_of?(String)
319
- argv.unshift('--')
334
+ argv.unshift('--') unless argv[0] =~ BREAK
320
335
 
336
+ @current_index = -1
337
+ @current = nil
321
338
  escape = false
322
- current_task = nil
323
339
  while !argv.empty?
324
340
  arg = argv.shift
325
341
 
@@ -329,7 +345,7 @@ module Tap
329
345
  if arg == ESCAPE_END
330
346
  escape = false
331
347
  else
332
- (current_task ||= task(current_index)) << arg
348
+ current << arg
333
349
  end
334
350
 
335
351
  next
@@ -345,13 +361,9 @@ module Tap
345
361
  break
346
362
 
347
363
  when BREAK
348
- # a breaking argument was reached:
349
- # unless the current argv is empty,
350
- # append and start a new definition
351
- if current_task && !current_task.empty?
352
- self.current_index += 1
353
- current_task = nil
354
- end
364
+ # a breaking argument was reached
365
+ @current_index += 1
366
+ @current = nil
355
367
 
356
368
  # parse the break string for any
357
369
  # schema modifications
@@ -361,18 +373,28 @@ module Tap
361
373
  # add all other non-breaking args to
362
374
  # the current argv; this includes
363
375
  # both inputs and configurations
364
- (current_task ||= task(current_index)) << arg
376
+ current << arg
365
377
 
366
378
  end
367
379
  end
368
380
 
381
+ # determine the queue as all tasks not
382
+ # used as a join output
383
+ queue = schema.tasks.keys
384
+ schema.joins.each {|join| queue -= join[1] }
385
+ schema.queue.concat(queue)
386
+
369
387
  schema
370
388
  end
371
389
 
372
390
  protected
373
391
 
374
392
  # The index of the task currently being parsed.
375
- attr_accessor :current_index # :nodoc:
393
+ attr_reader :current_index # :nodoc:
394
+
395
+ def current
396
+ @current ||= task(current_index)
397
+ end
376
398
 
377
399
  # helper to initialize a task at the specified index
378
400
  def task(index) # :nodoc:
@@ -388,26 +410,16 @@ module Tap
388
410
  def parse_break(arg) # :nodoc:
389
411
  case arg
390
412
  when ""
391
- unless schema.queue.include?(current_index)
392
- schema.queue << current_index
393
- end
394
413
  when SEQUENCE
395
- parse_sequence($1, $2).each {|join| set_join(join) }
414
+ schema.joins.concat parse_sequence($1, $2)
396
415
  when JOIN
397
- set_join(parse_join($1, $2, $3))
416
+ schema.joins << parse_join($1, $2, $3)
417
+ when MIDDLEWARE
418
+ schema.middleware << parse_middleware($1)
398
419
  else
399
420
  raise ArgumentError, "invalid break argument: #{arg}"
400
421
  end
401
422
  end
402
-
403
- # constructs the specified join and removes the targets of the
404
- # join from the queue
405
- def set_join(join) # :nodoc:
406
- join[1].each do |output|
407
- schema.queue.delete(output)
408
- end
409
- schema.joins << join
410
- end
411
423
  end
412
424
  end
413
425
  end
@@ -1,19 +1,19 @@
1
1
  module Tap
2
2
  class Schema
3
3
  module Utils
4
- module_function
5
4
 
6
5
  def instantiate(data, app)
7
6
  case data
8
7
  when Hash then data['class'].instantiate(symbolize(data), app)
9
- when Array then data.shift.parse(data, app)
8
+ when Array then data.shift.parse!(data, app)
9
+ else raise "cannot instantiate: #{data.inspect}"
10
10
  end
11
11
  end
12
12
 
13
13
  def resolved?(data)
14
14
  case data
15
15
  when Hash then data['class'].respond_to?(:instantiate)
16
- when Array then data[0].respond_to?(:parse)
16
+ when Array then data[0].respond_to?(:parse!)
17
17
  else false
18
18
  end
19
19
  end
@@ -23,9 +23,11 @@ module Tap
23
23
 
24
24
  case data
25
25
  when Hash
26
- data['class'] = yield(data['id'], data)
26
+ unless resolved?(data)
27
+ data['class'] = yield(data['id']) || data['id']
28
+ end
27
29
  when Array
28
- data[0] = yield(data[0], data)
30
+ data[0] = yield(data[0]) || data[0]
29
31
  end
30
32
 
31
33
  data
@@ -34,8 +36,6 @@ module Tap
34
36
  # Symbolizes the keys of hash. Returns non-hash values directly and
35
37
  # raises an error in the event of a symbolize conflict.
36
38
  def symbolize(hash)
37
- return hash unless hash.kind_of?(Hash)
38
-
39
39
  result = {}
40
40
  hash.each_pair do |key, value|
41
41
  key = key.to_sym || key
@@ -49,34 +49,6 @@ module Tap
49
49
  result
50
50
  end
51
51
 
52
- # Returns the values for hash sorted by key. Returns non-hash objects
53
- # directly.
54
- def dehashify(obj)
55
- case obj
56
- when nil then []
57
- when Hash then obj.keys.sort.collect {|key| obj[key] }
58
- else obj
59
- end
60
- end
61
-
62
- # Returns obj as a hash, using the index of each element as the
63
- # key for the element. The object must respond to each. Returns
64
- # hashes directly.
65
- def hashify(obj)
66
- case obj
67
- when nil then {}
68
- when Hash then obj
69
- else
70
- index = 0
71
- hash = {}
72
- obj.each do |entry|
73
- hash[index] = entry
74
- index += 1
75
- end
76
- hash
77
- end
78
- end
79
-
80
52
  end
81
53
  end
82
54
  end
@@ -156,8 +156,7 @@ module Tap
156
156
  instance
157
157
  end
158
158
 
159
- # Parses the argv into an instance of self and an array of arguments
160
- # (implicitly to be enqued to the instance). By default parse
159
+ # Parses the argv into an instance of self. By default parse
161
160
  # parses an argh then calls instantiate, but there is no requirement
162
161
  # that this occurs in subclasses.
163
162
  def parse(argv=ARGV, app=Tap::App.instance)
@@ -197,12 +196,8 @@ module Tap
197
196
  # (note defaults are not added so they will not
198
197
  # conflict with string keys from a config file)
199
198
  argv = opts.parse!(argv, :add_defaults => false)
200
- argh = {
201
- :config => opts.nested_config,
202
- :args => argv
203
- }
204
199
 
205
- instantiate(argh, app)
200
+ instantiate({:config => opts.nested_config}, app)
206
201
  end
207
202
 
208
203
  # Instantiates an instance of self and returns an instance of self and
@@ -219,7 +214,7 @@ module Tap
219
214
  app.cache[self] = instance
220
215
  end
221
216
 
222
- [instance, argh[:args]]
217
+ instance
223
218
  end
224
219
 
225
220
  DEFAULT_HELP_TEMPLATE = %Q{<% desc = task_class::desc %>
@@ -30,7 +30,7 @@ module Tap
30
30
  # Dump serves as a baseclass for more complicated dumps. A YAML dump
31
31
  # (see {tap-tasks}[http://tap.rubyforge.org/tap-tasks]) looks like this:
32
32
  #
33
- # class Yaml < Tap::Dump
33
+ # class Yaml < Tap::Tasks::Dump
34
34
  # def dump(obj, io)
35
35
  # YAML.dump(obj, io)
36
36
  # end
@@ -24,39 +24,97 @@ module Tap
24
24
  # Load serves as a baseclass for more complicated loads. A YAML load
25
25
  # (see {tap-tasks}[http://tap.rubyforge.org/tap-tasks]) looks like this:
26
26
  #
27
- # class Yaml < Tap::Load
27
+ # class Yaml < Tap::Tasks::Load
28
28
  # def load(io)
29
29
  # YAML.load(io)
30
30
  # end
31
31
  # end
32
32
  #
33
+ # Load is constructed to reque itself in cases where objects are to
34
+ # be loaded sequentially from the same io. Load will reque until the
35
+ # end-of-file is reached, but this behavior can be modified by
36
+ # overriding the complete? method. An example is a prompt task:
37
+ #
38
+ # class Prompt < Tap::Tasks::Load
39
+ # config :exit_seq, "\n"
40
+ #
41
+ # def load(io)
42
+ # if io.eof?
43
+ # nil
44
+ # else
45
+ # io.readline
46
+ # end
47
+ # end
48
+ #
49
+ # def complete?(io, line)
50
+ # line == nil || line == exit_seq
51
+ # end
52
+ # end
53
+ #
54
+ # If the use_close configuration is specified, load will close io upon
55
+ # completion. Files opened by load are always closed upon completion.
56
+ #
33
57
  class Load < Tap::Task
34
58
 
35
- config :file, false, &c.flag # opens the input as a file
59
+ config :file, false, &c.flag # Opens the input as a file
60
+ config :use_close, false, :long => :close, &c.flag # Close the input when complete
36
61
 
37
- # The default process simply reads the input data and returns it.
38
- # See load.
62
+ # Loads data from io. Process will open the input io object, load
63
+ # a result, then check to see if the loading is complete (using the
64
+ # complete? method). Unless loading is complete, process will enque
65
+ # io to self. Process will close io when loading is complete, provided
66
+ # use_close or file is specified.
39
67
  def process(io=$stdin)
40
- # read on an empty stdin ties up the command line;
41
- # this facilitates the intended behavior
42
- if io.kind_of?(IO) && io.stat.size == 0
43
- io = ''
68
+ io = open(io)
69
+ result = load(io)
70
+
71
+ if complete?(io, result)
72
+ if use_close || file
73
+ close(io)
74
+ end
75
+ else
76
+ enq(io)
44
77
  end
78
+
79
+ result
80
+ end
45
81
 
46
- if io.kind_of?(String)
47
- io = StringIO.new(io)
48
- end unless file
49
-
50
- open_io(io) do |data|
51
- load(data)
82
+ # Opens the io; specifically this means:
83
+ #
84
+ # * Opening a File (file true)
85
+ # * Creating a StringIO for String inputs
86
+ # * Opening an IO for integer file descriptors
87
+ # * Returning all other objects
88
+ #
89
+ def open(io)
90
+ return File.open(io) if file
91
+
92
+ case io
93
+ when String
94
+ StringIO.new(io)
95
+ when Integer
96
+ IO.open(io)
97
+ else
98
+ io
52
99
  end
53
100
  end
54
-
55
- # Loads data from the io; the return of load is the return of process. By
56
- # default load simply reads data from io.
101
+
102
+ # Loads data from io using io.read. Load is intended as a hook
103
+ # for subclasses.
57
104
  def load(io)
58
105
  io.read
59
106
  end
107
+
108
+ # Closes io.
109
+ def close(io)
110
+ io.close
111
+ end
112
+
113
+ # Returns io.eof? Override in subclasses for the desired behavior
114
+ # (see process).
115
+ def complete?(io, last)
116
+ io.eof?
117
+ end
60
118
  end
61
119
  end
62
120
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.17.0
4
+ version: 0.17.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simon Chiang
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-05-25 00:00:00 -06:00
12
+ date: 2009-06-06 00:00:00 -06:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency