sai 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -1
  3. data/README.md +11 -3
  4. data/docs/USAGE.md +57 -9
  5. data/lib/sai/ansi/color_parser.rb +109 -0
  6. data/lib/sai/ansi/sequence_processor.rb +15 -126
  7. data/lib/sai/ansi/style_parser.rb +66 -0
  8. data/lib/sai/ansi.rb +0 -27
  9. data/lib/sai/conversion/color_sequence.rb +4 -4
  10. data/lib/sai/conversion/rgb/color_classifier.rb +209 -0
  11. data/lib/sai/conversion/rgb/color_indexer.rb +48 -0
  12. data/lib/sai/conversion/rgb/color_space.rb +192 -0
  13. data/lib/sai/conversion/rgb/color_transformer.rb +140 -0
  14. data/lib/sai/conversion/rgb.rb +23 -269
  15. data/lib/sai/decorator/color_manipulations.rb +157 -0
  16. data/lib/sai/decorator/delegation.rb +84 -0
  17. data/lib/sai/decorator/gradients.rb +363 -0
  18. data/lib/sai/decorator/hex_colors.rb +56 -0
  19. data/lib/sai/decorator/named_colors.rb +780 -0
  20. data/lib/sai/decorator/named_styles.rb +276 -0
  21. data/lib/sai/decorator/rgb_colors.rb +64 -0
  22. data/lib/sai/decorator.rb +29 -775
  23. data/lib/sai/named_colors.rb +437 -0
  24. data/lib/sai.rb +731 -23
  25. data/sig/sai/ansi/color_parser.rbs +77 -0
  26. data/sig/sai/ansi/sequence_processor.rbs +0 -75
  27. data/sig/sai/ansi/style_parser.rbs +59 -0
  28. data/sig/sai/ansi.rbs +0 -10
  29. data/sig/sai/conversion/rgb/color_classifier.rbs +165 -0
  30. data/sig/sai/conversion/rgb/color_indexer.rbs +41 -0
  31. data/sig/sai/conversion/rgb/color_space.rbs +129 -0
  32. data/sig/sai/conversion/rgb/color_transformer.rbs +99 -0
  33. data/sig/sai/conversion/rgb.rbs +15 -198
  34. data/sig/sai/decorator/color_manipulations.rbs +125 -0
  35. data/sig/sai/decorator/delegation.rbs +47 -0
  36. data/sig/sai/decorator/gradients.rbs +267 -0
  37. data/sig/sai/decorator/hex_colors.rbs +48 -0
  38. data/sig/sai/decorator/named_colors.rbs +1491 -0
  39. data/sig/sai/decorator/named_styles.rbs +72 -0
  40. data/sig/sai/decorator/rgb_colors.rbs +52 -0
  41. data/sig/sai/decorator.rbs +21 -195
  42. data/sig/sai/named_colors.rbs +65 -0
  43. data/sig/sai.rbs +1468 -44
  44. metadata +32 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 77b862fa47bb8861e21f03dc023a4b411b22b40e345a7e74895eab4c2d7e416b
4
- data.tar.gz: b579615c7b59bc268ac222ec678b3d4656a886f892229c794429f82d400c3d68
3
+ metadata.gz: 0c45105e60b5f7a3a93d837080975996e96a340aef0ad3fd86cd4b5a1be17e8f
4
+ data.tar.gz: baf93c18ef40221c86c329deeeaabbe7a3619744d3a80a9349314624a4af3995
5
5
  SHA512:
6
- metadata.gz: 131c38f0d73c9899fe2b7baf6a98af00bb1afdbfd26141d630362ba08da157430ad3cd5cd89a3cf0b2e5c786630f29a06cdd087704807c882db0ba96ccbbaedd
7
- data.tar.gz: 8ea163a5901b333aa06589434b553a3655225b222341ad542ffdb078e96916f89b8feee995c2efb7058fae882871c2f35e891c3186fd1d488ad7fcdc3f1ca15a
6
+ metadata.gz: c1540fe3fd55f0a338ff04ff6f9794e831b831c040637f4cdd4f0de0ab0a689a55f95d5c1a744f3ae25b0bc7452983044568e83b6e665439672142cd2abd811b
7
+ data.tar.gz: 4244b0401b7fd0af26665a0a5b5622dd09bd364e4166bc8d12a6c69f94a52b09511d56afe2cd8eaedf1401c402b3ba63845aa900c147e408312221dcc243e21d
data/CHANGELOG.md CHANGED
@@ -6,6 +6,26 @@ The format is based on [Keep a Changelog], and this project adheres to [Break Ve
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.3.1] - 2025-01-22
10
+
11
+ ### Added
12
+
13
+ * [#8](https://github.com/aaronmallen/sai/pull/8) - Add color manipulation methods for darkening and lightening colors
14
+ by [@aaronmallen](https://github.com/aaronmallen)
15
+ * [#9](https://github.com/aaronmallen/sai/pull/9) - Add gradient and rainbow color effects for text and backgrounds by
16
+ [@aaronmallen](https://github.com/aaronmallen)
17
+ * [#13](https://github.com/aaronmallen/sai/pull/13) - Add support for over 360 named colors expanding the color palette
18
+ by [@aaronmallen](https://github.com/aaronmallen)
19
+
20
+ ### Changed
21
+
22
+ * [#10](https://github.com/aaronmallen/sai/pull/10) - Refactor `Sai::Decorator` into component modules by
23
+ [@aaronmallen](https://github.com/aaronmallen)
24
+ * [#11](https://github.com/aaronmallen/sai/pull/11) - Refactor `Sai::Conversion::RGB` into component modules by
25
+ [@aaronmallen](https://github.com/aaronmallen)
26
+ * [#12](https://github.com/aaronmallen/sai/pull/12) - Refactor ANSI sequence processing into specialized parser classes
27
+ by [@aaronmallen](https://github.com/aaronmallen)
28
+
9
29
  ## [0.3.0] - 2025-01-20
10
30
 
11
31
  ### Added
@@ -44,6 +64,7 @@ The format is based on [Keep a Changelog], and this project adheres to [Break Ve
44
64
 
45
65
  <!-- versions -->
46
66
 
47
- [Unreleased]: https://github.com/aaronmallen/sai/compare/0.3.0..HEAD
67
+ [Unreleased]: https://github.com/aaronmallen/sai/compare/0.3.1..HEAD
68
+ [0.3.1]: https://github.com/aaronmallen/sai/compare/0.3.0..0.3.1
48
69
  [0.3.0]: https://github.com/aaronmallen/sai/compare/0.2.0..0.3.0
49
70
  [0.2.0]: https://github.com/aaronmallen/sai/compare/0.1.0..0.2.0
data/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  [![Sai Codacy grade](https://img.shields.io/codacy/grade/0f9a91b573ed4768a773867b95ed4894/main?style=for-the-badge&logo=codacy&logoColor=white&logoSize=auto)](https://app.codacy.com/gh/aaronmallen/sai)
5
5
  [![Sai Codacy coverage](https://img.shields.io/codacy/coverage/0f9a91b573ed4768a773867b95ed4894/main?style=for-the-badge&logo=codacy&logoColor=white&logoSize=auto)](https://app.codacy.com/gh/aaronmallen/sai/coverage)
6
6
  [![Sai License](https://img.shields.io/github/license/aaronmallen/sai?style=for-the-badge&logo=opensourceinitiative&logoColor=white&logoSize=auto)](./LICENSE)
7
- [![Sai Docs](https://img.shields.io/badge/rubydoc-blue?style=for-the-badge&logo=readthedocs&logoColor=white&logoSize=auto&label=docs)](https://rubydoc.info/gems/sai/0.3.0)
7
+ [![Sai Docs](https://img.shields.io/badge/rubydoc-blue?style=for-the-badge&logo=readthedocs&logoColor=white&logoSize=auto&label=docs)](https://rubydoc.info/gems/sai/0.3.1)
8
8
  [![Sai Open Issues](https://img.shields.io/github/issues-search/aaronmallen/sai?query=state%3Aopen&style=for-the-badge&logo=github&logoColor=white&logoSize=auto&label=issues&color=red)](https://github.com/aaronmallen/sai/issues?q=state%3Aopen%20)
9
9
 
10
10
  An elegant color management system for crafting sophisticated CLI applications
@@ -15,6 +15,10 @@ puts Sai.rgb(255, 128, 0).bold.decorate('Warning: Battery Low')
15
15
  puts Sai.hex('#4834d4').italic.decorate('Processing request...')
16
16
  puts Sai.bright_cyan.on_blue.underline.decorate('Download Complete!')
17
17
 
18
+ # Adjust color brightness
19
+ puts Sai.red.darken_text(0.3).decorate('Subtle Error Message')
20
+ puts Sai.blue.lighten_text(0.5).decorate('Highlighted Info')
21
+
18
22
  # Analyze and manipulate ANSI-encoded text
19
23
  text = Sai.sequence("\e[31mError:\e[0m Connection failed")
20
24
  puts text.without_color # Keep formatting, remove colors
@@ -29,7 +33,7 @@ harmony to terminal interfaces through its sophisticated color management.
29
33
 
30
34
  * Rich color support (True Color, 256 colors, ANSI, and basic modes)
31
35
  * Automatic terminal capability detection and color mode adaptation
32
- * RGB, Hex, and named color support with bright variants
36
+ * RGB, Hex, and named color (**over 360 named colors**) support with bright variants
33
37
  * Comprehensive text styling (bold, italic, underline, etc.)
34
38
  * Advanced ANSI sequence parsing and manipulation
35
39
  * Intelligent color downgrading for compatibility
@@ -66,6 +70,10 @@ puts Sai.bright_blue.on_white.decorate('Info')
66
70
  puts Sai.rgb(255, 128, 0).decorate('Custom color')
67
71
  puts Sai.on_rgb(0, 255, 128).decorate('Custom background')
68
72
 
73
+ # Color manipulation
74
+ puts Sai.blue.lighten_text(0.5).decorate('Lightened blue')
75
+ puts Sai.red.darken_text(0.3).decorate('Darkened red')
76
+
69
77
  # Applying styles
70
78
  puts Sai.bold.underline.decorate('Important')
71
79
  puts Sai.red.bold.italic.decorate('Error!')
@@ -80,7 +88,7 @@ puts text.stripped # Get plain text
80
88
  ## Documentation
81
89
 
82
90
  * [Complete Usage Guide](docs/USAGE.md) - Comprehensive documentation of all features
83
- * [API Documentation](https://rubydoc.info/gems/sai/0.3.0) - Detailed API reference
91
+ * [API Documentation](https://rubydoc.info/gems/sai/0.3.1) - Detailed API reference
84
92
 
85
93
  ## Contributing
86
94
 
data/docs/USAGE.md CHANGED
@@ -9,6 +9,8 @@ This guide provides comprehensive documentation for using Sai in your applicatio
9
9
  * [RGB Colors](#rgb-colors)
10
10
  * [Hex Colors](#hex-colors)
11
11
  * [Named Colors](#named-colors)
12
+ * [Color Manipulation](#color-manipulation)
13
+ * [Gradient and Rainbow Effects](#gradient-and-rainbow-effects)
12
14
  * [Text Styles](#text-styles)
13
15
  * [ANSI Sequence Manipulation](#ansi-sequence-manipulation)
14
16
  * [Color Mode Management](#color-mode-management)
@@ -82,21 +84,66 @@ Sai.bright_red.decorate('Bright red text')
82
84
  Sai.bright_blue.decorate('Bright blue text')
83
85
  Sai.on_bright_green.decorate('Bright green background')
84
86
  ```
87
+ ### Named Colors
88
+
89
+ Common ANSI, XTERM, and CSS colors have convenient shortcuts. You can:
90
+
91
+ ```ruby
92
+ # View all available colors programmatically
93
+ Sai::NamedColors.names # Returns array of all color names
85
94
 
86
- Available named colors:
87
- * black/bright_black
88
- * red/bright_red
89
- * green/bright_green
90
- * yellow/bright_yellow
91
- * blue/bright_blue
92
- * magenta/bright_magenta
93
- * cyan/bright_cyan
94
- * white/bright_white
95
+ # Use colors in your code
96
+ Sai.red.decorate('Red text')
97
+ Sai.blue.decorate('Blue text')
98
+ Sai.on_green.decorate('Green background')
99
+ ```
95
100
 
96
101
  > [!TIP]
97
102
  > While named colors provide convenient shortcuts, remember that Sai supports the full RGB color space. Don't feel
98
103
  > limited to just these predefined colors!
99
104
 
105
+ For a complete list of available colors, see:
106
+
107
+ * [Online Color Reference](https://github.com/aaronmallen/sai/blob/main/docs/AVAILABLE_NAMED_COLORS.md)
108
+ * `Sai::NamedColors.names` in your code
109
+
110
+ ### Color Manipulation
111
+
112
+ Adjust the brightness of colors:
113
+
114
+ ```ruby
115
+ # Darken colors
116
+ Sai.red.darken_text(0.5).decorate('Darkened red text')
117
+ Sai.on_blue.darken_background(0.3).decorate('Darkened blue background')
118
+
119
+ # Lighten colors
120
+ Sai.blue.lighten_text(0.5).decorate('Lightened blue text')
121
+ Sai.on_red.lighten_background(0.3).decorate('Lightened red background')
122
+ ```
123
+
124
+ ### Gradient and Rainbow Effects
125
+
126
+ Create dynamic color transitions across text:
127
+
128
+ ```ruby
129
+ # Text gradients (foreground)
130
+ Sai.gradient('#000000', '#FFFFFF', 5).decorate('Black to white gradient')
131
+ Sai.gradient(:red, :blue, 10).decorate('Red to blue gradient')
132
+ Sai.rainbow(6).decorate('Rainbow text')
133
+
134
+ # Background gradients
135
+ Sai.on_gradient('#FF0000', '#0000FF', 8).decorate('Red to blue background')
136
+ Sai.on_gradient(:yellow, :green, 5).decorate('Yellow to green background')
137
+ Sai.on_rainbow(6).decorate('Rainbow background')
138
+ ```
139
+
140
+ Gradients can use any combination of color formats (hex, RGB, or named colors) and will automatically adjust to fit your
141
+ text length. Spaces in text are preserved without color effects.
142
+
143
+ > [!TIP]
144
+ > The number of gradient steps affects the smoothness of the transition. More steps create smoother gradients, but
145
+ > consider terminal performance for very long text.
146
+
100
147
  ## Text Styles
101
148
 
102
149
  Sai supports a variety of text styles:
@@ -286,6 +333,7 @@ end
286
333
  * Use named colors for standard indicators (red for errors, etc.)
287
334
  * Use RGB/Hex for brand colors or specific design requirements
288
335
  * Consider color blindness when choosing colors
336
+ * Use `darken_text`/`lighten_text` to create visual hierarchy or emphasis
289
337
 
290
338
  3. **Style Organization**
291
339
  * Create reusable styles for consistency
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sai
4
+ module ANSI
5
+ # Handles parsing of ANSI color codes
6
+ #
7
+ # @author {https://aaronmallen.me Aaron Allen}
8
+ # @since 0.3.1
9
+ #
10
+ # @api private
11
+ class ColorParser
12
+ # The current segment being processed
13
+ #
14
+ # @author {https://aaronmallen.me Aaron Allen}
15
+ # @since 0.3.1
16
+ #
17
+ # @api private
18
+ #
19
+ # @return [Hash] the current segment being processed
20
+ attr_reader :segment #: Hash[Symbol, untyped]
21
+
22
+ # Initialize a new instance of ColorParser
23
+ #
24
+ # @author {https://aaronmallen.me Aaron Allen}
25
+ # @since 0.3.1
26
+ #
27
+ # @api private
28
+ #
29
+ # @param segment [Hash] the segment to update
30
+ #
31
+ # @return [ColorParser] the new instance of ColorParser
32
+ # @rbs (Hash[Symbol, untyped] segment) -> void
33
+ def initialize(segment)
34
+ @segment = segment
35
+ end
36
+
37
+ # Parse a 256-color code
38
+ #
39
+ # @author {https://aaronmallen.me Aaron Allen}
40
+ # @since 0.3.1
41
+ #
42
+ # @api private
43
+ #
44
+ # @param codes [Array<Integer>] array of color codes
45
+ # @param index [Integer] current position in array
46
+ #
47
+ # @return [Integer] new position in array
48
+ # @rbs (Array[Integer] codes, Integer index) -> Integer
49
+ def parse256(codes, index)
50
+ base_code = codes[index]
51
+ color_number = codes[index + 2]
52
+
53
+ if base_code == 38
54
+ segment[:foreground] = "38;5;#{color_number}"
55
+ else
56
+ segment[:background] = "48;5;#{color_number}"
57
+ end
58
+
59
+ index + 3
60
+ end
61
+
62
+ # Parse a 24-bit color code
63
+ #
64
+ # @author {https://aaronmallen.me Aaron Allen}
65
+ # @since 0.3.1
66
+ #
67
+ # @api private
68
+ #
69
+ # @param codes [Array<Integer>] array of color codes
70
+ # @param index [Integer] current position in array
71
+ #
72
+ # @return [Integer] new position in array
73
+ # @rbs (Array[Integer] codes, Integer index) -> Integer
74
+ def parse_24bit(codes, index)
75
+ base_code = codes[index]
76
+ r = codes[index + 2]
77
+ g = codes[index + 3]
78
+ b = codes[index + 4]
79
+
80
+ if base_code == 38
81
+ segment[:foreground] = "38;2;#{r};#{g};#{b}"
82
+ else
83
+ segment[:background] = "48;2;#{r};#{g};#{b}"
84
+ end
85
+
86
+ index + 5
87
+ end
88
+
89
+ # Parse a basic color code
90
+ #
91
+ # @author {https://aaronmallen.me Aaron Allen}
92
+ # @since 0.3.1
93
+ #
94
+ # @api private
95
+ #
96
+ # @param code [Integer] the color code
97
+ #
98
+ # @return [void]
99
+ # @rbs (Integer code) -> void
100
+ def parse_basic(code)
101
+ if (30..37).cover?(code)
102
+ segment[:foreground] = code.to_s
103
+ else # 40..47
104
+ segment[:background] = code.to_s
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'sai/ansi'
4
+ require 'sai/ansi/color_parser'
5
+ require 'sai/ansi/style_parser'
4
6
  require 'strscan'
5
7
 
6
8
  module Sai
@@ -11,7 +13,7 @@ module Sai
11
13
  # @since 0.3.0
12
14
  #
13
15
  # @api private
14
- class SequenceProcessor # rubocop:disable Metrics/ClassLength
16
+ class SequenceProcessor
15
17
  # The pattern to extract ANSI sequences from a string
16
18
  #
17
19
  # @author {https://aaronmallen.me Aaron Allen}
@@ -23,17 +25,6 @@ module Sai
23
25
  SEQUENCE_PATTERN = /\e\[([0-9;]*)m/ #: Regexp
24
26
  private_constant :SEQUENCE_PATTERN
25
27
 
26
- # Matches the code portion of style sequences
27
- #
28
- # @author {https://aaronmallen.me Aaron Allen}
29
- # @since 0.3.0
30
- #
31
- # @api private
32
- #
33
- # @return [Regexp] the pattern
34
- STYLE_CODE_PATTERN = /(?:[1-9]|2[1-9])/ #: Regexp
35
- private_constant :STYLE_CODE_PATTERN
36
-
37
28
  # Initialize a new instance of SequenceProcessor and parse the provided string
38
29
  #
39
30
  # @author {https://aaronmallen.me Aaron Allen}
@@ -61,11 +52,13 @@ module Sai
61
52
  # @return [SequenceProcessor] the new instance of SequenceProcessor
62
53
  # @rbs (String string) -> void
63
54
  def initialize(string)
64
- @scanner = StringScanner.new(string)
65
- @segments = [] #: Array[Hash[Symbol, untyped]]
66
- @current_segment = blank_segment
67
- @encoded_pos = 0
68
- @stripped_pos = 0
55
+ @scanner = StringScanner.new(string)
56
+ @segments = []
57
+ @current_segment = { text: +'', foreground: nil, background: nil, styles: [] }
58
+ @encoded_pos = 0
59
+ @stripped_pos = 0
60
+ @color_parser = ColorParser.new(@current_segment)
61
+ @style_parser = StyleParser.new(@current_segment)
69
62
  end
70
63
 
71
64
  # Parse a string and return a hash of segments
@@ -86,55 +79,6 @@ module Sai
86
79
 
87
80
  private
88
81
 
89
- # Applies 24-bit truecolor (e.g., 38;2;R;G;B or 48;2;R;G;B)
90
- #
91
- # @author {https://aaronmallen.me Aaron Allen}
92
- # @since 0.3.0
93
- #
94
- # @api private
95
- #
96
- # @param codes_array [Array<Integer>]
97
- # @param index [Integer]
98
- #
99
- # @return [Integer] the updated index (consumed 5 codes)
100
- # @rbs (Array[Integer] codes_array, Integer index) -> Integer
101
- def apply_24bit_color(codes_array, index)
102
- base_code = codes_array[index]
103
- r = codes_array[index + 2]
104
- g = codes_array[index + 3]
105
- b = codes_array[index + 4]
106
-
107
- if base_code == 38
108
- @current_segment[:foreground] = "38;2;#{r};#{g};#{b}"
109
- else
110
- @current_segment[:background] = "48;2;#{r};#{g};#{b}"
111
- end
112
- index + 5
113
- end
114
-
115
- # Applies 256-color mode (e.g., 38;5;160 or 48;5;21)
116
- #
117
- # @author {https://aaronmallen.me Aaron Allen}
118
- # @since 0.3.0
119
- #
120
- # @api private
121
- #
122
- # @param codes_array [Array<Integer>]
123
- # @param index [Integer]
124
- #
125
- # @return [Integer] the updated index (consumed 3 codes)
126
- # @rbs (Array[Integer] codes_array, Integer index) -> Integer
127
- def apply_256_color(codes_array, index)
128
- base_code = codes_array[index]
129
- color_number = codes_array[index + 2]
130
- if base_code == 38
131
- @current_segment[:foreground] = "38;5;#{color_number}"
132
- else
133
- @current_segment[:background] = "48;5;#{color_number}"
134
- end
135
- index + 3 # consumed 3 codes total
136
- end
137
-
138
82
  # Applies the appropriate action for the provided ANSI sequence
139
83
  #
140
84
  # @author {https://aaronmallen.me Aaron Allen}
@@ -155,25 +99,6 @@ module Sai
155
99
  apply_codes(codes)
156
100
  end
157
101
 
158
- # Applies a basic color (FG or BG) in the range 30..37 (FG) or 40..47 (BG)
159
- #
160
- # @author {https://aaronmallen.me Aaron Allen}
161
- # @since 0.3.0
162
- #
163
- # @api private
164
- #
165
- # @param code [Integer] the numeric color code
166
- #
167
- # @return [void]
168
- # @rbs (Integer code) -> void
169
- def apply_basic_color(code)
170
- if (30..37).cover?(code)
171
- @current_segment[:foreground] = code.to_s
172
- else # 40..47
173
- @current_segment[:background] = code.to_s
174
- end
175
- end
176
-
177
102
  # Parse all numeric codes in the provided string, applying them in order (just like a real ANSI terminal)
178
103
  #
179
104
  # @author {https://aaronmallen.me Aaron Allen}
@@ -217,48 +142,16 @@ module Sai
217
142
  reset_segment!
218
143
  index + 1
219
144
  when 30..37, 40..47
220
- apply_basic_color(code)
145
+ @color_parser.parse_basic(code)
221
146
  index + 1
222
147
  when 38, 48
223
148
  parse_extended_color(codes_array, index)
224
149
  else
225
- apply_style_code(code)
150
+ @style_parser.parse(code)
226
151
  index + 1
227
152
  end
228
153
  end
229
154
 
230
- # Applies a single style code (e.g. 1=bold, 2=dim, 4=underline, etc.) if it matches
231
- #
232
- # @author {https://aaronmallen.me Aaron Allen}
233
- # @since 0.3.0
234
- #
235
- # @api private
236
- #
237
- # @param code [Integer] the numeric code to check
238
- #
239
- # @return [void]
240
- # @rbs (Integer code) -> void
241
- def apply_style_code(code)
242
- # If it matches the existing style pattern, add it to @current_segment[:styles]
243
- # Typically: 1..9, or 21..29, etc. (Your STYLE_CODE_PATTERN is /(?:[1-9]|2[1-9])/)
244
- return unless code.to_s.match?(STYLE_CODE_PATTERN)
245
-
246
- @current_segment[:styles] << code.to_s
247
- end
248
-
249
- # Creates and returns a fresh, blank segment
250
- #
251
- # @author {https://aaronmallen.me Aaron Allen}
252
- # @since 0.3.0
253
- #
254
- # @api private
255
- #
256
- # @return [Hash{Symbol => Object}] a new, empty segment
257
- # @rbs () -> Hash[Symbol, untyped]
258
- def blank_segment
259
- { text: +'', foreground: nil, background: nil, styles: [] }
260
- end
261
-
262
155
  # Scans the string for ANSI sequences or individual characters
263
156
  #
264
157
  # @author {https://aaronmallen.me Aaron Allen}
@@ -347,16 +240,12 @@ module Sai
347
240
  # @rbs (Array[Integer] codes_array, Integer index) -> Integer
348
241
  def parse_extended_color(codes_array, index)
349
242
  mode_code = codes_array[index + 1]
350
-
351
243
  return index + 1 unless mode_code
352
244
 
353
245
  case mode_code
354
- when 5
355
- apply_256_color(codes_array, index)
356
- when 2
357
- apply_24bit_color(codes_array, index)
358
- else
359
- index + 1
246
+ when 5 then @color_parser.parse256(codes_array, index)
247
+ when 2 then @color_parser.parse_24bit(codes_array, index)
248
+ else index + 1
360
249
  end
361
250
  end
362
251
 
@@ -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