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.
- checksums.yaml +8 -8
- data/bin/rbbt +23 -10
- data/bin/rbbt_monitor.rb +8 -8
- data/lib/rbbt/annotations.rb +22 -1
- data/lib/rbbt/annotations/util.rb +1 -1
- data/lib/rbbt/entity.rb +162 -0
- data/lib/rbbt/fix_width_table.rb +7 -0
- data/lib/rbbt/persist.rb +16 -9
- data/lib/rbbt/persist/tsv.rb +14 -8
- data/lib/rbbt/resource.rb +1 -6
- data/lib/rbbt/resource/path.rb +23 -27
- data/lib/rbbt/tsv.rb +33 -4
- data/lib/rbbt/tsv/accessor.rb +100 -57
- data/lib/rbbt/tsv/attach.rb +3 -1
- data/lib/rbbt/tsv/attach/util.rb +34 -10
- data/lib/rbbt/tsv/index.rb +12 -3
- data/lib/rbbt/tsv/manipulate.rb +25 -1
- data/lib/rbbt/tsv/parser.rb +1 -0
- data/lib/rbbt/util/R.rb +36 -6
- data/lib/rbbt/util/cmd.rb +2 -1
- data/lib/rbbt/util/color.rb +250 -0
- data/lib/rbbt/util/colorize.rb +57 -0
- data/lib/rbbt/util/misc.rb +57 -19
- data/lib/rbbt/util/named_array.rb +66 -14
- data/lib/rbbt/util/open.rb +134 -10
- data/lib/rbbt/util/semaphore.rb +71 -0
- data/lib/rbbt/workflow.rb +34 -7
- data/lib/rbbt/workflow/accessor.rb +12 -8
- data/lib/rbbt/workflow/step.rb +44 -28
- data/lib/rbbt/workflow/usage.rb +3 -0
- data/share/lib/R/util.R +31 -0
- data/share/rbbt_commands/app/start +5 -4
- data/share/rbbt_commands/study/task +222 -0
- data/share/rbbt_commands/tsv/attach +13 -0
- data/share/rbbt_commands/tsv/change_id +15 -0
- data/share/rbbt_commands/tsv/info +3 -1
- data/share/rbbt_commands/workflow/task +14 -15
- data/test/rbbt/test_entity.rb +221 -0
- data/test/rbbt/test_tsv.rb +2 -1
- data/test/rbbt/test_workflow.rb +0 -2
- data/test/rbbt/tsv/test_accessor.rb +2 -2
- data/test/rbbt/util/test_R.rb +9 -2
- data/test/rbbt/util/test_colorize.rb +12 -0
- data/test/rbbt/util/test_misc.rb +0 -5
- data/test/rbbt/util/test_open.rb +31 -0
- data/test/rbbt/workflow/test_step.rb +32 -0
- metadata +13 -2
data/lib/rbbt/tsv/attach.rb
CHANGED
@@ -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)
|
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.")
|
data/lib/rbbt/tsv/attach/util.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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] =
|
37
|
+
self[key] = current.concat [[]] * fields.length
|
30
38
|
else
|
31
|
-
self[key] =
|
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.
|
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
|
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]
|
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 =>
|
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
|
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]
|
data/lib/rbbt/tsv/index.rb
CHANGED
@@ -85,8 +85,8 @@ module TSV
|
|
85
85
|
values.unshift key
|
86
86
|
|
87
87
|
values.uniq.each do |value|
|
88
|
-
case
|
89
|
-
when
|
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, :
|
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
|
data/lib/rbbt/tsv/manipulate.rb
CHANGED
@@ -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
|
-
|
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
|
|
data/lib/rbbt/tsv/parser.rb
CHANGED
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
+
|