rbbt-util 5.2.4 → 5.3.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. 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
+