sai 0.2.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
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