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
data/docs/USAGE.md ADDED
@@ -0,0 +1,351 @@
1
+ # Sai Usage Guide
2
+
3
+ This guide provides comprehensive documentation for using Sai in your applications.
4
+
5
+ ## Table of Contents
6
+
7
+ * [Basic Usage](#basic-usage)
8
+ * [Color Support](#color-support)
9
+ * [RGB Colors](#rgb-colors)
10
+ * [Hex Colors](#hex-colors)
11
+ * [Named Colors](#named-colors)
12
+ * [Color Manipulation](#color-manipulation)
13
+ * [Gradient and Rainbow Effects](#gradient-and-rainbow-effects)
14
+ * [Text Styles](#text-styles)
15
+ * [ANSI Sequence Manipulation](#ansi-sequence-manipulation)
16
+ * [Color Mode Management](#color-mode-management)
17
+ * [Terminal Capabilities](#terminal-capabilities)
18
+ * [Integration Patterns](#integration-patterns)
19
+ * [Best Practices](#best-practices)
20
+
21
+ ## Basic Usage
22
+
23
+ Sai can be used directly or included in your own classes and modules:
24
+
25
+ ```ruby
26
+ # Direct usage
27
+ puts Sai.red.decorate('Error!')
28
+ puts Sai.bright_blue.on_white.decorate('Info')
29
+
30
+ # Include in your own classes
31
+ class CLI
32
+ include Sai
33
+
34
+ def error(message)
35
+ puts decorator.red.bold.decorate(message)
36
+ end
37
+
38
+ def info(message)
39
+ puts decorator.bright_blue.decorate(message)
40
+ end
41
+ end
42
+ ```
43
+
44
+ ## Color Support
45
+
46
+ ### RGB Colors
47
+
48
+ Use any RGB color (0-255 per channel):
49
+
50
+ ```ruby
51
+ # Foreground colors
52
+ Sai.rgb(255, 128, 0).decorate('Orange text')
53
+ Sai.rgb(100, 149, 237).decorate('Cornflower blue')
54
+
55
+ # Background colors
56
+ Sai.on_rgb(0, 255, 128).decorate('Custom green background')
57
+ ```
58
+
59
+ ### Hex Colors
60
+
61
+ Use any hex color code:
62
+
63
+ ```ruby
64
+ # Foreground colors
65
+ Sai.hex('#FF8000').decorate('Orange text')
66
+ Sai.hex('#6495ED').decorate('Cornflower blue')
67
+
68
+ # Background colors
69
+ Sai.on_hex('#00FF80').decorate('Custom green background')
70
+ ```
71
+
72
+ ### Named Colors
73
+
74
+ Common ANSI colors have convenient shortcuts:
75
+
76
+ ```ruby
77
+ # Standard colors
78
+ Sai.red.decorate('Red text')
79
+ Sai.blue.decorate('Blue text')
80
+ Sai.on_green.decorate('Green background')
81
+
82
+ # Bright variants
83
+ Sai.bright_red.decorate('Bright red text')
84
+ Sai.bright_blue.decorate('Bright blue text')
85
+ Sai.on_bright_green.decorate('Bright green background')
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
94
+
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
+ ```
100
+
101
+ > [!TIP]
102
+ > While named colors provide convenient shortcuts, remember that Sai supports the full RGB color space. Don't feel
103
+ > limited to just these predefined colors!
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
+
147
+ ## Text Styles
148
+
149
+ Sai supports a variety of text styles:
150
+
151
+ ```ruby
152
+ Sai.bold.decorate('Bold text')
153
+ Sai.italic.decorate('Italic text')
154
+ Sai.underline.decorate('Underlined text')
155
+ Sai.strike.decorate('Strikethrough text')
156
+ ```
157
+
158
+ Available styles:
159
+ * bold
160
+ * dim
161
+ * italic
162
+ * underline
163
+ * blink
164
+ * rapid_blink
165
+ * reverse
166
+ * conceal
167
+ * strike
168
+
169
+ Style removal:
170
+ * no_blink
171
+ * no_italic
172
+ * no_underline
173
+ * no_reverse
174
+ * no_conceal
175
+ * no_strike
176
+ * normal_intensity
177
+
178
+ ## ANSI Sequence Manipulation
179
+
180
+ Sai provides powerful tools for working with ANSI-encoded strings:
181
+
182
+ ```ruby
183
+ # Create a SequencedString from decorated text
184
+ text = Sai.red.bold.decorate("Warning!")
185
+
186
+ # Or parse existing ANSI text
187
+ text = Sai.sequence("\e[31mred\e[0m and \e[32mgreen\e[0m")
188
+
189
+ # Get plain text without formatting
190
+ plain = text.stripped # => "red and green"
191
+
192
+ # Remove specific attributes
193
+ text.without_color # Remove all colors but keep styles
194
+ text.without_style # Remove all styles but keep colors
195
+ text.without_style(:bold, :italic) # Remove specific styles
196
+
197
+ # Access individual segments
198
+ text.each do |segment|
199
+ puts "Text: #{segment.text}"
200
+ puts "Foreground: #{segment.foreground}"
201
+ puts "Background: #{segment.background}"
202
+ puts "Styles: #{segment.styles}"
203
+ puts "Position: #{segment.encoded_location.start_position}..#{segment.encoded_location.end_position}"
204
+ end
205
+ ```
206
+
207
+ ## Color Mode Management
208
+
209
+ Sai by default automatically detects your terminal's capabilities and automatically downgrades colors based on those
210
+ capabilities but also allows manual control:
211
+
212
+ ```ruby
213
+ # Use automatic mode detection (default)
214
+ Sai.with_mode(Sai.mode.auto)
215
+
216
+ # Force specific color modes
217
+ puts Sai.with_mode(Sai.mode.true_color).red.decorate('24-bit color')
218
+ puts Sai.with_mode(Sai.mode.advanced).red.decorate('256 colors')
219
+ puts Sai.with_mode(Sai.mode.ansi).red.decorate('16 colors')
220
+ puts Sai.with_mode(Sai.mode.basic).red.decorate('8 colors')
221
+ puts Sai.with_mode(Sai.mode.no_color).red.decorate('No color')
222
+
223
+ # Use automatic downgrading
224
+ puts Sai.with_mode(Sai.mode.advanced_auto).red.decorate('256 colors or less')
225
+ puts Sai.with_mode(Sai.mode.ansi_auto).red.decorate('16 colors or less')
226
+ puts Sai.with_mode(Sai.mode.basic_auto).red.decorate('8 colors or less')
227
+ ```
228
+
229
+ > [!WARNING]
230
+ > When using fixed color modes (like `true_color` or `advanced`), Sai will not automatically downgrade colors for
231
+ > terminals with lower color support. For automatic color mode adjustment, use modes ending in `_auto`
232
+ > (like `advanced_auto` or `ansi_auto`).
233
+
234
+ Color Mode Hierarchy:
235
+
236
+ 1. True Color (24-bit): 16.7 million colors
237
+ 2. Advanced (8-bit): 256 colors
238
+ 3. ANSI (4-bit): 16 colors
239
+ 4. Basic (3-bit): 8 colors
240
+ 5. No Color: All formatting stripped
241
+
242
+ ## Terminal Capabilities
243
+
244
+ Check terminal color support:
245
+
246
+ ```ruby
247
+ # Using directly
248
+ Sai.support.true_color? # => true/false
249
+ Sai.support.advanced? # => true/false
250
+ Sai.support.ansi? # => true/false
251
+ Sai.support.basic? # => true/false
252
+ Sai.support.color? # => true/false
253
+
254
+ # Using included module
255
+ class CLI
256
+ include Sai
257
+
258
+ def check_support
259
+ if terminal_color_support.true_color?
260
+ puts "Terminal supports true color!"
261
+ end
262
+ end
263
+ end
264
+ ```
265
+
266
+ ## Integration Patterns
267
+
268
+ ### Defining Reusable Styles
269
+
270
+ Create consistent styling across your application:
271
+
272
+ ```ruby
273
+ module Style
274
+ ERROR = Sai.bold.bright_white.on_red
275
+ WARNING = Sai.black.on_yellow
276
+ SUCCESS = Sai.bright_green
277
+ INFO = Sai.bright_blue
278
+ HEADER = Sai.bold.bright_cyan.underline
279
+ end
280
+
281
+ # Use your defined styles
282
+ puts Style::ERROR.decorate('Something went wrong!')
283
+ puts Style::SUCCESS.decorate('Operation completed successfully')
284
+
285
+ # Styles can be further customized
286
+ puts Style::ERROR.italic.decorate('Critical error!')
287
+ ```
288
+
289
+ > [!TIP]
290
+ > This pattern is particularly useful for maintaining consistent styling across your application and creating theme
291
+ > systems. You can even build on existing styles:
292
+ >
293
+ > ```ruby
294
+ > module Theme
295
+ > PRIMARY = Sai.rgb(63, 81, 181)
296
+ > SECONDARY = Sai.rgb(255, 87, 34)
297
+ >
298
+ > BUTTON = PRIMARY.bold
299
+ > LINK = SECONDARY.underline
300
+ > HEADER = PRIMARY.bold.underline
301
+ > end
302
+ > ```
303
+
304
+ ### Class Integration
305
+
306
+ Integrate Sai into your classes:
307
+
308
+ ```ruby
309
+ class Logger
310
+ include Sai
311
+
312
+ def error(message)
313
+ puts decorator.red.bold.decorate(message)
314
+ end
315
+
316
+ def warn(message)
317
+ puts decorator.yellow.decorate(message)
318
+ end
319
+
320
+ def info(message)
321
+ puts decorator.with_mode(color_mode.true_color).bright_blue.decorate(message)
322
+ end
323
+ end
324
+ ```
325
+
326
+ ## Best Practices
327
+
328
+ 1. **Automatic Mode Detection**
329
+ * Use `Sai.mode.auto` by default to respect terminal capabilities
330
+ * Only force specific color modes when necessary
331
+
332
+ 2. **Color Usage**
333
+ * Use named colors for standard indicators (red for errors, etc.)
334
+ * Use RGB/Hex for brand colors or specific design requirements
335
+ * Consider color blindness when choosing colors
336
+ * Use `darken_text`/`lighten_text` to create visual hierarchy or emphasis
337
+
338
+ 3. **Style Organization**
339
+ * Create reusable styles for consistency
340
+ * Group related styles in modules
341
+ * Consider creating a theme system for larger applications
342
+
343
+ 4. **Performance**
344
+ * Cache decorator instances when using repeatedly
345
+ * Use `SequencedString` parsing for complex manipulations
346
+ * Consider terminal capabilities when designing output
347
+
348
+ 5. **Accessibility**
349
+ * Don't rely solely on color for important information
350
+ * Use styles (bold, underline) to enhance meaning
351
+ * Respect NO_COLOR environment variable
@@ -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