tap 0.17.0 → 0.17.1
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.
- data/History +9 -0
- data/README +1 -1
- data/cmd/run.rb +20 -24
- data/doc/API +3 -3
- data/lib/tap/app.rb +31 -20
- data/lib/tap/app/stack.rb +9 -1
- data/lib/tap/constants.rb +1 -1
- data/lib/tap/exe.rb +1 -29
- data/lib/tap/schema.rb +40 -82
- data/lib/tap/schema/parser.rb +54 -42
- data/lib/tap/schema/utils.rb +7 -35
- data/lib/tap/task.rb +3 -8
- data/lib/tap/tasks/dump.rb +1 -1
- data/lib/tap/tasks/load.rb +75 -17
- metadata +2 -2
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}[
|
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 -
|
6
|
-
# tap run -
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
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
|
|
data/lib/tap/app.rb
CHANGED
@@ -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
|
-
|
319
|
-
|
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
|
-
|
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)
|
data/lib/tap/app/stack.rb
CHANGED
@@ -4,11 +4,19 @@ module Tap
|
|
4
4
|
# The base of the application call stack.
|
5
5
|
class Stack
|
6
6
|
|
7
|
-
#
|
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
|
data/lib/tap/constants.rb
CHANGED
data/lib/tap/exe.rb
CHANGED
@@ -94,20 +94,7 @@ module Tap
|
|
94
94
|
end
|
95
95
|
end
|
96
96
|
|
97
|
-
def
|
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
|
data/lib/tap/schema.rb
CHANGED
@@ -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.
|
42
|
-
#
|
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]
|
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 =
|
55
|
-
|
56
|
-
@
|
57
|
-
|
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.
|
62
|
+
tasks.each_pair do |key, task|
|
70
63
|
task ||= {}
|
71
|
-
tasks[key] = resolve(task) do |id
|
72
|
-
yield(:task, id || key
|
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
|
79
|
-
yield(:join, id || 'join'
|
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
|
86
|
-
yield(:middleware, id
|
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.
|
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
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
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}
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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,
|
data/lib/tap/schema/parser.rb
CHANGED
@@ -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
|
34
|
-
#
|
35
|
-
#
|
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 --
|
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
|
-
# # [[
|
48
|
-
# # [[1],[2]],
|
46
|
+
# # [[1],[2]]
|
49
47
|
# # ]
|
50
48
|
#
|
51
|
-
# schema = Parser.new("a --1:2
|
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
|
262
|
-
#
|
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
|
-
|
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
|
-
|
350
|
-
|
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
|
-
|
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
|
-
|
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)
|
414
|
+
schema.joins.concat parse_sequence($1, $2)
|
396
415
|
when JOIN
|
397
|
-
|
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
|
data/lib/tap/schema/utils.rb
CHANGED
@@ -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
|
-
|
26
|
+
unless resolved?(data)
|
27
|
+
data['class'] = yield(data['id']) || data['id']
|
28
|
+
end
|
27
29
|
when Array
|
28
|
-
data[0] = yield(data[0]
|
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
|
data/lib/tap/task.rb
CHANGED
@@ -156,8 +156,7 @@ module Tap
|
|
156
156
|
instance
|
157
157
|
end
|
158
158
|
|
159
|
-
# Parses the argv into an instance of self
|
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(
|
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
|
-
|
217
|
+
instance
|
223
218
|
end
|
224
219
|
|
225
220
|
DEFAULT_HELP_TEMPLATE = %Q{<% desc = task_class::desc %>
|
data/lib/tap/tasks/dump.rb
CHANGED
@@ -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
|
data/lib/tap/tasks/load.rb
CHANGED
@@ -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
|
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
|
-
#
|
38
|
-
#
|
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
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
56
|
-
#
|
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.
|
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-
|
12
|
+
date: 2009-06-06 00:00:00 -06:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|