rbbt-util 5.2.4 → 5.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +8 -8
  2. data/bin/rbbt +23 -10
  3. data/bin/rbbt_monitor.rb +8 -8
  4. data/lib/rbbt/annotations.rb +22 -1
  5. data/lib/rbbt/annotations/util.rb +1 -1
  6. data/lib/rbbt/entity.rb +162 -0
  7. data/lib/rbbt/fix_width_table.rb +7 -0
  8. data/lib/rbbt/persist.rb +16 -9
  9. data/lib/rbbt/persist/tsv.rb +14 -8
  10. data/lib/rbbt/resource.rb +1 -6
  11. data/lib/rbbt/resource/path.rb +23 -27
  12. data/lib/rbbt/tsv.rb +33 -4
  13. data/lib/rbbt/tsv/accessor.rb +100 -57
  14. data/lib/rbbt/tsv/attach.rb +3 -1
  15. data/lib/rbbt/tsv/attach/util.rb +34 -10
  16. data/lib/rbbt/tsv/index.rb +12 -3
  17. data/lib/rbbt/tsv/manipulate.rb +25 -1
  18. data/lib/rbbt/tsv/parser.rb +1 -0
  19. data/lib/rbbt/util/R.rb +36 -6
  20. data/lib/rbbt/util/cmd.rb +2 -1
  21. data/lib/rbbt/util/color.rb +250 -0
  22. data/lib/rbbt/util/colorize.rb +57 -0
  23. data/lib/rbbt/util/misc.rb +57 -19
  24. data/lib/rbbt/util/named_array.rb +66 -14
  25. data/lib/rbbt/util/open.rb +134 -10
  26. data/lib/rbbt/util/semaphore.rb +71 -0
  27. data/lib/rbbt/workflow.rb +34 -7
  28. data/lib/rbbt/workflow/accessor.rb +12 -8
  29. data/lib/rbbt/workflow/step.rb +44 -28
  30. data/lib/rbbt/workflow/usage.rb +3 -0
  31. data/share/lib/R/util.R +31 -0
  32. data/share/rbbt_commands/app/start +5 -4
  33. data/share/rbbt_commands/study/task +222 -0
  34. data/share/rbbt_commands/tsv/attach +13 -0
  35. data/share/rbbt_commands/tsv/change_id +15 -0
  36. data/share/rbbt_commands/tsv/info +3 -1
  37. data/share/rbbt_commands/workflow/task +14 -15
  38. data/test/rbbt/test_entity.rb +221 -0
  39. data/test/rbbt/test_tsv.rb +2 -1
  40. data/test/rbbt/test_workflow.rb +0 -2
  41. data/test/rbbt/tsv/test_accessor.rb +2 -2
  42. data/test/rbbt/util/test_R.rb +9 -2
  43. data/test/rbbt/util/test_colorize.rb +12 -0
  44. data/test/rbbt/util/test_misc.rb +0 -5
  45. data/test/rbbt/util/test_open.rb +31 -0
  46. data/test/rbbt/workflow/test_step.rb +32 -0
  47. metadata +13 -2
@@ -0,0 +1,71 @@
1
+ require 'inline'
2
+
3
+ module RbbtSemaphore
4
+ inline(:C) do |builder|
5
+ builder.prefix <<-EOF
6
+ #include <unistd.h>
7
+ #include <stdio.h>
8
+ #include <stdlib.h>
9
+ #include <semaphore.h>
10
+ #include <time.h>
11
+ #include <assert.h>
12
+ #include <errno.h>
13
+ #include <signal.h>
14
+ #include <fcntl.h>
15
+ EOF
16
+
17
+ builder.c_singleton <<-EOF
18
+ void create_semaphore(char* name, int value){
19
+ sem_open(name, O_CREAT, S_IRWXU, value);
20
+ }
21
+ EOF
22
+ builder.c_singleton <<-EOF
23
+ void delete_semaphore(char* name){
24
+ sem_unlink(name);
25
+ }
26
+ EOF
27
+
28
+ builder.c_singleton <<-EOF
29
+ void wait_semaphore(char* name){
30
+ sem_t* sem;
31
+ sem = sem_open(name, O_EXCL);
32
+ sem_wait(sem);
33
+ sem_close(sem);
34
+ }
35
+ EOF
36
+
37
+ builder.c_singleton <<-EOF
38
+ void post_semaphore(char* name){
39
+ sem_t* sem;
40
+ sem = sem_open(name, O_EXCL);
41
+ sem_post(sem);
42
+ sem_close(sem);
43
+ }
44
+ EOF
45
+ end
46
+
47
+ def self.with_semaphore(size, file = nil)
48
+ file = Misc.digest(rand.to_s) if file.nil?
49
+ file.gsub!('/', '_')
50
+ begin
51
+ RbbtSemaphore.create_semaphore(file, size)
52
+ yield file
53
+ ensure
54
+ RbbtSemaphore.delete_semaphore(file)
55
+ end
56
+ end
57
+
58
+ def self.fork_each_on_semaphore(elems, size, file = nil)
59
+ with_semaphore(size, file) do |file|
60
+ pids = elems.collect do |elem|
61
+ Process.fork do
62
+ RbbtSemaphore.wait_semaphore(file)
63
+ yield elem
64
+ RbbtSemaphore.post_semaphore(file)
65
+ end
66
+ end
67
+ pids.each do |pid| Process.waitpid pid end
68
+ end
69
+ end
70
+ end
71
+
data/lib/rbbt/workflow.rb CHANGED
@@ -58,8 +58,9 @@ module Workflow
58
58
  else
59
59
  case
60
60
  # Points to workflow file
61
- when ((File.exists?(wf_name) and not File.directory?(wf_name)) or File.exists?(wf_name + '.rb'))
61
+ when ((File.exists?(wf_name) and not File.directory?(wf_name)) or File.exists?(wf_name + '.rb') or File.exists?(wf_name))
62
62
  $LOAD_PATH.unshift(File.join(File.expand_path(File.dirname(wf_name)), 'lib'))
63
+ wf_name = "./" << wf_name unless wf_name[0] == "/"
63
64
  require wf_name
64
65
  Log.medium "Workflow loaded from file: #{ wf_name }"
65
66
  return true
@@ -112,19 +113,17 @@ module Workflow
112
113
  begin
113
114
  require_local_workflow(wf_name)
114
115
  rescue Exception
115
- Log.debug $!.message
116
- Log.debug $!.backtrace.first
117
116
  raise "Workflow not found: #{ wf_name }" if wf_name == Misc.snake_case(wf_name)
118
- Log.debug "Trying with humanized: '#{Misc.snake_case wf_name}'"
119
117
  begin
120
118
  require_local_workflow(Misc.snake_case(wf_name))
121
119
  rescue Exception
122
- Log.debug $!.message
123
- raise "Workflow not found: #{ wf_name }"
120
+ Log.error("Workflow not found: #{ wf_name }")
121
+ raise $!
124
122
  end
125
123
  end
126
124
  end
127
125
 
126
+ attr_accessor :description
128
127
  attr_accessor :libdir, :workdir
129
128
  attr_accessor :helpers, :tasks
130
129
  attr_accessor :task_dependencies, :task_description, :last_task
@@ -134,7 +133,8 @@ module Workflow
134
133
 
135
134
  def workdir
136
135
  @workdir ||= if defined? Rbbt
137
- Rbbt.var.jobs[self].find
136
+ text = Module === self ? self.to_s : "Misc"
137
+ Rbbt.var.jobs[text].find
138
138
  else
139
139
  Path.setup('var/jobs')
140
140
  end
@@ -144,6 +144,17 @@ module Workflow
144
144
  @libdir = Path.caller_lib_dir if @libdir.nil?
145
145
  @libdir
146
146
  end
147
+
148
+ def workflow_description
149
+ @workflow_description ||= begin
150
+ file = @libdir['workflow.md']
151
+ if file.exists?
152
+ file.read
153
+ else
154
+ ""
155
+ end
156
+ end
157
+ end
147
158
 
148
159
  def helpers
149
160
  @helpers ||= {}
@@ -232,4 +243,20 @@ module Workflow
232
243
  Misc.path_relative_to(task_dir, f).sub(".info",'')
233
244
  }
234
245
  end
246
+
247
+ def local_persist_setup
248
+ class << self
249
+ include LocalPersist
250
+ end
251
+ self.local_persist_dir = Rbbt.var.cache.persistence.find :lib
252
+ end
253
+
254
+ def local_workdir_setup
255
+ self.workdir = Rbbt.var.jobs.find :lib
256
+ end
257
+
258
+ def make_local
259
+ local_persist_setup
260
+ local_workdir_setup
261
+ end
235
262
  end
@@ -2,6 +2,8 @@ require 'rbbt/util/open'
2
2
  require 'yaml'
3
3
 
4
4
  class Step
5
+
6
+ INFO_SERIALIAZER = Marshal
5
7
 
6
8
  def name
7
9
  @path.sub(/.*\/#{Regexp.quote task.name.to_s}\/(.*)/, '\1')
@@ -22,22 +24,24 @@ class Step
22
24
  end
23
25
 
24
26
  def info
25
- return {} if not File.exists? info_file
27
+ return {} if not Open.exists? info_file
26
28
  begin
27
- File.open(info_file) do |file|
28
- YAML.load(file) || {}
29
+ Open.open(info_file) do |file|
30
+ INFO_SERIALIAZER.load(file) || {}
29
31
  end
30
32
  rescue Exception
31
- Log.debug "Error loading yaml: " + info_file
33
+ Log.debug "Error loading info file: " + info_file
32
34
  raise $!
33
35
  end
34
36
  end
35
37
 
36
38
  def set_info(key, value)
37
- Misc.lock(info_file) do
39
+ return nil if @exec
40
+ value = Annotated.purge value
41
+ Open.lock(info_file) do
38
42
  i = info
39
43
  i[key] = value
40
- Open.write(info_file, i.to_yaml)
44
+ Open.write(info_file, INFO_SERIALIAZER.dump(i))
41
45
  value
42
46
  end
43
47
  end
@@ -71,7 +75,7 @@ class Step
71
75
  end
72
76
 
73
77
  def started?
74
- File.exists? info_file
78
+ Open.exists? info_file
75
79
  end
76
80
 
77
81
  def done?
@@ -79,7 +83,7 @@ class Step
79
83
  end
80
84
 
81
85
  def running?
82
- return nil if not File.exists? info_file
86
+ return nil if not Open.exists? info_file
83
87
  return nil if info[:pid].nil?
84
88
  return Misc.pid_exists? info[:pid]
85
89
  end
@@ -1,11 +1,13 @@
1
1
  require 'rbbt/persist'
2
2
  require 'rbbt/persist/tsv'
3
3
  require 'rbbt/util/log'
4
+ require 'rbbt/util/semaphore'
4
5
  require 'rbbt/workflow/accessor'
5
6
 
6
7
  class Step
7
8
  attr_accessor :path, :task, :inputs, :dependencies, :bindings
8
9
  attr_accessor :pid
10
+ attr_accessor :exec
9
11
 
10
12
  class Aborted < Exception; end
11
13
 
@@ -62,6 +64,7 @@ class Step
62
64
  end
63
65
 
64
66
  def exec
67
+ @exec = true if @exec.nil?
65
68
  result = @task.exec_in((bindings ? bindings : self), *@inputs)
66
69
  prepare_result result, @task.result_description
67
70
  end
@@ -83,11 +86,12 @@ class Step
83
86
 
84
87
  def run(no_load = false)
85
88
  result = Persist.persist "Job", @task.result_type, :file => @path, :check => rec_dependencies.collect{|dependency| dependency.path }.uniq, :no_load => no_load do
89
+ @exec = false
86
90
  if Step === Step.log_relay_step and not self == Step.log_relay_step
87
91
  relay_log(Step.log_relay_step) unless self.respond_to? :relay_step and self.relay_step
88
92
  end
89
93
 
90
- FileUtils.rm info_file if File.exists? info_file
94
+ Open.rm info_file if Open.exists? info_file
91
95
 
92
96
  set_info :pid, Process.pid
93
97
 
@@ -95,6 +99,7 @@ class Step
95
99
  dependencies.each{|dependency|
96
100
  begin
97
101
  dependency.relay_log self
102
+ dependency.clean if not dependency.done? and dependency.error?
98
103
  dependency.run true
99
104
  rescue Exception
100
105
  backtrace = $!.backtrace
@@ -158,37 +163,43 @@ class Step
158
163
  end
159
164
  end
160
165
 
161
- def fork
166
+ def fork(semaphore = nil)
162
167
  raise "Can not fork: Step is waiting for proces #{@pid} to finish" if not @pid.nil?
163
168
  @pid = Process.fork do
164
169
  trap(:INT) { raise Step::Aborted.new "INT signal recieved" }
165
- FileUtils.mkdir_p File.dirname(path) unless File.exists? File.dirname(path)
166
170
  begin
167
- run
168
- rescue Exception
169
- Log.debug("Exception caught on forked process: #{$!.message}")
170
- exit -1
171
- end
171
+ RbbtSemaphore.wait_semaphore(semaphore) if semaphore
172
+ FileUtils.mkdir_p File.dirname(path) unless Open.exists? File.dirname(path)
173
+ begin
174
+ run(true)
175
+ rescue Exception
176
+ Log.debug("Exception caught on forked process: #{$!.message}")
177
+ exit -1
178
+ end
172
179
 
173
- begin
174
- children_pids = info[:children_pids]
175
- if children_pids
176
- children_pids.each do |pid|
177
- if Misc.pid_exists? pid
178
- begin
179
- Process.waitpid pid
180
- rescue Errno::ECHILD
181
- Log.error "Waiting on #{ pid } failed: #{$!.message}"
180
+ begin
181
+ children_pids = info[:children_pids]
182
+ if children_pids
183
+ children_pids.each do |pid|
184
+ if Misc.pid_exists? pid
185
+ begin
186
+ Process.waitpid pid
187
+ rescue Errno::ECHILD
188
+ Log.error "Waiting on #{ pid } failed: #{$!.message}"
189
+ end
182
190
  end
183
191
  end
192
+ set_info :children_done, Time.now
184
193
  end
194
+ rescue Exception
195
+ Log.debug("Exception waiting for children: #{$!.message}")
196
+ exit -1
185
197
  end
186
- rescue Exception
187
- Log.debug("Exception waiting for children: #{$!.message}")
188
- exit -1
198
+ set_info :pid, nil
199
+ exit 0
200
+ ensure
201
+ RbbtSemaphore.post_semaphore(semaphore) if semaphore
189
202
  end
190
- set_info :pid, nil
191
- exit 0
192
203
  end
193
204
  Process.detach(@pid)
194
205
  self
@@ -234,13 +245,13 @@ class Step
234
245
  end
235
246
 
236
247
  def clean
237
- if File.exists?(path) or File.exists?(info_file)
248
+ if Open.exists?(path) or Open.exists?(info_file)
238
249
  begin
239
- FileUtils.rm info_file if File.exists? info_file
240
- FileUtils.rm info_file + '.lock' if File.exists? info_file + '.lock'
241
- FileUtils.rm path if File.exists? path
242
- FileUtils.rm path + '.lock' if File.exists? path + '.lock'
243
- FileUtils.rm_rf files_dir if File.exists? files_dir
250
+ Open.rm info_file if Open.exists? info_file
251
+ Open.rm info_file + '.lock' if Open.exists? info_file + '.lock'
252
+ Open.rm path if Open.exists? path
253
+ Open.rm path + '.lock' if Open.exists? path + '.lock'
254
+ Open.rm_rf files_dir if Open.exists? files_dir
244
255
  end
245
256
  end
246
257
  self
@@ -250,6 +261,11 @@ class Step
250
261
  @dependencies.collect{|step| step.rec_dependencies}.flatten.concat @dependencies
251
262
  end
252
263
 
264
+ def recursive_clean
265
+ rec_dependencies.each{|step| step.clean }
266
+ clean
267
+ end
268
+
253
269
  def step(name)
254
270
  rec_dependencies.select{|step| step.task.name.to_sym == name.to_sym}.first
255
271
  end
@@ -15,6 +15,7 @@ module Task
15
15
  puts " #{dep.name}:"
16
16
  puts
17
17
  puts SOPT.input_doc(dep.inputs, dep.input_types, dep.input_descriptions, dep.input_defaults)
18
+ puts
18
19
  end
19
20
  end
20
21
  end
@@ -27,6 +28,8 @@ module Workflow
27
28
  puts self.to_s
28
29
  puts "=" * self.to_s.length
29
30
  puts
31
+ puts "\n" << workflow_description if workflow_description and not workflow_description.empty?
32
+ puts
30
33
 
31
34
  puts "## TASKS"
32
35
  puts
data/share/lib/R/util.R CHANGED
@@ -167,7 +167,38 @@ rbbt.acc <- function(data, new){
167
167
  rbbt.png_plot <- function(filename, width, height, p, ...){
168
168
  png(filename=filename, width=width, height=height, ...);
169
169
  eval(parse(text=p));
170
+ }
171
+
172
+ rbbt.heatmap <- function(filename, width, height, data, take_log=FALSE, ...){
173
+ require(gplots, quietly = TRUE, warn.conflicts = FALSE)
174
+ library(pls, quietly = TRUE, warn.conflicts = FALSE)
175
+ opar = par()
176
+ png(filename=filename, width=width, height=height);
177
+
178
+ #par(cex.lab=0.5, cex=0.5, ...)
179
+
180
+ data = as.matrix(data)
181
+ data[is.nan(data)] = NA
182
+
183
+ #data = data[rowSums(!is.na(data))!=0, colSums(!is.na(data))!=0]
184
+ data = data[rowSums(is.na(data))==0, ]
185
+
186
+ if (take_log){
187
+ for (study in colnames(data)){
188
+ skip = sum(data[, study] <= 0) != 0
189
+ if (!skip){
190
+ data[, study] = log(data[, study])
191
+ }
192
+ }
193
+ data = data[, colSums(is.na(data))==0]
194
+ }
195
+
196
+ data = stdize(data)
197
+
198
+ heatmap.2(data, margins = c(20,5), scale='column')
199
+
170
200
  dev.off();
201
+ par(opar)
171
202
  }
172
203
 
173
204
  rbbt.init <- function(data, new){
@@ -3,7 +3,8 @@
3
3
  require 'rbbt-util'
4
4
  require 'rbbt/util/simpleopt'
5
5
 
6
- options = SOPT.get "-e--environment*:-p--port*:-s--server*"
6
+ options = SOPT.get "-e--environment*:-p--port*:-s--server*:-f--finder"
7
+ options[:Port] ||= options[:port]
7
8
 
8
9
  app = ARGV.shift
9
10
 
@@ -12,8 +13,8 @@ app_dir = Rbbt.etc.app_dir.exists? ? Path.setup(Rbbt.etc.app_dir.read.strip) : R
12
13
  app_dir = app_dir[app]
13
14
 
14
15
  server = options[:server] || 'thin'
15
-
16
16
  Misc.in_dir(app_dir) do
17
- `env RBBT_LOG=#{Log.severity} #{options.include?(:environment)? "env RACK_ENV=#{options[:environment]}" : ""} \
18
- #{server} start -p #{options[:port]} #{ARGV.collect{|a| "'#{a}'"} * " "}`
17
+ require 'rack'
18
+ ENV["RBBT_FINDER"] = true if options.include?(:finder)
19
+ Rack::Server.start(options.merge(:config => 'config.ru'))
19
20
  end
@@ -0,0 +1,222 @@
1
+ #!/usr/bin/env ruby
2
+
3
+
4
+ require 'rbbt/util/simpleopt'
5
+ require 'rbbt/workflow'
6
+ require 'rbbt/workflow/usage'
7
+ require 'rbbt/entity/study'
8
+ require 'rbbt/entity/study/genotypes'
9
+ require 'rbbt/entity/study/cnv'
10
+ require 'rbbt/entity/study/expression'
11
+
12
+ def usage(workflow = nil, task = nil)
13
+ puts SOPT.doc
14
+ puts "## WORKFLOW"
15
+ puts
16
+ if workflow.nil?
17
+ puts "No workflow specified"
18
+ exit -1
19
+ end
20
+
21
+ if task.nil?
22
+ workflow.load_tasks if workflow.respond_to? :load_tasks
23
+ workflow.doc
24
+ else
25
+ puts workflow.to_s
26
+ puts "=" * workflow.to_s.length
27
+ puts
28
+ puts workflow.workflow_description
29
+ puts
30
+ workflow.doc(task)
31
+ end
32
+
33
+ exit 0
34
+ end
35
+
36
+ def SOPT_options(workflow, task)
37
+ sopt_options = []
38
+ workflow.rec_inputs(task.name).each do |name|
39
+ short = name.to_s.chars.first
40
+ boolean = workflow.rec_input_types(task.name)[name].to_sym == :boolean
41
+
42
+ sopt_options << "-#{short}--#{name}#{boolean ? '' : '*'}"
43
+ end
44
+
45
+ sopt_options * ":"
46
+ end
47
+
48
+ def fix_options(workflow, task, job_options)
49
+ option_types = workflow.rec_input_types(task.name)
50
+
51
+ job_options_cleaned = {}
52
+
53
+ job_options.each do |name, value|
54
+ value = case option_types[name].to_sym
55
+ when :float
56
+ value.to_f
57
+ when :integer
58
+ value.to_i
59
+ when :string, :text
60
+ case
61
+ when value == '-'
62
+ STDIN.read
63
+ when (String === value and File.exists?(value) and not File.directory?(value))
64
+ Open.read(value)
65
+ else
66
+ value
67
+ end
68
+ when :array
69
+ if Array === value
70
+ value
71
+ else
72
+ str = case
73
+ when value == '-'
74
+ STDIN.read
75
+ when (String === value and File.exists?(value))
76
+ Open.read(value)
77
+ else
78
+ value
79
+ end
80
+
81
+ if $array_separator
82
+ str.split(/#{$array_separator}/)
83
+ else
84
+ str.split(/[,|\s]/)
85
+ end
86
+ end
87
+ when :tsv
88
+ case value
89
+ when TSV
90
+ value
91
+ when '-'
92
+ TSV.open(STDIN)
93
+ else
94
+ TSV.open(value)
95
+ end
96
+ else
97
+ value
98
+ end
99
+ job_options_cleaned[name] = value
100
+ end
101
+
102
+ job_options_cleaned
103
+ end
104
+
105
+ options = SOPT.get <<EOF
106
+ -h--help Show this help:
107
+ -as--array_separator* Change the character that separates elements of Arrays, ',', '|', or '\\n' by default:
108
+ -cl--clean Clean the last step of the job so that it gets recomputed:
109
+ -rcl--recursive_clean Clean the last step and its dependencies to recompute the job completely:
110
+ -n--name* Job name to use. The name 'Default' is used by default:
111
+ -pn--printname Print the name of the job and exit without starting it:
112
+ -rw--require_workflow* Workflows to require, separated by commas
113
+ EOF
114
+
115
+ (options[:require_workflow] || "").split(',').each do |workflow|
116
+ Workflow.require_workflow workflow
117
+ end
118
+
119
+ study = ARGV.shift.dup
120
+ if Open.exists? study
121
+ dir = study
122
+ study = Study.setup(File.basename(study))
123
+ study.dir = Path.setup(dir, Study)
124
+ else
125
+ Study.setup(study)
126
+ end
127
+ workflow = study.workflow
128
+
129
+ usage if workflow.nil?
130
+
131
+ task = ARGV.shift
132
+
133
+ # Set log, fork, clean, recursive_clean and help
134
+ help = !!options.delete(:help)
135
+ do_fork = !!options.delete(:fork)
136
+ do_exec = !!options.delete(:exec)
137
+ clean = !!options.delete(:clean)
138
+ recursive_clean = !!options.delete(:recursive_clean)
139
+ $array_separator = options.delete(:array_separator)
140
+
141
+ # Set task
142
+ namespace = nil, nil
143
+
144
+ case
145
+ when task.nil?
146
+ usage workflow
147
+ when (task =~ /\./)
148
+ namespace, task = options.delete(:task).split('.')
149
+ namespace = Misc.string2const(namespace)
150
+ else
151
+ task_name = task.to_sym
152
+ task = workflow.tasks[task_name]
153
+ raise "Task not found: #{ task_name }" if task.nil?
154
+ end
155
+
156
+ usage workflow, task if help
157
+
158
+ name = options.delete(:name) || "Default"
159
+
160
+ # get job args
161
+ sopt_option_string = SOPT_options(workflow, task)
162
+ job_options = SOPT.get sopt_option_string
163
+ job_options = fix_options(workflow, task, job_options)
164
+
165
+ #- get job
166
+
167
+ job = study.job(task.name, job_options)
168
+
169
+ # clean job
170
+ if clean and job.done? != false
171
+ job.clean
172
+ sleep 1
173
+ job = study.job(task.name, job_options)
174
+ end
175
+
176
+ if recursive_clean and job.done?
177
+ job.recursive_clean
178
+ sleep 1
179
+ job = study.job(task.name, job_options)
180
+ end
181
+
182
+ # run
183
+ if do_exec
184
+ res = job.exec
185
+ case
186
+ when Array === res
187
+ puts res * "\n"
188
+ when TSV === res
189
+ puts res
190
+ when Hash === res
191
+ puts res.to_yaml
192
+ else
193
+ puts res
194
+ end
195
+ exit 0
196
+ end
197
+
198
+ if do_fork
199
+ job.fork
200
+ while not job.done?
201
+ Log.debug "#{job.step}: #{job.messages.last}"
202
+ sleep 2
203
+ end
204
+ raise job.messages.last if job.error?
205
+ else
206
+ res = job.run(true)
207
+ end
208
+
209
+ if options.delete(:printname)
210
+ puts job.name
211
+ exit 0
212
+ else
213
+ Log.low "Job name: #{job.name}"
214
+ end
215
+
216
+ if Step === res
217
+ puts Open.read(res.path) if File.exists? res.path
218
+ else
219
+ puts res
220
+ end
221
+
222
+ exit 0