ruby-ansi-formatter 0.1.0
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 +7 -0
- data/lib/ansi.rb +248 -0
- metadata +43 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 1b078adce12fac81b76b16aa90260a235bf855f2082ffd1a268fa7cd8abff7eb
|
|
4
|
+
data.tar.gz: c00bbaf68b69b8ea0f807a91541e6ca5db5caf29a7e0ce8c25b3cbb8471fc803
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: c0e0503b5d526dddc7a440565d2a64700e7d890af38d7b50d13ae780f0a35dce16144c99d4226b7ec8a00556ffc49d40bad9edc00daa9172a1b5e002f17f2b32
|
|
7
|
+
data.tar.gz: '08459dcc670e3c0d58dc71a6c0f8dabd35ef3618eededa7ec3ddb5e756f58e8357d0a1d82de70ab328ec75abb6f04b6fb8c29a7335795e1b6d5249cf50ed5e43'
|
data/lib/ansi.rb
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Helpers for wrapping text in ANSI escape codes and expanding inline DSL tokens.
|
|
4
|
+
module Ansi
|
|
5
|
+
NAME = "ruby-ansi-formatter"
|
|
6
|
+
VERSION = "0.1.0"
|
|
7
|
+
|
|
8
|
+
# Maps public formatter names to their DSL prefixes and ANSI escape codes.
|
|
9
|
+
FORMATS = {
|
|
10
|
+
reset: {
|
|
11
|
+
prefix: "R",
|
|
12
|
+
code: "\e[0m",
|
|
13
|
+
},
|
|
14
|
+
bold: {
|
|
15
|
+
prefix: "B",
|
|
16
|
+
code: "\e[1m",
|
|
17
|
+
},
|
|
18
|
+
dim: {
|
|
19
|
+
prefix: "D",
|
|
20
|
+
code: "\e[2m",
|
|
21
|
+
},
|
|
22
|
+
italic: {
|
|
23
|
+
prefix: "I",
|
|
24
|
+
code: "\e[3m",
|
|
25
|
+
},
|
|
26
|
+
underline: {
|
|
27
|
+
prefix: "U",
|
|
28
|
+
code: "\e[4m",
|
|
29
|
+
},
|
|
30
|
+
invert: {
|
|
31
|
+
prefix: "X",
|
|
32
|
+
code: "\e[7m",
|
|
33
|
+
},
|
|
34
|
+
hidden: {
|
|
35
|
+
prefix: "H",
|
|
36
|
+
code: "\e[8m",
|
|
37
|
+
},
|
|
38
|
+
strikethrough: {
|
|
39
|
+
prefix: "S",
|
|
40
|
+
code: "\e[9m",
|
|
41
|
+
},
|
|
42
|
+
double_underline: {
|
|
43
|
+
prefix: "DU",
|
|
44
|
+
code: "\e[21m",
|
|
45
|
+
},
|
|
46
|
+
overline: {
|
|
47
|
+
prefix: "OV",
|
|
48
|
+
code: "\e[53m",
|
|
49
|
+
},
|
|
50
|
+
red: {
|
|
51
|
+
prefix: "r",
|
|
52
|
+
code: "\e[31m",
|
|
53
|
+
},
|
|
54
|
+
green: {
|
|
55
|
+
prefix: "g",
|
|
56
|
+
code: "\e[32m",
|
|
57
|
+
},
|
|
58
|
+
yellow: {
|
|
59
|
+
prefix: "y",
|
|
60
|
+
code: "\e[33m",
|
|
61
|
+
},
|
|
62
|
+
blue: {
|
|
63
|
+
prefix: "b",
|
|
64
|
+
code: "\e[34m",
|
|
65
|
+
},
|
|
66
|
+
magenta: {
|
|
67
|
+
prefix: "m",
|
|
68
|
+
code: "\e[35m",
|
|
69
|
+
},
|
|
70
|
+
cyan: {
|
|
71
|
+
prefix: "c",
|
|
72
|
+
code: "\e[36m",
|
|
73
|
+
},
|
|
74
|
+
white: {
|
|
75
|
+
prefix: "w",
|
|
76
|
+
code: "\e[37m",
|
|
77
|
+
},
|
|
78
|
+
bright_red: {
|
|
79
|
+
prefix: "br",
|
|
80
|
+
code: "\e[91m",
|
|
81
|
+
},
|
|
82
|
+
bright_green: {
|
|
83
|
+
prefix: "bg",
|
|
84
|
+
code: "\e[92m",
|
|
85
|
+
},
|
|
86
|
+
bright_yellow: {
|
|
87
|
+
prefix: "by",
|
|
88
|
+
code: "\e[93m",
|
|
89
|
+
},
|
|
90
|
+
bright_blue: {
|
|
91
|
+
prefix: "bb",
|
|
92
|
+
code: "\e[94m",
|
|
93
|
+
},
|
|
94
|
+
bright_magenta: {
|
|
95
|
+
prefix: "bm",
|
|
96
|
+
code: "\e[95m",
|
|
97
|
+
},
|
|
98
|
+
bright_cyan: {
|
|
99
|
+
prefix: "bc",
|
|
100
|
+
code: "\e[96m",
|
|
101
|
+
},
|
|
102
|
+
bright_white: {
|
|
103
|
+
prefix: "bw",
|
|
104
|
+
code: "\e[97m",
|
|
105
|
+
},
|
|
106
|
+
}.freeze
|
|
107
|
+
|
|
108
|
+
class << self
|
|
109
|
+
# Defines formatter helpers for each entry in {FORMATS}.
|
|
110
|
+
#
|
|
111
|
+
# Each generated method accepts text and returns it wrapped in matching ANSI
|
|
112
|
+
# format code followed by reset code. Helpers are exposed under both format
|
|
113
|
+
# names like `bold` and DSL prefixes like `B`.
|
|
114
|
+
FORMATS.each do |format, data|
|
|
115
|
+
define_method(format) do |text|
|
|
116
|
+
"#{data[:code]}#{text}#{FORMATS[:reset][:code]}"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
define_method(data[:prefix]) do |text|
|
|
120
|
+
"#{data[:code]}#{text}#{FORMATS[:reset][:code]}"
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Returns registered formatter names.
|
|
125
|
+
#
|
|
126
|
+
# @return [Array<Symbol>]
|
|
127
|
+
def formats
|
|
128
|
+
@formats ||= FORMATS.keys
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Returns DSL prefixes for all registered formatters.
|
|
132
|
+
#
|
|
133
|
+
# @return [Array<String>]
|
|
134
|
+
def prefixes
|
|
135
|
+
@prefixes ||= FORMATS.map do |_format, data|
|
|
136
|
+
data[:prefix]
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Returns ANSI escape codes for all registered formatters.
|
|
141
|
+
#
|
|
142
|
+
# @return [Array<String>]
|
|
143
|
+
def codes
|
|
144
|
+
@codes ||= FORMATS.map do |_format, data|
|
|
145
|
+
data[:code]
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Expands `%<prefix>(text)` and `%<format_name>(text)` DSL tokens in string.
|
|
150
|
+
#
|
|
151
|
+
# Supports multiline content, nested tokens, and restoration of outer active
|
|
152
|
+
# formatting after nested token resets.
|
|
153
|
+
#
|
|
154
|
+
# @param string [String]
|
|
155
|
+
# @return [String]
|
|
156
|
+
def format(string)
|
|
157
|
+
parse_text(string)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
private
|
|
161
|
+
|
|
162
|
+
# Recursively expands DSL tokens while preserving outer active ANSI codes.
|
|
163
|
+
#
|
|
164
|
+
# @param string [String]
|
|
165
|
+
# @param active_codes [Array<String>]
|
|
166
|
+
# @return [String]
|
|
167
|
+
def parse_text(string, active_codes = [])
|
|
168
|
+
result = +""
|
|
169
|
+
index = 0
|
|
170
|
+
|
|
171
|
+
while index < string.length
|
|
172
|
+
token = extract_token(string, index)
|
|
173
|
+
|
|
174
|
+
unless token
|
|
175
|
+
result << string[index]
|
|
176
|
+
index += 1
|
|
177
|
+
next
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
code = token_data.fetch(token[:token]).fetch(:code)
|
|
181
|
+
|
|
182
|
+
result << code
|
|
183
|
+
result << parse_text(token[:text], active_codes + [code])
|
|
184
|
+
result << FORMATS.fetch(:reset).fetch(:code)
|
|
185
|
+
result << active_codes.join
|
|
186
|
+
|
|
187
|
+
index = token[:next_index]
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
result
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Extracts next complete DSL token starting at start_index.
|
|
194
|
+
#
|
|
195
|
+
# @param string [String]
|
|
196
|
+
# @param start_index [Integer]
|
|
197
|
+
# @return [Hash, nil]
|
|
198
|
+
def extract_token(string, start_index)
|
|
199
|
+
return unless string[start_index] == "%"
|
|
200
|
+
|
|
201
|
+
token = sorted_tokens.find do |candidate|
|
|
202
|
+
string[start_index + 1, candidate.length] == candidate &&
|
|
203
|
+
string[start_index + candidate.length + 1] == "("
|
|
204
|
+
end
|
|
205
|
+
return unless token
|
|
206
|
+
|
|
207
|
+
content_start = start_index + token.length + 2
|
|
208
|
+
depth = 1
|
|
209
|
+
index = content_start
|
|
210
|
+
|
|
211
|
+
while index < string.length
|
|
212
|
+
case string[index]
|
|
213
|
+
when "("
|
|
214
|
+
depth += 1
|
|
215
|
+
when ")"
|
|
216
|
+
depth -= 1
|
|
217
|
+
|
|
218
|
+
if depth.zero?
|
|
219
|
+
return {
|
|
220
|
+
token:,
|
|
221
|
+
text: string[content_start...index],
|
|
222
|
+
next_index: index + 1,
|
|
223
|
+
}
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
index += 1
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# Builds lookup map from DSL token to formatter metadata.
|
|
232
|
+
#
|
|
233
|
+
# @return [Hash{String => Hash}]
|
|
234
|
+
def token_data
|
|
235
|
+
@token_data ||= FORMATS.each_with_object({}) do |(format, data), result|
|
|
236
|
+
result[data[:prefix]] = data
|
|
237
|
+
result[format.to_s] = data
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Returns DSL tokens sorted longest-first for greedy token matching.
|
|
242
|
+
#
|
|
243
|
+
# @return [Array<String>]
|
|
244
|
+
def sorted_tokens
|
|
245
|
+
@sorted_tokens ||= token_data.keys.sort_by { |token| -token.length }
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ruby-ansi-formatter
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Matthew Greenfield
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: Provides helper methods to apply ANSI formatting to text as well as a
|
|
13
|
+
DSL for applying several formats in a single string
|
|
14
|
+
email:
|
|
15
|
+
- mattgreenfield1@gmail.com
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- lib/ansi.rb
|
|
21
|
+
homepage: https://github.com/omgreenfield/ruby-ansi-formatter
|
|
22
|
+
licenses:
|
|
23
|
+
- MIT
|
|
24
|
+
metadata:
|
|
25
|
+
rubygems_mfa_required: 'true'
|
|
26
|
+
rdoc_options: []
|
|
27
|
+
require_paths:
|
|
28
|
+
- lib
|
|
29
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: 3.4.0
|
|
34
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - ">="
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '0'
|
|
39
|
+
requirements: []
|
|
40
|
+
rubygems_version: 4.0.10
|
|
41
|
+
specification_version: 4
|
|
42
|
+
summary: Ruby ANSI formatter
|
|
43
|
+
test_files: []
|