ansi256 0.5.0 → 0.6.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 +4 -4
- data/README.md +19 -40
- data/bin/ansi256 +3 -0
- data/lib/ansi256/code.rb +8 -0
- data/lib/ansi256/mixin.rb +12 -1
- data/lib/ansi256/version.rb +1 -1
- data/lib/ansi256.rb +191 -45
- data/test/test_ansi256.rb +172 -26
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5390f3352df81912c5dfb618a66256f8f41b21fab087748185b313a7f09913b3
|
|
4
|
+
data.tar.gz: a8bf49a8391b3b5221611026505fa27451a5a683526763776ad0d9344bf4f7f8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2078048aa2d6064a9f3886de024c485c12e266dc9bc37f0fc69ea95be98e6371e2e3e183c3ca357fc1fcc6a543070217cdc109b2c81d973f583e444f318af8e1
|
|
7
|
+
data.tar.gz: 9c7011943787d75ee81005820f1521317c869aa922136d5c6d0be5fc50070ddcb87ad324f698cf1c7a54656b5584505d706b3b994f06586f99ed683f47f519b9
|
data/README.md
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# ansi256
|
|
2
2
|
|
|
3
|
-
ansi256 is a rubygem for colorizing text with
|
|
3
|
+
ansi256 is a rubygem for colorizing text with ANSI escape codes.
|
|
4
4
|
|
|
5
5
|
Features:
|
|
6
6
|
|
|
7
|
-
-
|
|
7
|
+
- True 24-bit color, named colors, and 256-color support
|
|
8
|
+
- Underline styles (curly, double, dotted, dashed) and underline colors
|
|
8
9
|
- Allows nesting of colored text
|
|
9
10
|
- Generates optimal(shortest) code sequence
|
|
10
11
|
|
|
@@ -29,6 +30,13 @@ puts "Colorize me".fg(111).bg(226)
|
|
|
29
30
|
# Also with underline
|
|
30
31
|
puts "Colorize me".fg(111).bg(226).underline
|
|
31
32
|
|
|
33
|
+
# Underline styles (single, double, curly, dotted, dashed)
|
|
34
|
+
puts "Colorize me".underline(:curly)
|
|
35
|
+
|
|
36
|
+
# Underline with color (256-color index or hex RGB)
|
|
37
|
+
puts "Colorize me".underline(:curly, 'ff9900')
|
|
38
|
+
puts "Colorize me".underline(214)
|
|
39
|
+
|
|
32
40
|
# Strip ANSI codes
|
|
33
41
|
puts "Colorize me".fg(111).bg(226).underline.plain
|
|
34
42
|
```
|
|
@@ -51,7 +59,9 @@ puts [ s.white, s.white.bold, s.white.bold.on_white ].join ' '
|
|
|
51
59
|
|
|
52
60
|

|
|
53
61
|
|
|
54
|
-
### RGB
|
|
62
|
+
### RGB hex colors
|
|
63
|
+
|
|
64
|
+
RGB hex strings produce true 24-bit color (`\e[38;2;R;G;Bm`) by default.
|
|
55
65
|
|
|
56
66
|
```ruby
|
|
57
67
|
puts "RGB Color (RRGGBB)".fg('ff9930').bg('203366')
|
|
@@ -61,6 +71,12 @@ puts "RGB Color (R-G-B-)".fg('f90').bg('036')
|
|
|
61
71
|
puts "RGB Color (Monochrome)".fg('ef').bg('3f')
|
|
62
72
|
```
|
|
63
73
|
|
|
74
|
+
To approximate RGB to 256-color indices instead (for legacy terminals):
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
Ansi256.truecolor = false
|
|
78
|
+
```
|
|
79
|
+
|
|
64
80
|
Nesting
|
|
65
81
|
-------
|
|
66
82
|
|
|
@@ -110,43 +126,6 @@ Ansi256.enabled = $stdout.tty?
|
|
|
110
126
|
# Hello
|
|
111
127
|
```
|
|
112
128
|
|
|
113
|
-
ansi256 executable
|
|
114
|
-
------------------
|
|
115
|
-
|
|
116
|
-
Ansi256 comes with `ansi256` script which can be used as follows
|
|
117
|
-
|
|
118
|
-
```bash
|
|
119
|
-
usage: ansi256 [-b] [-d] [-i] [-u] <[fg][/bg]> [message]
|
|
120
|
-
|
|
121
|
-
# Numeric color codes
|
|
122
|
-
ansi256 232 "Hello world"
|
|
123
|
-
ansi256 /226 "Hello world"
|
|
124
|
-
ansi256 232/226 "Hello world"
|
|
125
|
-
|
|
126
|
-
# Named color codes
|
|
127
|
-
ansi256 yellow "Hello world"
|
|
128
|
-
ansi256 /blue "Hello world"
|
|
129
|
-
ansi256 yellow/blue "Hello world"
|
|
130
|
-
|
|
131
|
-
# RGB colors (only support 6-letter hex codes)
|
|
132
|
-
ansi256 ff9900/000033 "Hello world"
|
|
133
|
-
|
|
134
|
-
# Mixed color codes
|
|
135
|
-
ansi256 yellow/232 "Hello world"
|
|
136
|
-
|
|
137
|
-
# Bold yellow
|
|
138
|
-
ansi256 yellow/232 -b "Hello world"
|
|
139
|
-
|
|
140
|
-
# With underline
|
|
141
|
-
ansi256 yellow/232 -b -u "Hello world"
|
|
142
|
-
|
|
143
|
-
# Colorizing STDIN
|
|
144
|
-
find / | ansi256 -u /226
|
|
145
|
-
|
|
146
|
-
# Nesting
|
|
147
|
-
ansi256 30 "Say '$(ansi256 230/75 "Hello $(ansi256 -u 232/226 World)")'"
|
|
148
|
-
```
|
|
149
|
-
|
|
150
129
|
Color chart
|
|
151
130
|
-----------
|
|
152
131
|
|
data/bin/ansi256
CHANGED
data/lib/ansi256/code.rb
CHANGED
data/lib/ansi256/mixin.rb
CHANGED
|
@@ -27,6 +27,12 @@ module Ansi256
|
|
|
27
27
|
Ansi256.send name, self
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
|
+
|
|
31
|
+
# Override underline to accept style and color
|
|
32
|
+
undef_method(:underline) if method_defined?(:underline)
|
|
33
|
+
define_method :underline do |*props|
|
|
34
|
+
Ansi256.underline(self, *props)
|
|
35
|
+
end
|
|
30
36
|
end
|
|
31
37
|
|
|
32
38
|
Ansi256.class_eval do
|
|
@@ -58,6 +64,12 @@ module Ansi256
|
|
|
58
64
|
self
|
|
59
65
|
end
|
|
60
66
|
end
|
|
67
|
+
|
|
68
|
+
# Override underline to accept style and color (disabled mode)
|
|
69
|
+
undef_method(:underline) if method_defined?(:underline)
|
|
70
|
+
define_method :underline do |*props|
|
|
71
|
+
self
|
|
72
|
+
end
|
|
61
73
|
end
|
|
62
74
|
|
|
63
75
|
Ansi256.class_eval do
|
|
@@ -74,4 +86,3 @@ module Ansi256
|
|
|
74
86
|
end
|
|
75
87
|
end
|
|
76
88
|
end
|
|
77
|
-
|
data/lib/ansi256/version.rb
CHANGED
data/lib/ansi256.rb
CHANGED
|
@@ -7,6 +7,8 @@ require 'ansi256/mixin'
|
|
|
7
7
|
|
|
8
8
|
module Ansi256
|
|
9
9
|
class << self
|
|
10
|
+
attr_accessor :truecolor
|
|
11
|
+
|
|
10
12
|
def fg(code, str = nil)
|
|
11
13
|
if str
|
|
12
14
|
wrap str, Ansi256.fg(code)
|
|
@@ -14,8 +16,12 @@ module Ansi256
|
|
|
14
16
|
"\e[#{CODE[code]}m"
|
|
15
17
|
elsif code.is_a?(Integer) && (0..255).include?(code)
|
|
16
18
|
"\e[38;5;#{code}m"
|
|
17
|
-
elsif
|
|
18
|
-
|
|
19
|
+
elsif rgb = parse_rgb(code)
|
|
20
|
+
if truecolor
|
|
21
|
+
"\e[38;2;#{rgb.join(';')}m"
|
|
22
|
+
else
|
|
23
|
+
"\e[38;5;#{ansicode_for_rgb(code)}m"
|
|
24
|
+
end
|
|
19
25
|
else
|
|
20
26
|
raise ArgumentError, "Invalid color code: #{code}"
|
|
21
27
|
end
|
|
@@ -28,8 +34,12 @@ module Ansi256
|
|
|
28
34
|
"\e[#{CODE[code] + 10}m"
|
|
29
35
|
elsif code.is_a?(Integer) && (0..255).include?(code)
|
|
30
36
|
"\e[48;5;#{code}m"
|
|
31
|
-
elsif
|
|
32
|
-
|
|
37
|
+
elsif rgb = parse_rgb(code)
|
|
38
|
+
if truecolor
|
|
39
|
+
"\e[48;2;#{rgb.join(';')}m"
|
|
40
|
+
else
|
|
41
|
+
"\e[48;5;#{ansicode_for_rgb(code)}m"
|
|
42
|
+
end
|
|
33
43
|
else
|
|
34
44
|
raise ArgumentError, "Invalid color code: #{code}"
|
|
35
45
|
end
|
|
@@ -45,88 +55,223 @@ module Ansi256
|
|
|
45
55
|
end
|
|
46
56
|
end
|
|
47
57
|
|
|
58
|
+
# Override underline to support styles and colors
|
|
59
|
+
def underline(str = nil, *props)
|
|
60
|
+
if str.nil?
|
|
61
|
+
build_underline_sgr(*props)
|
|
62
|
+
else
|
|
63
|
+
wrap str, build_underline_sgr(*props)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
48
67
|
def plain(str)
|
|
49
68
|
str.gsub(PATTERN, '')
|
|
50
69
|
end
|
|
51
70
|
|
|
52
71
|
private
|
|
53
72
|
|
|
54
|
-
PATTERN = /\e\[[0-9
|
|
55
|
-
MULTI_PATTERN = /(?:\e\[[0-9
|
|
56
|
-
|
|
73
|
+
PATTERN = /\e\[[0-9;:]+m/
|
|
74
|
+
MULTI_PATTERN = /(?:\e\[[0-9;:]+m)+/
|
|
75
|
+
# State: [fg, bg, ul_style, ul_color, attrs_set]
|
|
76
|
+
# ul_style is only for sub-parameter styles (4:1, 4:2, etc.)
|
|
77
|
+
# Basic underline (4) stays in attrs_set for backward compat
|
|
78
|
+
EMPTY_STATE = [nil, nil, nil, nil, Set.new].freeze
|
|
79
|
+
|
|
80
|
+
def build_underline_sgr(*args)
|
|
81
|
+
style = nil
|
|
82
|
+
color = nil
|
|
83
|
+
|
|
84
|
+
args.each do |arg|
|
|
85
|
+
case arg
|
|
86
|
+
when nil
|
|
87
|
+
next
|
|
88
|
+
when Symbol
|
|
89
|
+
if UNDERLINE_STYLES[arg]
|
|
90
|
+
style = arg
|
|
91
|
+
elsif NAMED_COLORS.include?(arg)
|
|
92
|
+
color = arg
|
|
93
|
+
else
|
|
94
|
+
raise ArgumentError, "Invalid underline argument: #{arg}"
|
|
95
|
+
end
|
|
96
|
+
when Integer, String
|
|
97
|
+
color = arg
|
|
98
|
+
else
|
|
99
|
+
raise ArgumentError, "Invalid underline argument: #{arg}"
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
return "\e[4m" unless style || color
|
|
104
|
+
|
|
105
|
+
parts = []
|
|
106
|
+
parts << (style ? UNDERLINE_STYLES[style] : '4')
|
|
107
|
+
parts << underline_color_code(color) if color
|
|
108
|
+
|
|
109
|
+
"\e[#{parts.join(';')}m"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def underline_color_code(color)
|
|
113
|
+
case color
|
|
114
|
+
when Symbol
|
|
115
|
+
raise ArgumentError, "Invalid underline color: #{color}" unless NAMED_COLORS.include?(color)
|
|
116
|
+
"58;5;#{CODE[color] - 30}"
|
|
117
|
+
when Integer
|
|
118
|
+
raise ArgumentError, "Invalid underline color: #{color}" unless (0..255).include?(color)
|
|
119
|
+
"58;5;#{color}"
|
|
120
|
+
when String
|
|
121
|
+
rgb = parse_rgb(color)
|
|
122
|
+
raise ArgumentError, "Invalid underline color: #{color}" unless rgb
|
|
123
|
+
if truecolor
|
|
124
|
+
"58;2;#{rgb.join(';')}"
|
|
125
|
+
else
|
|
126
|
+
"58;5;#{ansicode_for_rgb(color)}"
|
|
127
|
+
end
|
|
128
|
+
else
|
|
129
|
+
raise ArgumentError, "Invalid underline color: #{color}"
|
|
130
|
+
end
|
|
131
|
+
end
|
|
57
132
|
|
|
58
133
|
def ansify(prev, curr)
|
|
59
134
|
nums = []
|
|
60
135
|
nums << curr[0] if prev[0] != curr[0]
|
|
61
136
|
nums << curr[1] if prev[1] != curr[1]
|
|
62
|
-
nums
|
|
137
|
+
nums << curr[2] if prev[2] != curr[2]
|
|
138
|
+
nums << curr[3] if prev[3] != curr[3]
|
|
139
|
+
nums.concat curr[4].to_a if prev[4] != curr[4]
|
|
63
140
|
"\e[#{nums.compact.join ';'}m"
|
|
64
141
|
end
|
|
65
142
|
|
|
66
|
-
def
|
|
67
|
-
return unless
|
|
68
|
-
|
|
143
|
+
def parse_rgb(code)
|
|
144
|
+
return unless code.is_a?(String) &&
|
|
145
|
+
code =~ /^#?([0-9a-f]+)$/i
|
|
69
146
|
|
|
70
|
-
|
|
147
|
+
hex = ::Regexp.last_match(1)
|
|
71
148
|
|
|
72
|
-
case
|
|
149
|
+
case hex.length
|
|
73
150
|
when 2
|
|
74
|
-
|
|
75
|
-
|
|
151
|
+
v = hex.to_i(16)
|
|
152
|
+
[v, v, v]
|
|
76
153
|
when 3
|
|
77
|
-
|
|
154
|
+
hex.each_char.map { |e| (e * 2).to_i(16) }
|
|
78
155
|
when 6
|
|
79
|
-
|
|
80
|
-
else
|
|
81
|
-
return
|
|
156
|
+
hex.each_char.each_slice(2).map { |e| e.join.to_i(16) }
|
|
82
157
|
end
|
|
158
|
+
end
|
|
83
159
|
|
|
160
|
+
def rgb_to_ansi256(r, g, b)
|
|
84
161
|
r, g, b = [r, g, b].map { |e| 6 * e / 256 }
|
|
85
162
|
r * 36 + g * 6 + b + 16
|
|
86
163
|
end
|
|
87
164
|
|
|
165
|
+
def ansicode_for_rgb(code)
|
|
166
|
+
return unless code.is_a?(String) &&
|
|
167
|
+
code =~ /^#?([0-9a-f]+)$/i
|
|
168
|
+
|
|
169
|
+
hex = ::Regexp.last_match(1)
|
|
170
|
+
|
|
171
|
+
# 2-char monochrome uses grayscale ramp (232-255)
|
|
172
|
+
if hex.length == 2
|
|
173
|
+
m = 25 * hex.to_i(16) / 256
|
|
174
|
+
return m == 0 ? 16 : (231 + m)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
rgb = parse_rgb(code)
|
|
178
|
+
return unless rgb
|
|
179
|
+
|
|
180
|
+
rgb_to_ansi256(*rgb)
|
|
181
|
+
end
|
|
182
|
+
|
|
88
183
|
def wrap(str, color)
|
|
89
|
-
current = [nil, nil, Set.new]
|
|
184
|
+
current = [nil, nil, nil, nil, Set.new]
|
|
90
185
|
|
|
91
186
|
(color + str.gsub(PATTERN) do |m|
|
|
92
|
-
if m
|
|
187
|
+
if m == "\e[0m"
|
|
93
188
|
m + color
|
|
94
189
|
else
|
|
95
190
|
m
|
|
96
191
|
end
|
|
97
192
|
end << reset).gsub(MULTI_PATTERN) do |ansi|
|
|
98
193
|
prev = current.dup
|
|
99
|
-
prev[
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
194
|
+
prev[4] = prev[4].dup
|
|
195
|
+
|
|
196
|
+
# Extract individual SGR sequences and parse each
|
|
197
|
+
seqs = ansi.scan(/\e\[([^\e]*?)m/).map { |cap| cap[0] }
|
|
198
|
+
seqs.each do |seq|
|
|
199
|
+
# Split by semicolon, preserving colon sub-params within groups
|
|
200
|
+
groups = seq.split(';')
|
|
201
|
+
|
|
202
|
+
idx = -1
|
|
203
|
+
while (idx += 1) < groups.length
|
|
204
|
+
group = groups[idx]
|
|
205
|
+
|
|
206
|
+
if group.include?(':')
|
|
207
|
+
# Sub-parameter group (e.g., "4:3")
|
|
208
|
+
main, sub = group.split(':', 2)
|
|
209
|
+
main_i = main.to_i
|
|
210
|
+
if main_i == 4
|
|
211
|
+
if sub == '0'
|
|
212
|
+
current[2] = nil
|
|
213
|
+
current[4].delete(4)
|
|
214
|
+
else
|
|
215
|
+
current[2] = group
|
|
216
|
+
current[4].delete(4)
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
else
|
|
220
|
+
code = group.to_i
|
|
221
|
+
case code
|
|
222
|
+
when 38
|
|
223
|
+
if groups[idx + 1] == '2'
|
|
224
|
+
current[0] = groups[idx, 5].join(';')
|
|
225
|
+
idx += 4
|
|
226
|
+
else
|
|
227
|
+
current[0] = groups[idx, 3].join(';')
|
|
228
|
+
idx += 2
|
|
229
|
+
end
|
|
230
|
+
when 48
|
|
231
|
+
if groups[idx + 1] == '2'
|
|
232
|
+
current[1] = groups[idx, 5].join(';')
|
|
233
|
+
idx += 4
|
|
234
|
+
else
|
|
235
|
+
current[1] = groups[idx, 3].join(';')
|
|
236
|
+
idx += 2
|
|
237
|
+
end
|
|
238
|
+
when 58
|
|
239
|
+
if groups[idx + 1] == '2'
|
|
240
|
+
current[3] = groups[idx, 5].join(';')
|
|
241
|
+
idx += 4
|
|
242
|
+
else
|
|
243
|
+
current[3] = groups[idx, 3].join(';')
|
|
244
|
+
idx += 2
|
|
245
|
+
end
|
|
246
|
+
when 30..37
|
|
247
|
+
current[0] = code
|
|
248
|
+
when 40..47
|
|
249
|
+
current[1] = code
|
|
250
|
+
when 0
|
|
251
|
+
current[0] = current[1] = current[2] = current[3] = nil
|
|
252
|
+
current[4].clear
|
|
253
|
+
when 24
|
|
254
|
+
current[2] = nil
|
|
255
|
+
current[4].delete(4)
|
|
256
|
+
when 59
|
|
257
|
+
current[3] = nil
|
|
258
|
+
when 4
|
|
259
|
+
# Basic underline goes in attrs set for backward compat
|
|
260
|
+
current[4] << 4
|
|
261
|
+
else
|
|
262
|
+
current[4] << code
|
|
263
|
+
end
|
|
264
|
+
end
|
|
120
265
|
end
|
|
121
266
|
end
|
|
122
267
|
|
|
123
268
|
if current == prev
|
|
124
269
|
''
|
|
125
|
-
elsif current ==
|
|
270
|
+
elsif current == EMPTY_STATE
|
|
126
271
|
reset
|
|
127
272
|
else
|
|
128
|
-
if (0..
|
|
129
|
-
prev =
|
|
273
|
+
if (0..3).any? { |i| prev[i] && !current[i] } || current[4].proper_subset?(prev[4])
|
|
274
|
+
prev = EMPTY_STATE
|
|
130
275
|
reset
|
|
131
276
|
else
|
|
132
277
|
''
|
|
@@ -138,3 +283,4 @@ module Ansi256
|
|
|
138
283
|
end
|
|
139
284
|
|
|
140
285
|
Ansi256.enabled = true
|
|
286
|
+
Ansi256.truecolor = true
|
data/test/test_ansi256.rb
CHANGED
|
@@ -149,34 +149,39 @@ class TestAnsi256 < Minitest::Test
|
|
|
149
149
|
end
|
|
150
150
|
|
|
151
151
|
def test_rgb
|
|
152
|
+
# Truecolor mode (default): 38;2;R;G;B
|
|
152
153
|
{
|
|
153
|
-
'00'
|
|
154
|
-
'000000' =>
|
|
155
|
-
'111'
|
|
156
|
-
'11'
|
|
157
|
-
'101010' => 16,
|
|
158
|
-
'1a'
|
|
159
|
-
'1a1a1a' =>
|
|
160
|
-
'20'
|
|
161
|
-
'22'
|
|
162
|
-
'202020' =>
|
|
163
|
-
'222222' =>
|
|
164
|
-
'
|
|
165
|
-
'
|
|
166
|
-
'
|
|
167
|
-
'
|
|
168
|
-
'
|
|
169
|
-
'
|
|
170
|
-
'
|
|
171
|
-
'
|
|
172
|
-
'
|
|
173
|
-
'
|
|
174
|
-
'
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
assert_equal
|
|
178
|
-
assert_equal ansi, Ansi256.bg(rgb).scan(/[0-9]+/).last.to_i
|
|
154
|
+
'00' => [0, 0, 0],
|
|
155
|
+
'000000' => [0, 0, 0],
|
|
156
|
+
'111' => [17, 17, 17],
|
|
157
|
+
'11' => [17, 17, 17],
|
|
158
|
+
'101010' => [16, 16, 16],
|
|
159
|
+
'1a' => [26, 26, 26],
|
|
160
|
+
'1a1a1a' => [26, 26, 26],
|
|
161
|
+
'20' => [32, 32, 32],
|
|
162
|
+
'22' => [34, 34, 34],
|
|
163
|
+
'202020' => [32, 32, 32],
|
|
164
|
+
'222222' => [34, 34, 34],
|
|
165
|
+
'ff' => [255, 255, 255],
|
|
166
|
+
'ffffff' => [255, 255, 255],
|
|
167
|
+
'FFFFFF' => [255, 255, 255],
|
|
168
|
+
'ff0' => [255, 255, 0],
|
|
169
|
+
'ffff00' => [255, 255, 0],
|
|
170
|
+
'ff0000' => [255, 0, 0],
|
|
171
|
+
'00ff00' => [0, 255, 0],
|
|
172
|
+
'0000ff' => [0, 0, 255],
|
|
173
|
+
'ff9900' => [255, 153, 0],
|
|
174
|
+
'00ffff' => [0, 255, 255],
|
|
175
|
+
'0ff' => [0, 255, 255],
|
|
176
|
+
}.each do |hex, (r, g, b)|
|
|
177
|
+
assert_equal "\e[38;2;#{r};#{g};#{b}m", Ansi256.fg(hex)
|
|
178
|
+
assert_equal "\e[48;2;#{r};#{g};#{b}m", Ansi256.bg(hex)
|
|
179
179
|
end
|
|
180
|
+
# # prefix
|
|
181
|
+
assert_equal "\e[38;2;255;153;0m", Ansi256.fg('#ff9900')
|
|
182
|
+
assert_equal "\e[48;2;0;51;102m", Ansi256.bg('#003366')
|
|
183
|
+
|
|
184
|
+
# Visual output
|
|
180
185
|
%i[bg fg].each do |m|
|
|
181
186
|
(0..255).each do |r|
|
|
182
187
|
color = r.to_s(16).rjust(2, '0') * 3
|
|
@@ -201,6 +206,59 @@ class TestAnsi256 < Minitest::Test
|
|
|
201
206
|
puts 'RGB Color (Monochrome)'.fg('ef').bg('3a')
|
|
202
207
|
end
|
|
203
208
|
|
|
209
|
+
def test_rgb_nesting
|
|
210
|
+
# Truecolor inner nested inside truecolor outer
|
|
211
|
+
assert_equal "\e[38;2;0;51;102mHello \e[38;2;255;153;0mWorld\e[38;2;0;51;102m !\e[0m",
|
|
212
|
+
"Hello #{'World'.fg('ff9900')} !".fg('003366')
|
|
213
|
+
|
|
214
|
+
# Truecolor fg with integer bg
|
|
215
|
+
assert_equal "\e[38;2;255;153;0;48;5;100mHello\e[0m",
|
|
216
|
+
'Hello'.fg('ff9900').bg(100)
|
|
217
|
+
|
|
218
|
+
# Integer fg nested inside truecolor fg
|
|
219
|
+
assert_equal "\e[38;2;0;51;102mHello \e[38;5;100mWorld\e[38;2;0;51;102m !\e[0m",
|
|
220
|
+
"Hello #{'World'.fg(100)} !".fg('003366')
|
|
221
|
+
|
|
222
|
+
# Truecolor underline color in nesting
|
|
223
|
+
inner = 'world'.underline(:curly, 'ff0000')
|
|
224
|
+
result = "hello #{inner} end".fg('003366')
|
|
225
|
+
assert_equal "\e[38;2;0;51;102mhello \e[4:3;58;2;255;0;0mworld\e[0m\e[38;2;0;51;102m end\e[0m", result
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def test_rgb_approximation
|
|
229
|
+
# When truecolor is disabled, fall back to 256-color approximation
|
|
230
|
+
Ansi256.truecolor = false
|
|
231
|
+
{
|
|
232
|
+
'00' => 16,
|
|
233
|
+
'000000' => 16,
|
|
234
|
+
'111' => 16,
|
|
235
|
+
'11' => 232,
|
|
236
|
+
'101010' => 16,
|
|
237
|
+
'1a' => 233,
|
|
238
|
+
'1a1a1a' => 16,
|
|
239
|
+
'20' => 234,
|
|
240
|
+
'22' => 234,
|
|
241
|
+
'202020' => 16,
|
|
242
|
+
'222222' => 16,
|
|
243
|
+
'ff' => 255,
|
|
244
|
+
'ffffff' => 231,
|
|
245
|
+
'FFFFFF' => 231,
|
|
246
|
+
'ff0' => 226,
|
|
247
|
+
'ffff00' => 226,
|
|
248
|
+
'ff0000' => 196,
|
|
249
|
+
'00ff00' => 46,
|
|
250
|
+
'0000ff' => 21,
|
|
251
|
+
'ff9900' => 214,
|
|
252
|
+
'00ffff' => 51,
|
|
253
|
+
'0ff' => 51,
|
|
254
|
+
}.each do |rgb, ansi|
|
|
255
|
+
assert_equal ansi, Ansi256.fg(rgb).scan(/[0-9]+/).last.to_i
|
|
256
|
+
assert_equal ansi, Ansi256.bg(rgb).scan(/[0-9]+/).last.to_i
|
|
257
|
+
end
|
|
258
|
+
ensure
|
|
259
|
+
Ansi256.truecolor = true
|
|
260
|
+
end
|
|
261
|
+
|
|
204
262
|
def test_enabled
|
|
205
263
|
2.times do
|
|
206
264
|
%i[fg bg].each do |m|
|
|
@@ -219,4 +277,92 @@ class TestAnsi256 < Minitest::Test
|
|
|
219
277
|
end
|
|
220
278
|
end
|
|
221
279
|
end
|
|
280
|
+
|
|
281
|
+
# --- Extended underline tests ---
|
|
282
|
+
|
|
283
|
+
def test_underline_backward_compat
|
|
284
|
+
# No-arg: returns SGR code
|
|
285
|
+
assert_equal "\e[4m", Ansi256.underline
|
|
286
|
+
|
|
287
|
+
# String arg: wraps with basic underline
|
|
288
|
+
assert_equal "\e[4mhello\e[0m", Ansi256.underline('hello')
|
|
289
|
+
assert_equal "\e[4mhello\e[0m", 'hello'.underline
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def test_underline_styles
|
|
293
|
+
assert_equal "\e[4:1mhello\e[0m", 'hello'.underline(:single)
|
|
294
|
+
assert_equal "\e[4:2mhello\e[0m", 'hello'.underline(:double)
|
|
295
|
+
assert_equal "\e[4:3mhello\e[0m", 'hello'.underline(:curly)
|
|
296
|
+
assert_equal "\e[4:4mhello\e[0m", 'hello'.underline(:dotted)
|
|
297
|
+
assert_equal "\e[4:5mhello\e[0m", 'hello'.underline(:dashed)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def test_underline_color_integer
|
|
301
|
+
assert_equal "\e[58;5;214;4mhello\e[0m", 'hello'.underline(214)
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def test_underline_color_hex
|
|
305
|
+
assert_equal "\e[58;2;255;153;0;4mhello\e[0m", 'hello'.underline('ff9900')
|
|
306
|
+
assert_equal "\e[58;2;255;153;0;4mhello\e[0m", 'hello'.underline('#ff9900')
|
|
307
|
+
assert_equal "\e[58;2;255;153;0;4mhello\e[0m", 'hello'.underline('f90')
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def test_underline_style_with_color
|
|
311
|
+
assert_equal "\e[4:3;58;2;255;153;0mhello\e[0m", 'hello'.underline(:curly, 'ff9900')
|
|
312
|
+
assert_equal "\e[4:2;58;5;100mhello\e[0m", 'hello'.underline(:double, 100)
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def test_underline_invalid_style
|
|
316
|
+
assert_raises(ArgumentError) { 'hello'.underline(:wavy) }
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def test_underline_invalid_color
|
|
320
|
+
assert_raises(ArgumentError) { 'hello'.underline(:curly, 'zzzzzz') }
|
|
321
|
+
assert_raises(ArgumentError) { 'hello'.underline(:curly, -1) }
|
|
322
|
+
assert_raises(ArgumentError) { 'hello'.underline(:curly, 256) }
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def test_underline_style_nesting_with_fg
|
|
326
|
+
# Styled underline nested inside fg color
|
|
327
|
+
inner = 'world'.underline(:curly)
|
|
328
|
+
result = "hello #{inner} end".fg(100)
|
|
329
|
+
assert_equal "\e[38;5;100mhello \e[4:3mworld\e[0m\e[38;5;100m end\e[0m", result
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def test_underline_color_nesting_with_fg
|
|
333
|
+
inner = 'world'.underline(:curly, 196)
|
|
334
|
+
result = "hello #{inner} end".fg(100)
|
|
335
|
+
assert_equal "\e[38;5;100mhello \e[4:3;58;5;196mworld\e[0m\e[38;5;100m end\e[0m", result
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def test_underline_disabled_mode
|
|
339
|
+
Ansi256.enabled = false
|
|
340
|
+
assert_equal 'hello', 'hello'.underline
|
|
341
|
+
assert_equal 'hello', 'hello'.underline(:curly)
|
|
342
|
+
assert_equal 'hello', 'hello'.underline(:curly, 'ff9900')
|
|
343
|
+
assert_equal 'hello', 'hello'.underline(214)
|
|
344
|
+
Ansi256.enabled = true
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def test_underline_module_method_with_style
|
|
348
|
+
assert_equal "\e[4:3mhello\e[0m", Ansi256.underline('hello', :curly)
|
|
349
|
+
assert_equal "\e[4:3;58;2;255;153;0mhello\e[0m", Ansi256.underline('hello', :curly, 'ff9900')
|
|
350
|
+
assert_equal "\e[58;5;214;4mhello\e[0m", Ansi256.underline('hello', 214)
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def test_underline_named_color
|
|
354
|
+
# Named color as sole argument (basic underline + color)
|
|
355
|
+
assert_equal "\e[58;5;1;4mhello\e[0m", 'hello'.underline(:red)
|
|
356
|
+
assert_equal "\e[58;5;4;4mhello\e[0m", 'hello'.underline(:blue)
|
|
357
|
+
assert_equal "\e[58;5;0;4mhello\e[0m", 'hello'.underline(:black)
|
|
358
|
+
assert_equal "\e[58;5;7;4mhello\e[0m", 'hello'.underline(:white)
|
|
359
|
+
|
|
360
|
+
# Named color as second argument (style + color)
|
|
361
|
+
assert_equal "\e[4:3;58;5;1mhello\e[0m", 'hello'.underline(:curly, :red)
|
|
362
|
+
assert_equal "\e[4:2;58;5;4mhello\e[0m", 'hello'.underline(:double, :blue)
|
|
363
|
+
|
|
364
|
+
# Swapped order: color first, style second
|
|
365
|
+
assert_equal "\e[4:3;58;5;1mhello\e[0m", 'hello'.underline(:red, :curly)
|
|
366
|
+
assert_equal "\e[4:2;58;5;4mhello\e[0m", 'hello'.underline(:blue, :double)
|
|
367
|
+
end
|
|
222
368
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ansi256
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Junegunn Choi
|
|
@@ -89,7 +89,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
89
89
|
- !ruby/object:Gem::Version
|
|
90
90
|
version: '0'
|
|
91
91
|
requirements: []
|
|
92
|
-
rubygems_version:
|
|
92
|
+
rubygems_version: 4.0.3
|
|
93
93
|
specification_version: 4
|
|
94
94
|
summary: Colorize text using 256-color ANSI codes
|
|
95
95
|
test_files:
|