sai 0.3.0 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -1
- data/README.md +11 -3
- data/docs/USAGE.md +71 -9
- data/lib/sai/ansi/color_parser.rb +109 -0
- data/lib/sai/ansi/sequence_processor.rb +15 -126
- 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 +85 -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 +792 -0
- data/lib/sai/decorator/named_styles.rb +276 -0
- data/lib/sai/decorator/rgb_colors.rb +64 -0
- data/lib/sai/decorator.rb +29 -775
- data/lib/sai/named_colors.rb +522 -0
- data/lib/sai.rb +753 -23
- data/sig/sai/ansi/color_parser.rbs +77 -0
- data/sig/sai/ansi/sequence_processor.rbs +0 -75
- 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 +1504 -0
- data/sig/sai/decorator/named_styles.rbs +72 -0
- data/sig/sai/decorator/rgb_colors.rbs +52 -0
- data/sig/sai/decorator.rbs +21 -195
- data/sig/sai/named_colors.rbs +127 -0
- data/sig/sai.rbs +1488 -44
- metadata +36 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '091a65a91b7e31cfea072bfad910f4af966edf3370301aa1bfbad8e6292d8e25'
|
4
|
+
data.tar.gz: 2296a5bb8b8b8d4c713b91afdbd777d7e69b16dd3550683d8a799dc6ac9c434d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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
|
[](https://app.codacy.com/gh/aaronmallen/sai)
|
5
5
|
[](https://app.codacy.com/gh/aaronmallen/sai/coverage)
|
6
6
|
[](./LICENSE)
|
7
|
-
[](https://rubydoc.info/gems/sai/0.3.
|
7
|
+
[](https://rubydoc.info/gems/sai/0.3.2)
|
8
8
|
[](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.
|
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
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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
|
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
|
65
|
-
@segments
|
66
|
-
@current_segment =
|
67
|
-
@encoded_pos
|
68
|
-
@stripped_pos
|
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
|
-
|
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
|
-
|
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
|
-
|
356
|
-
|
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.
|
64
|
+
RGB.index.grayscale(rgb)
|
65
65
|
else
|
66
|
-
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
|