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
@@ -145,7 +145,7 @@ module TSV
145
145
 
146
146
  unless TSV === other
147
147
  other_identifier_files = other.identifier_files if other.respond_to? :identifier_files
148
- other = TSV.open(other, :persist => options[:persist_input] == true) unless TSV === other
148
+ other = TSV.open(other, :persist => options[:persist_input] == true)
149
149
  other.identifiers = other_identifier_files
150
150
  end
151
151
 
@@ -161,6 +161,7 @@ module TSV
161
161
 
162
162
  case
163
163
  when key_field == other.key_field
164
+ Log.debug "Attachment with same key: #{other.key_field}"
164
165
  attach_same_key other, fields
165
166
  when (not in_namespace and self.fields.include?(other.key_field))
166
167
  Log.debug "Found other's key field: #{other.key_field}"
@@ -171,6 +172,7 @@ module TSV
171
172
  else
172
173
  index = TSV.find_traversal(self, other, options)
173
174
  raise "Cannot traverse identifiers" if index.nil?
175
+ Log.debug "Attachment with index: #{other.key_field}"
174
176
  attach_index other, index, fields
175
177
  end
176
178
  Log.debug("Attachment of fields:#{fields.inspect} from #{other.filename.inspect} finished.")
@@ -10,10 +10,16 @@ module TSV
10
10
  with_unnamed do
11
11
  through do |key, values|
12
12
  self[key] = [] if self[key].nil?
13
+ current = self[key]
14
+ current = [current] unless Array === current
13
15
  if other.include? key
14
16
  case
15
17
  when other.type == :flat
16
- new_values = [other[key]]
18
+ if type == :flat
19
+ new_values = other[key]
20
+ else
21
+ new_values = [other[key]]
22
+ end
17
23
  when other.type == :single
18
24
  new_values = [other[key]]
19
25
  else
@@ -23,19 +29,23 @@ module TSV
23
29
  new_values.collect!{|v| [v]} if type == :double and not other.type == :double
24
30
  new_values.collect!{|v| v.nil? ? nil : (other.type == :single ? v : v.first)} if not type == :double and other.type == :double
25
31
 
26
- self[key] = self[key].concat new_values
32
+ new_values.flatten if type == :flat
33
+
34
+ self[key] = current.concat new_values
27
35
  else
28
36
  if type == :double
29
- self[key] = self[key].concat [[]] * fields.length
37
+ self[key] = current.concat [[]] * fields.length
30
38
  else
31
- self[key] = self[key].concat [""] * fields.length
39
+ self[key] = current.concat [""] * fields.length
32
40
  end
33
41
  end
34
42
  end
35
43
  end
36
44
  end
37
45
 
38
- self.fields = self.fields.concat other.fields.values_at *fields
46
+ self.type = :list if self.type == :single
47
+
48
+ self.fields = self.fields.concat fields
39
49
  end
40
50
 
41
51
  def attach_source_key(other, source, options = {})
@@ -141,11 +151,16 @@ module TSV
141
151
  source_key
142
152
  end
143
153
  else
144
- other[source_key][pos]
154
+ if other.type == :flat
155
+ other[source_key]
156
+ else
157
+ other[source_key][pos]
158
+ end
145
159
  end
146
160
  end
147
161
  new_values.collect!{|v| v.nil? ? [[]] : [v]} if type == :double and not other.type == :double
148
162
  new_values.collect!{|v| v.nil? ? nil : (other.type == :single ? v : v.first)} if not type == :double and other.type == :double
163
+ new_values.flatten! if type == :flat
149
164
  all_new_values << new_values
150
165
  end
151
166
  end
@@ -160,6 +175,8 @@ module TSV
160
175
 
161
176
  current = self[key] || [[]] * fields.length
162
177
 
178
+ current = [current] unless Array === current
179
+
163
180
  if current.length > length
164
181
  all_new_values << current.slice!(length..current.length - 1)
165
182
  end
@@ -172,12 +189,14 @@ module TSV
172
189
 
173
190
  current += all_new_values
174
191
 
175
- self[key] = current
192
+ self[key].replace current
176
193
  end
177
194
  end
178
195
  end
179
196
  end
180
197
 
198
+ self.type = :list if self.type == :single
199
+
181
200
  self.fields = self.fields.concat field_names
182
201
  end
183
202
 
@@ -219,7 +238,7 @@ module TSV
219
238
  end
220
239
 
221
240
  def self.build_traverse_index(files, options = {})
222
- options = Misc.add_defaults options, :in_namespace => false, :persist_input => false
241
+ options = Misc.add_defaults options, :in_namespace => false, :persist_input => true
223
242
  in_namespace = options[:in_namespace]
224
243
  persist_input = options[:persist_input]
225
244
 
@@ -237,7 +256,7 @@ module TSV
237
256
  nil
238
257
  else
239
258
  Log.debug "Data index required"
240
- data_file.index :target => data_key, :fields => [data_file.key_field], :persist => false
259
+ data_file.index :target => data_key, :fields => [data_file.key_field], :persist => false, :type => (data_file.type == :single ? :single : :flat)
241
260
  end
242
261
 
243
262
  current_index = data_index
@@ -257,7 +276,12 @@ module TSV
257
276
  if values.nil?
258
277
  nil
259
278
  else
260
- next_index.values_at(*values).flatten.collect.to_a
279
+ new_values = next_index.values_at(*values).flatten
280
+ if current_index.type == :single
281
+ new_values.first
282
+ else
283
+ new_values
284
+ end
261
285
  end
262
286
  end
263
287
  current_index.fields = [next_key]
@@ -85,8 +85,8 @@ module TSV
85
85
  values.unshift key
86
86
 
87
87
  values.uniq.each do |value|
88
- case
89
- when index_type == :double
88
+ case index_type
89
+ when :double
90
90
  if not new.include? value
91
91
  new[value] = [[key]]
92
92
  else
@@ -94,6 +94,15 @@ module TSV
94
94
  current[0] << key
95
95
  new[value] = current
96
96
  end
97
+ when :flat
98
+ if not new.include? value
99
+ new[value] = [key]
100
+ else
101
+ current = new[value]
102
+ current << key
103
+ new[value] = current
104
+ end
105
+
97
106
  else
98
107
  new[value] = key unless new.include? value
99
108
  end
@@ -101,7 +110,7 @@ module TSV
101
110
  end
102
111
  end
103
112
 
104
- TSV.setup(new, :serializer => index_type, :type => index_type, :filename => filename, :fields => [new_key_field], :key_field => new_fields * ", ")
113
+ TSV.setup(new, :type => index_type, :filename => filename, :fields => [new_key_field], :key_field => new_fields * ", ")
105
114
  end
106
115
  end
107
116
  end
@@ -345,6 +345,8 @@ module TSV
345
345
  end
346
346
  else
347
347
  pos = identify_field method
348
+ raise "Field #{ method } not identified. Available: #{ fields * ", " }" if pos.nil?
349
+
348
350
  through do |key, values|
349
351
  new[key] = values if yield(values[pos])
350
352
  end
@@ -451,11 +453,32 @@ module TSV
451
453
  end
452
454
  end
453
455
 
454
- new.type = :single
456
+ case type
457
+ when :double, :flat
458
+ new.type = :flat
459
+ else
460
+ new.type = :single
461
+ end
455
462
 
456
463
  new
457
464
  end
458
465
 
466
+ def process_key(&block)
467
+ new = annotate({})
468
+ through do |key, values|
469
+ key = case
470
+ when block.arity == 1
471
+ yield(key)
472
+ when block.arity == 2
473
+ yield(key, values)
474
+ else
475
+ raise "Unexpected arity in block, must be 1, 2 or 3: #{block.arity}"
476
+ end
477
+ new[key] = values
478
+ end
479
+ new
480
+ end
481
+
459
482
  def process(field, &block)
460
483
  field_pos = identify_field field
461
484
 
@@ -498,6 +521,7 @@ module TSV
498
521
  self[key] = values
499
522
  end
500
523
  end
524
+
501
525
  self
502
526
  end
503
527
 
@@ -370,6 +370,7 @@ module TSV
370
370
  data.key_field = @key_field
371
371
  data.fields = @fields
372
372
  data.namespace = @namespace
373
+ data.cast = @cast if Symbol === @cast
373
374
  data
374
375
  end
375
376
  end
data/lib/rbbt/util/R.rb CHANGED
@@ -7,7 +7,12 @@ module R
7
7
  UTIL = File.join(LIB_DIR, 'util.R')
8
8
 
9
9
  def self.run(command, options = {})
10
- cmd = "source('#{UTIL}');\n"
10
+ cmd =<<-EOF
11
+ # Loading basic rbbt environment
12
+ source('#{UTIL}');
13
+
14
+ EOF
15
+
11
16
  case
12
17
  when IO === command
13
18
  cmd << command.read
@@ -19,7 +24,15 @@ module R
19
24
 
20
25
  Log.debug "R Script:\n#{ cmd }"
21
26
 
22
- CMD.cmd('R --vanilla --slave --quiet', options.merge(:in => cmd))
27
+ if options.delete :monitor
28
+ io = CMD.cmd('R --vanilla --slave --quiet', options.merge(:in => cmd, :pipe => true))
29
+ while line = io.gets
30
+ puts line
31
+ end
32
+ nil
33
+ else
34
+ CMD.cmd('R --vanilla --slave --quiet', options.merge(:in => cmd))
35
+ end
23
36
  end
24
37
 
25
38
  def self.interactive(init_file, options = {})
@@ -29,9 +42,9 @@ module R
29
42
  def self.interactive(script, options = {})
30
43
  TmpFile.with_file do |init_file|
31
44
  Open.write(init_file) do |file|
32
- profile = File.join(ENV["HOME"], ".Rprofile")
33
- file.puts "source('#{profile}');\n" if File.exists? profile
45
+ file.puts "# Loading basic rbbt environment"
34
46
  file.puts "source('#{R::UTIL}');\n"
47
+ file.puts
35
48
  file.puts script
36
49
  end
37
50
  CMD.cmd("env R_PROFILE='#{init_file}' xterm R")
@@ -40,12 +53,21 @@ module R
40
53
 
41
54
  def self.ruby2R(object)
42
55
  case object
56
+ when nil
57
+ "NULL"
58
+ when TSV
59
+ #"as.matrix(data.frame(c(#{object.transpose("Field").collect{|k,v| "#{k}=" << R.ruby2R(v)}.flatten * ", "}), row.names=#{R.ruby2R object.keys}))"
60
+ "matrix(#{R.ruby2R object.values},dimnames=list(#{R.ruby2R object.keys}, #{R.ruby2R object.fields}))"
61
+ when Symbol
62
+ "#{ object }"
43
63
  when String
44
64
  "'#{ object }'"
45
- when Fixnum
65
+ when Fixnum, Float
46
66
  object
47
67
  when Array
48
68
  "c(#{object.collect{|e| ruby2R(e) } * ", "})"
69
+ else
70
+ raise "Type of object not known: #{ object.inspect }"
49
71
  end
50
72
  end
51
73
 
@@ -58,13 +80,21 @@ module TSV
58
80
  Open.write(f, self.to_s)
59
81
  Log.debug(R.run(
60
82
  <<-EOF
83
+ ## Loading tsv into data
61
84
  data = rbbt.tsv('#{f}');
85
+
62
86
  #{script.strip}
87
+
88
+ ## Resaving data
63
89
  if (! is.null(data)){ rbbt.tsv.write('#{f}', data); }
64
90
  EOF
65
91
  ).read)
66
92
  open_options = Misc.add_defaults open_options, :type => :list
67
- TSV.open(f, open_options) unless open_options[:ignore_output]
93
+ if open_options[:raw]
94
+ Open.read(f)
95
+ else
96
+ TSV.open(f, open_options) unless open_options[:ignore_output]
97
+ end
68
98
  end
69
99
  end
70
100
 
data/lib/rbbt/util/cmd.rb CHANGED
@@ -105,6 +105,7 @@ module CMD
105
105
  pipe = options.delete(:pipe)
106
106
  post = options.delete(:post)
107
107
  log = options.delete(:log)
108
+ dont_close_in = options.delete(:dont_close_in)
108
109
 
109
110
  log = true if log.nil?
110
111
 
@@ -186,7 +187,7 @@ module CMD
186
187
  end
187
188
 
188
189
  sin.close unless sin.closed?
189
- in_content.close unless in_content.closed?
190
+ in_content.close unless in_content.closed? or dont_close_in
190
191
  rescue
191
192
  Process.kill "INT", pid
192
193
  raise $!
@@ -0,0 +1,250 @@
1
+ # Copyright (c) 2007 McClain Looney
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+
22
+ # Implements a color (r,g,b + a) with conversion to/from web format (eg #aabbcc), and
23
+ # with a number of utilities to lighten, darken and blend values.
24
+ class Color
25
+
26
+ attr_reader :r, :g, :b, :a
27
+
28
+ # Table for conversion to hex
29
+ HEXVAL = (('0'..'9').to_a).concat(('A'..'F').to_a).freeze
30
+ # Default value for #darken, #lighten etc.
31
+ BRIGHTNESS_DEFAULT = 0.2
32
+
33
+ # Constructor. Inits to white (#FFFFFF) by default, or accepts any params
34
+ # supported by #parse.
35
+ def initialize(*args)
36
+ @r = 255
37
+ @g = 255
38
+ @b = 255
39
+ @a = 255
40
+
41
+ if args.size.between?(3,4)
42
+ self.r = args[0]
43
+ self.g = args[1]
44
+ self.b = args[2]
45
+ self.a = args[3] if args[3]
46
+ else
47
+ set(*args)
48
+ end
49
+ end
50
+
51
+ # All-purpose setter - pass in another Color, '#000000', rgb vals... whatever
52
+ def set(*args)
53
+ val = Color.parse(*args)
54
+ unless val.nil?
55
+ self.r = val.r
56
+ self.g = val.g
57
+ self.b = val.b
58
+ self.a = val.a
59
+ end
60
+ self
61
+ end
62
+
63
+ # Test for equality, accepts string vals as well, eg Color.new('aaa') == '#AAAAAA' => true
64
+ def ==(val)
65
+ val = Color.parse(val)
66
+ return false if val.nil?
67
+ return r == val.r && g == val.g && b == val.b && a == val.a
68
+ end
69
+
70
+ # Setters for individual channels - take 0-255 or '00'-'FF' values
71
+ def r=(val); @r = from_hex(val); end
72
+ def g=(val); @g = from_hex(val); end
73
+ def b=(val); @b = from_hex(val); end
74
+ def a=(val); @a = from_hex(val); end
75
+
76
+ # Attempt to read in a string and parse it into values
77
+ def self.parse(*args)
78
+ case args.size
79
+
80
+ when 0 then
81
+ return nil
82
+
83
+ when 1 then
84
+ val = args[0]
85
+
86
+ # Trivial parse... :-)
87
+ return val if val.is_a?(Color)
88
+
89
+ # Single value, assume grayscale
90
+ return Color.new(val, val, val) if val.is_a?(Fixnum)
91
+
92
+ # Assume string
93
+ str = val.to_s.upcase
94
+ str = str[/[0-9A-F]{3,8}/] || ''
95
+ case str.size
96
+ when 3, 4 then
97
+ r, g, b, a = str.scan(/[0-9A-F]/)
98
+ when 6,8 then
99
+ r, g, b, a = str.scan(/[0-9A-F]{2}/)
100
+ else
101
+ return nil
102
+ end
103
+
104
+ return Color.new(r,g,b,a || 255)
105
+
106
+ when 3,4 then
107
+ return Color.new(*args)
108
+
109
+ end
110
+ nil
111
+ end
112
+
113
+ def inspect
114
+ to_s(true)
115
+ end
116
+
117
+ def to_s(add_hash = true)
118
+ trans? ? to_rgba(add_hash) : to_rgb(add_hash)
119
+ end
120
+
121
+ def to_rgb(add_hash = true)
122
+ (add_hash ? '#' : '') + to_hex(r) + to_hex(g) + to_hex(b)
123
+ end
124
+
125
+ def to_rgba(add_hash = true)
126
+ to_rgb(add_hash) + to_hex(a)
127
+ end
128
+
129
+ def opaque?
130
+ @a == 255
131
+ end
132
+
133
+ def trans?
134
+ @a != 255
135
+ end
136
+
137
+ def grayscale?
138
+ @r == @g && @g == @b
139
+ end
140
+
141
+ # Lighten color towards white. 0.0 is a no-op, 1.0 will return #FFFFFF
142
+ def lighten(amt = BRIGHTNESS_DEFAULT)
143
+ return self if amt <= 0
144
+ return WHITE if amt >= 1.0
145
+ val = Color.new(self)
146
+ val.r += ((255-val.r) * amt).to_i
147
+ val.g += ((255-val.g) * amt).to_i
148
+ val.b += ((255-val.b) * amt).to_i
149
+ val
150
+ end
151
+
152
+ # In place version of #lighten
153
+ def lighten!(amt = BRIGHTNESS_DEFAULT)
154
+ set(lighten(amt))
155
+ self
156
+ end
157
+
158
+ # Darken a color towards full black. 0.0 is a no-op, 1.0 will return #000000
159
+ def darken(amt = BRIGHTNESS_DEFAULT)
160
+ return self if amt <= 0
161
+ return BLACK if amt >= 1.0
162
+ val = Color.new(self)
163
+ val.r -= (val.r * amt).to_i
164
+ val.g -= (val.g * amt).to_i
165
+ val.b -= (val.b * amt).to_i
166
+ val
167
+ end
168
+
169
+ # In place version of #darken
170
+ def darken!(amt = BRIGHTNESS_DEFAULT)
171
+ set(darken(amt))
172
+ self
173
+ end
174
+
175
+ # Convert to grayscale, using perception-based weighting
176
+ def grayscale
177
+ val = Color.new(self)
178
+ val.r = val.g = val.b = (0.2126 * val.r + 0.7152 * val.g + 0.0722 * val.b)
179
+ val
180
+ end
181
+
182
+ # In place version of #grayscale
183
+ def grayscale!
184
+ set(grayscale)
185
+ self
186
+ end
187
+
188
+ # Blend to a color amt % towards another color value, eg
189
+ # red.blend(blue, 0.5) will be purple, white.blend(black, 0.5) will be gray, etc.
190
+ def blend(other, amt)
191
+ other = Color.parse(other)
192
+ return Color.new(self) if amt <= 0 || other.nil?
193
+ return Color.new(other) if amt >= 1.0
194
+ val = Color.new(self)
195
+ val.r += ((other.r - val.r)*amt).to_i
196
+ val.g += ((other.g - val.g)*amt).to_i
197
+ val.b += ((other.b - val.b)*amt).to_i
198
+ val
199
+ end
200
+
201
+ # In place version of #blend
202
+ def blend!(other, amt)
203
+ set(blend(other, amt))
204
+ self
205
+ end
206
+
207
+ # Class-level version for explicit blends of two values, useful with constants
208
+ def self.blend(col1, col2, amt)
209
+ col1 = Color.parse(col1)
210
+ col2 = Color.parse(col2)
211
+ col1.blend(col2, amt)
212
+ end
213
+
214
+ protected
215
+
216
+ # Convert int to string hex, eg 255 => 'FF'
217
+ def to_hex(val)
218
+ HEXVAL[val / 16] + HEXVAL[val % 16]
219
+ end
220
+
221
+ # Convert int or string to int, eg 80 => 80, 'FF' => 255, '7' => 119
222
+ def from_hex(val)
223
+ if val.is_a?(String)
224
+ # Double up if single char form
225
+ val = val + val if val.size == 1
226
+ # Convert to integer
227
+ val = val.hex
228
+ end
229
+ # Clamp
230
+ val = 0 if val < 0
231
+ val = 255 if val > 255
232
+ val
233
+ end
234
+
235
+ public
236
+
237
+ # Some constants for general use
238
+ WHITE = Color.new(255,255,255).freeze
239
+ BLACK = Color.new(0,0,0).freeze
240
+
241
+ end
242
+
243
+ # "Global" method for creating Color objects, eg:
244
+ # new_color = rgb(params[:new_color])
245
+ # style="border: 1px solid <%= rgb(10,50,80).lighten %>"
246
+ def rgb(*args)
247
+ Color.parse(*args)
248
+ end
249
+
250
+