sai 0.2.0 → 0.3.1

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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -1
  3. data/CHANGELOG.md +38 -1
  4. data/README.md +39 -241
  5. data/docs/USAGE.md +351 -0
  6. data/lib/sai/ansi/color_parser.rb +109 -0
  7. data/lib/sai/ansi/sequence_processor.rb +269 -0
  8. data/lib/sai/ansi/sequenced_string.rb +475 -0
  9. data/lib/sai/ansi/style_parser.rb +66 -0
  10. data/lib/sai/ansi.rb +0 -27
  11. data/lib/sai/conversion/color_sequence.rb +4 -4
  12. data/lib/sai/conversion/rgb/color_classifier.rb +209 -0
  13. data/lib/sai/conversion/rgb/color_indexer.rb +48 -0
  14. data/lib/sai/conversion/rgb/color_space.rb +192 -0
  15. data/lib/sai/conversion/rgb/color_transformer.rb +140 -0
  16. data/lib/sai/conversion/rgb.rb +23 -269
  17. data/lib/sai/decorator/color_manipulations.rb +157 -0
  18. data/lib/sai/decorator/delegation.rb +84 -0
  19. data/lib/sai/decorator/gradients.rb +363 -0
  20. data/lib/sai/decorator/hex_colors.rb +56 -0
  21. data/lib/sai/decorator/named_colors.rb +780 -0
  22. data/lib/sai/decorator/named_styles.rb +276 -0
  23. data/lib/sai/decorator/rgb_colors.rb +64 -0
  24. data/lib/sai/decorator.rb +35 -795
  25. data/lib/sai/mode_selector.rb +19 -19
  26. data/lib/sai/named_colors.rb +437 -0
  27. data/lib/sai.rb +753 -23
  28. data/sig/manifest.yaml +3 -0
  29. data/sig/sai/ansi/color_parser.rbs +77 -0
  30. data/sig/sai/ansi/sequence_processor.rbs +178 -0
  31. data/sig/sai/ansi/sequenced_string.rbs +380 -0
  32. data/sig/sai/ansi/style_parser.rbs +59 -0
  33. data/sig/sai/ansi.rbs +0 -10
  34. data/sig/sai/conversion/rgb/color_classifier.rbs +165 -0
  35. data/sig/sai/conversion/rgb/color_indexer.rbs +41 -0
  36. data/sig/sai/conversion/rgb/color_space.rbs +129 -0
  37. data/sig/sai/conversion/rgb/color_transformer.rbs +99 -0
  38. data/sig/sai/conversion/rgb.rbs +15 -198
  39. data/sig/sai/decorator/color_manipulations.rbs +125 -0
  40. data/sig/sai/decorator/delegation.rbs +47 -0
  41. data/sig/sai/decorator/gradients.rbs +267 -0
  42. data/sig/sai/decorator/hex_colors.rbs +48 -0
  43. data/sig/sai/decorator/named_colors.rbs +1491 -0
  44. data/sig/sai/decorator/named_styles.rbs +72 -0
  45. data/sig/sai/decorator/rgb_colors.rbs +52 -0
  46. data/sig/sai/decorator.rbs +25 -202
  47. data/sig/sai/mode_selector.rbs +19 -19
  48. data/sig/sai/named_colors.rbs +65 -0
  49. data/sig/sai.rbs +1485 -44
  50. metadata +38 -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
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sai
4
+ module ANSI
5
+ # Handles parsing of ANSI style codes
6
+ #
7
+ # @author {https://aaronmallen.me Aaron Allen}
8
+ # @since 0.3.1
9
+ #
10
+ # @api private
11
+ class StyleParser
12
+ # Matches the code portion of style sequences
13
+ #
14
+ # @author {https://aaronmallen.me Aaron Allen}
15
+ # @since 0.3.1
16
+ #
17
+ # @api private
18
+ #
19
+ # @return [Regexp] the pattern
20
+ STYLE_CODE_PATTERN = /^(?:[1-9]|2[1-9])$/ #: Regexp
21
+ private_constant :STYLE_CODE_PATTERN
22
+
23
+ # The current segment being processed
24
+ #
25
+ # @author {https://aaronmallen.me Aaron Allen}
26
+ # @since 0.3.1
27
+ #
28
+ # @api private
29
+ #
30
+ # @return [Hash] the current segment being processed
31
+ attr_reader :segment #: Hash[Symbol, untyped]
32
+
33
+ # Initialize a new instance of StyleParser
34
+ #
35
+ # @author {https://aaronmallen.me Aaron Allen}
36
+ # @since 0.3.1
37
+ #
38
+ # @api private
39
+ #
40
+ # @param segment [Hash] the segment to update
41
+ #
42
+ # @return [StyleParser] the new instance of StyleParser
43
+ # @rbs (Hash[Symbol, untyped] segment) -> void
44
+ def initialize(segment)
45
+ @segment = segment
46
+ end
47
+
48
+ # Parse a style code
49
+ #
50
+ # @author {https://aaronmallen.me Aaron Allen}
51
+ # @since 0.3.1
52
+ #
53
+ # @api private
54
+ #
55
+ # @param code [Integer] the style code
56
+ #
57
+ # @return [void]
58
+ # @rbs (Integer code) -> void
59
+ def parse(code)
60
+ return unless code.to_s.match?(STYLE_CODE_PATTERN)
61
+
62
+ segment[:styles] << code.to_s
63
+ end
64
+ end
65
+ end
66
+ end
data/lib/sai/ansi.rb CHANGED
@@ -27,33 +27,6 @@ module Sai
27
27
  white: 7
28
28
  }.freeze # Hash[Symbol, Integer]
29
29
 
30
- # Standard ANSI color names and their RGB values
31
- #
32
- # @author {https://aaronmallen.me Aaron Allen}
33
- # @since 0.1.0
34
- #
35
- # @api private
36
- #
37
- # @return [Hash{Symbol => Array<Integer>}] the color names and RGB values
38
- COLOR_NAMES = {
39
- black: [0, 0, 0],
40
- red: [205, 0, 0],
41
- green: [0, 205, 0],
42
- yellow: [205, 205, 0],
43
- blue: [0, 0, 238],
44
- magenta: [205, 0, 205],
45
- cyan: [0, 205, 205],
46
- white: [229, 229, 229],
47
- bright_black: [127, 127, 127],
48
- bright_red: [255, 0, 0],
49
- bright_green: [0, 255, 0],
50
- bright_yellow: [255, 255, 0],
51
- bright_blue: [92, 92, 255],
52
- bright_magenta: [255, 0, 255],
53
- bright_cyan: [0, 255, 255],
54
- bright_white: [255, 255, 255]
55
- }.freeze # Hash[Symbol, Array[Integer]]
56
-
57
30
  # ANSI escape sequence for resetting text formatting
58
31
  #
59
32
  # @author {https://aaronmallen.me Aaron Allen}
@@ -61,9 +61,9 @@ module Sai
61
61
  def advanced(rgb, style_type)
62
62
  code = style_type == :background ? 48 : 38
63
63
  color_code = if rgb.uniq.size == 1
64
- RGB.to_grayscale_index(rgb)
64
+ RGB.index.grayscale(rgb)
65
65
  else
66
- RGB.to_color_cube_index(rgb)
66
+ RGB.index.color_cube(rgb)
67
67
  end
68
68
 
69
69
  "\e[#{code};5;#{color_code}m"
@@ -86,7 +86,7 @@ module Sai
86
86
  brightness = (r + g + b) / 3.0
87
87
  is_bright = brightness > 0.5
88
88
 
89
- color = RGB.closest_ansi_color(r, g, b)
89
+ color = RGB.classify.closest_ansi_color(r, g, b)
90
90
  code = base_color_for_style_type(ANSI::COLOR_CODES[color], style_type)
91
91
  code += 60 if is_bright
92
92
  "\e[#{code}m"
@@ -122,7 +122,7 @@ module Sai
122
122
  # @rbs (Array[Integer] rgb, style_type style_type) -> String
123
123
  def basic(rgb, style_type)
124
124
  r, g, b = rgb.map { |c| c / 255.0 } #: [Float, Float, Float]
125
- color = RGB.closest_ansi_color(r, g, b)
125
+ color = RGB.classify.closest_ansi_color(r, g, b)
126
126
  code = base_color_for_style_type(ANSI::COLOR_CODES[color], style_type)
127
127
  "\e[#{code}m"
128
128
  end