xpflow 0.1b
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/bin/xpflow +96 -0
- data/lib/colorado.rb +198 -0
- data/lib/json/add/core.rb +243 -0
- data/lib/json/add/rails.rb +8 -0
- data/lib/json/common.rb +423 -0
- data/lib/json/editor.rb +1369 -0
- data/lib/json/ext.rb +28 -0
- data/lib/json/pure/generator.rb +442 -0
- data/lib/json/pure/parser.rb +320 -0
- data/lib/json/pure.rb +15 -0
- data/lib/json/version.rb +8 -0
- data/lib/json.rb +62 -0
- data/lib/mime/types.rb +881 -0
- data/lib/mime-types.rb +3 -0
- data/lib/restclient/abstract_response.rb +106 -0
- data/lib/restclient/exceptions.rb +193 -0
- data/lib/restclient/net_http_ext.rb +55 -0
- data/lib/restclient/payload.rb +235 -0
- data/lib/restclient/raw_response.rb +34 -0
- data/lib/restclient/request.rb +316 -0
- data/lib/restclient/resource.rb +169 -0
- data/lib/restclient/response.rb +24 -0
- data/lib/restclient.rb +174 -0
- data/lib/xpflow/bash.rb +341 -0
- data/lib/xpflow/bundle.rb +113 -0
- data/lib/xpflow/cmdline.rb +249 -0
- data/lib/xpflow/collection.rb +122 -0
- data/lib/xpflow/concurrency.rb +79 -0
- data/lib/xpflow/data.rb +393 -0
- data/lib/xpflow/dsl.rb +816 -0
- data/lib/xpflow/engine.rb +574 -0
- data/lib/xpflow/ensemble.rb +135 -0
- data/lib/xpflow/events.rb +56 -0
- data/lib/xpflow/experiment.rb +65 -0
- data/lib/xpflow/exts/facter.rb +30 -0
- data/lib/xpflow/exts/g5k.rb +931 -0
- data/lib/xpflow/exts/g5k_use.rb +50 -0
- data/lib/xpflow/exts/gui.rb +140 -0
- data/lib/xpflow/exts/model.rb +155 -0
- data/lib/xpflow/graph.rb +1603 -0
- data/lib/xpflow/graph_xpflow.rb +251 -0
- data/lib/xpflow/import.rb +196 -0
- data/lib/xpflow/library.rb +349 -0
- data/lib/xpflow/logging.rb +153 -0
- data/lib/xpflow/manager.rb +147 -0
- data/lib/xpflow/nodes.rb +1250 -0
- data/lib/xpflow/runs.rb +773 -0
- data/lib/xpflow/runtime.rb +125 -0
- data/lib/xpflow/scope.rb +168 -0
- data/lib/xpflow/ssh.rb +186 -0
- data/lib/xpflow/stat.rb +50 -0
- data/lib/xpflow/stdlib.rb +381 -0
- data/lib/xpflow/structs.rb +369 -0
- data/lib/xpflow/taktuk.rb +193 -0
- data/lib/xpflow/templates/ssh-config.basic +14 -0
- data/lib/xpflow/templates/ssh-config.inria +18 -0
- data/lib/xpflow/templates/ssh-config.proxy +13 -0
- data/lib/xpflow/templates/taktuk +6590 -0
- data/lib/xpflow/templates/utils/batch +4 -0
- data/lib/xpflow/templates/utils/bootstrap +12 -0
- data/lib/xpflow/templates/utils/hostname +3 -0
- data/lib/xpflow/templates/utils/ping +3 -0
- data/lib/xpflow/templates/utils/rsync +12 -0
- data/lib/xpflow/templates/utils/scp +17 -0
- data/lib/xpflow/templates/utils/scp_many +8 -0
- data/lib/xpflow/templates/utils/ssh +3 -0
- data/lib/xpflow/templates/utils/ssh-interactive +4 -0
- data/lib/xpflow/templates/utils/taktuk +19 -0
- data/lib/xpflow/threads.rb +187 -0
- data/lib/xpflow/utils.rb +569 -0
- data/lib/xpflow/visual.rb +230 -0
- data/lib/xpflow/with_g5k.rb +7 -0
- data/lib/xpflow.rb +349 -0
- metadata +135 -0
@@ -0,0 +1,230 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
#
|
4
|
+
# Visualization of engine execution/data.
|
5
|
+
#
|
6
|
+
|
7
|
+
def dict(h)
|
8
|
+
return XPFlow::Visual::Dict.new(h)
|
9
|
+
end
|
10
|
+
|
11
|
+
module XPFlow
|
12
|
+
|
13
|
+
module Visual
|
14
|
+
|
15
|
+
class Dict
|
16
|
+
|
17
|
+
def initialize(h)
|
18
|
+
@h = h
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_h
|
22
|
+
return @h
|
23
|
+
end
|
24
|
+
|
25
|
+
def method_missing(method)
|
26
|
+
return @h[method]
|
27
|
+
end
|
28
|
+
|
29
|
+
def [](key)
|
30
|
+
return @h[key]
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_s
|
34
|
+
return "d(#{@h.to_s})"
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.pair_activities(events)
|
40
|
+
# returns a list with pairs named
|
41
|
+
# after the activities, and values
|
42
|
+
# being start, finish
|
43
|
+
|
44
|
+
starts = events.select { |x| x.type == :start_activity }
|
45
|
+
starts = starts.sort { |x, y| x.meta[:time] <=> y.meta[:time] }
|
46
|
+
ends = events.select { |x| x.type == :finish_activity }
|
47
|
+
contents = ends.map { |x| [ x.meta[:name], x.meta[:time] ] }
|
48
|
+
ends = Hash[ contents ]
|
49
|
+
|
50
|
+
return starts.map do |ev|
|
51
|
+
name = ev.meta[:name]
|
52
|
+
start = ev.meta[:time]
|
53
|
+
finish = ends[name]
|
54
|
+
dict({ :name => name, :start => start, :finish => finish, :meta => ev.meta })
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.get_times(activities)
|
59
|
+
t_start = activities.map(&:start).min
|
60
|
+
t_finish = activities.map(&:finish).compact.max
|
61
|
+
return [ t_start, t_finish, t_finish - t_start ]
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.terminal_size
|
65
|
+
begin
|
66
|
+
return `tput cols`.to_i - 14
|
67
|
+
rescue Errno::ENOENT
|
68
|
+
return nil
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.show_gantt(events)
|
73
|
+
|
74
|
+
if events.empty?
|
75
|
+
puts
|
76
|
+
puts "=== GANTT DIAGRAM IS EMPTY ===".red
|
77
|
+
puts
|
78
|
+
return
|
79
|
+
end
|
80
|
+
|
81
|
+
times = compute_times(events)
|
82
|
+
activities = times[:activities]
|
83
|
+
t_start, t_finish, t_span = times[:time_start], times[:time_finish], times[:time_span]
|
84
|
+
left_size = activities.map { |x| x[:name].length }.max # length of names
|
85
|
+
|
86
|
+
width = terminal_size()
|
87
|
+
width = width - left_size unless width.nil?
|
88
|
+
width = 80 if (width.nil? or width < 80)
|
89
|
+
|
90
|
+
puts
|
91
|
+
puts "=== ACTIVITY GANTT DIAGRAM ===".red
|
92
|
+
puts
|
93
|
+
activities.each do |a|
|
94
|
+
a = dict(a)
|
95
|
+
chars = (width * a.graph_span).to_i
|
96
|
+
margin = (width * a.graph_start).to_i
|
97
|
+
bar = ('=' * chars).yellow
|
98
|
+
indent = (' ' * margin)
|
99
|
+
status = (a.finished ? '%.3f s' % a.secs_span : 'running')
|
100
|
+
puts "%s %s%s (%s)" % \
|
101
|
+
[ a.name.ljust(left_size).white!, indent, bar, status ]
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.compute_times(events)
|
106
|
+
activities = pair_activities(events)
|
107
|
+
activities = activities.select { |x| !x.name.start_with?("__") }
|
108
|
+
t_start, t_finish, t_span = get_times(activities)
|
109
|
+
activities = activities.map do |x|
|
110
|
+
finished = (!x.finish.nil?)
|
111
|
+
v = {
|
112
|
+
:name => x.name,
|
113
|
+
:finished => finished,
|
114
|
+
:time_start => x.start,
|
115
|
+
:time_finish => x.finish,
|
116
|
+
:time_span => finished ? x.finish - x.start : nil,
|
117
|
+
:secs_start => x.start - t_start,
|
118
|
+
:secs_finish => finished ? x.finish - t_start : t_span,
|
119
|
+
:secs_span => finished ? x.finish - x.start : t_finish - x.start,
|
120
|
+
}
|
121
|
+
v[:graph_start] = v[:secs_start] / t_span
|
122
|
+
v[:graph_finish] = v[:secs_finish] / t_span
|
123
|
+
v[:graph_span] = v[:secs_span] / t_span
|
124
|
+
v
|
125
|
+
end
|
126
|
+
return {
|
127
|
+
:time_start => t_start,
|
128
|
+
:time_finish => t_finish,
|
129
|
+
:time_span => t_span,
|
130
|
+
:activities => activities
|
131
|
+
}
|
132
|
+
end
|
133
|
+
|
134
|
+
def self.save_gantt(filename, events)
|
135
|
+
times = compute_times(events)
|
136
|
+
IO.write(filename, times.to_yaml)
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.show_activities()
|
140
|
+
events = $engine.runtime.events()
|
141
|
+
activities = pair_activities(events)
|
142
|
+
activities = activities.select { |x| x.finish.nil? }
|
143
|
+
$console.synchronize do
|
144
|
+
bar = ("=" * 80).red
|
145
|
+
tab = ' ' * 4
|
146
|
+
puts; puts bar
|
147
|
+
puts "CURRENT ACTIVITIES".blue
|
148
|
+
activities.each do |x|
|
149
|
+
puts "#{tab}#{x.meta[:name].green} #{x.meta.inspect}"
|
150
|
+
end
|
151
|
+
puts bar
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def self.show_stacktrace(e)
|
157
|
+
trace = e.stacktrace
|
158
|
+
|
159
|
+
puts
|
160
|
+
puts "=== ERRORS ===".red
|
161
|
+
|
162
|
+
tab = ' ' * 4
|
163
|
+
for error in trace
|
164
|
+
msg, frames = error
|
165
|
+
puts tab + "#{msg}".yellow
|
166
|
+
for f in frames
|
167
|
+
line = (tab * 2) + "#{f.location_long}"
|
168
|
+
line += " (at #{f.obj.class})" if f.obj
|
169
|
+
puts line
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
class TerminalThread
|
175
|
+
# a thread that listens on the terminal
|
176
|
+
# to collect events
|
177
|
+
# assumes stdin is tty
|
178
|
+
|
179
|
+
def initialize
|
180
|
+
@config = %x(stty --save).strip
|
181
|
+
@r, @w = IO.pipe
|
182
|
+
@t = nil
|
183
|
+
end
|
184
|
+
|
185
|
+
def cols
|
186
|
+
size = %x(stty size).split
|
187
|
+
return size.last.to_i
|
188
|
+
end
|
189
|
+
|
190
|
+
def start
|
191
|
+
system("stty -icanon -echo")
|
192
|
+
@t = Thread.new do
|
193
|
+
loop do
|
194
|
+
ready = IO.select([ STDIN, @r ], [], []).first
|
195
|
+
if ready.include?(STDIN)
|
196
|
+
char = STDIN.readpartial(1)
|
197
|
+
c = cols()
|
198
|
+
case char
|
199
|
+
when 'i' then Visual.show_activities()
|
200
|
+
else begin
|
201
|
+
$console.synchronize do
|
202
|
+
puts "Key #{char.inspect} pressed.".rjust(c).blue
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
207
|
+
end
|
208
|
+
break if ready.include?(@r)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def stop
|
214
|
+
system("stty #{@config}")
|
215
|
+
@w.close
|
216
|
+
@t.join(1.0)
|
217
|
+
end
|
218
|
+
|
219
|
+
def self.start_thread
|
220
|
+
if STDIN.tty? and not ENV.key?("BATCH")
|
221
|
+
$__terminal_thread__ = TerminalThread.new
|
222
|
+
$__terminal_thread__.start
|
223
|
+
at_exit { $__terminal_thread__.stop }
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|
230
|
+
|
data/lib/xpflow.rb
ADDED
@@ -0,0 +1,349 @@
|
|
1
|
+
|
2
|
+
require 'thread'
|
3
|
+
require 'digest'
|
4
|
+
require 'etc'
|
5
|
+
require 'open3'
|
6
|
+
require 'yaml'
|
7
|
+
require 'optparse'
|
8
|
+
require 'monitor'
|
9
|
+
require 'colorado'
|
10
|
+
require 'pathname'
|
11
|
+
require 'highline'
|
12
|
+
|
13
|
+
def __get_my_requires__
|
14
|
+
me = File.dirname(__FILE__)
|
15
|
+
files = Dir.entries(File.join(me, 'xpflow'))
|
16
|
+
files = files.select { |f| File.extname(f) == '.rb' }
|
17
|
+
files = files.select { |f| !f.start_with?('with') }
|
18
|
+
modules = files.map { |f| File.basename(f, File.extname(f)) }
|
19
|
+
return modules
|
20
|
+
end
|
21
|
+
|
22
|
+
# some Ruby version trickery
|
23
|
+
|
24
|
+
_, $version = RUBY_VERSION.split('.')
|
25
|
+
$version = $version.to_i
|
26
|
+
$ruby18 = ($version == 8)
|
27
|
+
$ruby19 = ($version == 9)
|
28
|
+
|
29
|
+
class Symbol
|
30
|
+
|
31
|
+
def <=>(x)
|
32
|
+
return (self.to_s <=> x.to_s)
|
33
|
+
end
|
34
|
+
|
35
|
+
end if $ruby18
|
36
|
+
|
37
|
+
if $bundled.nil?
|
38
|
+
# necessary includes
|
39
|
+
require('xpflow/utils')
|
40
|
+
require('xpflow/structs')
|
41
|
+
require('xpflow/library')
|
42
|
+
require('xpflow/scope')
|
43
|
+
|
44
|
+
for m in __get_my_requires__ do
|
45
|
+
require('xpflow/' + m)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
Engine = $engine = XPFlow::Engine.new
|
50
|
+
|
51
|
+
def engine(&block)
|
52
|
+
$engine.instance_exec(&block)
|
53
|
+
end
|
54
|
+
|
55
|
+
def process(*args, &block)
|
56
|
+
engine { process(*args, &block) }
|
57
|
+
end
|
58
|
+
|
59
|
+
def activity(*args, &block)
|
60
|
+
engine { activity(*args, &block) }
|
61
|
+
end
|
62
|
+
|
63
|
+
def macro(*args, &block)
|
64
|
+
engine { macro(*args, &block) }
|
65
|
+
end
|
66
|
+
|
67
|
+
def dsl(name, *args, &block)
|
68
|
+
return XPFlow::ProcessDSL.new(name, $engine, &block)
|
69
|
+
end
|
70
|
+
|
71
|
+
def empty_activity(*args)
|
72
|
+
args.each do |name|
|
73
|
+
activity(name) do
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def script(name, path)
|
79
|
+
# TODO: put more magic
|
80
|
+
process(name) do |nodes|
|
81
|
+
execute_many(nodes, path)
|
82
|
+
end
|
83
|
+
single = "#{name}_seq".to_sym
|
84
|
+
process(single) do |node|
|
85
|
+
execute(node, path)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def activity_visibility(name)
|
90
|
+
return true
|
91
|
+
end
|
92
|
+
|
93
|
+
def __parse_variables__()
|
94
|
+
opts = XPFlow::Options.new(ARGV.dup)
|
95
|
+
$variables = opts.vars
|
96
|
+
end
|
97
|
+
|
98
|
+
def parse_g5k_query(query, opts = { :max => 1 })
|
99
|
+
lib = XPFlow::G5K::Library.new
|
100
|
+
lib.logging = proc { |x| puts x }
|
101
|
+
sites = lib.sites.map { |x| x['uid'] }
|
102
|
+
best_sites = [ "nancy", "rennes", "sophia" ]
|
103
|
+
sites = best_sites + (sites - best_sites)
|
104
|
+
|
105
|
+
max = opts[:max]
|
106
|
+
|
107
|
+
uniq_jobs = proc { |js| Hash[js.map { |j| [ [j['uid'], j['site']], j ] }].values }
|
108
|
+
jobs_at_site = proc do |site|
|
109
|
+
js = lib.jobs(site)
|
110
|
+
js = js.select { |x| x['state'] == 'running' }
|
111
|
+
js.select { |x| x['user_uid'] == lib.g5k.user }
|
112
|
+
end
|
113
|
+
site_from_job = proc { |j| j['links'].select { |l| l['rel'] == 'parent' }.first['href'].split("/").last }
|
114
|
+
|
115
|
+
jobs = []
|
116
|
+
|
117
|
+
query.split(":").each do |word|
|
118
|
+
if word == '*'
|
119
|
+
sites.each do |s|
|
120
|
+
jobs = uniq_jobs.call(jobs + jobs_at_site.call(s))
|
121
|
+
break if jobs.length >= max
|
122
|
+
end
|
123
|
+
break if jobs.length >= max
|
124
|
+
elsif (m = word.match(/^([a-z]+)\/(.+)/)) and (sites.include?(m.captures.first))
|
125
|
+
query_site, query_str = m.captures
|
126
|
+
site_jobs = jobs_at_site.call(query_site)
|
127
|
+
if query_str == '*'
|
128
|
+
jobs = uniq_jobs.call(jobs + site_jobs)
|
129
|
+
break if jobs.length >= max
|
130
|
+
elsif query_str.match(/^[0-9]+$/)
|
131
|
+
job_uid = query_str.to_i
|
132
|
+
site_job = site_jobs.select { |x| x['uid'] == job_uid }
|
133
|
+
if site_job.length == 0
|
134
|
+
raise "No job with id = #{job_uid} in '#{query_site}'."
|
135
|
+
end
|
136
|
+
jobs = uniq_jobs.call(jobs + [ site_job.first ])
|
137
|
+
break if jobs.length >= max
|
138
|
+
else
|
139
|
+
raise "Cannot parse '#{query_str}'"
|
140
|
+
end
|
141
|
+
else
|
142
|
+
raise "Cannot parse '#{word}'"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
grouped_jobs = Hash.new { |h, k| h[k] = [] }
|
147
|
+
jobs.each { |j| grouped_jobs[site_from_job.call(j)].push(j) }
|
148
|
+
jobs_info = grouped_jobs.each_pair.map { |site, jobs| "{" + jobs.map { |x| x['uid'] }.join(",") + "}@#{site}" }.join(" ")
|
149
|
+
|
150
|
+
puts "Found #{jobs.length} jobs: #{jobs_info} (but :max is #{max})"
|
151
|
+
|
152
|
+
jobs = jobs[0...max]
|
153
|
+
return jobs
|
154
|
+
end
|
155
|
+
|
156
|
+
def parse_g5k_query_one_wait(query)
|
157
|
+
lib = XPFlow::G5K::Library.new
|
158
|
+
lib.logging = proc { |x| puts x }
|
159
|
+
jobs = parse_g5k_query(query, :max => 1)
|
160
|
+
if jobs.length == 0
|
161
|
+
raise "Could not find any jobs."
|
162
|
+
end
|
163
|
+
job = jobs.first
|
164
|
+
j = lib.g5k.get_json_raw(job.rel_self)
|
165
|
+
j = lib.wait_for_job(j)
|
166
|
+
return j
|
167
|
+
end
|
168
|
+
|
169
|
+
def _bool_var(v)
|
170
|
+
return v if v.is_a?(TrueClass) or v.is_a?(FalseClass)
|
171
|
+
v = v.strip
|
172
|
+
if (v.to_i == 1) or ([ "true", "yes" ].include?(v.downcase))
|
173
|
+
return true
|
174
|
+
end
|
175
|
+
return false
|
176
|
+
end
|
177
|
+
|
178
|
+
class Seq
|
179
|
+
|
180
|
+
def initialize(arr)
|
181
|
+
@arr = arr
|
182
|
+
end
|
183
|
+
|
184
|
+
def range
|
185
|
+
return Seq.new(@arr.uniq.sort)
|
186
|
+
end
|
187
|
+
|
188
|
+
def self.parse(spec)
|
189
|
+
nums = []
|
190
|
+
parts = spec.strip.split("+").map(&:strip)
|
191
|
+
parts.each do |p|
|
192
|
+
if p.match(/^(\d+)$/)
|
193
|
+
nums += [ p.to_i ]
|
194
|
+
elsif (m = p.match(/^(\d+)(:|-|\.\.)(\d+)$/))
|
195
|
+
s, _, e = m.captures.map(&:to_i)
|
196
|
+
nums += (s..e).to_a
|
197
|
+
elsif (m = p.match(/^(\d+)(:|-|\.\.)(\d+)\/(\d+)$/))
|
198
|
+
s, _, e, div = m.captures.map(&:to_i)
|
199
|
+
raise "Dividing by zero here" if div == 0
|
200
|
+
raise "Division must be > 1" if div == 1
|
201
|
+
if (e - s) % (div - 1) == 0
|
202
|
+
d = (e - s) / (div - 1) # use integer arithmetic if possible
|
203
|
+
else
|
204
|
+
d = (e - s).to_f / (div - 1).to_f
|
205
|
+
end
|
206
|
+
pts = []
|
207
|
+
(1..(div - 2)).each do |i|
|
208
|
+
p = i * d + s
|
209
|
+
pts.push(p.to_i)
|
210
|
+
end
|
211
|
+
nums += ([ s ] + pts + [ e ])
|
212
|
+
elsif (m = p.match(/^(\d+):(\d+):(\d+)$/))
|
213
|
+
s, d, e = m.captures.map(&:to_i)
|
214
|
+
if d == 0
|
215
|
+
raise "Zero range increment (#{p})"
|
216
|
+
end
|
217
|
+
while s <= e
|
218
|
+
nums.push(s)
|
219
|
+
s += d
|
220
|
+
end
|
221
|
+
else
|
222
|
+
raise "Wrong range specification"
|
223
|
+
end
|
224
|
+
end
|
225
|
+
return Seq.new(nums)
|
226
|
+
end
|
227
|
+
|
228
|
+
def to_s
|
229
|
+
return "seq(#{@arr.inspect})"
|
230
|
+
end
|
231
|
+
|
232
|
+
def to_list
|
233
|
+
return @arr.dup
|
234
|
+
end
|
235
|
+
|
236
|
+
def max
|
237
|
+
return @arr.max
|
238
|
+
end
|
239
|
+
|
240
|
+
end
|
241
|
+
|
242
|
+
$__highline__ = HighLine.new
|
243
|
+
|
244
|
+
def ask_for_type(name, type, opts)
|
245
|
+
text = "Provide value of :#{name} (type :#{type}): "
|
246
|
+
text = opts[:text] if opts[:text]
|
247
|
+
if [ :seq, :str, :range, :pass ].include?(type)
|
248
|
+
value = $__highline__.ask(text) do |q|
|
249
|
+
q.echo = "*" if type == :pass
|
250
|
+
end
|
251
|
+
return value
|
252
|
+
elsif type == :int
|
253
|
+
value = $__highline__.ask(text, Integer)
|
254
|
+
return value
|
255
|
+
elsif type == :bool
|
256
|
+
value = $__highline__.agree(text) { |q| q.default = "no" }
|
257
|
+
return value
|
258
|
+
else
|
259
|
+
# TODO, support more variables
|
260
|
+
raise "Unknown value for variable :#{name} (type :#{type})"
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
def var(name, type = :str, default = nil, opts = {})
|
265
|
+
# TODO
|
266
|
+
if default.is_a?(Hash)
|
267
|
+
opts = default
|
268
|
+
default = opts[:default]
|
269
|
+
end
|
270
|
+
v = $variables[name.to_s]
|
271
|
+
if v.nil?
|
272
|
+
if default.nil?
|
273
|
+
# try to ask for a variable
|
274
|
+
if !STDIN.tty?
|
275
|
+
raise "Unknown value for variable :#{name} (type :#{type})"
|
276
|
+
end
|
277
|
+
v = $variables[name.to_s] = ask_for_type(name, type, opts)
|
278
|
+
if opts[:callback]
|
279
|
+
opts[:callback].call(v)
|
280
|
+
end
|
281
|
+
else
|
282
|
+
return default
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
result = case type
|
287
|
+
when :str then v
|
288
|
+
when :int then v.to_i
|
289
|
+
when :float then v.to_f
|
290
|
+
when :bool then _bool_var(v)
|
291
|
+
when :range then Seq.parse(v).range
|
292
|
+
when :seq then Seq.parse(v)
|
293
|
+
when :pass then v
|
294
|
+
when :g5k then parse_g5k_query_one_wait(v)
|
295
|
+
else
|
296
|
+
raise "Unknown type '#{type}'"
|
297
|
+
end
|
298
|
+
return result
|
299
|
+
end
|
300
|
+
|
301
|
+
def set_var(name, value)
|
302
|
+
$variables[name.to_s] = value
|
303
|
+
return value
|
304
|
+
end
|
305
|
+
|
306
|
+
def experiment(name, &block)
|
307
|
+
body_name = :"#{name}/body"
|
308
|
+
before_name = :"#{name}/before"
|
309
|
+
after_name = :"#{name}/after"
|
310
|
+
$engine.process(body_name, &block)
|
311
|
+
$engine.process(before_name) { }
|
312
|
+
$engine.process(after_name) { }
|
313
|
+
$engine.process(name) do |*args|
|
314
|
+
run :"/new_experiment" # create a new experiment
|
315
|
+
log "Starting experiment '#{name}'"
|
316
|
+
run(before_name, *args)
|
317
|
+
result = run(body_name, *args)
|
318
|
+
run(after_name, *args)
|
319
|
+
log "Finished experiment '#{name}'"
|
320
|
+
value(result)
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
def after_activity(name, &block)
|
325
|
+
# we replace the given activity with
|
326
|
+
# a one that executes the previous one and then
|
327
|
+
# the one given
|
328
|
+
|
329
|
+
after_block = block
|
330
|
+
activity(name) do |*args|
|
331
|
+
value = parent(*args)
|
332
|
+
self.set_result(value)
|
333
|
+
value = self.instance_exec(*args, &after_block)
|
334
|
+
value
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
def before_activity(name, &block)
|
339
|
+
# similarly to after_activity
|
340
|
+
before_block = block
|
341
|
+
activity(name) do |*args|
|
342
|
+
self.instance_exec(*args, &before_block)
|
343
|
+
parent(*args)
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
def realize(path)
|
348
|
+
return Pathname.new(path).realpath.to_s
|
349
|
+
end
|