col 1.0.0 → 1.0.1a
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.
- data/README +35 -35
- data/lib/col.rb +350 -342
- data/test/col.rb +241 -229
- data/test/col_db.rb +14 -14
- metadata +33 -57
data/lib/col.rb
CHANGED
@@ -1,342 +1,350 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'term/ansicolor'
|
3
|
-
|
4
|
-
# --------------------------------------------------------------------------- #
|
5
|
-
|
6
|
-
class Col
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
#
|
49
|
-
#
|
50
|
-
#
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
end
|
71
|
-
|
72
|
-
|
73
|
-
Col.
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
#
|
113
|
-
#
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
#
|
121
|
-
#
|
122
|
-
#
|
123
|
-
#
|
124
|
-
#
|
125
|
-
# Col["
|
126
|
-
#
|
127
|
-
#
|
128
|
-
#
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
#
|
148
|
-
#
|
149
|
-
#
|
150
|
-
#
|
151
|
-
# [
|
152
|
-
# [
|
153
|
-
#
|
154
|
-
#
|
155
|
-
# [
|
156
|
-
# [
|
157
|
-
#
|
158
|
-
#
|
159
|
-
#
|
160
|
-
#
|
161
|
-
#
|
162
|
-
#
|
163
|
-
#
|
164
|
-
#
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
#
|
186
|
-
#
|
187
|
-
# :
|
188
|
-
# :
|
189
|
-
# :
|
190
|
-
#
|
191
|
-
# "
|
192
|
-
# :
|
193
|
-
# [:
|
194
|
-
# [:
|
195
|
-
#
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
#
|
227
|
-
#
|
228
|
-
#
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
[]
|
233
|
-
|
234
|
-
[
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
[
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
[
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
'
|
282
|
-
'
|
283
|
-
'
|
284
|
-
'
|
285
|
-
'
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
'
|
290
|
-
'
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
'
|
295
|
-
'
|
296
|
-
'
|
297
|
-
'
|
298
|
-
'
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
def self.
|
318
|
-
|
319
|
-
end
|
320
|
-
|
321
|
-
def self.
|
322
|
-
get_value
|
323
|
-
end
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
1
|
+
require 'rubygems'
|
2
|
+
require 'term/ansicolor'
|
3
|
+
|
4
|
+
# --------------------------------------------------------------------------- #
|
5
|
+
|
6
|
+
class Col
|
7
|
+
|
8
|
+
VERSION = "1.0.1a"
|
9
|
+
|
10
|
+
# args: array of strings (to_s is called on each)
|
11
|
+
def initialize(*args)
|
12
|
+
@strings = args.map { |a| a.to_s }
|
13
|
+
end
|
14
|
+
|
15
|
+
def Col.[](*args)
|
16
|
+
Col.new(*args)
|
17
|
+
end
|
18
|
+
|
19
|
+
COLORED_REGEXP = /\e\[ # opening character sequence
|
20
|
+
(?: [34][0-7] | [0-9] ) ? # optional code
|
21
|
+
( ; (?: [34][0-7] | [0-9] ))* # more optional codes
|
22
|
+
m # closing character
|
23
|
+
/x
|
24
|
+
|
25
|
+
# Convenience method to remove color codes from a string.
|
26
|
+
# Also available as Col.plain.
|
27
|
+
def Col.uncolored(string)
|
28
|
+
string.gsub(COLORED_REGEXP, '')
|
29
|
+
end
|
30
|
+
|
31
|
+
# Convenience method to remove color codes from a string.
|
32
|
+
# Also available as Col.uncolored.
|
33
|
+
def Col.plain(string)
|
34
|
+
string.gsub(COLORED_REGEXP, '')
|
35
|
+
end
|
36
|
+
|
37
|
+
def Col.inline(*args)
|
38
|
+
unless args.size.even?
|
39
|
+
raise Col::Error, "Col.inline requires an even number of arguments"
|
40
|
+
end
|
41
|
+
String.new.tap { |result|
|
42
|
+
args.each_slice(2) do |string, format|
|
43
|
+
result << Col(string.to_s).fmt(format)
|
44
|
+
end
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
# e.g.
|
49
|
+
# Col("one", "two", "three", "four").fmt "rb,y,cb,_b"
|
50
|
+
# # same as:
|
51
|
+
# "one".red.bold + "two".yellow + "three".cyan.bold + "four".bold
|
52
|
+
#
|
53
|
+
# Col
|
54
|
+
def fmt(*spec)
|
55
|
+
::Col::Formatter.new(@strings, *spec).result
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_s
|
59
|
+
@strings.join
|
60
|
+
end
|
61
|
+
|
62
|
+
# Works nicely with puts. E.g. puts Col("...").red.bold
|
63
|
+
def to_str
|
64
|
+
to_s
|
65
|
+
end
|
66
|
+
|
67
|
+
def method_missing(message, *args, &block)
|
68
|
+
unless args.empty?
|
69
|
+
super # We're not interested in a message with arguments; NoMethodError
|
70
|
+
end
|
71
|
+
if Col::DB.method?(message)
|
72
|
+
Col.new( self.fmt(message) ) # Col["..."].yellow -> Col
|
73
|
+
# to allow Col["..."].yellow.bold
|
74
|
+
else
|
75
|
+
self.fmt(message) # Col["..."].gbow -> String
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def Col(*args)
|
81
|
+
Col.new(*args)
|
82
|
+
end
|
83
|
+
|
84
|
+
# --------------------------------------------------------------------------- #
|
85
|
+
|
86
|
+
class Col::Error < StandardError
|
87
|
+
end
|
88
|
+
|
89
|
+
# --------------------------------------------------------------------------- #
|
90
|
+
|
91
|
+
class Col::Formatter
|
92
|
+
def initialize(strings, *spec)
|
93
|
+
check_correct_number_of_arguments(strings, *spec)
|
94
|
+
@strings = strings
|
95
|
+
@format_spec = normalise_format_spec(*spec)
|
96
|
+
end
|
97
|
+
|
98
|
+
def result
|
99
|
+
unless @strings.size == @format_spec.size
|
100
|
+
raise Col::Error, "mismatching strings and specs"
|
101
|
+
end
|
102
|
+
String.new.tap { |str|
|
103
|
+
@strings.zip(@format_spec).each do |string, spec|
|
104
|
+
d = decorated_string(string, spec)
|
105
|
+
str << d
|
106
|
+
end
|
107
|
+
}
|
108
|
+
end
|
109
|
+
|
110
|
+
# e.g.
|
111
|
+
# string = "hello"
|
112
|
+
# spec = [:yellow, :bold, :on_red]
|
113
|
+
# result = "hello".send(:yellow).send(:bold).send(:on_red)
|
114
|
+
def decorated_string(string, spec)
|
115
|
+
raise Col::Error unless string.is_a? String and spec.is_a? Array \
|
116
|
+
and spec.all? { |e| e.is_a? Symbol }
|
117
|
+
spec.inject(string) { |str, symbol| Term::ANSIColor.send(symbol, str) }
|
118
|
+
end
|
119
|
+
|
120
|
+
#
|
121
|
+
# In general, there should be the same number of arguments as there are
|
122
|
+
# strings:
|
123
|
+
#
|
124
|
+
# Col["one"].fmt( :b )
|
125
|
+
# Col["one", "two"].fmt( [:red, :on_white], [:bold, :negative] )
|
126
|
+
# Col["one", "two", "three"].fmt( :yellow, [:green, :bold], :italic )
|
127
|
+
# Col["one", "two", "three", "four"].fmt(:rb, :y, :cb, :m)
|
128
|
+
# Col["one", "two", "three", "four"].fmt "rb,y,cb,m"
|
129
|
+
#
|
130
|
+
# As a special case, if there is only one string, it can have any number of
|
131
|
+
# arguments:
|
132
|
+
#
|
133
|
+
# Col["string"].fmt( :yellow, :bold, :italic, :blink, :negative, :on_magenta )
|
134
|
+
#
|
135
|
+
# If the number of arguments is incorrect, a Col::Error is thrown.
|
136
|
+
#
|
137
|
+
def check_correct_number_of_arguments(strings, *spec)
|
138
|
+
nargs = spec.size
|
139
|
+
if nargs == 1 and spec.first.is_a? String
|
140
|
+
nargs = spec.first.split(/,/).size
|
141
|
+
end
|
142
|
+
if strings.size > 1 and nargs != strings.size
|
143
|
+
raise Col::Error, "incorrect number of arguments: #{render(spec)}"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
#
|
148
|
+
# Each spec in the following groups is equivalent. The last one is
|
149
|
+
# normalised.
|
150
|
+
#
|
151
|
+
# [ :rb ]
|
152
|
+
# [ "rb" ]
|
153
|
+
# [ [:red, :bold] ]
|
154
|
+
#
|
155
|
+
# [ :rb, :_n, :y ]
|
156
|
+
# [ "rb", "_n", "y" ]
|
157
|
+
# [ [:red, :bold], [:negative], [:yellow] ]
|
158
|
+
#
|
159
|
+
# [ [:green, :concealed], :blue, :bold ]
|
160
|
+
# [ [:green, :concealed], [:blue], [:bold] ]
|
161
|
+
#
|
162
|
+
# [ "rb,y,_i,g_ow" ]
|
163
|
+
# [ "rb", "y", "_i", "g_ow" ]
|
164
|
+
# [ [:red, :bold], [:yellow], [:italic], [:green, :on_white] ]
|
165
|
+
#
|
166
|
+
# {spec} is definitely an array because it was gathered like this:
|
167
|
+
# def fmt(*spec)
|
168
|
+
#
|
169
|
+
# "Normalised" means an array with one element for each string.
|
170
|
+
# Each element is itself an array of all the properties that apply to that
|
171
|
+
# string.
|
172
|
+
#
|
173
|
+
def normalise_format_spec(*spec)
|
174
|
+
if spec.size == 1 and spec.first.is_a? String and spec.first.index(',')
|
175
|
+
# ^^^ "rb,y,_n"
|
176
|
+
spec = spec.first.split(',') # ['rb', 'y', '_n']
|
177
|
+
normalise_format_spec(*spec)
|
178
|
+
else
|
179
|
+
# We have an array of items. We need to treat each item individually and
|
180
|
+
# put the items together. We remove nil elements.
|
181
|
+
spec.map { |item| normalise_item(item) }.compact
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# Examples
|
186
|
+
# Item Normalised item
|
187
|
+
# :r [:red]
|
188
|
+
# "r" [:red]
|
189
|
+
# :red [:red]
|
190
|
+
# [:red] [:red]
|
191
|
+
# "rb" [:red, :bold]
|
192
|
+
# :rb [:red, :bold]
|
193
|
+
# [:red, :bold] [:red, :bold]
|
194
|
+
# :b [:blue]
|
195
|
+
# :_b [:bold]
|
196
|
+
# :__b error
|
197
|
+
# :__ob [:on_blue]
|
198
|
+
# "gsow" [:green, :strikethrough, :on_white]
|
199
|
+
# "_noB" [:negative, :on_black]
|
200
|
+
# :_ []
|
201
|
+
# [:_] []
|
202
|
+
# [:_, :_] []
|
203
|
+
# (etc.)
|
204
|
+
def normalise_item(item)
|
205
|
+
case item
|
206
|
+
when Symbol then normalise_string(item.to_s)
|
207
|
+
when Array then normalise_array(item)
|
208
|
+
when String then normalise_string(item)
|
209
|
+
else raise Col::Error, "Invalid item type: #{item.class}"
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# Input: array of symbols
|
214
|
+
# Result: array of symbols, each of which is a legitimate ANSIColor method
|
215
|
+
# Note: underscores and nil items are removed from the array
|
216
|
+
def normalise_array(array)
|
217
|
+
array.reject! { |x| x.nil? or x == :_ or x == '_' }
|
218
|
+
invalid_items = array.select { |item| not Col::DB.method? item }
|
219
|
+
case invalid_items.size
|
220
|
+
when 0 then return array
|
221
|
+
when 1 then raise Col::Error, "Invalid item: #{invalid_items.first.inspect}"
|
222
|
+
else raise Col::Error, "Invalid items: #{invalid_items.inspect}"
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
# Examples
|
227
|
+
# Input Output
|
228
|
+
# r [:red]
|
229
|
+
# b [:blue]
|
230
|
+
# rb [:red, :bold]
|
231
|
+
# red [:red]
|
232
|
+
# bold [:bold]
|
233
|
+
# gsow [:green, :strikethrough, :on_white]
|
234
|
+
# _b [:bold]
|
235
|
+
# __ob [:on_blue]
|
236
|
+
# _ []
|
237
|
+
def normalise_string(string)
|
238
|
+
# Is it already one of the methods? If so, easy. If not, split and parse.
|
239
|
+
if string == "_"
|
240
|
+
[]
|
241
|
+
elsif Col::DB.method? string
|
242
|
+
[ string.intern ]
|
243
|
+
elsif (1..4).include? string.size # say: "g", "gb", "gbow"
|
244
|
+
color, style, backg = extract(string)
|
245
|
+
color = Col::DB.color(color) # 'g' -> :green
|
246
|
+
style = Col::DB.style(style) # 'b' -> :bold
|
247
|
+
backg = Col::DB.background(backg) # 'ow' -> :on_white
|
248
|
+
[ color, style, backg ].compact # remove nil elements
|
249
|
+
else
|
250
|
+
raise Col::Error, "Invalid item: #{string.inspect}"
|
251
|
+
end
|
252
|
+
rescue Col::Error => e
|
253
|
+
raise Col::Error, "Invalid item: #{string.inspect} (#{e.message})"
|
254
|
+
end
|
255
|
+
|
256
|
+
# Extracts color, style and background color.
|
257
|
+
# "gbow" -> ["g", "b", "ow"]
|
258
|
+
# "g" -> ["g", nil, nil]
|
259
|
+
# "_b" -> [nil, "b", nil]
|
260
|
+
def extract(string)
|
261
|
+
string += " "
|
262
|
+
color, style, backg = /^(.)(.)(..)/.match(string).captures
|
263
|
+
color = nil if [' ', '_'].include? color
|
264
|
+
style = nil if [' ', '_'].include? style
|
265
|
+
backg = nil if [' ', '__'].include? backg
|
266
|
+
[color, style, backg]
|
267
|
+
end
|
268
|
+
|
269
|
+
def render(spec)
|
270
|
+
( (spec.size == 1) ? spec.first : spec ).inspect
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
# --------------------------------------------------------------------------- #
|
275
|
+
|
276
|
+
class Col::DB
|
277
|
+
COLORS = {
|
278
|
+
'B' => :black,
|
279
|
+
'r' => :red,
|
280
|
+
'g' => :green,
|
281
|
+
'y' => :yellow,
|
282
|
+
'b' => :blue,
|
283
|
+
'm' => :magenta,
|
284
|
+
'c' => :cyan,
|
285
|
+
'w' => :white
|
286
|
+
}
|
287
|
+
|
288
|
+
STYLES = {
|
289
|
+
'b' => :bold,
|
290
|
+
'd' => :dark,
|
291
|
+
'i' => :italic,
|
292
|
+
'u' => :underline,
|
293
|
+
'U' => :underscore,
|
294
|
+
'k' => :blink,
|
295
|
+
'r' => :rapid_blink,
|
296
|
+
'n' => :negative,
|
297
|
+
'c' => :concealed,
|
298
|
+
's' => :strikethrough,
|
299
|
+
}
|
300
|
+
|
301
|
+
BACKGROUND = {
|
302
|
+
'oB' => :on_black,
|
303
|
+
'or' => :on_red,
|
304
|
+
'og' => :on_green,
|
305
|
+
'oy' => :on_yellow,
|
306
|
+
'ob' => :on_blue,
|
307
|
+
'om' => :on_magenta,
|
308
|
+
'oc' => :on_cyan,
|
309
|
+
'ow' => :on_white
|
310
|
+
}
|
311
|
+
|
312
|
+
ALL_METHODS_SYMBOL = COLORS.values + STYLES.values + BACKGROUND.values
|
313
|
+
ALL_METHODS_STRING = ALL_METHODS_SYMBOL.map { |x| x.to_s }
|
314
|
+
require 'set'
|
315
|
+
ALL_METHODS = Set[*ALL_METHODS_SYMBOL] + Set[*ALL_METHODS_STRING]
|
316
|
+
|
317
|
+
def self.method?(x)
|
318
|
+
ALL_METHODS.include? x
|
319
|
+
end
|
320
|
+
|
321
|
+
def self.color(key)
|
322
|
+
get_value COLORS, key, "color"
|
323
|
+
end
|
324
|
+
|
325
|
+
def self.style(key)
|
326
|
+
get_value STYLES, key, "style"
|
327
|
+
end
|
328
|
+
|
329
|
+
def self.background(key)
|
330
|
+
get_value BACKGROUND, key, "background color"
|
331
|
+
end
|
332
|
+
|
333
|
+
# If the 'key' is nil, we return nil. Otherwise, we insist that the key be a
|
334
|
+
# valid color, style or background color. If it's not, we raise an error
|
335
|
+
# (that's what 'name' is for).
|
336
|
+
#
|
337
|
+
# Return the method name sought: :green, :bold, :on_white, etc.
|
338
|
+
def self.get_value(hash, key, name)
|
339
|
+
if key.nil?
|
340
|
+
nil
|
341
|
+
else
|
342
|
+
method = hash[key.to_s]
|
343
|
+
if method.nil?
|
344
|
+
raise Col::Error, "Invalid #{name} code: #{key}"
|
345
|
+
end
|
346
|
+
method
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
end # Col::DB
|