rbbt-util 5.1.0 → 5.2.0

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.
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
-