sai 0.3.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/CHANGELOG.md +22 -1
- data/README.md +11 -3
- data/docs/USAGE.md +57 -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 +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 +29 -775
- data/lib/sai/named_colors.rb +437 -0
- data/lib/sai.rb +731 -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 +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 +21 -195
- data/sig/sai/named_colors.rbs +65 -0
- data/sig/sai.rbs +1468 -44
- metadata +32 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0c45105e60b5f7a3a93d837080975996e96a340aef0ad3fd86cd4b5a1be17e8f
|
4
|
+
data.tar.gz: baf93c18ef40221c86c329deeeaabbe7a3619744d3a80a9349314624a4af3995
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c1540fe3fd55f0a338ff04ff6f9794e831b831c040637f4cdd4f0de0ab0a689a55f95d5c1a744f3ae25b0bc7452983044568e83b6e665439672142cd2abd811b
|
7
|
+
data.tar.gz: 4244b0401b7fd0af26665a0a5b5622dd09bd364e4166bc8d12a6c69f94a52b09511d56afe2cd8eaedf1401c402b3ba63845aa900c147e408312221dcc243e21d
|
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,26 @@ 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.1] - 2025-01-22
|
10
|
+
|
11
|
+
### Added
|
12
|
+
|
13
|
+
* [#8](https://github.com/aaronmallen/sai/pull/8) - Add color manipulation methods for darkening and lightening colors
|
14
|
+
by [@aaronmallen](https://github.com/aaronmallen)
|
15
|
+
* [#9](https://github.com/aaronmallen/sai/pull/9) - Add gradient and rainbow color effects for text and backgrounds by
|
16
|
+
[@aaronmallen](https://github.com/aaronmallen)
|
17
|
+
* [#13](https://github.com/aaronmallen/sai/pull/13) - Add support for over 360 named colors expanding the color palette
|
18
|
+
by [@aaronmallen](https://github.com/aaronmallen)
|
19
|
+
|
20
|
+
### Changed
|
21
|
+
|
22
|
+
* [#10](https://github.com/aaronmallen/sai/pull/10) - Refactor `Sai::Decorator` into component modules by
|
23
|
+
[@aaronmallen](https://github.com/aaronmallen)
|
24
|
+
* [#11](https://github.com/aaronmallen/sai/pull/11) - Refactor `Sai::Conversion::RGB` into component modules by
|
25
|
+
[@aaronmallen](https://github.com/aaronmallen)
|
26
|
+
* [#12](https://github.com/aaronmallen/sai/pull/12) - Refactor ANSI sequence processing into specialized parser classes
|
27
|
+
by [@aaronmallen](https://github.com/aaronmallen)
|
28
|
+
|
9
29
|
## [0.3.0] - 2025-01-20
|
10
30
|
|
11
31
|
### Added
|
@@ -44,6 +64,7 @@ The format is based on [Keep a Changelog], and this project adheres to [Break Ve
|
|
44
64
|
|
45
65
|
<!-- versions -->
|
46
66
|
|
47
|
-
[Unreleased]: https://github.com/aaronmallen/sai/compare/0.3.
|
67
|
+
[Unreleased]: https://github.com/aaronmallen/sai/compare/0.3.1..HEAD
|
68
|
+
[0.3.1]: https://github.com/aaronmallen/sai/compare/0.3.0..0.3.1
|
48
69
|
[0.3.0]: https://github.com/aaronmallen/sai/compare/0.2.0..0.3.0
|
49
70
|
[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.1)
|
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.1) - 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,66 @@ 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
|
+
### 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
|
+
|
100
147
|
## Text Styles
|
101
148
|
|
102
149
|
Sai supports a variety of text styles:
|
@@ -286,6 +333,7 @@ end
|
|
286
333
|
* Use named colors for standard indicators (red for errors, etc.)
|
287
334
|
* Use RGB/Hex for brand colors or specific design requirements
|
288
335
|
* Consider color blindness when choosing colors
|
336
|
+
* Use `darken_text`/`lighten_text` to create visual hierarchy or emphasis
|
289
337
|
|
290
338
|
3. **Style Organization**
|
291
339
|
* 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
|