rbbt-util 5.1.0 → 5.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/LICENSE +1 -1
  2. data/README.rdoc +2 -2
  3. data/bin/rbbt +45 -0
  4. data/bin/rbbt_dangling_locks.rb +9 -0
  5. data/bin/rbbt_monitor.rb +12 -10
  6. data/bin/run_workflow.rb +80 -18
  7. data/lib/rbbt.rb +1 -1
  8. data/lib/rbbt/annotations.rb +1 -19
  9. data/lib/rbbt/annotations/annotated_array.rb +23 -0
  10. data/lib/rbbt/fix_width_table.rb +2 -2
  11. data/lib/rbbt/persist.rb +13 -5
  12. data/lib/rbbt/persist/tsv.rb +2 -0
  13. data/lib/rbbt/resource.rb +4 -4
  14. data/lib/rbbt/resource/path.rb +35 -10
  15. data/lib/rbbt/resource/util.rb +54 -47
  16. data/lib/rbbt/tsv.rb +17 -15
  17. data/lib/rbbt/tsv/accessor.rb +35 -37
  18. data/lib/rbbt/tsv/excel.rb +3 -1
  19. data/lib/rbbt/tsv/manipulate.rb +27 -4
  20. data/lib/rbbt/tsv/parser.rb +13 -7
  21. data/lib/rbbt/util/R.rb +11 -1
  22. data/lib/rbbt/util/misc.rb +182 -26
  23. data/lib/rbbt/util/named_array.rb +14 -7
  24. data/lib/rbbt/util/open.rb +2 -1
  25. data/lib/rbbt/util/tmpfile.rb +16 -1
  26. data/lib/rbbt/workflow.rb +63 -101
  27. data/lib/rbbt/workflow/accessor.rb +19 -9
  28. data/lib/rbbt/workflow/annotate.rb +33 -64
  29. data/lib/rbbt/workflow/definition.rb +71 -0
  30. data/lib/rbbt/workflow/soap.rb +15 -5
  31. data/lib/rbbt/workflow/step.rb +57 -8
  32. data/lib/rbbt/workflow/usage.rb +72 -0
  33. data/share/lib/R/util.R +12 -0
  34. data/share/rbbt_commands/conf/web_user/add +26 -0
  35. data/share/rbbt_commands/conf/web_user/list +9 -0
  36. data/share/rbbt_commands/conf/web_user/remove +18 -0
  37. data/share/rbbt_commands/workflow/remote/add +11 -0
  38. data/share/rbbt_commands/workflow/remote/list +9 -0
  39. data/share/rbbt_commands/workflow/remote/remove +9 -0
  40. data/share/rbbt_commands/workflow/task +181 -0
  41. data/test/rbbt/test_resource.rb +2 -1
  42. data/test/rbbt/test_workflow.rb +13 -0
  43. data/test/rbbt/tsv/test_manipulate.rb +18 -0
  44. data/test/rbbt/util/test_misc.rb +19 -39
  45. data/test/rbbt/util/test_tmpfile.rb +8 -0
  46. data/test/rbbt/workflow/test_soap.rb +2 -0
  47. metadata +31 -2
@@ -8,35 +8,42 @@ module NamedArray
8
8
  attr_accessor :fields
9
9
  attr_accessor :key
10
10
  attr_accessor :entity_options
11
+ attr_accessor :entity_templates
11
12
 
12
- def self.setup(array, fields, key = nil, entity_options = nil)
13
+ def entity_templates
14
+ @entity_templates ||= {}
15
+ end
16
+
17
+ def self.setup(array, fields, key = nil, entity_options = nil, entity_templates = nil)
13
18
  array.extend NamedArray unless NamedArray === array
14
19
  array.fields = fields
15
20
  array.key = key
16
- array.entity_options = entity_options
21
+ array.entity_options = entity_options unless entity_options.nil?
22
+ array.entity_templates = entity_templates unless entity_templates.nil?
17
23
  array
18
24
  end
19
25
 
20
26
  def prepare_entity(entity, field, options = {})
21
27
  return entity if entity.nil?
22
28
  return entity unless defined? Entity
23
- @entity_templates ||= {}
24
- if (template = @entity_templates[field])
29
+ template = entity_templates[field]
30
+ entity_templates ||= {}
31
+ if template
25
32
  entity = template.annotate(entity.frozen? ? entity.dup : entity)
26
33
  entity.extend AnnotatedArray if Array === entity
27
34
  entity
28
35
  else
29
- if @entity_templates.include? field
36
+ if entity_templates.include? field
30
37
  entity
31
38
  else
32
39
  template = Misc.prepare_entity("TEMPLATE", field, options)
33
40
  if Annotated === template
34
- @entity_templates[field] = template
41
+ entity_templates[field] = template
35
42
  entity = template.annotate(entity.frozen? ? entity.dup : entity)
36
43
  entity.extend AnnotatedArray if Array === entity
37
44
  entity
38
45
  else
39
- @entity_templates[field] = nil
46
+ entity_templates[field] = nil
40
47
  entity
41
48
  end
42
49
  end
@@ -180,6 +180,7 @@ module Open
180
180
  end
181
181
 
182
182
  def self.open(url, options = {})
183
+ return url if IO === url
183
184
  options = Misc.add_defaults options, :noz => false, :mode => 'r'
184
185
 
185
186
  mode = Misc.process_options options, :mode
@@ -283,7 +284,7 @@ module Open
283
284
  f.flock(File::LOCK_UN)
284
285
  end
285
286
  rescue Exception
286
- FileUtils.rm file if File.exists? file
287
+ FileUtils.rm_rf file if File.exists? file
287
288
  raise $!
288
289
  end
289
290
  content.close
@@ -28,8 +28,11 @@ module TmpFile
28
28
  File.join(TMPDIR, random_name(s,max))
29
29
  end
30
30
 
31
- def self.with_file(content = nil, erase = true)
31
+ def self.with_file(content = nil, erase = true, options = {})
32
32
  tmpfile = tmp_file
33
+ if options[:extension]
34
+ tmpfile += ".#{options[:extension]}"
35
+ end
33
36
 
34
37
  File.open(tmpfile, 'w') do |f| f.write content end if content != nil
35
38
 
@@ -39,4 +42,16 @@ module TmpFile
39
42
 
40
43
  result
41
44
  end
45
+
46
+ def self.with_dir(erase = true, options = {})
47
+ tmpdir = tmp_file
48
+
49
+ FileUtils.mkdir_p tmpdir
50
+
51
+ result = yield(tmpdir)
52
+
53
+ FileUtils.rm_rf tmpdir if File.exists?(tmpdir) and erase
54
+
55
+ result
56
+ end
42
57
  end
data/lib/rbbt/workflow.rb CHANGED
@@ -1,14 +1,33 @@
1
+ require 'rbbt/workflow/definition'
1
2
  require 'rbbt/workflow/task'
2
3
  require 'rbbt/workflow/step'
3
- require 'rbbt/workflow/annotate'
4
4
  require 'rbbt/workflow/accessor'
5
5
 
6
6
  module Workflow
7
+ def self.resolve_locals(inputs)
8
+ inputs.each do |name, value|
9
+ if value =~ /^local:(.*?):(.*)/ or
10
+ (Array === value and value.length == 1 and value.first =~ /^local:(.*?):(.*)/) or
11
+ (TSV === value and value.size == 1 and value.keys.first =~ /^local:(.*?):(.*)/)
12
+ task_name = $1
13
+ jobname = $2
14
+ value = load_id(File.join(task_name, jobname)).load
15
+ end
16
+ inputs[name] = value
17
+ end
18
+ end
19
+
20
+ #{{{ WORKFLOW MANAGEMENT
7
21
  class << self
8
22
  attr_accessor :workflows
9
23
  end
10
24
  self.workflows = []
11
25
 
26
+ def self.extended(base)
27
+ self.workflows << base
28
+ base.libdir = Path.caller_lib_dir.tap{|p| p.resource = base}
29
+ end
30
+
12
31
  def self.require_remote_workflow(wf_name, url)
13
32
  require 'rbbt/workflow/rest/client'
14
33
  eval "Object::#{wf_name} = RbbtRestClient.new '#{ url }', '#{wf_name}'"
@@ -95,10 +114,10 @@ module Workflow
95
114
  rescue Exception
96
115
  Log.debug $!.message
97
116
  Log.debug $!.backtrace.first
98
- raise "Workflow not found: #{ wf_name }" if wf_name == Misc.humanize(wf_name)
99
- Log.debug "Trying with humanized: '#{Misc.humanize wf_name}'"
117
+ 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}'"
100
119
  begin
101
- require_local_workflow(Misc.humanize(wf_name))
120
+ require_local_workflow(Misc.snake_case(wf_name))
102
121
  rescue Exception
103
122
  Log.debug $!.message
104
123
  raise "Workflow not found: #{ wf_name }"
@@ -106,121 +125,65 @@ module Workflow
106
125
  end
107
126
  end
108
127
 
109
- def self.extended(base)
110
- if not base.respond_to? :workdir
111
- base.extend AnnotatedModule
112
- class << base
113
- attr_accessor :libdir, :workdir, :tasks, :task_dependencies, :task_description, :dependencies, :asynchronous_exports, :synchronous_exports, :exec_exports, :last_task
114
-
115
- alias prev_workflow_extended extended if methods.include? "extended"
116
-
117
- def extended(object)
118
- self.send(:prev_workflow_extended, object) if methods.include? "prev_workflow_extended"
119
- object.extend Workflow unless Workflow === object
120
-
121
- object.tasks.merge! self.tasks
122
- object.task_dependencies.merge! self.task_dependencies
123
- end
124
-
125
- def dependencies
126
- i = @dependencies; @dependencies = []; i
127
- end
128
-
129
- def task_dependencies
130
- IndiferentHash.setup(@task_dependencies || {})
131
- end
132
-
133
- def tasks
134
- IndiferentHash.setup(@tasks || {})
135
- end
136
- end
128
+ attr_accessor :libdir, :workdir
129
+ attr_accessor :helpers, :tasks
130
+ attr_accessor :task_dependencies, :task_description, :last_task
131
+ attr_accessor :asynchronous_exports, :synchronous_exports, :exec_exports
137
132
 
138
- if defined? Rbbt
139
- base.workdir = Rbbt.var.jobs.find
140
- else
141
- base.workdir = Path.setup('var/jobs')
142
- end
143
- base.tasks = {}
144
- base.dependencies = []
145
- base.task_dependencies = {}
146
- base.task_description = {}
147
- base.asynchronous_exports = []
148
- base.synchronous_exports = []
149
- base.exec_exports = []
150
- base.libdir = Path.caller_lib_dir
151
- end
152
- self.workflows << base
153
- end
133
+ #{{{ ATTR DEFAULTS
154
134
 
155
- # {{{ Task definition helpers
135
+ def workdir
136
+ @workdir ||= if defined? Rbbt
137
+ Rbbt.var.jobs[self].find
138
+ else
139
+ Path.setup('var/jobs')
140
+ end
141
+ end
156
142
 
157
- def task(name, &block)
158
- if Hash === name
159
- result_type = name.first.last
160
- name = name.first.first
161
- else
162
- result_type = :marshal
163
- end
143
+ def libdir
144
+ @libdir = Path.caller_lib_dir if @libdir.nil?
145
+ @libdir
146
+ end
164
147
 
165
- name = name.to_sym
166
-
167
- block = self.method(name) unless block_given?
168
-
169
- result_type = result_type
170
- annotations = {
171
- :name => name,
172
- :inputs => inputs,
173
- :description => description,
174
- :input_types => input_types,
175
- :result_type => Array == result_type ? result_type.to_sym : result_type,
176
- :input_defaults => input_defaults,
177
- :input_descriptions => input_descriptions,
178
- :input_options => input_options,
179
- :result_description => result_description
180
- }
181
-
182
- task = Task.setup(annotations, &block)
148
+ def helpers
149
+ @helpers ||= {}
150
+ end
183
151
 
184
- @last_task = task
185
- @tasks[name] = task
186
- @task_dependencies[name] = dependencies
152
+ def tasks
153
+ @tasks ||= {}
187
154
  end
188
155
 
189
- def export_exec(*names)
190
- @exec_exports.concat names
156
+ def task_dependencies
157
+ @task_dependencies ||= {}
191
158
  end
192
159
 
193
- def export_asynchronous(*names)
194
- @asynchronous_exports.concat names
160
+ def task_description
161
+ @task_description ||= {}
195
162
  end
196
163
 
197
- def export_synchronous(*names)
198
- @synchronous_exports.concat names
164
+ def asynchronous_exports
165
+ @asynchronous_exports ||= []
199
166
  end
200
167
 
201
- # {{{ Job management
168
+ def synchronous_exports
169
+ @synchronous_exports ||= []
170
+ end
202
171
 
203
- def resolve_locals(inputs)
204
- inputs.each do |name, value|
205
- if value =~ /^local:(.*?):(.*)/ or
206
- (Array === value and value.length == 1 and value.first =~ /^local:(.*?):(.*)/) or
207
- (TSV === value and value.size == 1 and value.keys.first =~ /^local:(.*?):(.*)/)
208
- task_name = $1
209
- jobname = $2
210
- value = load_id(File.join(task_name, jobname)).load
211
- end
212
- inputs[name] = value
213
- end
172
+ def exec_exports
173
+ @exec_exports ||= []
214
174
  end
215
175
 
176
+ # {{{ JOB MANAGEMENT
177
+
216
178
  def job(taskname, jobname = nil, inputs = {})
179
+ taskname = taskname.to_sym
217
180
  jobname = "Default" if jobname.nil? or jobname.empty?
218
181
  task = tasks[taskname]
219
182
  raise "Task not found: #{ taskname }" if task.nil?
220
183
 
221
184
  IndiferentHash.setup(inputs)
222
185
 
223
- resolve_locals(inputs)
186
+ Workflow.resolve_locals(inputs)
224
187
 
225
188
  dependencies = real_dependencies(task, jobname, inputs, task_dependencies[taskname] || [])
226
189
 
@@ -230,9 +193,8 @@ module Workflow
230
193
 
231
194
  step = Step.new step_path, task, input_values, dependencies
232
195
 
233
- helpers = @helpers
234
- (class << step; self; end).class_eval do
235
- helpers.each do |name, block|
196
+ helpers.each do |name, block|
197
+ (class << step; self; end).instance_eval do
236
198
  define_method name, &block
237
199
  end
238
200
  end
@@ -242,13 +204,13 @@ module Workflow
242
204
 
243
205
  def load_step(path)
244
206
  task = task_for path
245
- Step.new path, tasks[task]
207
+ Step.new path, tasks[task.to_sym]
246
208
  end
247
209
 
248
210
  def load_id(id)
249
211
  path = File.join(workdir, id)
250
212
  task = task_for path
251
- step = Step.new path, tasks[task]
213
+ step = Step.new path, tasks[task.to_sym]
252
214
  if step.info.include? :dependencies
253
215
  step.dependencies = step.info[:dependencies].collect do |task, job|
254
216
  load_id(File.join(task.to_s, job))
@@ -11,6 +11,10 @@ class Step
11
11
  name.sub(/(.*)_.*/, '\1')
12
12
  end
13
13
 
14
+ def task_name
15
+ @task_name ||= task.name
16
+ end
17
+
14
18
  # {{{ INFO
15
19
 
16
20
  def info_file
@@ -71,8 +75,13 @@ class Step
71
75
  end
72
76
 
73
77
  def done?
74
- status = info[:status]
75
- status == :done or status == :error
78
+ path.exists?
79
+ end
80
+
81
+ def running?
82
+ return nil if not File.exists? info_file
83
+ return nil if info[:pid].nil?
84
+ return Misc.pid_exists? info[:pid]
76
85
  end
77
86
 
78
87
  def error?
@@ -166,6 +175,7 @@ module Workflow
166
175
  end
167
176
 
168
177
  def task_info(name)
178
+ name = name.to_sym
169
179
  task = tasks[name]
170
180
  description = task.description
171
181
  result_description = task.result_description
@@ -187,7 +197,7 @@ module Workflow
187
197
  end
188
198
 
189
199
 
190
- dependencies = @task_dependencies[name].select{|dep| String === dep or Symbol === dep}
200
+ dependencies = task_dependencies[name].select{|dep| String === dep or Symbol === dep}
191
201
  { :id => File.join(self.to_s, name.to_s),
192
202
  :description => description,
193
203
  :export => export,
@@ -213,23 +223,23 @@ module Workflow
213
223
  end
214
224
 
215
225
  def rec_inputs(taskname)
216
- [taskname].concat(rec_dependencies(taskname)).inject([]){|acc, tn| acc.concat tasks[tn].inputs}
226
+ [taskname].concat(rec_dependencies(taskname)).inject([]){|acc, tn| acc.concat tasks[tn.to_sym].inputs}
217
227
  end
218
228
 
219
229
  def rec_input_defaults(taskname)
220
- [taskname].concat(rec_dependencies(taskname)).inject({}){|acc, tn| acc.merge tasks[tn].input_defaults}
230
+ [taskname].concat(rec_dependencies(taskname)).inject({}){|acc, tn| acc.merge tasks[tn.to_sym].input_defaults}
221
231
  end
222
232
 
223
233
  def rec_input_types(taskname)
224
- [taskname].concat(rec_dependencies(taskname)).inject({}){|acc, tn| acc.merge tasks[tn].input_types}
234
+ [taskname].concat(rec_dependencies(taskname)).inject({}){|acc, tn| acc.merge tasks[tn.to_sym].input_types}
225
235
  end
226
236
 
227
237
  def rec_input_descriptions(taskname)
228
- [taskname].concat(rec_dependencies(taskname)).inject({}){|acc, tn| acc.merge tasks[tn].input_descriptions}
238
+ [taskname].concat(rec_dependencies(taskname)).inject({}){|acc, tn| acc.merge tasks[tn.to_sym].input_descriptions}
229
239
  end
230
240
 
231
241
  def rec_input_options(taskname)
232
- [taskname].concat(rec_dependencies(taskname)).inject({}){|acc, tn| acc.merge tasks[tn].input_options}
242
+ [taskname].concat(rec_dependencies(taskname)).inject({}){|acc, tn| acc.merge tasks[tn.to_sym].input_options}
233
243
  end
234
244
 
235
245
 
@@ -240,7 +250,7 @@ module Workflow
240
250
  when Step === dependency
241
251
  dependency
242
252
  when Symbol === dependency
243
- job dependency, jobname, inputs
253
+ job(dependency, jobname, inputs)
244
254
  when Proc === dependency
245
255
  dependency.call jobname, inputs
246
256
  end
@@ -1,79 +1,48 @@
1
1
  module AnnotatedModule
2
- def self.extended(base)
3
- if not base.respond_to? :inputs
4
- class << base
5
- attr_accessor :description, :inputs, :input_types, :input_descriptions, :input_defaults, :input_options, :result_description, :helpers
6
2
 
7
- def description
8
- i = @description; @description = ""; i
3
+ def self.add_consummable_annotation(target, *annotations)
4
+ if annotations.length == 1 and Hash === annotations.first
5
+ annotations.first.each do |annotation, default|
6
+ target.send(:attr_accessor, annotation)
7
+ target.send(:define_method, "consume_#{annotation}") do
8
+ value = instance_variable_get("@#{annotation}") || default.dup
9
+ instance_variable_set("@#{annotation}", default.dup)
10
+ value
9
11
  end
10
-
11
- def inputs
12
- i = @inputs; @inputs = []; i
13
- end
14
-
15
- def input_types
16
- i = @input_types; @input_types = {}; i
17
- end
18
-
19
- def input_descriptions
20
- i = @input_descriptions; @input_descriptions = {}; i
21
- end
22
-
23
- def input_defaults
24
- i = @input_defaults; @input_defaults = {}; i
25
- end
26
-
27
- def input_options
28
- i = @input_options; @input_options = {}; i
29
- end
30
-
31
- def description
32
- i = @description; @description = ""; i
33
- end
34
-
35
- def result_description
36
- i = @result_description; @result_description = nil; i
12
+ end
13
+ else
14
+ annotations.each do |annotation|
15
+ target.send(:attr_accessor, annotation)
16
+ target.send(:define_method, "consume_#{annotation}") do
17
+ value = instance_variable_get("@#{annotation}")
18
+ instance_variable_set("@#{annotation}", nil)
37
19
  end
38
20
  end
39
-
40
- base.description = ""
41
- base.inputs = []
42
- base.input_types = {}
43
- base.input_descriptions = {}
44
- base.input_defaults = {}
45
- base.input_options = {}
46
- base.helpers = {}
47
-
48
21
  end
49
22
  end
50
23
 
51
- def helper(name, &block)
52
- @helpers[name] = block
53
- end
54
-
55
- def returns(text)
56
- @result_description = text
57
- end
58
-
59
- def desc(description)
60
- @description = description
61
- end
62
-
63
- def dep(*dependencies, &block)
64
- dependencies << block if block_given?
65
- @dependencies.concat dependencies
66
- end
24
+ add_consummable_annotation(self,
25
+ :description => "",
26
+ :inputs => [],
27
+ :input_types => {},
28
+ :input_descriptions => {},
29
+ :input_defaults => {},
30
+ :input_options => {})
67
31
 
68
32
  def input(name, type = nil, desc = nil, default = nil, options = nil)
69
33
  name = name.to_sym
70
34
  type = type.to_sym
71
- @inputs << name
72
- @input_types[name] = type unless type.nil?
35
+
36
+ @inputs = [] if @inputs.nil?
37
+ @input_types = {} if @input_types.nil?
38
+ @input_descriptions = {} if @input_descriptions.nil?
39
+ @input_defaults = {} if @input_defaults.nil?
40
+ @input_options = {} if @input_options.nil?
41
+
42
+ @inputs << name
43
+ @input_types[name] = type unless type.nil?
73
44
  @input_descriptions[name] = desc unless desc.nil?
74
- @input_defaults[name] = default unless default.nil?
75
- @input_options[name] = options unless options.nil?
45
+ @input_defaults[name] = default unless default.nil?
46
+ @input_options[name] = options unless options.nil?
76
47
  end
77
48
  end
78
-
79
-