scout-gear 1.1.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.
@@ -0,0 +1,169 @@
1
+ require_relative 'color_class'
2
+ require_relative '../indiferent_hash'
3
+
4
+ require 'term/ansicolor'
5
+
6
+ module Colorize
7
+ def self.colors=(colors)
8
+ @colors = colors
9
+ end
10
+
11
+ def self.colors
12
+ @colors ||= IndiferentHash.setup({green: "#00cd00" , red: "#cd0000" , yellow: "#ffd700" })
13
+ end
14
+
15
+ def self.diverging_colors=(colors)
16
+ @diverging_colors = colors
17
+ end
18
+
19
+ def self.diverging_colors
20
+ @diverging_colors ||=<<~EOF.split("\n")
21
+ #a6cee3
22
+ #1f78b4
23
+ #b2df8a
24
+ #33a02c
25
+ #fb9a99
26
+ #e31a1c
27
+ #fdbf6f
28
+ #ff7f00
29
+ #cab2d6
30
+ #6a3d9a
31
+ #ffff99
32
+ #b15928
33
+ EOF
34
+ end
35
+
36
+
37
+ def self.from_name(color)
38
+ return color if color =~ /^#?[0-9A-F]+$/i
39
+ return colors[color.to_s] if colors.include?(color.to_s)
40
+
41
+ case color.to_s
42
+ when "white"
43
+ '#000'
44
+ when "black"
45
+ '#fff'
46
+ when 'green'
47
+ colors["green3"]
48
+ when 'red'
49
+ colors["red3"]
50
+ when 'yellow'
51
+ colors["gold1"]
52
+ when 'blue'
53
+ colors["RoyalBlue"]
54
+ else
55
+ colors[color.to_s] || color
56
+ end
57
+ end
58
+
59
+ def self.continuous(array, start = "#40324F", eend = "#EABD5D", percent = false)
60
+ start_color = Color.new from_name(start)
61
+ end_color = Color.new from_name(eend)
62
+
63
+ if percent
64
+ array = array.collect{|v| n = v.to_f; n = n > 100 ? 100 : n; n < 0.001 ? 0.001 : n}
65
+ else
66
+ array = array.collect{|v| n = v.to_f; }
67
+ end
68
+ max = array.max
69
+ min = array.min
70
+ range = max - min
71
+ array.collect do |v|
72
+ ratio = (v.to_f-min) / range
73
+ start_color.blend end_color, ratio
74
+ end
75
+ end
76
+
77
+ def self.gradient(array, value, start = :green, eend = :red, percent = false)
78
+ index = array.index value
79
+ colors = continuous(array, start, eend, percent)
80
+ colors[index]
81
+ end
82
+
83
+ def self.rank_gradient(array, value, start = :green, eend = :red, percent = false)
84
+ index = array.index value
85
+ sorted = array.sort
86
+ array = array.collect{|e| sorted.index e}
87
+ colors = continuous(array, start, eend, percent)
88
+ colors[index]
89
+ end
90
+
91
+
92
+ def self.distinct(array)
93
+ colors = diverging_colors.collect{|c| Color.new c }
94
+
95
+ num = array.uniq.length
96
+ times = num / 12
97
+
98
+ all_colors = colors.dup
99
+ factor = 0.3 / times
100
+ times.times do
101
+ all_colors.concat colors.collect{|n| n.darken(factor) }
102
+ end
103
+
104
+ value_color = Hash[*array.uniq.zip(all_colors).flatten]
105
+
106
+ value_color.values_at *array
107
+ end
108
+
109
+ def self.tsv(tsv, options = {})
110
+ values = tsv.values.flatten
111
+ if Numeric === values.first or (values.first.to_f != 0 and values[0] != "0" and values[0] != "0.0")
112
+ value_colors = IndiferentHash.process_to_hash(values){continuous(values)}
113
+ else
114
+ value_colors = IndiferentHash.process_to_hash(values){distinct(values)}
115
+ end
116
+
117
+ if tsv.type == :single
118
+ Hash[*tsv.keys.zip(value_colors.values_at(*values)).flatten]
119
+ else
120
+ Hash[*tsv.keys.zip(values.collect{|vs| value_colors.values_at(*vs)}).flatten]
121
+ end
122
+ end
123
+ end
124
+
125
+ module Log
126
+ extend Term::ANSIColor
127
+
128
+ class << self
129
+ attr_accessor :nocolor
130
+ end
131
+
132
+ self.nocolor = ENV["RBBT_NOCOLOR"] == 'true'
133
+
134
+ WHITE, DARK, GREEN, YELLOW, RED = Color::SOLARIZED.values_at :base0, :base00, :green, :yellow, :magenta
135
+
136
+ SEVERITY_COLOR = [reset, cyan, green, magenta, blue, yellow, red] #.collect{|e| "\033[#{e}"}
137
+ HIGHLIGHT = "\033[1m"
138
+
139
+ def self.uncolor(str)
140
+ "" << Term::ANSIColor.uncolor(str)
141
+ end
142
+
143
+ def self.reset_color
144
+ reset
145
+ end
146
+
147
+ def self.color(severity, str = nil, reset = false)
148
+ return str.dup || "" if nocolor
149
+ color = reset ? Term::ANSIColor.reset : ""
150
+ color << SEVERITY_COLOR[severity] if Integer === severity
151
+ color << Term::ANSIColor.send(severity) if Symbol === severity and Term::ANSIColor.respond_to? severity
152
+ if str.nil?
153
+ color
154
+ else
155
+ color + str.to_s + self.color(0)
156
+ end
157
+ end
158
+
159
+ def self.highlight(str = nil)
160
+ if str.nil?
161
+ return "" if nocolor
162
+ HIGHLIGHT
163
+ else
164
+ return str if nocolor
165
+ HIGHLIGHT + str + color(0)
166
+ end
167
+ end
168
+
169
+ end
@@ -0,0 +1,269 @@
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
+ SOLARIZED = {
34
+ :base03 => '#002b36',
35
+ :base02 => '#073642',
36
+ :base01 => '#586e75',
37
+ :base00 => '#657b83',
38
+ :base0 => '#839496',
39
+ :base1 => '#93a1a1',
40
+ :base2 => '#eee8d5',
41
+ :base3 => '#fdf6e3',
42
+ :yellow => '#b58900',
43
+ :orange => '#cb4b16',
44
+ :red => '#dc322f',
45
+ :magenta => '#d33682',
46
+ :violet => '#6c71c4',
47
+ :blue => '#268bd2',
48
+ :cyan => '#2aa198',
49
+ :green => '#859900',
50
+ }
51
+
52
+ # Constructor. Inits to white (#FFFFFF) by default, or accepts any params
53
+ # supported by #parse.
54
+ def initialize(*args)
55
+ @r = 255
56
+ @g = 255
57
+ @b = 255
58
+ @a = 255
59
+
60
+ if args.size.between?(3,4)
61
+ self.r = args[0]
62
+ self.g = args[1]
63
+ self.b = args[2]
64
+ self.a = args[3] if args[3]
65
+ else
66
+ set(*args)
67
+ end
68
+ end
69
+
70
+ # All-purpose setter - pass in another Color, '#000000', rgb vals... whatever
71
+ def set(*args)
72
+ val = Color.parse(*args)
73
+ unless val.nil?
74
+ self.r = val.r
75
+ self.g = val.g
76
+ self.b = val.b
77
+ self.a = val.a
78
+ end
79
+ self
80
+ end
81
+
82
+ # Test for equality, accepts string vals as well, eg Color.new('aaa') == '#AAAAAA' => true
83
+ def ==(val)
84
+ val = Color.parse(val)
85
+ return false if val.nil?
86
+ return r == val.r && g == val.g && b == val.b && a == val.a
87
+ end
88
+
89
+ # Setters for individual channels - take 0-255 or '00'-'FF' values
90
+ def r=(val); @r = from_hex(val); end
91
+ def g=(val); @g = from_hex(val); end
92
+ def b=(val); @b = from_hex(val); end
93
+ def a=(val); @a = from_hex(val); end
94
+
95
+ # Attempt to read in a string and parse it into values
96
+ def self.parse(*args)
97
+ case args.size
98
+
99
+ when 0 then
100
+ return nil
101
+
102
+ when 1 then
103
+ val = args[0]
104
+
105
+ # Trivial parse... :-)
106
+ return val if val.is_a?(Color)
107
+
108
+ # Single value, assume grayscale
109
+ return Color.new(val, val, val) if val.is_a?(Numeric)
110
+
111
+ # Assume string
112
+ str = val.to_s.upcase
113
+ str = str[/[0-9A-F]{3,8}/] || ''
114
+ case str.size
115
+ when 3, 4 then
116
+ r, g, b, a = str.scan(/[0-9A-F]/)
117
+ when 6,8 then
118
+ r, g, b, a = str.scan(/[0-9A-F]{2}/)
119
+ else
120
+ return nil
121
+ end
122
+
123
+ return Color.new(r,g,b,a || 255)
124
+
125
+ when 3,4 then
126
+ return Color.new(*args)
127
+
128
+ end
129
+ nil
130
+ end
131
+
132
+ def inspect
133
+ to_s(true)
134
+ end
135
+
136
+ def to_s(add_hash = true)
137
+ trans? ? to_rgba(add_hash) : to_rgb(add_hash)
138
+ end
139
+
140
+ def to_rgb(add_hash = true)
141
+ (add_hash ? '#' : '') + to_hex(r) + to_hex(g) + to_hex(b)
142
+ end
143
+
144
+ def to_rgba(add_hash = true)
145
+ to_rgb(add_hash) + to_hex(a)
146
+ end
147
+
148
+ def opaque?
149
+ @a == 255
150
+ end
151
+
152
+ def trans?
153
+ @a != 255
154
+ end
155
+
156
+ def grayscale?
157
+ @r == @g && @g == @b
158
+ end
159
+
160
+ # Lighten color towards white. 0.0 is a no-op, 1.0 will return #FFFFFF
161
+ def lighten(amt = BRIGHTNESS_DEFAULT)
162
+ return self if amt <= 0
163
+ return WHITE if amt >= 1.0
164
+ val = Color.new(self)
165
+ val.r += ((255-val.r) * amt).to_i
166
+ val.g += ((255-val.g) * amt).to_i
167
+ val.b += ((255-val.b) * amt).to_i
168
+ val
169
+ end
170
+
171
+ # In place version of #lighten
172
+ def lighten!(amt = BRIGHTNESS_DEFAULT)
173
+ set(lighten(amt))
174
+ self
175
+ end
176
+
177
+ # Darken a color towards full black. 0.0 is a no-op, 1.0 will return #000000
178
+ def darken(amt = BRIGHTNESS_DEFAULT)
179
+ return self if amt <= 0
180
+ return BLACK if amt >= 1.0
181
+ val = Color.new(self)
182
+ val.r -= (val.r * amt).to_i
183
+ val.g -= (val.g * amt).to_i
184
+ val.b -= (val.b * amt).to_i
185
+ val
186
+ end
187
+
188
+ # In place version of #darken
189
+ def darken!(amt = BRIGHTNESS_DEFAULT)
190
+ set(darken(amt))
191
+ self
192
+ end
193
+
194
+ # Convert to grayscale, using perception-based weighting
195
+ def grayscale
196
+ val = Color.new(self)
197
+ val.r = val.g = val.b = (0.2126 * val.r + 0.7152 * val.g + 0.0722 * val.b)
198
+ val
199
+ end
200
+
201
+ # In place version of #grayscale
202
+ def grayscale!
203
+ set(grayscale)
204
+ self
205
+ end
206
+
207
+ # Blend to a color amt % towards another color value, eg
208
+ # red.blend(blue, 0.5) will be purple, white.blend(black, 0.5) will be gray, etc.
209
+ def blend(other, amt)
210
+ other = Color.parse(other)
211
+ return Color.new(self) if amt <= 0 || other.nil?
212
+ return Color.new(other) if amt >= 1.0
213
+ val = Color.new(self)
214
+ val.r += ((other.r - val.r)*amt).to_i
215
+ val.g += ((other.g - val.g)*amt).to_i
216
+ val.b += ((other.b - val.b)*amt).to_i
217
+ val
218
+ end
219
+
220
+ # In place version of #blend
221
+ def blend!(other, amt)
222
+ set(blend(other, amt))
223
+ self
224
+ end
225
+
226
+ # Class-level version for explicit blends of two values, useful with constants
227
+ def self.blend(col1, col2, amt)
228
+ col1 = Color.parse(col1)
229
+ col2 = Color.parse(col2)
230
+ col1.blend(col2, amt)
231
+ end
232
+
233
+ protected
234
+
235
+ # Convert int to string hex, eg 255 => 'FF'
236
+ def to_hex(val)
237
+ HEXVAL[val / 16] + HEXVAL[val % 16]
238
+ end
239
+
240
+ # Convert int or string to int, eg 80 => 80, 'FF' => 255, '7' => 119
241
+ def from_hex(val)
242
+ if val.is_a?(String)
243
+ # Double up if single char form
244
+ val = val + val if val.size == 1
245
+ # Convert to integer
246
+ val = val.hex
247
+ end
248
+ # Clamp
249
+ val = 0 if val < 0
250
+ val = 255 if val > 255
251
+ val
252
+ end
253
+
254
+ public
255
+
256
+ # Some constants for general use
257
+ WHITE = Color.new(255,255,255).freeze
258
+ BLACK = Color.new(0,0,0).freeze
259
+
260
+ end
261
+
262
+ # "Global" method for creating Color objects, eg:
263
+ # new_color = rgb(params[:new_color])
264
+ # style="border: 1px solid <%= rgb(10,50,80).lighten %>"
265
+ def rgb(*args)
266
+ Color.parse(*args)
267
+ end
268
+
269
+
@@ -0,0 +1,59 @@
1
+ require 'digest/md5'
2
+ module Log
3
+ def self.fingerprint(obj)
4
+ return obj.fingerprint if obj.respond_to?(:fingerprint)
5
+
6
+ case obj
7
+ when nil
8
+ "nil"
9
+ when TrueClass
10
+ "true"
11
+ when FalseClass
12
+ "false"
13
+ when Symbol
14
+ ":" << obj.to_s
15
+ when String
16
+ if obj.length > 100
17
+ digest = Digest::MD5.hexdigest(obj)
18
+ "'" << obj.slice(0,30) << "<...#{obj.length} - #{digest[0..4]}...>" << obj.slice(-10,30)<< "'"
19
+ else
20
+ "'" << obj << "'"
21
+ end
22
+ when IO
23
+ (obj.respond_to?(:filename) and obj.filename ) ? "<IO:" + (obj.filename || obj.inspect + rand(100000)) + ">" : obj.inspect
24
+ when File
25
+ "<File:" + obj.path + ">"
26
+ when Array
27
+ if (length = obj.length) > 10
28
+ "[#{length}--" << (obj.values_at(0,1, length / 2, -2, -1).collect{|e| fingerprint(e)} * ",") << "]"
29
+ else
30
+ "[" << (obj.collect{|e| fingerprint(e) } * ", ") << "]"
31
+ end
32
+ when Hash
33
+ if obj.length > 10
34
+ "H:{"<< fingerprint(obj.keys) << ";" << fingerprint(obj.values) << "}"
35
+ else
36
+ new = "{"
37
+ obj.each do |k,v|
38
+ new << fingerprint(k) << '=>' << fingerprint(v) << ' '
39
+ end
40
+ if new.length > 1
41
+ new[-1] = "}"
42
+ else
43
+ new << '}'
44
+ end
45
+ new
46
+ end
47
+ when Float
48
+ if obj.abs > 10
49
+ "%.1f" % obj
50
+ elsif obj.abs > 1
51
+ "%.3f" % obj
52
+ else
53
+ "%.6f" % obj
54
+ end
55
+ else
56
+ obj.to_s
57
+ end
58
+ end
59
+ end