sai 0.1.0 → 0.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 +4 -4
- data/.yardopts +1 -1
- data/CHANGELOG.md +32 -1
- data/README.md +32 -209
- data/docs/USAGE.md +303 -0
- data/lib/sai/ansi/sequence_processor.rb +380 -0
- data/lib/sai/ansi/sequenced_string.rb +475 -0
- data/lib/sai/ansi.rb +5 -5
- data/lib/sai/conversion/color_sequence.rb +31 -31
- data/lib/sai/conversion/rgb.rb +17 -17
- data/lib/sai/decorator.rb +256 -240
- data/lib/sai/mode_selector.rb +298 -0
- data/lib/sai/support.rb +99 -98
- data/lib/sai/terminal/capabilities.rb +22 -22
- data/lib/sai/terminal/color_mode.rb +7 -7
- data/lib/sai.rb +128 -77
- data/sig/manifest.yaml +3 -0
- data/sig/sai/ansi/sequence_processor.rbs +253 -0
- data/sig/sai/ansi/sequenced_string.rbs +380 -0
- data/sig/sai/ansi.rbs +5 -5
- data/sig/sai/conversion/color_sequence.rbs +21 -21
- data/sig/sai/conversion/rgb.rbs +17 -17
- data/sig/sai/decorator.rbs +111 -87
- data/sig/sai/mode_selector.rbs +319 -0
- data/sig/sai/support.rbs +50 -37
- data/sig/sai/terminal/capabilities.rbs +16 -16
- data/sig/sai/terminal/color_mode.rbs +7 -7
- data/sig/sai.rbs +109 -66
- metadata +12 -4
@@ -0,0 +1,475 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
require 'sai/ansi'
|
5
|
+
require 'sai/ansi/sequence_processor'
|
6
|
+
|
7
|
+
module Sai
|
8
|
+
module ANSI
|
9
|
+
# A representation of a ANSI encoded string and its individual {SequencedString::Segment segments}
|
10
|
+
#
|
11
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
12
|
+
# @since 0.3.0
|
13
|
+
#
|
14
|
+
# @api public
|
15
|
+
class SequencedString
|
16
|
+
# @rbs skip
|
17
|
+
extend Forwardable
|
18
|
+
include Enumerable #[Segment]
|
19
|
+
|
20
|
+
# @!method each
|
21
|
+
# Iterate over each segment
|
22
|
+
#
|
23
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
24
|
+
# @since 0.3.0
|
25
|
+
#
|
26
|
+
# @api public
|
27
|
+
# @return [Enumerator] the Enumerator
|
28
|
+
#
|
29
|
+
# @!method map
|
30
|
+
# Map over segments
|
31
|
+
#
|
32
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
33
|
+
# @since 0.3.0
|
34
|
+
#
|
35
|
+
# @api public
|
36
|
+
# @return [Array] the segments to map over
|
37
|
+
#
|
38
|
+
# @!method size
|
39
|
+
# Number of segments
|
40
|
+
#
|
41
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
42
|
+
# @since 0.3.0
|
43
|
+
#
|
44
|
+
# @api public
|
45
|
+
#
|
46
|
+
# @return [Integer] the number of segments
|
47
|
+
def_delegators :@segments, :each, :empty?, :map, :size # steep:ignore NoMethod
|
48
|
+
|
49
|
+
# @rbs!
|
50
|
+
# def each: () { (Segment) -> void } -> SequencedString
|
51
|
+
# def empty?: () -> bool
|
52
|
+
# def map: () { (Segment) -> untyped } -> Array[untyped]
|
53
|
+
# def size: () -> Integer
|
54
|
+
|
55
|
+
# Initialize a new instance of SequencedString
|
56
|
+
#
|
57
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
58
|
+
# @since 0.3.0
|
59
|
+
#
|
60
|
+
# @api private
|
61
|
+
#
|
62
|
+
# @param string [String] the sequenced string to Segment
|
63
|
+
#
|
64
|
+
# @return [SequencedString] the new instance of SequencedString
|
65
|
+
# @rbs (String string) -> void
|
66
|
+
def initialize(string)
|
67
|
+
@segments = ANSI::SequenceProcessor.process(string).map do |segment_options|
|
68
|
+
Segment.new(**segment_options) # steep:ignore InsufficientKeywordArguments
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Fetch a segment by index
|
73
|
+
#
|
74
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
75
|
+
# @since 0.3.0
|
76
|
+
#
|
77
|
+
# @api public
|
78
|
+
#
|
79
|
+
# @example
|
80
|
+
# string = SequencedString.new("\e[31mred\e[0m")
|
81
|
+
# string[0] #=> #<SequencedString::Segment:0x00007f9b3b8b3e10>
|
82
|
+
#
|
83
|
+
# @param index [Integer] the index of the segment to fetch
|
84
|
+
#
|
85
|
+
# @return [Segment, nil] the segment at the index
|
86
|
+
# @rbs (Integer index) -> Segment?
|
87
|
+
def [](index)
|
88
|
+
@segments[index]
|
89
|
+
end
|
90
|
+
|
91
|
+
# Compare the SequencedString to another object
|
92
|
+
#
|
93
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
94
|
+
# @since 0.3.0
|
95
|
+
#
|
96
|
+
# @api public
|
97
|
+
#
|
98
|
+
# @example
|
99
|
+
# string = "\e[31mred\e[0m"
|
100
|
+
# SequencedString.new(string) == string #=> true
|
101
|
+
#
|
102
|
+
# @param other [Object] the object to compare to
|
103
|
+
#
|
104
|
+
# @return [Boolean] `true` if the SequencedString is equal to the other object, `false` otherwise
|
105
|
+
# @rbs (untyped other) -> bool
|
106
|
+
def ==(other)
|
107
|
+
(other.is_a?(self.class) && to_s == other.to_s) ||
|
108
|
+
(other.is_a?(String) && to_s == self.class.new(other).to_s)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Combine a sequenced string with another object
|
112
|
+
#
|
113
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
114
|
+
# @since 0.3.0
|
115
|
+
#
|
116
|
+
# @api public
|
117
|
+
#
|
118
|
+
# @example
|
119
|
+
# sequenced_string = SequencedString.new("\e[31mred\e[0m")
|
120
|
+
# sequenced_string + " is a color" #=> "\e[31mred\e[0m is a color"
|
121
|
+
#
|
122
|
+
# @param other [Object] the object to combine with
|
123
|
+
#
|
124
|
+
# @return [SequencedString] the combined string
|
125
|
+
# @rbs (untyped other) -> SequencedString
|
126
|
+
def +(other)
|
127
|
+
string = to_s + other.to_s
|
128
|
+
self.class.new(string)
|
129
|
+
end
|
130
|
+
|
131
|
+
# Return just the raw text content with **no ANSI sequences**
|
132
|
+
#
|
133
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
134
|
+
# @since 0.3.0
|
135
|
+
#
|
136
|
+
# @api public
|
137
|
+
#
|
138
|
+
# @example
|
139
|
+
# string = SequencedString.new("Normal \e[31mred\e[0m")
|
140
|
+
# string.stripped #=> "Normal red"
|
141
|
+
#
|
142
|
+
# @return [String] the concatenation of all segment text without color or style
|
143
|
+
def stripped
|
144
|
+
map(&:text).join
|
145
|
+
end
|
146
|
+
|
147
|
+
# Return the fully reconstructed string with **all ANSI sequences** (foreground, background, style)
|
148
|
+
#
|
149
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
150
|
+
# @since 0.3.0
|
151
|
+
#
|
152
|
+
# @api public
|
153
|
+
#
|
154
|
+
# @example
|
155
|
+
# string = SequencedString.new("\e[31mred\e[0m")
|
156
|
+
# string.to_s #=> "\e[31mred\e[0m"
|
157
|
+
#
|
158
|
+
# @return [String]
|
159
|
+
def to_s
|
160
|
+
build_string
|
161
|
+
end
|
162
|
+
alias to_str to_s
|
163
|
+
|
164
|
+
# Return a string with everything except **background** color sequences removed
|
165
|
+
#
|
166
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
167
|
+
# @since 0.3.0
|
168
|
+
#
|
169
|
+
# @api public
|
170
|
+
#
|
171
|
+
# @example Remove all background colors
|
172
|
+
# string = SequencedString.new("\e[41mBack\e[0m \e[1mBold\e[0m")
|
173
|
+
# string.without_background #=> "\e[1mBold\e[0m"
|
174
|
+
#
|
175
|
+
# @return [SequencedString] new instance with background colors removed
|
176
|
+
# @rbs () -> SequencedString
|
177
|
+
def without_background
|
178
|
+
self.class.new(build_string(skip_background: true))
|
179
|
+
end
|
180
|
+
|
181
|
+
# Return a string containing *style* sequences but **no foreground or background colors**
|
182
|
+
#
|
183
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
184
|
+
# @since 0.3.0
|
185
|
+
#
|
186
|
+
# @api public
|
187
|
+
#
|
188
|
+
# @example Remove all colors
|
189
|
+
# string = SequencedString.new("\e[31mred\e[0m \e[1mbold\e[0m")
|
190
|
+
# string.without_color #=> "\e[1mbold\e[0m"
|
191
|
+
#
|
192
|
+
# @return [SequencedString] new instance with all colors removed
|
193
|
+
# @rbs () -> SequencedString
|
194
|
+
def without_color
|
195
|
+
self.class.new(build_string(skip_background: true, skip_foreground: true))
|
196
|
+
end
|
197
|
+
|
198
|
+
# Return a string with everything except **foreground** color sequences removed
|
199
|
+
#
|
200
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
201
|
+
# @since 0.3.0
|
202
|
+
#
|
203
|
+
# @api public
|
204
|
+
#
|
205
|
+
# @example Remove all foreground colors
|
206
|
+
# string = SequencedString.new("\e[41mBack\e[0m \e[1mBold\e[0m")
|
207
|
+
# string.without_foreground #=> "\e[41mBack\e[0m \e[1mBold\e[0m"
|
208
|
+
#
|
209
|
+
# @return [SequencedString] new instance with foreground colors removed
|
210
|
+
# @rbs () -> SequencedString
|
211
|
+
def without_foreground
|
212
|
+
self.class.new(build_string(skip_foreground: true))
|
213
|
+
end
|
214
|
+
|
215
|
+
# Return a string with specified styles removed
|
216
|
+
#
|
217
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
218
|
+
# @since 0.3.0
|
219
|
+
#
|
220
|
+
# @api public
|
221
|
+
#
|
222
|
+
# @example Remove all styles
|
223
|
+
# string = SequencedString.new("\e[31mred\e[0m \e[1mbold\e[0m")
|
224
|
+
# string.without_style #=> "\e[31mred\e[0m"
|
225
|
+
#
|
226
|
+
# @example Remove specific style
|
227
|
+
# string = SequencedString.new("\e[1;4mBold and Underlined\e[0m")
|
228
|
+
# string.without_style(:bold) #=> "\e[4mUnderlined\e[0m"
|
229
|
+
#
|
230
|
+
# @param styles [Array<Symbol>] specific styles to remove (default: all)
|
231
|
+
#
|
232
|
+
# @return [SequencedString] new instance with specified styles removed
|
233
|
+
def without_style(*styles)
|
234
|
+
skipped_styles = styles.empty? ? ANSI::STYLES.keys : styles.map(&:to_sym)
|
235
|
+
self.class.new(build_string(skip_styles: skipped_styles))
|
236
|
+
end
|
237
|
+
|
238
|
+
private
|
239
|
+
|
240
|
+
# Build the color sequences for a segment
|
241
|
+
#
|
242
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
243
|
+
# @since 0.3.0
|
244
|
+
#
|
245
|
+
# @api private
|
246
|
+
#
|
247
|
+
# @param segment [Segment] the segment to build color sequences for
|
248
|
+
# @param skip_background [Boolean] whether to skip background colors
|
249
|
+
# @param skip_foreground [Boolean] whether to skip foreground colors
|
250
|
+
#
|
251
|
+
# @return [Array<String>] the color sequences
|
252
|
+
# @rbs (Segment segment, ?skip_background: bool, ?skip_foreground: bool) -> Array[String]
|
253
|
+
def build_color_sequences(segment, skip_background: false, skip_foreground: false)
|
254
|
+
[
|
255
|
+
(skip_foreground ? nil : segment.foreground),
|
256
|
+
(skip_background ? nil : segment.background)
|
257
|
+
].compact
|
258
|
+
end
|
259
|
+
|
260
|
+
# Build a string with specified parts skipped
|
261
|
+
#
|
262
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
263
|
+
# @since 0.3.0
|
264
|
+
#
|
265
|
+
# @api private
|
266
|
+
#
|
267
|
+
# @param skip_background [Boolean] whether to skip background colors
|
268
|
+
# @param skip_foreground [Boolean] whether to skip foreground colors
|
269
|
+
# @param skip_styles [Array<Symbol>] styles to skip
|
270
|
+
#
|
271
|
+
# @return [String] the built string
|
272
|
+
# @rbs (?skip_background: bool, ?skip_foreground: bool, ?skip_styles: Array[Symbol]) -> String
|
273
|
+
def build_string(skip_background: false, skip_foreground: false, skip_styles: [])
|
274
|
+
map do |segment|
|
275
|
+
color_sequences = build_color_sequences(segment, skip_background:, skip_foreground:)
|
276
|
+
style_sequences = build_style_sequences(segment, skip_styles: skip_styles)
|
277
|
+
sequences = color_sequences + style_sequences
|
278
|
+
|
279
|
+
out = sequences.empty? ? +'' : "\e[#{sequences.compact.join(';')}m"
|
280
|
+
out << segment.text
|
281
|
+
out << ANSI::RESET unless sequences.empty?
|
282
|
+
out
|
283
|
+
end.join
|
284
|
+
end
|
285
|
+
|
286
|
+
# Build the style sequences for a segment
|
287
|
+
#
|
288
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
289
|
+
# @since 0.3.0
|
290
|
+
#
|
291
|
+
# @api private
|
292
|
+
#
|
293
|
+
# @param segment [Segment] the segment to build style sequences for
|
294
|
+
# @param skip_styles [Array<Symbol>] styles to skip
|
295
|
+
#
|
296
|
+
# @return [Array<String>] the style sequences
|
297
|
+
# @rbs (Segment segment, ?skip_styles: Array[Symbol]) -> Array[String]
|
298
|
+
def build_style_sequences(segment, skip_styles: [])
|
299
|
+
return [] if skip_styles.include?(:all)
|
300
|
+
|
301
|
+
segment.styles.filter_map do |style_code|
|
302
|
+
style_name = ANSI::STYLES.key(style_code.to_i)
|
303
|
+
style_code unless skip_styles.include?(style_name)
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
# A segment of an ANSI encoded string
|
308
|
+
#
|
309
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
310
|
+
# @since 0.3.0
|
311
|
+
#
|
312
|
+
# @api public
|
313
|
+
class Segment
|
314
|
+
# The background color sequences for the Segment
|
315
|
+
#
|
316
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
317
|
+
# @since 0.3.0
|
318
|
+
#
|
319
|
+
# @api public
|
320
|
+
#
|
321
|
+
# @return [String, nil] the background color sequences
|
322
|
+
attr_reader :background #: String?
|
323
|
+
|
324
|
+
# The foreground color sequences for the Segment
|
325
|
+
#
|
326
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
327
|
+
# @since 0.3.0
|
328
|
+
#
|
329
|
+
# @api public
|
330
|
+
#
|
331
|
+
# @return [String, nil] the foreground color sequences
|
332
|
+
attr_reader :foreground #: String?
|
333
|
+
|
334
|
+
# The {Location} of the encoded string within the {SequencedString}
|
335
|
+
#
|
336
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
337
|
+
# @since 0.3.0
|
338
|
+
#
|
339
|
+
# @api public
|
340
|
+
#
|
341
|
+
# @return [Location] the {Location}
|
342
|
+
attr_reader :encoded_location #: Location
|
343
|
+
alias encoded_loc encoded_location
|
344
|
+
|
345
|
+
# The {Location} of the encoded string without it's encoding within the {SequencedString}
|
346
|
+
#
|
347
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
348
|
+
# @since 0.3.0
|
349
|
+
#
|
350
|
+
# @api public
|
351
|
+
#
|
352
|
+
# @return [Location] the {Location}
|
353
|
+
attr_reader :stripped_location #: Location
|
354
|
+
alias stripped_loc stripped_location
|
355
|
+
|
356
|
+
# The style sequences (bold, underline, etc...) for the segment
|
357
|
+
#
|
358
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
359
|
+
# @since 0.3.0
|
360
|
+
#
|
361
|
+
# @api public
|
362
|
+
#
|
363
|
+
# @return [Array<String>] the style sequences
|
364
|
+
attr_reader :styles #: Array[String]
|
365
|
+
|
366
|
+
# The raw text of the Segment without any of its ANSI sequences
|
367
|
+
#
|
368
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
369
|
+
# @since 0.3.0
|
370
|
+
#
|
371
|
+
# @api public
|
372
|
+
#
|
373
|
+
# @return [String]
|
374
|
+
attr_reader :text #: String
|
375
|
+
|
376
|
+
# Initialize a new instance of Segment
|
377
|
+
#
|
378
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
379
|
+
# @since 0.3.0
|
380
|
+
#
|
381
|
+
# @api private
|
382
|
+
#
|
383
|
+
# @param options [Hash{Symbol => Object}] the options to initialize the Segment with
|
384
|
+
# @option options background [String, nil] the Segment {#background}
|
385
|
+
# @option options foreground [String, nil] the Segment {#foreground}
|
386
|
+
# @option options encoded_end [Integer] the {Location#end_position end_position} of the Segment
|
387
|
+
# {#encoded_location}
|
388
|
+
# @option options encoded_start [Integer] the {Location#start_position start_position} of the Segment
|
389
|
+
# {#encoded_location}
|
390
|
+
# @option options stripped_end [Integer] the {Location#end_position end_position} of the Segment
|
391
|
+
# {#stripped_location}
|
392
|
+
# @option options stripped_start [Integer] the {Location#start_position start_position} of the Segment
|
393
|
+
# {#stripped_location}
|
394
|
+
# @option options styles [Array<String>] the Segment {#styles}
|
395
|
+
# @option options text [String] the Segment {#text}
|
396
|
+
#
|
397
|
+
# @return [Segment] the new instance of Segment
|
398
|
+
# @rbs (
|
399
|
+
# ?background: String?,
|
400
|
+
# ?foreground: String?,
|
401
|
+
# encoded_end: Integer,
|
402
|
+
# encoded_start: Integer,
|
403
|
+
# stripped_end: Integer,
|
404
|
+
# stripped_start: Integer,
|
405
|
+
# ?styles: Array[String],
|
406
|
+
# text: String
|
407
|
+
# ) -> void
|
408
|
+
def initialize(**options)
|
409
|
+
@background = options.fetch(:background, nil)
|
410
|
+
@foreground = options.fetch(:foreground, nil)
|
411
|
+
@encoded_location = Location.new(
|
412
|
+
end_position: options.fetch(:encoded_end), #: Integer
|
413
|
+
start_position: options.fetch(:encoded_start) #: Integer
|
414
|
+
)
|
415
|
+
@stripped_location = Location.new(
|
416
|
+
end_position: options.fetch(:stripped_end), #: Integer
|
417
|
+
start_position: options.fetch(:stripped_start) #: Integer
|
418
|
+
)
|
419
|
+
@styles = options.fetch(:styles, [])
|
420
|
+
@text = options.fetch(:text)
|
421
|
+
|
422
|
+
freeze
|
423
|
+
end
|
424
|
+
|
425
|
+
# The location of the {Segment} within a {SequencedString}
|
426
|
+
#
|
427
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
428
|
+
# @since 0.3.0
|
429
|
+
#
|
430
|
+
# @api public
|
431
|
+
class Location
|
432
|
+
# The ending position of the Location
|
433
|
+
#
|
434
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
435
|
+
# @since 0.3.0
|
436
|
+
#
|
437
|
+
# @api public
|
438
|
+
#
|
439
|
+
# @return [Integer] the end position
|
440
|
+
attr_reader :end_position #: Integer
|
441
|
+
alias end_pos end_position
|
442
|
+
|
443
|
+
# The starting position of the Location
|
444
|
+
#
|
445
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
446
|
+
# @since 0.3.0
|
447
|
+
#
|
448
|
+
# @api public
|
449
|
+
#
|
450
|
+
# @return [Integer] the start position
|
451
|
+
attr_reader :start_position #: Integer
|
452
|
+
alias start_pos start_position
|
453
|
+
|
454
|
+
# Initialize a new instance of Location
|
455
|
+
#
|
456
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
457
|
+
# @since 0.3.0
|
458
|
+
#
|
459
|
+
# @api private
|
460
|
+
#
|
461
|
+
# @param end_position [Integer] the {#end_position} of the location
|
462
|
+
# @param start_position [Integer] the {#start_position} of the location
|
463
|
+
#
|
464
|
+
# @return [Location] the new instance of Location
|
465
|
+
# @rbs (end_position: Integer, start_position: Integer) -> void
|
466
|
+
def initialize(end_position:, start_position:)
|
467
|
+
@end_position = end_position
|
468
|
+
@start_position = start_position
|
469
|
+
freeze
|
470
|
+
end
|
471
|
+
end
|
472
|
+
end
|
473
|
+
end
|
474
|
+
end
|
475
|
+
end
|
data/lib/sai/ansi.rb
CHANGED
@@ -4,14 +4,14 @@ module Sai
|
|
4
4
|
# ANSI constants for encoding text styles and colors
|
5
5
|
#
|
6
6
|
# @author {https://aaronmallen.me Aaron Allen}
|
7
|
-
# @since
|
7
|
+
# @since 0.1.0
|
8
8
|
#
|
9
9
|
# @api private
|
10
10
|
module ANSI
|
11
11
|
# ANSI color code mappings
|
12
12
|
#
|
13
13
|
# @author {https://aaronmallen.me Aaron Allen}
|
14
|
-
# @since
|
14
|
+
# @since 0.1.0
|
15
15
|
#
|
16
16
|
# @api private
|
17
17
|
#
|
@@ -30,7 +30,7 @@ module Sai
|
|
30
30
|
# Standard ANSI color names and their RGB values
|
31
31
|
#
|
32
32
|
# @author {https://aaronmallen.me Aaron Allen}
|
33
|
-
# @since
|
33
|
+
# @since 0.1.0
|
34
34
|
#
|
35
35
|
# @api private
|
36
36
|
#
|
@@ -57,7 +57,7 @@ module Sai
|
|
57
57
|
# ANSI escape sequence for resetting text formatting
|
58
58
|
#
|
59
59
|
# @author {https://aaronmallen.me Aaron Allen}
|
60
|
-
# @since
|
60
|
+
# @since 0.1.0
|
61
61
|
#
|
62
62
|
# @api private
|
63
63
|
#
|
@@ -67,7 +67,7 @@ module Sai
|
|
67
67
|
# Standard ANSI style codes
|
68
68
|
#
|
69
69
|
# @author {https://aaronmallen.me Aaron Allen}
|
70
|
-
# @since
|
70
|
+
# @since 0.1.0
|
71
71
|
#
|
72
72
|
# @api private
|
73
73
|
#
|
@@ -9,7 +9,7 @@ module Sai
|
|
9
9
|
# ANSI escape sequence utilities
|
10
10
|
#
|
11
11
|
# @author {https://aaronmallen.me Aaron Allen}
|
12
|
-
# @since
|
12
|
+
# @since 0.1.0
|
13
13
|
#
|
14
14
|
# @api private
|
15
15
|
module ColorSequence
|
@@ -20,7 +20,7 @@ module Sai
|
|
20
20
|
# Convert a color to the appropriate ANSI escape sequence
|
21
21
|
#
|
22
22
|
# @author {https://aaronmallen.me Aaron Allen}
|
23
|
-
# @since
|
23
|
+
# @since 0.1.0
|
24
24
|
#
|
25
25
|
# @api private
|
26
26
|
#
|
@@ -36,7 +36,7 @@ module Sai
|
|
36
36
|
|
37
37
|
case mode
|
38
38
|
when Terminal::ColorMode::TRUE_COLOR then true_color(rgb, style_type)
|
39
|
-
when Terminal::ColorMode::
|
39
|
+
when Terminal::ColorMode::ADVANCED then advanced(rgb, style_type)
|
40
40
|
when Terminal::ColorMode::ANSI then ansi(rgb, style_type)
|
41
41
|
when Terminal::ColorMode::BASIC then basic(rgb, style_type)
|
42
42
|
else
|
@@ -46,10 +46,33 @@ module Sai
|
|
46
46
|
|
47
47
|
private
|
48
48
|
|
49
|
+
# Convert RGB values to an 8-bit color sequence
|
50
|
+
#
|
51
|
+
# @author {https://aaronmallen.me Aaron Allen}
|
52
|
+
# @since 0.1.0
|
53
|
+
#
|
54
|
+
# @api private
|
55
|
+
#
|
56
|
+
# @param rgb [Array<Integer>] the RGB components
|
57
|
+
# @param style_type [Symbol] the type of color (foreground or background)
|
58
|
+
#
|
59
|
+
# @return [String] the ANSI escape sequence
|
60
|
+
# @rbs (Array[Integer] rgb, style_type type) -> String
|
61
|
+
def advanced(rgb, style_type)
|
62
|
+
code = style_type == :background ? 48 : 38
|
63
|
+
color_code = if rgb.uniq.size == 1
|
64
|
+
RGB.to_grayscale_index(rgb)
|
65
|
+
else
|
66
|
+
RGB.to_color_cube_index(rgb)
|
67
|
+
end
|
68
|
+
|
69
|
+
"\e[#{code};5;#{color_code}m"
|
70
|
+
end
|
71
|
+
|
49
72
|
# Convert RGB values to a 4-bit ANSI color sequence
|
50
73
|
#
|
51
74
|
# @author {https://aaronmallen.me Aaron Allen}
|
52
|
-
# @since
|
75
|
+
# @since 0.1.0
|
53
76
|
#
|
54
77
|
# @api private
|
55
78
|
#
|
@@ -72,7 +95,7 @@ module Sai
|
|
72
95
|
# Convert a base color to a foreground or background sequence
|
73
96
|
#
|
74
97
|
# @author {https://aaronmallen.me Aaron Allen}
|
75
|
-
# @since
|
98
|
+
# @since 0.1.0
|
76
99
|
#
|
77
100
|
# @api private
|
78
101
|
#
|
@@ -88,7 +111,7 @@ module Sai
|
|
88
111
|
# Convert RGB values to a 3-bit basic color sequence
|
89
112
|
#
|
90
113
|
# @author {https://aaronmallen.me Aaron Allen}
|
91
|
-
# @since
|
114
|
+
# @since 0.1.0
|
92
115
|
#
|
93
116
|
# @api private
|
94
117
|
#
|
@@ -104,33 +127,10 @@ module Sai
|
|
104
127
|
"\e[#{code}m"
|
105
128
|
end
|
106
129
|
|
107
|
-
# Convert RGB values to an 8-bit color sequence
|
108
|
-
#
|
109
|
-
# @author {https://aaronmallen.me Aaron Allen}
|
110
|
-
# @since unreleased
|
111
|
-
#
|
112
|
-
# @api private
|
113
|
-
#
|
114
|
-
# @param rgb [Array<Integer>] the RGB components
|
115
|
-
# @param style_type [Symbol] the type of color (foreground or background)
|
116
|
-
#
|
117
|
-
# @return [String] the ANSI escape sequence
|
118
|
-
# @rbs (Array[Integer] rgb, style_type type) -> String
|
119
|
-
def bit8(rgb, style_type)
|
120
|
-
code = style_type == :background ? 48 : 38
|
121
|
-
color_code = if rgb.uniq.size == 1
|
122
|
-
RGB.to_grayscale_index(rgb)
|
123
|
-
else
|
124
|
-
RGB.to_color_cube_index(rgb)
|
125
|
-
end
|
126
|
-
|
127
|
-
"\e[#{code};5;#{color_code}m"
|
128
|
-
end
|
129
|
-
|
130
130
|
# Convert RGB values to a true color (24-bit) sequence
|
131
131
|
#
|
132
132
|
# @author {https://aaronmallen.me Aaron Allen}
|
133
|
-
# @since
|
133
|
+
# @since 0.1.0
|
134
134
|
#
|
135
135
|
# @api private
|
136
136
|
#
|
@@ -147,7 +147,7 @@ module Sai
|
|
147
147
|
# Validate a color style type
|
148
148
|
#
|
149
149
|
# @author {https://aaronmallen.me Aaron Allen}
|
150
|
-
# @since
|
150
|
+
# @since 0.1.0
|
151
151
|
#
|
152
152
|
# @api private
|
153
153
|
#
|