sai 0.3.0 → 0.3.2

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -1
  3. data/README.md +11 -3
  4. data/docs/USAGE.md +71 -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 +85 -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 +792 -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 +522 -0
  24. data/lib/sai.rb +753 -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 +1504 -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 +127 -0
  43. data/sig/sai.rbs +1488 -44
  44. metadata +36 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 77b862fa47bb8861e21f03dc023a4b411b22b40e345a7e74895eab4c2d7e416b
4
- data.tar.gz: b579615c7b59bc268ac222ec678b3d4656a886f892229c794429f82d400c3d68
3
+ metadata.gz: '091a65a91b7e31cfea072bfad910f4af966edf3370301aa1bfbad8e6292d8e25'
4
+ data.tar.gz: 2296a5bb8b8b8d4c713b91afdbd777d7e69b16dd3550683d8a799dc6ac9c434d
5
5
  SHA512:
6
- metadata.gz: 131c38f0d73c9899fe2b7baf6a98af00bb1afdbfd26141d630362ba08da157430ad3cd5cd89a3cf0b2e5c786630f29a06cdd087704807c882db0ba96ccbbaedd
7
- data.tar.gz: 8ea163a5901b333aa06589434b553a3655225b222341ad542ffdb078e96916f89b8feee995c2efb7058fae882871c2f35e891c3186fd1d488ad7fcdc3f1ca15a
6
+ metadata.gz: 548ca06d57fe14768900f6d04191b52d876c38a9c5cca96b26da9d2c8afc3b7dffdd44b9d075b92bda97d1d80e2b6b75d3d8782c3e592d1a4547dfc72dceba1f
7
+ data.tar.gz: 48d9f58551111cd95a61c7c11ba2e83506239bf7e4805c93a3c38c653f28dfb94680637f35cf4a0325702ad4fe320f32060d794b26be2c308dd0b216f9cfbf64
data/CHANGELOG.md CHANGED
@@ -6,6 +6,33 @@ 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.2] - 2025-01-22
10
+
11
+ ### Added
12
+
13
+ * [#15](https://github.com/aaronmallen/sai/pull/15) - Add support for registering custom named colors by
14
+ [@aaronmallen](https://github.com/aaronmallen)
15
+
16
+ ## [0.3.1] - 2025-01-22
17
+
18
+ ### Added
19
+
20
+ * [#8](https://github.com/aaronmallen/sai/pull/8) - Add color manipulation methods for darkening and lightening colors
21
+ by [@aaronmallen](https://github.com/aaronmallen)
22
+ * [#9](https://github.com/aaronmallen/sai/pull/9) - Add gradient and rainbow color effects for text and backgrounds by
23
+ [@aaronmallen](https://github.com/aaronmallen)
24
+ * [#13](https://github.com/aaronmallen/sai/pull/13) - Add support for over 360 named colors expanding the color palette
25
+ by [@aaronmallen](https://github.com/aaronmallen)
26
+
27
+ ### Changed
28
+
29
+ * [#10](https://github.com/aaronmallen/sai/pull/10) - Refactor `Sai::Decorator` into component modules by
30
+ [@aaronmallen](https://github.com/aaronmallen)
31
+ * [#11](https://github.com/aaronmallen/sai/pull/11) - Refactor `Sai::Conversion::RGB` into component modules by
32
+ [@aaronmallen](https://github.com/aaronmallen)
33
+ * [#12](https://github.com/aaronmallen/sai/pull/12) - Refactor ANSI sequence processing into specialized parser classes
34
+ by [@aaronmallen](https://github.com/aaronmallen)
35
+
9
36
  ## [0.3.0] - 2025-01-20
10
37
 
11
38
  ### Added
@@ -44,6 +71,8 @@ The format is based on [Keep a Changelog], and this project adheres to [Break Ve
44
71
 
45
72
  <!-- versions -->
46
73
 
47
- [Unreleased]: https://github.com/aaronmallen/sai/compare/0.3.0..HEAD
74
+ [Unreleased]: https://github.com/aaronmallen/sai/compare/0.3.2..HEAD
75
+ [0.3.2]: https://github.com/aaronmallen/sai/compare/0.3.1..0.3.2
76
+ [0.3.1]: https://github.com/aaronmallen/sai/compare/0.3.0..0.3.1
48
77
  [0.3.0]: https://github.com/aaronmallen/sai/compare/0.2.0..0.3.0
49
78
  [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.2)
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.2) - 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,80 @@ 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
+ #### Registering Custom Named Colors
111
+
112
+ Sai allows you to register custom named colors for easy reuse with `Sai.register`:
113
+
114
+ ```ruby
115
+ # Register custom colors
116
+ Sai.register(:my_color, '#CF4C5F')
117
+
118
+ # or alternatively
119
+ Sai.register(:my_color, [207, 76, 95])
120
+
121
+ Sai.my_color.decorate('Hello, world!')
122
+ ```
123
+
124
+ ### Color Manipulation
125
+
126
+ Adjust the brightness of colors:
127
+
128
+ ```ruby
129
+ # Darken colors
130
+ Sai.red.darken_text(0.5).decorate('Darkened red text')
131
+ Sai.on_blue.darken_background(0.3).decorate('Darkened blue background')
132
+
133
+ # Lighten colors
134
+ Sai.blue.lighten_text(0.5).decorate('Lightened blue text')
135
+ Sai.on_red.lighten_background(0.3).decorate('Lightened red background')
136
+ ```
137
+
138
+ ### Gradient and Rainbow Effects
139
+
140
+ Create dynamic color transitions across text:
141
+
142
+ ```ruby
143
+ # Text gradients (foreground)
144
+ Sai.gradient('#000000', '#FFFFFF', 5).decorate('Black to white gradient')
145
+ Sai.gradient(:red, :blue, 10).decorate('Red to blue gradient')
146
+ Sai.rainbow(6).decorate('Rainbow text')
147
+
148
+ # Background gradients
149
+ Sai.on_gradient('#FF0000', '#0000FF', 8).decorate('Red to blue background')
150
+ Sai.on_gradient(:yellow, :green, 5).decorate('Yellow to green background')
151
+ Sai.on_rainbow(6).decorate('Rainbow background')
152
+ ```
153
+
154
+ Gradients can use any combination of color formats (hex, RGB, or named colors) and will automatically adjust to fit your
155
+ text length. Spaces in text are preserved without color effects.
156
+
157
+ > [!TIP]
158
+ > The number of gradient steps affects the smoothness of the transition. More steps create smoother gradients, but
159
+ > consider terminal performance for very long text.
160
+
100
161
  ## Text Styles
101
162
 
102
163
  Sai supports a variety of text styles:
@@ -286,6 +347,7 @@ end
286
347
  * Use named colors for standard indicators (red for errors, etc.)
287
348
  * Use RGB/Hex for brand colors or specific design requirements
288
349
  * Consider color blindness when choosing colors
350
+ * Use `darken_text`/`lighten_text` to create visual hierarchy or emphasis
289
351
 
290
352
  3. **Style Organization**
291
353
  * 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