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.
- checksums.yaml +4 -4
- data/.yardopts +1 -1
- data/CHANGELOG.md +38 -1
- data/README.md +39 -241
- data/docs/USAGE.md +351 -0
- data/lib/sai/ansi/color_parser.rb +109 -0
- data/lib/sai/ansi/sequence_processor.rb +269 -0
- data/lib/sai/ansi/sequenced_string.rb +475 -0
- data/lib/sai/ansi/style_parser.rb +66 -0
- data/lib/sai/ansi.rb +0 -27
- data/lib/sai/conversion/color_sequence.rb +4 -4
- data/lib/sai/conversion/rgb/color_classifier.rb +209 -0
- data/lib/sai/conversion/rgb/color_indexer.rb +48 -0
- data/lib/sai/conversion/rgb/color_space.rb +192 -0
- data/lib/sai/conversion/rgb/color_transformer.rb +140 -0
- data/lib/sai/conversion/rgb.rb +23 -269
- data/lib/sai/decorator/color_manipulations.rb +157 -0
- data/lib/sai/decorator/delegation.rb +84 -0
- data/lib/sai/decorator/gradients.rb +363 -0
- data/lib/sai/decorator/hex_colors.rb +56 -0
- data/lib/sai/decorator/named_colors.rb +780 -0
- data/lib/sai/decorator/named_styles.rb +276 -0
- data/lib/sai/decorator/rgb_colors.rb +64 -0
- data/lib/sai/decorator.rb +35 -795
- data/lib/sai/mode_selector.rb +19 -19
- data/lib/sai/named_colors.rb +437 -0
- data/lib/sai.rb +753 -23
- data/sig/manifest.yaml +3 -0
- data/sig/sai/ansi/color_parser.rbs +77 -0
- data/sig/sai/ansi/sequence_processor.rbs +178 -0
- data/sig/sai/ansi/sequenced_string.rbs +380 -0
- data/sig/sai/ansi/style_parser.rbs +59 -0
- data/sig/sai/ansi.rbs +0 -10
- data/sig/sai/conversion/rgb/color_classifier.rbs +165 -0
- data/sig/sai/conversion/rgb/color_indexer.rbs +41 -0
- data/sig/sai/conversion/rgb/color_space.rbs +129 -0
- data/sig/sai/conversion/rgb/color_transformer.rbs +99 -0
- data/sig/sai/conversion/rgb.rbs +15 -198
- data/sig/sai/decorator/color_manipulations.rbs +125 -0
- data/sig/sai/decorator/delegation.rbs +47 -0
- data/sig/sai/decorator/gradients.rbs +267 -0
- data/sig/sai/decorator/hex_colors.rbs +48 -0
- data/sig/sai/decorator/named_colors.rbs +1491 -0
- data/sig/sai/decorator/named_styles.rbs +72 -0
- data/sig/sai/decorator/rgb_colors.rbs +52 -0
- data/sig/sai/decorator.rbs +25 -202
- data/sig/sai/mode_selector.rbs +19 -19
- data/sig/sai/named_colors.rbs +65 -0
- data/sig/sai.rbs +1485 -44
- 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
|