ansi256 0.4.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 +31 -21
- data/lib/ansi256/mixin.rb +14 -1
- data/lib/ansi256/version.rb +3 -1
- data/lib/ansi256.rb +204 -54
- data/test/test_ansi256.rb +199 -49
- metadata +3 -3
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
|
@@ -1,28 +1,38 @@
|
|
|
1
1
|
module Ansi256
|
|
2
2
|
CODE = {
|
|
3
|
-
:
|
|
4
|
-
:
|
|
5
|
-
:
|
|
6
|
-
:
|
|
7
|
-
:
|
|
3
|
+
reset: 0,
|
|
4
|
+
bold: 1,
|
|
5
|
+
dim: 2,
|
|
6
|
+
italic: 3,
|
|
7
|
+
underline: 4,
|
|
8
|
+
inverse: 7,
|
|
9
|
+
strikethrough: 9,
|
|
8
10
|
|
|
9
|
-
:
|
|
10
|
-
:
|
|
11
|
-
:
|
|
12
|
-
:
|
|
13
|
-
:
|
|
14
|
-
:
|
|
15
|
-
:
|
|
16
|
-
:
|
|
11
|
+
black: 30,
|
|
12
|
+
red: 31,
|
|
13
|
+
green: 32,
|
|
14
|
+
yellow: 33,
|
|
15
|
+
blue: 34,
|
|
16
|
+
magenta: 35,
|
|
17
|
+
cyan: 36,
|
|
18
|
+
white: 37,
|
|
17
19
|
|
|
18
|
-
:
|
|
19
|
-
:
|
|
20
|
-
:
|
|
21
|
-
:
|
|
22
|
-
:
|
|
23
|
-
:
|
|
24
|
-
:
|
|
25
|
-
:
|
|
20
|
+
on_black: 40,
|
|
21
|
+
on_red: 41,
|
|
22
|
+
on_green: 42,
|
|
23
|
+
on_yellow: 43,
|
|
24
|
+
on_blue: 44,
|
|
25
|
+
on_magenta: 45,
|
|
26
|
+
on_cyan: 46,
|
|
27
|
+
on_white: 47
|
|
28
|
+
}.freeze
|
|
29
|
+
|
|
30
|
+
UNDERLINE_STYLES = {
|
|
31
|
+
single: '4:1',
|
|
32
|
+
double: '4:2',
|
|
33
|
+
curly: '4:3',
|
|
34
|
+
dotted: '4:4',
|
|
35
|
+
dashed: '4:5',
|
|
26
36
|
}.freeze
|
|
27
37
|
|
|
28
38
|
NAMED_COLORS = Set[
|
data/lib/ansi256/mixin.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Ansi256
|
|
2
4
|
class << self
|
|
3
5
|
def enabled= bool
|
|
@@ -25,6 +27,12 @@ module Ansi256
|
|
|
25
27
|
Ansi256.send name, self
|
|
26
28
|
end
|
|
27
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
|
|
28
36
|
end
|
|
29
37
|
|
|
30
38
|
Ansi256.class_eval do
|
|
@@ -56,6 +64,12 @@ module Ansi256
|
|
|
56
64
|
self
|
|
57
65
|
end
|
|
58
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
|
|
59
73
|
end
|
|
60
74
|
|
|
61
75
|
Ansi256.class_eval do
|
|
@@ -72,4 +86,3 @@ module Ansi256
|
|
|
72
86
|
end
|
|
73
87
|
end
|
|
74
88
|
end
|
|
75
|
-
|
data/lib/ansi256/version.rb
CHANGED
data/lib/ansi256.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'set'
|
|
2
4
|
require 'ansi256/version'
|
|
3
5
|
require 'ansi256/code'
|
|
@@ -5,29 +7,39 @@ require 'ansi256/mixin'
|
|
|
5
7
|
|
|
6
8
|
module Ansi256
|
|
7
9
|
class << self
|
|
8
|
-
|
|
10
|
+
attr_accessor :truecolor
|
|
11
|
+
|
|
12
|
+
def fg(code, str = nil)
|
|
9
13
|
if str
|
|
10
14
|
wrap str, Ansi256.fg(code)
|
|
11
15
|
elsif NAMED_COLORS.include?(code)
|
|
12
16
|
"\e[#{CODE[code]}m"
|
|
13
17
|
elsif code.is_a?(Integer) && (0..255).include?(code)
|
|
14
18
|
"\e[38;5;#{code}m"
|
|
15
|
-
elsif
|
|
16
|
-
|
|
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
|
|
17
25
|
else
|
|
18
26
|
raise ArgumentError, "Invalid color code: #{code}"
|
|
19
27
|
end
|
|
20
28
|
end
|
|
21
29
|
|
|
22
|
-
def bg
|
|
30
|
+
def bg(code, str = nil)
|
|
23
31
|
if str
|
|
24
32
|
wrap str, Ansi256.bg(code)
|
|
25
33
|
elsif NAMED_COLORS.include?(code)
|
|
26
34
|
"\e[#{CODE[code] + 10}m"
|
|
27
35
|
elsif code.is_a?(Integer) && (0..255).include?(code)
|
|
28
36
|
"\e[48;5;#{code}m"
|
|
29
|
-
elsif
|
|
30
|
-
|
|
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
|
|
31
43
|
else
|
|
32
44
|
raise ArgumentError, "Invalid color code: #{code}"
|
|
33
45
|
end
|
|
@@ -43,94 +55,232 @@ module Ansi256
|
|
|
43
55
|
end
|
|
44
56
|
end
|
|
45
57
|
|
|
46
|
-
|
|
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
|
+
|
|
67
|
+
def plain(str)
|
|
47
68
|
str.gsub(PATTERN, '')
|
|
48
69
|
end
|
|
49
70
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
71
|
+
private
|
|
72
|
+
|
|
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
|
|
54
83
|
|
|
55
|
-
|
|
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
|
|
132
|
+
|
|
133
|
+
def ansify(prev, curr)
|
|
56
134
|
nums = []
|
|
57
135
|
nums << curr[0] if prev[0] != curr[0]
|
|
58
136
|
nums << curr[1] if prev[1] != curr[1]
|
|
59
|
-
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]
|
|
60
140
|
"\e[#{nums.compact.join ';'}m"
|
|
61
141
|
end
|
|
62
142
|
|
|
63
|
-
def
|
|
64
|
-
return unless
|
|
65
|
-
|
|
66
|
-
|
|
143
|
+
def parse_rgb(code)
|
|
144
|
+
return unless code.is_a?(String) &&
|
|
145
|
+
code =~ /^#?([0-9a-f]+)$/i
|
|
146
|
+
|
|
147
|
+
hex = ::Regexp.last_match(1)
|
|
67
148
|
|
|
68
|
-
case
|
|
149
|
+
case hex.length
|
|
69
150
|
when 2
|
|
70
|
-
|
|
71
|
-
|
|
151
|
+
v = hex.to_i(16)
|
|
152
|
+
[v, v, v]
|
|
72
153
|
when 3
|
|
73
|
-
|
|
154
|
+
hex.each_char.map { |e| (e * 2).to_i(16) }
|
|
74
155
|
when 6
|
|
75
|
-
|
|
76
|
-
else
|
|
77
|
-
return
|
|
156
|
+
hex.each_char.each_slice(2).map { |e| e.join.to_i(16) }
|
|
78
157
|
end
|
|
158
|
+
end
|
|
79
159
|
|
|
160
|
+
def rgb_to_ansi256(r, g, b)
|
|
80
161
|
r, g, b = [r, g, b].map { |e| 6 * e / 256 }
|
|
81
162
|
r * 36 + g * 6 + b + 16
|
|
82
163
|
end
|
|
83
164
|
|
|
84
|
-
def
|
|
85
|
-
|
|
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
|
+
|
|
183
|
+
def wrap(str, color)
|
|
184
|
+
current = [nil, nil, nil, nil, Set.new]
|
|
86
185
|
|
|
87
|
-
(color + str.gsub(PATTERN)
|
|
88
|
-
if m
|
|
186
|
+
(color + str.gsub(PATTERN) do |m|
|
|
187
|
+
if m == "\e[0m"
|
|
89
188
|
m + color
|
|
90
189
|
else
|
|
91
190
|
m
|
|
92
191
|
end
|
|
93
|
-
|
|
192
|
+
end << reset).gsub(MULTI_PATTERN) do |ansi|
|
|
94
193
|
prev = current.dup
|
|
95
|
-
prev[
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
|
116
265
|
end
|
|
117
266
|
end
|
|
118
267
|
|
|
119
268
|
if current == prev
|
|
120
269
|
''
|
|
121
|
-
elsif current ==
|
|
270
|
+
elsif current == EMPTY_STATE
|
|
122
271
|
reset
|
|
123
272
|
else
|
|
124
|
-
if (0..
|
|
125
|
-
prev =
|
|
273
|
+
if (0..3).any? { |i| prev[i] && !current[i] } || current[4].proper_subset?(prev[4])
|
|
274
|
+
prev = EMPTY_STATE
|
|
126
275
|
reset
|
|
127
276
|
else
|
|
128
277
|
''
|
|
129
278
|
end + ansify(prev, current)
|
|
130
279
|
end
|
|
131
|
-
|
|
280
|
+
end
|
|
132
281
|
end
|
|
133
282
|
end
|
|
134
283
|
end
|
|
135
284
|
|
|
136
285
|
Ansi256.enabled = true
|
|
286
|
+
Ansi256.truecolor = true
|
data/test/test_ansi256.rb
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
$VERBOSE = true
|
|
2
|
-
$LOAD_PATH.unshift File.expand_path('
|
|
4
|
+
$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
|
|
3
5
|
require 'ansi256'
|
|
4
6
|
require 'minitest/autorun'
|
|
5
7
|
|
|
6
8
|
class TestAnsi256 < Minitest::Test
|
|
7
|
-
def cfmt
|
|
9
|
+
def cfmt(col)
|
|
8
10
|
col.to_s.rjust(5).fg(232).bg(col)
|
|
9
11
|
end
|
|
10
12
|
|
|
@@ -28,40 +30,40 @@ class TestAnsi256 < Minitest::Test
|
|
|
28
30
|
|
|
29
31
|
def test_nesting_hello_world
|
|
30
32
|
# Nesting
|
|
31
|
-
puts world =
|
|
33
|
+
puts world = 'World'.bg(226).fg(232).underline
|
|
32
34
|
puts hello = "Hello #{world} !".fg(230).bg(75)
|
|
33
35
|
puts say_hello_world = "Say '#{hello}'".fg(30)
|
|
34
36
|
puts say_hello_world.plain.fg(27)
|
|
35
37
|
end
|
|
36
38
|
|
|
37
39
|
def test_nesting_hello_world2
|
|
38
|
-
puts world =
|
|
40
|
+
puts world = 'World'.bg(226).blue.underline
|
|
39
41
|
puts hello = "Hello #{world} Hello".white.bold
|
|
40
42
|
puts say_hello_world = "Say '#{hello}'".fg(30).underline
|
|
41
43
|
puts say_hello_world.plain.fg(27)
|
|
42
44
|
end
|
|
43
45
|
|
|
44
46
|
def test_nesting_hello_world3
|
|
45
|
-
puts world =
|
|
47
|
+
puts world = 'World'.blue.underline
|
|
46
48
|
puts hello = "Hello #{world} Hello".blue.bold
|
|
47
49
|
puts say_hello_world = "Say '#{hello}'".fg(30).underline
|
|
48
50
|
puts say_hello_world.plain.fg(27)
|
|
49
51
|
end
|
|
50
52
|
|
|
51
53
|
def test_named_colors
|
|
52
|
-
s =
|
|
53
|
-
puts [
|
|
54
|
-
puts [
|
|
55
|
-
puts [
|
|
56
|
-
puts [
|
|
57
|
-
puts [
|
|
58
|
-
puts [
|
|
59
|
-
puts [
|
|
60
|
-
puts [
|
|
54
|
+
s = 'Colorize me'
|
|
55
|
+
puts [s.black, s.black.bold, s.white.bold.on_black].join ' '
|
|
56
|
+
puts [s.red, s.red.bold, s.white.bold.on_red].join ' '
|
|
57
|
+
puts [s.green, s.green.bold, s.white.bold.on_green].join ' '
|
|
58
|
+
puts [s.yellow, s.yellow.bold, s.white.bold.on_yellow].join ' '
|
|
59
|
+
puts [s.blue, s.blue.bold, s.white.bold.on_blue].join ' '
|
|
60
|
+
puts [s.magenta, s.magenta.bold, s.white.bold.on_magenta].join ' '
|
|
61
|
+
puts [s.cyan, s.cyan.bold, s.white.bold.on_cyan].join ' '
|
|
62
|
+
puts [s.white, s.white.bold, s.white.bold.on_white].join ' '
|
|
61
63
|
end
|
|
62
64
|
|
|
63
65
|
def test_named_color_code_with_fg_bg
|
|
64
|
-
puts
|
|
66
|
+
puts 'Colorize me'.fg(:green).bg(:red).bold.underline
|
|
65
67
|
end
|
|
66
68
|
|
|
67
69
|
def test_just_code
|
|
@@ -70,6 +72,8 @@ class TestAnsi256 < Minitest::Test
|
|
|
70
72
|
assert_equal "\e[2m", Ansi256.dim
|
|
71
73
|
assert_equal "\e[3m", Ansi256.italic
|
|
72
74
|
assert_equal "\e[4m", Ansi256.underline
|
|
75
|
+
assert_equal "\e[7m", Ansi256.inverse
|
|
76
|
+
assert_equal "\e[9m", Ansi256.strikethrough
|
|
73
77
|
|
|
74
78
|
assert_equal "\e[30m", Ansi256.black
|
|
75
79
|
assert_equal "\e[31m", Ansi256.red
|
|
@@ -130,21 +134,100 @@ class TestAnsi256 < Minitest::Test
|
|
|
130
134
|
|
|
131
135
|
assert_equal "\e[38;5;100mHello\e[38;5;200m world\e[0m", "#{'Hello'.fg(100)} world".fg(200)
|
|
132
136
|
assert_equal "\e[38;5;200;4mWow \e[38;5;100mhello\e[38;5;200m world\e[0m",
|
|
133
|
-
|
|
137
|
+
"Wow #{'hello'.fg(100)} world".fg(200).underline
|
|
134
138
|
assert_equal "\e[38;5;200mWow \e[38;5;100;4mhello\e[0m\e[38;5;200m world\e[0m",
|
|
135
|
-
|
|
139
|
+
"Wow #{'hello'.fg(100).underline} world".fg(200)
|
|
136
140
|
assert_equal "\e[38;5;200mWow \e[38;5;100;48;5;50;4mhello\e[0m\e[38;5;200m world\e[0m",
|
|
137
|
-
|
|
141
|
+
"Wow #{'hello'.fg(100).underline.bg(50)} world".fg(200)
|
|
138
142
|
assert_equal "\e[38;5;200;48;5;250mWow \e[38;5;100;48;5;50;4mhello\e[0m\e[38;5;200;48;5;250m world\e[0m",
|
|
139
|
-
|
|
140
|
-
assert_equal
|
|
141
|
-
|
|
143
|
+
"Wow #{'hello'.fg(100).underline.bg(50)} world".fg(200).bg(250)
|
|
144
|
+
assert_equal 'Wow hello world',
|
|
145
|
+
"Wow #{'hello'.fg(100).underline.bg(50)} world".fg(200).bg(250).plain
|
|
142
146
|
|
|
143
147
|
assert_equal "\e[32;48;5;200;1mWow \e[38;5;100;44;1;4mhello\e[0m\e[32;48;5;200;1m world\e[0m",
|
|
144
|
-
|
|
148
|
+
"Wow #{'hello'.fg(100).underline.on_blue} world".green.bold.bg(200)
|
|
145
149
|
end
|
|
146
150
|
|
|
147
151
|
def test_rgb
|
|
152
|
+
# Truecolor mode (default): 38;2;R;G;B
|
|
153
|
+
{
|
|
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
|
+
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
|
|
185
|
+
%i[bg fg].each do |m|
|
|
186
|
+
(0..255).each do |r|
|
|
187
|
+
color = r.to_s(16).rjust(2, '0') * 3
|
|
188
|
+
print Ansi256.send(m, color, color + ' ')
|
|
189
|
+
end
|
|
190
|
+
(0..255).each do |r|
|
|
191
|
+
color = r.to_s(16).rjust(2, '0')
|
|
192
|
+
print Ansi256.send(m, color, color + ' ')
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
0.step(255, 20) do |r|
|
|
196
|
+
0.step(255, 20) do |g|
|
|
197
|
+
0.step(255, 20) do |b|
|
|
198
|
+
color = [r, g, b].map { |c| c.to_s(16).rjust(2, '0') }.join
|
|
199
|
+
print Ansi256.bg(color, color + ' ')
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
puts 'RGB Color'.fg('ff9900').bg('003366')
|
|
204
|
+
puts 'RGB Color'.fg('f90').bg('#036')
|
|
205
|
+
puts 'RGB Color'.fg('#ff9900').bg('#003366')
|
|
206
|
+
puts 'RGB Color (Monochrome)'.fg('ef').bg('3a')
|
|
207
|
+
end
|
|
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
|
|
148
231
|
{
|
|
149
232
|
'00' => 16,
|
|
150
233
|
'000000' => 16,
|
|
@@ -157,7 +240,6 @@ class TestAnsi256 < Minitest::Test
|
|
|
157
240
|
'22' => 234,
|
|
158
241
|
'202020' => 16,
|
|
159
242
|
'222222' => 16,
|
|
160
|
-
'222222' => 16,
|
|
161
243
|
'ff' => 255,
|
|
162
244
|
'ffffff' => 231,
|
|
163
245
|
'FFFFFF' => 231,
|
|
@@ -173,46 +255,114 @@ class TestAnsi256 < Minitest::Test
|
|
|
173
255
|
assert_equal ansi, Ansi256.fg(rgb).scan(/[0-9]+/).last.to_i
|
|
174
256
|
assert_equal ansi, Ansi256.bg(rgb).scan(/[0-9]+/).last.to_i
|
|
175
257
|
end
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
color = r.to_s(16).rjust(2, '0') * 3
|
|
179
|
-
print Ansi256.send(m, color, color + ' ')
|
|
180
|
-
end
|
|
181
|
-
(0..255).each do |r|
|
|
182
|
-
color = r.to_s(16).rjust(2, '0')
|
|
183
|
-
print Ansi256.send(m, color, color + ' ')
|
|
184
|
-
end
|
|
185
|
-
end
|
|
186
|
-
0.step(255, 20) do |r|
|
|
187
|
-
0.step(255, 20) do |g|
|
|
188
|
-
0.step(255, 20) do |b|
|
|
189
|
-
color = [r, g, b].map { |c| c.to_s(16).rjust(2, '0') }.join
|
|
190
|
-
print Ansi256.bg(color, color + ' ')
|
|
191
|
-
end
|
|
192
|
-
end
|
|
193
|
-
end
|
|
194
|
-
puts "RGB Color".fg('ff9900').bg('003366')
|
|
195
|
-
puts "RGB Color".fg('f90').bg('#036')
|
|
196
|
-
puts "RGB Color".fg('#ff9900').bg('#003366')
|
|
197
|
-
puts "RGB Color (Monochrome)".fg('ef').bg('3a')
|
|
258
|
+
ensure
|
|
259
|
+
Ansi256.truecolor = true
|
|
198
260
|
end
|
|
199
261
|
|
|
200
262
|
def test_enabled
|
|
201
263
|
2.times do
|
|
202
|
-
[
|
|
264
|
+
%i[fg bg].each do |m|
|
|
203
265
|
assert Ansi256.enabled?
|
|
204
|
-
hello =
|
|
266
|
+
hello = 'hello'.send(m, 'f90')
|
|
205
267
|
assert_equal 'hello', hello.plain
|
|
206
268
|
|
|
207
269
|
Ansi256.enabled = false
|
|
208
270
|
assert !Ansi256.enabled?
|
|
209
|
-
assert hello.length >
|
|
271
|
+
assert hello.length > 'hello'.send(m, 'f90').length
|
|
210
272
|
assert_equal 'hello', hello.plain
|
|
211
273
|
|
|
212
274
|
Ansi256.enabled = true
|
|
213
275
|
assert Ansi256.enabled?
|
|
214
|
-
assert_equal hello,
|
|
276
|
+
assert_equal hello, 'hello'.send(m, 'f90')
|
|
215
277
|
end
|
|
216
278
|
end
|
|
217
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
|
|
218
368
|
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
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
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: bundler
|
|
@@ -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:
|