bahuvrihi-tap 0.10.2 → 0.10.3
Sign up to get free protection for your applications and to get access to all the features.
- data/cgi/run.rb +36 -0
- data/cmd/run.rb +4 -3
- data/cmd/server.rb +36 -0
- data/lib/tap/app.rb +1 -1
- data/lib/tap/constants.rb +1 -1
- data/lib/tap/env.rb +8 -2
- data/lib/tap/exe.rb +1 -57
- data/lib/tap/support/declarations.rb +73 -51
- data/lib/tap/support/executable.rb +81 -2
- data/lib/tap/support/gems/rack.rb +151 -0
- data/lib/tap/support/parsers/base.rb +81 -0
- data/lib/tap/support/parsers/command_line.rb +90 -0
- data/lib/tap/support/parsers/server.rb +83 -0
- data/lib/tap/task.rb +327 -7
- data/lib/tap/workflow.rb +9 -48
- data/template/404.erb +12 -0
- data/template/index.erb +41 -0
- data/vendor/url_encoded_pair_parser.rb +93 -0
- metadata +10 -4
- data/lib/tap/support/command_line/parser.rb +0 -121
- data/lib/tap/support/framework.rb +0 -83
- data/lib/tap/support/framework_class.rb +0 -182
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'cgi'
|
3
|
+
|
4
|
+
module Tap
|
5
|
+
module Support
|
6
|
+
module Gems
|
7
|
+
|
8
|
+
Tap::Env.manifest(:public_paths, "public") do |search_path|
|
9
|
+
Dir.glob(File.join(search_path, "**/*")).collect do |path|
|
10
|
+
["/" + Tap::Root.relative_filepath(search_path, path), path]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
Tap::Env.manifest(:templates, "template") do |search_path|
|
15
|
+
Dir.glob(File.join(search_path, "**/*.erb")).collect do |path|
|
16
|
+
["/" + Tap::Root.relative_filepath(search_path, path), path]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
Tap::Env.manifest(:cgis, "cgi") do |search_path|
|
21
|
+
Dir.glob(File.join(search_path, "**/*.rb")).collect do |path|
|
22
|
+
["/" + Tap::Root.relative_filepath(search_path, path), path]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module Rack
|
27
|
+
def call(env)
|
28
|
+
path = env['PATH_INFO']
|
29
|
+
|
30
|
+
case
|
31
|
+
when public_page = search(:public_paths, path)
|
32
|
+
# serve named static pages
|
33
|
+
response(env) { File.read(public_page) }
|
34
|
+
|
35
|
+
when cgi_page = search(:cgis, path)
|
36
|
+
# serve cgis relative to a cgi path
|
37
|
+
run_cgi(cgi_page, env)
|
38
|
+
|
39
|
+
when path == "/" || path == "/index"
|
40
|
+
# serve up the homepage
|
41
|
+
if env["QUERY_STRING"] == "refresh=true"
|
42
|
+
reset(:cgis) do |key, path|
|
43
|
+
Support::Lazydoc[path].resolved = false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
template_response('index', env)
|
47
|
+
|
48
|
+
else
|
49
|
+
# handle all other requests as errors
|
50
|
+
template_response('404', env)
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
#--
|
56
|
+
# Runs a cgi and returns an array as demanded by rack.
|
57
|
+
def run_cgi(cgi_path, env)
|
58
|
+
current_output = $>
|
59
|
+
cgi_output = StringIO.new("")
|
60
|
+
|
61
|
+
begin
|
62
|
+
$> = cgi_output
|
63
|
+
|
64
|
+
with_env(env) { load(cgi_path) }
|
65
|
+
|
66
|
+
# collect the headers and body from the cgi output
|
67
|
+
headers, body = cgi_output.string.split(/\r?\n\r?\n/, 2)
|
68
|
+
|
69
|
+
raise "missing headers from: #{cgi_path}" if headers == nil
|
70
|
+
body = "" if body == nil
|
71
|
+
|
72
|
+
headers = headers.split(/\r?\n/).inject({}) do |hash, line|
|
73
|
+
key, value = line.split(/:/, 2)
|
74
|
+
hash[key] = value
|
75
|
+
hash
|
76
|
+
end
|
77
|
+
|
78
|
+
[headers.delete('Status') || 200, headers, body]
|
79
|
+
rescue(Exception)
|
80
|
+
# when an error occurs, return a standard cgi error with backtrace
|
81
|
+
[500, {'Content-Type' => 'text/plain'}, %Q{#{$!.class}: #{$!.message}\n#{$!.backtrace.join("\n")}}]
|
82
|
+
ensure
|
83
|
+
$> = current_output
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Executes block with ENV set to the specified hash. Non-string env variables are not set.
|
88
|
+
def with_env(env)
|
89
|
+
current_env = {}
|
90
|
+
ENV.each_pair {|key, value| current_env[key] = value }
|
91
|
+
|
92
|
+
begin
|
93
|
+
ENV.clear
|
94
|
+
env.each_pair {|key, value| ENV[key] = value if value.kind_of?(String)}
|
95
|
+
|
96
|
+
yield
|
97
|
+
ensure
|
98
|
+
ENV.clear
|
99
|
+
current_env.each_pair {|key, value| ENV[key] = value }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
DEFAULT_ERROR_TEMPLATE = %Q{
|
104
|
+
<html>
|
105
|
+
<body>
|
106
|
+
# Error handling request: <%= error.message %></br>
|
107
|
+
# <%= error.backtrace.join("<br/># ") %>
|
108
|
+
|
109
|
+
<code><pre>
|
110
|
+
<%= cgi.to_yaml %>
|
111
|
+
<%= rack.to_yaml %>
|
112
|
+
</pre></code>
|
113
|
+
</body>
|
114
|
+
</html>
|
115
|
+
}
|
116
|
+
|
117
|
+
def response(env)
|
118
|
+
::Rack::Response.new.finish do |res|
|
119
|
+
res.write begin
|
120
|
+
yield(res)
|
121
|
+
rescue
|
122
|
+
template(DEFAULT_ERROR_TEMPLATE, env, :error => $!)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def template(template, env, attributes={})
|
128
|
+
# partition and sort the env variables into
|
129
|
+
# cgi and rack variables.
|
130
|
+
rack, cgi = env.to_a.partition do |(key, value)|
|
131
|
+
key =~ /^rack/
|
132
|
+
end.collect do |part|
|
133
|
+
part.sort_by do |key, value|
|
134
|
+
key
|
135
|
+
end.inject({}) do |hash, (key,value)|
|
136
|
+
hash[key] = value
|
137
|
+
hash
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
Templater.new( template , {:server => self, :env => env, :cgi => cgi, :rack => rack}.merge(attributes) ).build
|
142
|
+
end
|
143
|
+
|
144
|
+
def template_response(name, env)
|
145
|
+
path = search(:templates, name)
|
146
|
+
response(env) { template(File.read(path), env) }
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Tap
|
2
|
+
module Support
|
3
|
+
module Parsers
|
4
|
+
class Base
|
5
|
+
class << self
|
6
|
+
# Parses the input string as YAML, if the string matches the YAML document
|
7
|
+
# specifier (ie it begins with "---\s*\n"). Otherwise returns the string.
|
8
|
+
#
|
9
|
+
# str = {'key' => 'value'}.to_yaml # => "--- \nkey: value\n"
|
10
|
+
# Tap::Script.parse_yaml(str) # => {'key' => 'value'}
|
11
|
+
# Tap::Script.parse_yaml("str") # => "str"
|
12
|
+
def parse_yaml(str)
|
13
|
+
str =~ /\A---\s*\n/ ? YAML.load(str) : str
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :argvs
|
18
|
+
attr_reader :rounds
|
19
|
+
attr_reader :sequences
|
20
|
+
attr_reader :forks
|
21
|
+
attr_reader :merges
|
22
|
+
attr_reader :sync_merges
|
23
|
+
|
24
|
+
def build(env, app)
|
25
|
+
# attempt lookup and instantiate the task class
|
26
|
+
task_declarations = argvs.collect do |argv|
|
27
|
+
pattern = argv.shift
|
28
|
+
|
29
|
+
const = env.search(:tasks, pattern) or raise ArgumentError, "unknown task: #{pattern}"
|
30
|
+
task_class = const.constantize or raise ArgumentError, "unknown task: #{pattern}"
|
31
|
+
task_class.instantiate(argv, app)
|
32
|
+
end
|
33
|
+
|
34
|
+
# remove tasks used by the workflow
|
35
|
+
tasks = targets.collect do |index|
|
36
|
+
task, args = task_declarations[index]
|
37
|
+
|
38
|
+
unless args.empty?
|
39
|
+
raise ArgumentError, "workflow target receives args: #{task} [#{args.join(', ')}]"
|
40
|
+
end
|
41
|
+
|
42
|
+
tasks[index] = nil
|
43
|
+
task
|
44
|
+
end
|
45
|
+
|
46
|
+
# build the workflow
|
47
|
+
[:sequence, :fork, :merge, :sync_merge].each do |type|
|
48
|
+
send("#{type}s").each do |source, targets|
|
49
|
+
source.send(type, *targets.collect {|t| tasks[t] })
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# build queues
|
54
|
+
queues = rounds.collect do |round|
|
55
|
+
round.each do |index|
|
56
|
+
task, args = task_declarations[index]
|
57
|
+
task.enq(*args) if task
|
58
|
+
end
|
59
|
+
|
60
|
+
app.queue.clear
|
61
|
+
end
|
62
|
+
queues.delete_if {|queue| queue.empty? }
|
63
|
+
|
64
|
+
queues
|
65
|
+
end
|
66
|
+
|
67
|
+
protected
|
68
|
+
|
69
|
+
def targets
|
70
|
+
results = sequences.collect {|source, targets| targets } +
|
71
|
+
forks.collect {|source, targets| targets } +
|
72
|
+
merges.collect {|target, sources| target } +
|
73
|
+
sync_merges.collect {|target, sources| target }
|
74
|
+
|
75
|
+
results.flatten.uniq.sort
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'tap/support/parsers/base'
|
2
|
+
|
3
|
+
module Tap
|
4
|
+
module Support
|
5
|
+
module Parsers
|
6
|
+
class CommandLine < Base
|
7
|
+
class << self
|
8
|
+
def parse_sequence(str, count=0)
|
9
|
+
seq = []
|
10
|
+
seq << count if str[0] == ?:
|
11
|
+
str.split(/:+/).each do |n|
|
12
|
+
seq << n.to_i unless n.empty?
|
13
|
+
end
|
14
|
+
seq << count + 1 if str[-1] == ?:
|
15
|
+
[seq.shift, seq]
|
16
|
+
end
|
17
|
+
|
18
|
+
def pairs_regexp(l, r)
|
19
|
+
/\A--(\d*)#{Regexp.escape(l)}([\d,]*)#{Regexp.escape(r)}\z/
|
20
|
+
end
|
21
|
+
|
22
|
+
def parse_pairs(lead, str, count=0)
|
23
|
+
bracket = []
|
24
|
+
str.split(/,+/).each do |n|
|
25
|
+
bracket << n.to_i unless n.empty?
|
26
|
+
end
|
27
|
+
|
28
|
+
[lead.empty? ? count : lead.to_i, bracket]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
ROUND = /\A--(\+(\d+)|\+*)\z/
|
33
|
+
SEQUENCE = /\A--(\d*(:\d*)+)\z/
|
34
|
+
FORK = pairs_regexp("[", "]")
|
35
|
+
MERGE = pairs_regexp("{", "}")
|
36
|
+
SYNC_MERGE = pairs_regexp("(", ")")
|
37
|
+
INVALID = /\A--(\z|[^A-Za-z])/
|
38
|
+
|
39
|
+
def initialize(argv)
|
40
|
+
@sequences = []
|
41
|
+
@forks = []
|
42
|
+
@merges = []
|
43
|
+
@sync_merges = []
|
44
|
+
|
45
|
+
current = []
|
46
|
+
current_round = []
|
47
|
+
@argvs = []
|
48
|
+
@rounds = [current_round]
|
49
|
+
|
50
|
+
argv.each do |arg|
|
51
|
+
unless arg =~ INVALID
|
52
|
+
current << arg
|
53
|
+
next
|
54
|
+
end
|
55
|
+
|
56
|
+
# for peformance split to match
|
57
|
+
# most arguments just once.
|
58
|
+
unless current.empty?
|
59
|
+
current_round << @argvs.length
|
60
|
+
@argvs << current
|
61
|
+
current = []
|
62
|
+
end
|
63
|
+
|
64
|
+
case arg
|
65
|
+
when ROUND
|
66
|
+
current_round = (@rounds[$2 ? $2.to_i : $1.length] ||= [])
|
67
|
+
when SEQUENCE
|
68
|
+
@sequences << CommandLine.parse_sequence($1, @argvs.length-1)
|
69
|
+
when FORK
|
70
|
+
@forks << CommandLine.parse_pairs($1, $2, @argvs.length-1)
|
71
|
+
when MERGE
|
72
|
+
@merges << CommandLine.parse_pairs($1, $2, @argvs.length-1)
|
73
|
+
when SYNC_MERGE
|
74
|
+
@sync_merges << CommandLine.parse_pairs($1, $2, @argvs.length-1)
|
75
|
+
else
|
76
|
+
raise ArgumentError, "invalid argument: #{arg}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
unless current.empty?
|
81
|
+
current_round << @argvs.length
|
82
|
+
@argvs << current
|
83
|
+
end
|
84
|
+
@rounds.delete_if {|round| round.nil? || round.empty? }
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'tap/support/parsers/base'
|
2
|
+
|
3
|
+
module Tap
|
4
|
+
module Support
|
5
|
+
module Parsers
|
6
|
+
|
7
|
+
# rounds syntax
|
8
|
+
# 0[task]=dump&0[config][key]=value&0[input][]=a&0[input][]=b
|
9
|
+
# sequence[1]=1,2,3
|
10
|
+
# fork[1]=2,3
|
11
|
+
# round[0]=1,2,3
|
12
|
+
# ....
|
13
|
+
|
14
|
+
class Server < Base
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def parse_argv(hash)
|
18
|
+
raise ArgumentError, "no task specified" unless hash.has_key?('task')
|
19
|
+
|
20
|
+
# parse task
|
21
|
+
argv = [hash['task']]
|
22
|
+
|
23
|
+
# parse configs
|
24
|
+
configs = hash['config']
|
25
|
+
configs = YAML.load(configs) if configs.kind_of?(String)
|
26
|
+
|
27
|
+
case configs
|
28
|
+
when Hash
|
29
|
+
configs.each_pair do |key, value|
|
30
|
+
argv << "--#{key}"
|
31
|
+
argv << value
|
32
|
+
end
|
33
|
+
when nil
|
34
|
+
else raise ArgumentError, "non-hash configs specified: #{configs}"
|
35
|
+
end
|
36
|
+
|
37
|
+
# parse inputs
|
38
|
+
inputs = hash['inputs']
|
39
|
+
inputs = YAML.load(inputs) if inputs.kind_of?(String)
|
40
|
+
|
41
|
+
case inputs
|
42
|
+
when Array then argv.concat(inputs)
|
43
|
+
when nil
|
44
|
+
else raise ArgumentError, "non-array inputs specified: #{inputs}"
|
45
|
+
end
|
46
|
+
|
47
|
+
argv
|
48
|
+
end
|
49
|
+
|
50
|
+
def parse_pairs(values)
|
51
|
+
[*values].collect do |value|
|
52
|
+
value.split(',').collect {|i| i.to_i }
|
53
|
+
end.collect do |split|
|
54
|
+
next if split.empty?
|
55
|
+
[split.shift, split]
|
56
|
+
end.compact
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
INDEX = /\A\d+\z/
|
61
|
+
|
62
|
+
def initialize(argh)
|
63
|
+
@argvs = []
|
64
|
+
|
65
|
+
argh.each_pair do |key, value|
|
66
|
+
case key
|
67
|
+
when INDEX
|
68
|
+
argvs[key.to_i] = Server.parse_argv(value)
|
69
|
+
else
|
70
|
+
instance_variable_set("@#{key}s", Server.parse_pairs(value))
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
@rounds ||= []
|
75
|
+
@sequences ||= []
|
76
|
+
@forks ||= []
|
77
|
+
@merges ||= []
|
78
|
+
@sync_merges ||= []
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/tap/task.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
require 'tap/support/
|
1
|
+
require 'tap/support/batchable'
|
2
|
+
require 'tap/support/executable'
|
3
|
+
require 'tap/support/command_line'
|
2
4
|
|
3
5
|
module Tap
|
4
6
|
|
@@ -154,19 +156,309 @@ module Tap
|
|
154
156
|
# t1.array == t2.array # => true
|
155
157
|
# t1.array.object_id == t2.array.object_id # => false
|
156
158
|
#
|
157
|
-
class Task
|
159
|
+
class Task
|
160
|
+
include Support::Batchable
|
161
|
+
include Support::Configurable
|
158
162
|
include Support::Executable
|
159
|
-
include Support::Framework
|
160
163
|
|
161
|
-
|
164
|
+
class << self
|
165
|
+
# Returns the default name for the class: to_s.underscore
|
166
|
+
attr_accessor :default_name
|
167
|
+
|
168
|
+
# Returns class dependencies
|
169
|
+
attr_reader :dependencies
|
170
|
+
|
171
|
+
def inherited(child)
|
172
|
+
unless child.instance_variable_defined?(:@source_file)
|
173
|
+
caller.first =~ Support::Lazydoc::CALLER_REGEXP
|
174
|
+
child.instance_variable_set(:@source_file, File.expand_path($1))
|
175
|
+
end
|
176
|
+
|
177
|
+
child.instance_variable_set(:@default_name, child.to_s.underscore)
|
178
|
+
child.instance_variable_set(:@dependencies, dependencies.dup)
|
179
|
+
super
|
180
|
+
end
|
181
|
+
|
182
|
+
def instance
|
183
|
+
@instance ||= new
|
184
|
+
end
|
185
|
+
|
186
|
+
# Generates or updates the specified subclass of self.
|
187
|
+
def subclass(const_name, configs={}, dependencies=[], options={}, &block)
|
188
|
+
#
|
189
|
+
# Lookup or create the subclass constant.
|
190
|
+
#
|
191
|
+
|
192
|
+
current, constants = const_name.to_s.constants_split
|
193
|
+
subclass = if constants.empty?
|
194
|
+
# The constant exists; validate the constant is a subclass of self.
|
195
|
+
unless current.kind_of?(Class) && current.ancestors.include?(self)
|
196
|
+
raise ArgumentError, "#{current} is already defined and is not a subclass of #{self}!"
|
197
|
+
end
|
198
|
+
current
|
199
|
+
else
|
200
|
+
# Generate the nesting module
|
201
|
+
subclass_const = constants.pop
|
202
|
+
constants.each {|const| current = current.const_set(const, Module.new)}
|
203
|
+
|
204
|
+
# Create and set the subclass constant
|
205
|
+
current.const_set(subclass_const, Class.new(self))
|
206
|
+
end
|
207
|
+
|
208
|
+
#
|
209
|
+
# Define the subclass
|
210
|
+
#
|
211
|
+
|
212
|
+
subclass.define_configurations(configs)
|
213
|
+
subclass.define_dependencies(dependencies)
|
214
|
+
subclass.define_process(block) if block_given?
|
215
|
+
|
216
|
+
#
|
217
|
+
# Register documentation
|
218
|
+
#
|
219
|
+
|
220
|
+
const_name = current == Object ? subclass_const : "#{current}::#{subclass_const}"
|
221
|
+
caller.each_with_index do |line, index|
|
222
|
+
case line
|
223
|
+
when /\/tap\/support\/declarations.rb/ then next
|
224
|
+
when Support::Lazydoc::CALLER_REGEXP
|
225
|
+
subclass.source_file = File.expand_path($1)
|
226
|
+
lzd = subclass.lazydoc(false)
|
227
|
+
lzd[const_name, false]['manifest'] = lzd.register($3.to_i - 1)
|
228
|
+
break
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
arity = options[:arity] || (block_given? ? block.arity : -1)
|
233
|
+
comment = Support::Comment.new
|
234
|
+
comment.subject = case
|
235
|
+
when arity > 0
|
236
|
+
Array.new(arity, "INPUT").join(' ')
|
237
|
+
when arity < 0
|
238
|
+
array = Array.new(-1 * arity - 1, "INPUT")
|
239
|
+
array << "INPUTS..."
|
240
|
+
array.join(' ')
|
241
|
+
else ""
|
242
|
+
end
|
243
|
+
subclass.lazydoc(false)[const_name, false]['args'] ||= comment
|
244
|
+
|
245
|
+
subclass.default_name = const_name.underscore
|
246
|
+
subclass
|
247
|
+
end
|
248
|
+
|
249
|
+
def instantiate(argv, app=Tap::App.instance) # => instance, argv
|
250
|
+
opts = OptionParser.new
|
251
|
+
|
252
|
+
# Add configurations
|
253
|
+
config = {}
|
254
|
+
unless configurations.empty?
|
255
|
+
opts.separator ""
|
256
|
+
opts.separator "configurations:"
|
257
|
+
end
|
258
|
+
|
259
|
+
configurations.each do |receiver, key, configuration|
|
260
|
+
opts.on(*Support::CommandLine.configv(configuration)) do |value|
|
261
|
+
config[key] = value
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
# Add options on_tail, giving priority to configurations
|
266
|
+
opts.separator ""
|
267
|
+
opts.separator "options:"
|
268
|
+
|
269
|
+
opts.on_tail("-h", "--help", "Print this help") do
|
270
|
+
opts.banner = "#{help}usage: tap run -- #{to_s.underscore} #{args.subject}"
|
271
|
+
puts opts
|
272
|
+
exit
|
273
|
+
end
|
274
|
+
|
275
|
+
# Add option for name
|
276
|
+
name = default_name
|
277
|
+
opts.on_tail('--name NAME', /^[^-].*/, 'Specify a name') do |value|
|
278
|
+
name = value
|
279
|
+
end
|
280
|
+
|
281
|
+
# Add option to add args
|
282
|
+
use_args = []
|
283
|
+
opts.on_tail('--use FILE', /^[^-].*/, 'Loads inputs from file') do |value|
|
284
|
+
obj = YAML.load_file(value)
|
285
|
+
case obj
|
286
|
+
when Hash
|
287
|
+
obj.values.each do |array|
|
288
|
+
# error if value isn't an array
|
289
|
+
use_args.concat(array)
|
290
|
+
end
|
291
|
+
when Array
|
292
|
+
use_args.concat(obj)
|
293
|
+
else
|
294
|
+
use_args << obj
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
opts.parse!(argv)
|
299
|
+
obj = new({}, name, app)
|
300
|
+
|
301
|
+
path_configs = load_config(app.config_filepath(name))
|
302
|
+
if path_configs.kind_of?(Array)
|
303
|
+
path_configs.each_with_index do |path_config, i|
|
304
|
+
obj.initialize_batch_obj(path_config, "#{name}_#{i}") unless i == 0
|
305
|
+
end
|
306
|
+
path_configs = path_configs[0]
|
307
|
+
end
|
308
|
+
|
309
|
+
argv = (argv + use_args).collect {|str| str =~ /\A---\s*\n/ ? YAML.load(str) : str }
|
310
|
+
|
311
|
+
[obj.reconfigure(path_configs).reconfigure(config), argv]
|
312
|
+
end
|
313
|
+
|
314
|
+
def lazydoc(resolve=true)
|
315
|
+
lazydoc = super(false)
|
316
|
+
lazydoc.register_method_pattern('args', :process) unless lazydoc.resolved?
|
317
|
+
super
|
318
|
+
end
|
319
|
+
|
320
|
+
DEFAULT_HELP_TEMPLATE = %Q{<% manifest = task_class.manifest %>
|
321
|
+
<%= task_class %><%= manifest.subject.to_s.strip.empty? ? '' : ' -- ' %><%= manifest.subject %>
|
322
|
+
|
323
|
+
<% unless manifest.empty? %>
|
324
|
+
<%= '-' * 80 %>
|
325
|
+
|
326
|
+
<% manifest.wrap(77, 2, nil).each do |line| %>
|
327
|
+
<%= line %>
|
328
|
+
<% end %>
|
329
|
+
<%= '-' * 80 %>
|
330
|
+
<% end %>
|
331
|
+
|
332
|
+
}
|
333
|
+
def help
|
334
|
+
Tap::Support::Templater.new(DEFAULT_HELP_TEMPLATE, :task_class => self).build
|
335
|
+
end
|
336
|
+
|
337
|
+
def depends_on(dependency_class, *args)
|
338
|
+
unless dependency_class.respond_to?(:instance)
|
339
|
+
raise ArgumentError, "dependency_class does not respond to instance: #{dependency_class}"
|
340
|
+
end
|
341
|
+
(dependencies << [dependency_class, args]).uniq!
|
342
|
+
self
|
343
|
+
end
|
344
|
+
|
345
|
+
protected
|
346
|
+
|
347
|
+
def dependency(name, dependency_class, *args)
|
348
|
+
depends_on(dependency_class, *args)
|
349
|
+
|
350
|
+
define_method(name) do
|
351
|
+
index = Support::Executable.index(dependency_class.instance, args)
|
352
|
+
Support::Executable.results[index]._current
|
353
|
+
end
|
354
|
+
|
355
|
+
public(name)
|
356
|
+
end
|
357
|
+
|
358
|
+
def define(name, klass=Tap::Task, &block)
|
359
|
+
instance_var = "@#{name}".to_sym
|
360
|
+
|
361
|
+
define_method(name) do |*args|
|
362
|
+
raise ArgumentError, "wrong number of arguments (#{args.length} for 1)" if args.length > 1
|
363
|
+
|
364
|
+
instance_name = args[0] || name
|
365
|
+
instance_variable_set(instance_var, {}) unless instance_variable_defined?(instance_var)
|
366
|
+
instance_variable_get(instance_var)[instance_name] ||= config_task(instance_name, klass, &block)
|
367
|
+
end
|
368
|
+
|
369
|
+
define_method("#{name}=") do |input|
|
370
|
+
input = {name => input} unless input.kind_of?(Hash)
|
371
|
+
instance_variable_set(instance_var, input)
|
372
|
+
end
|
373
|
+
|
374
|
+
public(name, "#{name}=")
|
375
|
+
end
|
376
|
+
|
377
|
+
def define_configurations(configs)
|
378
|
+
case configs
|
379
|
+
when Hash
|
380
|
+
# hash configs are simply added as default configurations
|
381
|
+
attr_accessor(*configs.keys)
|
382
|
+
configs.each_pair do |key, value|
|
383
|
+
configurations.add(key, value)
|
384
|
+
end
|
385
|
+
public(*configs.keys)
|
386
|
+
when Array
|
387
|
+
# array configs define configuration methods
|
388
|
+
configs.each do |method, key, value, opts, config_block|
|
389
|
+
send(method, key, value, opts, &config_block)
|
390
|
+
end
|
391
|
+
else
|
392
|
+
raise ArgumentError, "cannot define configurations from: #{configs}"
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
def define_dependencies(dependencies)
|
397
|
+
dependencies.each do |name, dependency_class, *args|
|
398
|
+
dependency(name, dependency_class, *args)
|
399
|
+
end if dependencies
|
400
|
+
end
|
401
|
+
|
402
|
+
def define_process(block)
|
403
|
+
send(:define_method, :process, &block)
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
instance_variable_set(:@source_file, __FILE__)
|
408
|
+
instance_variable_set(:@default_name, 'tap/task')
|
409
|
+
instance_variable_set(:@dependencies, [])
|
410
|
+
lazy_attr :manifest
|
411
|
+
lazy_attr :args
|
412
|
+
|
413
|
+
# The application used to load config_file templates
|
414
|
+
# (and hence, to initialize batched objects).
|
415
|
+
attr_reader :app
|
162
416
|
|
417
|
+
# The name of self.
|
418
|
+
#--
|
419
|
+
# Currently names may be any object. Audit makes use of name
|
420
|
+
# via to_s, as does app when figuring configuration filepaths.
|
421
|
+
attr_accessor :name
|
422
|
+
|
423
|
+
# The task block provided during initialization.
|
424
|
+
attr_reader :task_block
|
425
|
+
|
426
|
+
# Initializes a new instance and associated batch objects. Batch
|
427
|
+
# objects will be initialized for each configuration template
|
428
|
+
# specified by app.each_config_template(config_file) where
|
429
|
+
# config_file = app.config_filepath(name).
|
163
430
|
def initialize(config={}, name=nil, app=App.instance, &task_block)
|
164
|
-
super(
|
431
|
+
super()
|
165
432
|
|
433
|
+
@app = app
|
434
|
+
@name = name || self.class.default_name
|
166
435
|
@task_block = (task_block == nil ? default_task_block : task_block)
|
436
|
+
|
437
|
+
@_method_name = :execute
|
167
438
|
@multithread = false
|
168
439
|
@on_complete_block = nil
|
169
|
-
@
|
440
|
+
@dependencies = []
|
441
|
+
|
442
|
+
case config
|
443
|
+
when Support::InstanceConfiguration
|
444
|
+
@config = config
|
445
|
+
config.bind(self)
|
446
|
+
else
|
447
|
+
initialize_config(config)
|
448
|
+
end
|
449
|
+
|
450
|
+
self.class.dependencies.each do |task_class, args|
|
451
|
+
depends_on(task_class.instance, *args)
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
455
|
+
# Creates a new batched object and adds the object to batch. The batched object
|
456
|
+
# will be a duplicate of the current object but with a new name and/or
|
457
|
+
# configurations.
|
458
|
+
def initialize_batch_obj(overrides={}, name=nil)
|
459
|
+
obj = super().reconfigure(overrides)
|
460
|
+
obj.name = name if name
|
461
|
+
obj
|
170
462
|
end
|
171
463
|
|
172
464
|
# Enqueues self and self.batch to app with the inputs.
|
@@ -175,7 +467,7 @@ module Tap
|
|
175
467
|
def enq(*inputs)
|
176
468
|
app.queue.enq(self, inputs)
|
177
469
|
end
|
178
|
-
|
470
|
+
|
179
471
|
batch_function :enq, :multithread=
|
180
472
|
batch_function(:on_complete) {}
|
181
473
|
|
@@ -237,6 +529,26 @@ module Tap
|
|
237
529
|
task_block.call(*inputs)
|
238
530
|
end
|
239
531
|
|
532
|
+
# Logs the inputs to the application logger (via app.log)
|
533
|
+
def log(action, msg="", level=Logger::INFO)
|
534
|
+
# TODO - add a task identifier?
|
535
|
+
app.log(action, msg, level)
|
536
|
+
end
|
537
|
+
|
538
|
+
# Raises a TerminateError if app.state == State::TERMINATE.
|
539
|
+
# check_terminate may be called at any time to provide a
|
540
|
+
# breakpoint in long-running processes.
|
541
|
+
def check_terminate
|
542
|
+
if app.state == App::State::TERMINATE
|
543
|
+
raise App::TerminateError.new
|
544
|
+
end
|
545
|
+
end
|
546
|
+
|
547
|
+
# Returns self.name
|
548
|
+
def to_s
|
549
|
+
name.to_s
|
550
|
+
end
|
551
|
+
|
240
552
|
protected
|
241
553
|
|
242
554
|
# Hook to set a default task block. By default, nil.
|
@@ -255,5 +567,13 @@ module Tap
|
|
255
567
|
def on_execute_error(err)
|
256
568
|
raise err
|
257
569
|
end
|
570
|
+
|
571
|
+
private
|
572
|
+
|
573
|
+
def config_task(name, klass=Tap::Task, &block)
|
574
|
+
configs = config[name] || {}
|
575
|
+
raise ArgumentError, "config '#{name}' is not a hash" unless configs.kind_of?(Hash)
|
576
|
+
klass.new(configs, name, &block)
|
577
|
+
end
|
258
578
|
end
|
259
579
|
end
|