term_color 0.0.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 +7 -0
- data/README.md +110 -0
- data/lib/term_color/rule.rb +316 -0
- data/lib/term_color/rule_set.rb +214 -0
- data/lib/term_color.rb +16 -0
- metadata +68 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d7de87ceb0f3dfa90c19e011c9e2cddc1bf767a92ec88868e13574336601ba92
|
4
|
+
data.tar.gz: 20101cdbcf43876dfa89cab889443850a8f59d742db7dbd3a5d66336d34994da
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 36863a07e2d674605356d73295d0a903413fd713041abb977868b37cf4661e2ad07e5b3e0f4c461605c2b6fed05bf97eec5756a9ceae3a2f208ab6fe744f1266
|
7
|
+
data.tar.gz: 31333b2890a8497edd93781f62a4239692f4981d2bd719c2e45ee641b7406c9fa99d2d8da1ffd0940716e25968f17131512d149996393afb65027d1fc591ba30
|
data/README.md
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
# TermColor
|
2
|
+
|
3
|
+
Rule-based text coloring/styling library for terminal text
|
4
|
+
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
### Justifying Features
|
8
|
+
|
9
|
+
- Define named text style rules once, combining fg/bg color and styling that can be reused through out application
|
10
|
+
- Able to manually specify what gets reset after style use is finished in a line, which parts to reset, etc.
|
11
|
+
- Streamline including multiple styles within a single string, including allowing nested rules
|
12
|
+
|
13
|
+
### Requirements
|
14
|
+
|
15
|
+
- Tested/developed for Ruby 2.6.1
|
16
|
+
|
17
|
+
## Concepts/Syntax
|
18
|
+
|
19
|
+
Rules are grouped into `RuleSets`, which are represented by instances of {TermColor::RuleSet}.
|
20
|
+
|
21
|
+
### Rule Set
|
22
|
+
|
23
|
+
A set is constructed from a Hash of rule, where they keys are rule names and the values are the rule definition hashes.
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
rules = {
|
27
|
+
# Rule named :title
|
28
|
+
title: { fg: :yellow, enable: :underline }
|
29
|
+
}
|
30
|
+
# Create instance of RuleSet
|
31
|
+
rule_set = TermColor.create_rule_set(rules)
|
32
|
+
# or
|
33
|
+
rule_set = TermColor::RuleSet.new(rules)
|
34
|
+
```
|
35
|
+
|
36
|
+
#### Applying to Text
|
37
|
+
|
38
|
+
Once you've got a `RuleSet` instance, calling its `apply` or `print`/`printf` methods with a string parameter will give back a copy of that string with styles applied
|
39
|
+
|
40
|
+
__Methods__
|
41
|
+
|
42
|
+
- `apply` - {TermColor::RuleSet#apply}
|
43
|
+
- `print` - {TermColor::RuleSet#print}
|
44
|
+
- `printf` - {TermColor::RuleSet#printf}
|
45
|
+
|
46
|
+
__Use__
|
47
|
+
|
48
|
+
- To apply a style to a section of the input string, surround it with `%rule_name` and `%%`
|
49
|
+
- E.g.: `"%titleTitle Text%%"`
|
50
|
+
- `%%` indicates the end of rule application, after which any `after` rules will get applied
|
51
|
+
- Including `%%` when no rule is active will apply the `default` rule's `:after` options, which can either be overridden in your rule set, or make use of the built-in version which simply resets all colors and text styling to system default
|
52
|
+
- Rule application can be nested (`"%titleTitle of %otherBook%%%%"`)
|
53
|
+
|
54
|
+
### Rule Definitions
|
55
|
+
|
56
|
+
_For more details on rule definitions, see {file:docs/rule_dsl.md Rule DSL} ([View on GitHub](https://github.com/vdtdev/term_color/blob/master/docs/rule_dsl.md))_
|
57
|
+
|
58
|
+
Rule definitions are just hashes that include rule options. The included options can be divided into `:inside` (applied to text rule is applied to) and `:after` (applied to text following text rule is applied to). If neither of these sub hashes are included, all options are treated as being for `:inside`, and an `:after` set is auto-generated to unapply style changes made inside rule for following text. To prevent an `:after` section from being automatically generated, either include your own `:after` section or include `after: {}`.
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
# No groups, after will be generated
|
62
|
+
rule = { fg: :red }
|
63
|
+
# Same as above but with groups. after will be generated
|
64
|
+
rule = { inside: { fg: :red } }
|
65
|
+
# No groups, skip after generation by setting it to
|
66
|
+
# empty hash of options
|
67
|
+
rule = { fg: :red, after: {} }
|
68
|
+
# Both groups, same as others but with explicitly set after options,
|
69
|
+
# no auto-generation
|
70
|
+
rule = { inside: { fg: :red }, after: { reset: :fg } }
|
71
|
+
```
|
72
|
+
|
73
|
+
For more details on rule definitions, see {file:docs/rule_dsl.md Rule DSL} ([View on GitHub]{https://github.com/vdtdev/term_color/blob/master/docs/rule_dsl.md})
|
74
|
+
|
75
|
+
## Examples
|
76
|
+
|
77
|
+
[If example images are missing, view readme on github](https://github.com/vdtdev/term_color/blob/master/README.md)
|
78
|
+
|
79
|
+
### Basic
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
rule_set = TermColor.create_rule_set({
|
83
|
+
opt: { fg: :cyan },
|
84
|
+
err: { fg: :red }
|
85
|
+
})
|
86
|
+
|
87
|
+
rule_set.printf "%errInvalid%% option: %opt%s%%\n", "fruit"
|
88
|
+
```
|
89
|
+
|
90
|
+

|
91
|
+

|
92
|
+
|
93
|
+
### Nested /w XTerm RGB
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
rule_set = TermColor.create_rule_set({
|
97
|
+
title: { fg: :yellow, enable: :underline },
|
98
|
+
emph: { fg: [0xa0,0xa0,0xff], enable: :italic },
|
99
|
+
author: { fg: :green, bg: :blue, enable: :bold }
|
100
|
+
})
|
101
|
+
|
102
|
+
rule_set.print "book: %%titleHarry Potter (%emph%%)%% by %authorJ. K. Rowling%%\n"
|
103
|
+
```
|
104
|
+
|
105
|
+

|
106
|
+

|
107
|
+
|
108
|
+
## License
|
109
|
+
|
110
|
+
(c) 2020, Wade H. (vdtdev.prod@gmail.com). All Rights Reserved. Released under MIT license.
|
@@ -0,0 +1,316 @@
|
|
1
|
+
module TermColor
|
2
|
+
##
|
3
|
+
# Rule processor
|
4
|
+
# @example Basic Rule
|
5
|
+
# # Foreground color set to blue
|
6
|
+
# # Underline style turned on
|
7
|
+
# # Not broken into `a:` and `z:`, so rules will
|
8
|
+
# # be treated as `a:`, `z:` will be auto-generated
|
9
|
+
# # to reset forground color and disable underline
|
10
|
+
# rule = { fg: :blue, enable: :underline }
|
11
|
+
# @example Full after reset
|
12
|
+
# # Insize: (`a:`) Foreground yellow, bg red, dark style on
|
13
|
+
# # After: Resets all color and style options to default,
|
14
|
+
# # including those set by other rules
|
15
|
+
# rule = {
|
16
|
+
# a: {
|
17
|
+
# fg: :yellow, bg: :red, enable: :dark
|
18
|
+
# },
|
19
|
+
# z: {
|
20
|
+
# reset: :all
|
21
|
+
# }
|
22
|
+
# }
|
23
|
+
# @example Italic red, only clearing color at end
|
24
|
+
# # Inside: red fg, italic style
|
25
|
+
# # After: color will reset to default, italic will remain on
|
26
|
+
# rule = { a: { fg: :red, enable: :italic }, z: { reset: :fg }}
|
27
|
+
# @author Wade H. <vdtdev.prod@gmail.com>
|
28
|
+
module Rule
|
29
|
+
extend self
|
30
|
+
|
31
|
+
##
|
32
|
+
# Named Standard ANSI Color constants
|
33
|
+
# (Basic named values for `fg` and `bg` rule option attributes)
|
34
|
+
Colors = {
|
35
|
+
black: 0,
|
36
|
+
red: 1,
|
37
|
+
green: 2,
|
38
|
+
yellow: 3,
|
39
|
+
blue: 4,
|
40
|
+
magenta: 5,
|
41
|
+
cyan: 6,
|
42
|
+
white: 7
|
43
|
+
}.freeze
|
44
|
+
|
45
|
+
##
|
46
|
+
# Numerical modifiers used with Color Values
|
47
|
+
# to target foreground or background.
|
48
|
+
#
|
49
|
+
# - For {Colors Named Standard Colors}, value is added to given
|
50
|
+
# color's numerical value
|
51
|
+
# - For XTerm 256/16m color codes, value is added to mode base
|
52
|
+
#
|
53
|
+
# @example Named Standard Color Background
|
54
|
+
# { bg: :red } #=> 40 + 1 = 41
|
55
|
+
# @example XTerm 256 Foreground
|
56
|
+
# { fg: [208] } #=> 8 + 30 = 38
|
57
|
+
ColorTargets = {
|
58
|
+
fg: 30, # Foreground target
|
59
|
+
bg: 40 # Background target
|
60
|
+
}.freeze
|
61
|
+
|
62
|
+
##
|
63
|
+
# Style option constants
|
64
|
+
# (Values that can be included in style `enable` and `disable`
|
65
|
+
# rule option attributes)
|
66
|
+
Styles = {
|
67
|
+
bold: 1,
|
68
|
+
##
|
69
|
+
# Alias for bold
|
70
|
+
intense: 1,
|
71
|
+
dim: 2,
|
72
|
+
##
|
73
|
+
# Alias for dim
|
74
|
+
dark: 2,
|
75
|
+
italic: 3,
|
76
|
+
underline: 4,
|
77
|
+
inverse: 7,
|
78
|
+
strikethrough: 9
|
79
|
+
}.freeze
|
80
|
+
|
81
|
+
##
|
82
|
+
# Style action codes
|
83
|
+
# (Numerical modifiers applied to {Styles Style Codes} to
|
84
|
+
# enable/disable them based on which option attribute action
|
85
|
+
# was used)
|
86
|
+
# @example Disable italic
|
87
|
+
# (:disable) + (:italic) #=> 20 + 3 = 23
|
88
|
+
StyleActions = {
|
89
|
+
# Enable style(s) action
|
90
|
+
enable: 0,
|
91
|
+
# Disable style(s) action
|
92
|
+
disable: 20
|
93
|
+
}
|
94
|
+
|
95
|
+
##
|
96
|
+
# Reset option constants
|
97
|
+
# (Values for `reset` rule option attribute)
|
98
|
+
Resets = {
|
99
|
+
# Reset everything
|
100
|
+
all: 0,
|
101
|
+
# Reset foreground color only
|
102
|
+
fg: 39,
|
103
|
+
# Reset background color only
|
104
|
+
bg: 49
|
105
|
+
}.freeze
|
106
|
+
|
107
|
+
##
|
108
|
+
# Descriptive aliases for part names
|
109
|
+
Parts = {
|
110
|
+
# Style applied on rule open
|
111
|
+
inside: :inside,
|
112
|
+
# Style appled when rule close is given
|
113
|
+
after: :after
|
114
|
+
}
|
115
|
+
|
116
|
+
##
|
117
|
+
# Valid rule operations mapped to accepted const values
|
118
|
+
# (colors [fg, bg] can also accept integers)
|
119
|
+
Ops = {
|
120
|
+
# Foreground color option
|
121
|
+
fg: Colors.keys,
|
122
|
+
# Background color option
|
123
|
+
bg: Colors.keys,
|
124
|
+
# Enable style(s) action
|
125
|
+
enable: Styles.keys,
|
126
|
+
# Disable style(s) action
|
127
|
+
disable: Styles.keys,
|
128
|
+
# Reset action
|
129
|
+
reset: [
|
130
|
+
:fg, # Reset fg color
|
131
|
+
:bg, # Reset bg color
|
132
|
+
:style, # Reset all styles
|
133
|
+
:all # Reset colors and styles
|
134
|
+
]
|
135
|
+
}
|
136
|
+
|
137
|
+
##
|
138
|
+
# Value added to ColorTarget when using XTerm colors
|
139
|
+
XTERM_COLOR_TARGET = 8
|
140
|
+
##
|
141
|
+
# Mode constant for XTerm 256 Colors
|
142
|
+
XTERM_COLOR_256 = 5
|
143
|
+
##
|
144
|
+
# Mode constant for XTerm 16m Colors
|
145
|
+
XTERM_COLOR_16M = 2
|
146
|
+
|
147
|
+
##
|
148
|
+
# Structure used to hold compiled rule
|
149
|
+
# @!attribute [r] original
|
150
|
+
# Original rule hash
|
151
|
+
# @return [Hash]
|
152
|
+
# @!attribute [r] evaluated
|
153
|
+
# Evaluated copy of rule, including generated :after.
|
154
|
+
# Consists of code arrays
|
155
|
+
# @return [Hash]
|
156
|
+
# @!attribute [r] rule
|
157
|
+
# Hash of inside and after ANSI code strings
|
158
|
+
# @return [Hash]
|
159
|
+
Compiled = Struct.new(:original, :evaluated, :rule) do
|
160
|
+
##
|
161
|
+
# Get codes for part of compiled rule
|
162
|
+
def codes(part)
|
163
|
+
rule[part]
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
##
|
168
|
+
# Compile rule into frozen instance of `Compiled` struct
|
169
|
+
# @param [Hash] rule Rule hash
|
170
|
+
# @return [Compiled] Frozen instance of `Compiled` struct
|
171
|
+
# containing compiled rule
|
172
|
+
def compile(rule)
|
173
|
+
evaluated = evaluate(rule)
|
174
|
+
return Compiled.new(
|
175
|
+
rule,
|
176
|
+
evaluated,
|
177
|
+
codes(evaluated)
|
178
|
+
).freeze
|
179
|
+
end
|
180
|
+
|
181
|
+
private
|
182
|
+
|
183
|
+
def evaluate_ops(ops)
|
184
|
+
codes = []
|
185
|
+
v_ops = ops.filter{|k,v| Ops.keys.include?(k.to_sym)}
|
186
|
+
color_keys = ColorTargets.keys
|
187
|
+
style_keys = StyleActions.keys
|
188
|
+
reset_keys = Resets.keys
|
189
|
+
v_ops.each_pair do |k,v|
|
190
|
+
k=k.to_sym
|
191
|
+
if color_keys.include?(k)
|
192
|
+
codes << resolve_color(v, k)
|
193
|
+
elsif style_keys.include?(k)
|
194
|
+
[v].flatten.each do |val|
|
195
|
+
codes << resolve_style(val, k)
|
196
|
+
end
|
197
|
+
elsif k == :reset
|
198
|
+
[v].flatten.each do |val|
|
199
|
+
codes << resolve_reset(val)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
codes = codes.flatten.compact.uniq
|
204
|
+
end
|
205
|
+
|
206
|
+
def resolve_color(color, target = :fg)
|
207
|
+
if color.is_a?(Array)
|
208
|
+
color = color[0..2]
|
209
|
+
return xterm_color(color, target)
|
210
|
+
end
|
211
|
+
|
212
|
+
if !color.is_a?(Integer)
|
213
|
+
|
214
|
+
if color.is_a?(Hash) && ColorsAdvanced.keys.include?(color.keys[0])
|
215
|
+
return self.method(ColorsAdvanced[color.keys[0]]).call(color.values[0])
|
216
|
+
end
|
217
|
+
color = Colors[color.to_sym].to_i
|
218
|
+
end
|
219
|
+
(color + ColorTargets[target.to_sym].to_i)
|
220
|
+
end
|
221
|
+
|
222
|
+
def resolve_style(style, state = :enable)
|
223
|
+
if !style.is_a?(Integer)
|
224
|
+
style = Styles[style.to_sym].to_i
|
225
|
+
end
|
226
|
+
(style + StyleActions[state.to_sym].to_i)
|
227
|
+
end
|
228
|
+
|
229
|
+
def resolve_reset(target)
|
230
|
+
if Resets.keys.include?(target.to_sym)
|
231
|
+
return Resets[target.to_sym]
|
232
|
+
elsif Styles.keys.include?(target.to_sym)
|
233
|
+
return resolve_style(target, :disable)
|
234
|
+
else
|
235
|
+
return nil
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def xterm_color(val,target)
|
240
|
+
r,g,b = [val].flatten
|
241
|
+
if g.nil? && b.nil?
|
242
|
+
[
|
243
|
+
ColorTargets[target] + XTERM_COLOR_TARGET,
|
244
|
+
XTERM_COLOR_256,
|
245
|
+
r
|
246
|
+
].join(';')
|
247
|
+
else
|
248
|
+
[
|
249
|
+
ColorTargets[target] + XTERM_COLOR_TARGET,
|
250
|
+
XTERM_COLOR_16M,
|
251
|
+
r,g,b
|
252
|
+
].join(';')
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
##
|
257
|
+
# Evaluate rule, returning new hash containing list of numerical
|
258
|
+
# codes to use for inside (`:inside`) and after (`:after`)
|
259
|
+
# @param [Hash] rule Rule hash to evaluate
|
260
|
+
# @return [Hash] evaluated version of rule, containing code numbers
|
261
|
+
def evaluate(rule)
|
262
|
+
# error if not hash
|
263
|
+
return nil if !rule.is_a?(Hash)
|
264
|
+
|
265
|
+
inside_part_key = Parts[:inside]
|
266
|
+
after_part_key = Parts[:after]
|
267
|
+
rule_keys = rule.keys.map{|k|k.to_sym}
|
268
|
+
|
269
|
+
# Find 'inside' rule options
|
270
|
+
if rule_keys.include?(inside_part_key)
|
271
|
+
# 'inside' key explicitly defined, so pull from that
|
272
|
+
inside_part = rule[:inside]
|
273
|
+
else
|
274
|
+
# no 'inside' key, so pull from entire hash excluding
|
275
|
+
# 'after' key, if present
|
276
|
+
inside_part = rule.filter { |k,v| k != after_part_key }
|
277
|
+
end
|
278
|
+
|
279
|
+
# Find 'after' rule options, using nil if not present
|
280
|
+
# This means that if it is defined but as an empty hash,
|
281
|
+
# no 'after' rule options will be auto-generated
|
282
|
+
after_part = rule.fetch(after_part_key, nil)
|
283
|
+
|
284
|
+
# Auto-generate 'after' rule options if not explicitly defined
|
285
|
+
if after_part.nil?
|
286
|
+
resets = inside_part.keys.filter { |k| ColorTargets.keys.include?(k) }
|
287
|
+
disables = inside_part.fetch(:enable, [])
|
288
|
+
after_part = {}
|
289
|
+
after_part[:reset] = resets if resets.length > 0
|
290
|
+
after_part[:disable] = disables if disables.length > 0
|
291
|
+
end
|
292
|
+
|
293
|
+
parts = {}
|
294
|
+
|
295
|
+
parts[inside_part_key] = evaluate_ops(inside_part)
|
296
|
+
parts[after_part_key] = evaluate_ops(after_part)
|
297
|
+
|
298
|
+
return parts.merge({evaluated: true})
|
299
|
+
|
300
|
+
end
|
301
|
+
|
302
|
+
##
|
303
|
+
# Return ANSI color codes from evaluated rule
|
304
|
+
# @param [Hash] rule Full rule
|
305
|
+
# @return [Hash] Hash with code strings for `:inside` and `:after`
|
306
|
+
def codes(rule)
|
307
|
+
code = Proc.new {|c| "\e[#{c}m" }
|
308
|
+
inside = Parts[:inside]
|
309
|
+
after = Parts[:after]
|
310
|
+
{
|
311
|
+
(inside) => rule[inside].map{|c| code.call(c) }.join(''),
|
312
|
+
(after) => rule[after].map{|c| code.call(c) }.join('')
|
313
|
+
}
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
module TermColor
|
2
|
+
##
|
3
|
+
# TermColor style rule setc class
|
4
|
+
# @author Wade H. <vdtdev.prod@gmail.com>
|
5
|
+
# @license MIT
|
6
|
+
class RuleSet
|
7
|
+
|
8
|
+
##
|
9
|
+
# Symbol used as prefix for rule name to denote rule start
|
10
|
+
RULE_SYMBOL='%'
|
11
|
+
##
|
12
|
+
# String used to denote rule close / reset
|
13
|
+
RESET_SYMBOL='%%'
|
14
|
+
|
15
|
+
DEFAULT_RESET_RULE = {z: {reset: :all} }
|
16
|
+
|
17
|
+
attr_reader :rules, :regexs
|
18
|
+
|
19
|
+
##
|
20
|
+
# Construct new rule set
|
21
|
+
# @param [Hash] rules Hash of rule names mapping to rule hashes,
|
22
|
+
# which can define before rules (`a:`), after rules (`z:`) or both.
|
23
|
+
# - If neither are given, content is treated as though it was inside a `a:` key.
|
24
|
+
# - If `a:` only is given, {TermColor::Rule#evaluate Rule evaluate method} attempts to
|
25
|
+
# auto guess `z:`, resetting any used color or style rules from `a:`
|
26
|
+
# @see TermColor::Rule
|
27
|
+
# @example
|
28
|
+
# rules = RuleSet.new({
|
29
|
+
# # Green underlined text; will auto reset fg and disable underline
|
30
|
+
# # for close, since no z: is provided
|
31
|
+
# name: {fg: :green, enable: :underline},
|
32
|
+
# # Italic text; will auto generate z: that disables italic
|
33
|
+
# quote: { enable: :italic },
|
34
|
+
# # A weird rule that will make fg red inside rule,
|
35
|
+
# # and change fg to blue after rule block ends
|
36
|
+
# weird: { a: { fg: :red }, z: { fg: :blue }}
|
37
|
+
# })
|
38
|
+
#
|
39
|
+
# print rules.colorize("%nameJohn%%: '%%quoteRoses are %%weirdRed%% (blue)%%.\n")
|
40
|
+
# # Result will be:
|
41
|
+
# # fg green+underline "John"
|
42
|
+
# # regular ": "
|
43
|
+
# # italic "Roses are "
|
44
|
+
# # fg red (still italic) "Red"
|
45
|
+
# # (fg blue)(still italic) "(blue)"
|
46
|
+
# # (regular) "."
|
47
|
+
def initialize(rules)
|
48
|
+
@base_rules = rules
|
49
|
+
@base_rules[:default] = @base_rules.fetch(:default, DEFAULT_RESET_RULE)
|
50
|
+
evaluate_rules
|
51
|
+
build_regexs
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# Apply styling to string using rule set
|
56
|
+
# @param [String] text Text to parse for stylization
|
57
|
+
# @return [String] Text with ANSI style codes injected
|
58
|
+
def apply(text)
|
59
|
+
raw = process_text(text)
|
60
|
+
last_rule = nil
|
61
|
+
str = ''
|
62
|
+
raw.each do |r|
|
63
|
+
if r.is_a?(Symbol)
|
64
|
+
# if (r == :close_rule && !last_rule.nil?)
|
65
|
+
# str.concat(Rule.codes(@rules[last_rule][:z]))
|
66
|
+
# last_rule = nil
|
67
|
+
# elsif r == :default
|
68
|
+
# str.concat(Rule.codes(@rules[r][:z]))
|
69
|
+
# last_rule = nil
|
70
|
+
# else
|
71
|
+
# last_rule = r
|
72
|
+
# str.concat(Rule.codes(@rules[r][:a]))
|
73
|
+
# end
|
74
|
+
if (r == :default) && !last_rule.nil?
|
75
|
+
str.concat(@rules[last_rule].codes(Rule::Parts[:after]))
|
76
|
+
last_rule = nil
|
77
|
+
elsif r == :default
|
78
|
+
str.concat(@rules[r].codes(Rule::Parts[:after]))
|
79
|
+
last_rule = nil
|
80
|
+
else
|
81
|
+
last_rule = r
|
82
|
+
str.concat(@rules[r].codes(Rule::Parts[:inside]))
|
83
|
+
end
|
84
|
+
else
|
85
|
+
str.concat(r)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
str
|
89
|
+
end
|
90
|
+
|
91
|
+
##
|
92
|
+
# Wraps STDOUT print method, passing output of `apply` to `print`
|
93
|
+
# @param [Array] args Print arguments, including TermColor style tags
|
94
|
+
# @param [Hash] opts Optional params
|
95
|
+
# @option opts [IO] :out Optional override for IO class to call `print`
|
96
|
+
# on (default `$stdout`)
|
97
|
+
def print(*args,**opts)
|
98
|
+
stdout = opts.fetch(:out, $stdout)
|
99
|
+
t = args.map{|a|apply(a)}
|
100
|
+
stdout.print *t
|
101
|
+
end
|
102
|
+
|
103
|
+
##
|
104
|
+
# Wraps STDOUT printf method, passing output of `apply` to `print`
|
105
|
+
# Doesn't actually use `printf`, instead passes result of
|
106
|
+
# `format_string % args` to `print`.
|
107
|
+
# @param [String] format_string printf format string,
|
108
|
+
# including TermColor style tags
|
109
|
+
# @param [Array] args printf values to use with format string
|
110
|
+
# @param [Hash] opts Optional params
|
111
|
+
# @option opts [IO] :out Optional override for IO class to call `print`
|
112
|
+
# on (default `$stdout`)
|
113
|
+
def printf(format_string,*args,**opts)
|
114
|
+
stdout = opts.fetch(:out, $stdout)
|
115
|
+
|
116
|
+
# Sanitize rule symbols
|
117
|
+
sanitized = format_string.dup
|
118
|
+
@rules.keys.each { |k| sanitized.gsub!("#{RULE_SYMBOL}#{k.to_s}","#{255.chr}#{k.to_s}") }
|
119
|
+
sanitized.gsub!(RESET_SYMBOL, 255.chr*2)
|
120
|
+
|
121
|
+
t = sanitized % args
|
122
|
+
# Reinstate rule symbols
|
123
|
+
@rules.keys.each { |k| t.gsub!("#{255.chr}#{k.to_s}","#{RULE_SYMBOL}#{k.to_s}") }
|
124
|
+
t.gsub!(255.chr*2,RESET_SYMBOL)
|
125
|
+
|
126
|
+
stdout.print apply(t)
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def evaluate_rules
|
132
|
+
@rules = {}
|
133
|
+
@base_rules.each_pair do |k,v|
|
134
|
+
@rules[k] = Rule.compile(v)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def build_regexs
|
139
|
+
@regexs = {}
|
140
|
+
src = @rules
|
141
|
+
src.each_pair do |k,v|
|
142
|
+
@regexs[k] = Regexp.compile(
|
143
|
+
"(?<#{k.to_s}>(#{RULE_SYMBOL}#{k.to_s}))"
|
144
|
+
)
|
145
|
+
end
|
146
|
+
@regexs[:default] = Regexp.compile(
|
147
|
+
"(?<default>(#{RESET_SYMBOL}))"
|
148
|
+
)
|
149
|
+
end
|
150
|
+
|
151
|
+
def locate_rules_in_string(text)
|
152
|
+
tracking = {}
|
153
|
+
@regexs.keys.each do |k|
|
154
|
+
tracking[k] = {index: 0, done: false}
|
155
|
+
end
|
156
|
+
tracking_done = Proc.new {
|
157
|
+
tracking.values.select{|t|!t[:done]}.length == 0
|
158
|
+
}
|
159
|
+
is_done = false
|
160
|
+
|
161
|
+
locations = []
|
162
|
+
# binding.pry
|
163
|
+
until is_done do
|
164
|
+
@regexs.each_pair do |k,v|
|
165
|
+
t = tracking[k]
|
166
|
+
# print "Tracking #{k} (#{t})\n"
|
167
|
+
unless t[:done]
|
168
|
+
m = v.match(text,t[:index])
|
169
|
+
if m.nil?
|
170
|
+
# print "No matches found\n"
|
171
|
+
tracking[k][:done] = true
|
172
|
+
else
|
173
|
+
tracking[k][:index] = m.end(0)
|
174
|
+
a=m.begin(0);b=m.end(0)
|
175
|
+
locations << {
|
176
|
+
symbol: k,
|
177
|
+
begin: a,
|
178
|
+
sym_end: b - 1,
|
179
|
+
continue_pos: b
|
180
|
+
}
|
181
|
+
# print "\tMatch found (#{a}..#{b}): #{text[a..b]}\n"
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
is_done = tracking_done.call()
|
186
|
+
end
|
187
|
+
locations.sort{|a,b|a[:continue_pos]<=>b[:continue_pos]}
|
188
|
+
end
|
189
|
+
|
190
|
+
def process_text(text)
|
191
|
+
locations = locate_rules_in_string(text)
|
192
|
+
|
193
|
+
working = []
|
194
|
+
return [text] if locations.length == 0
|
195
|
+
|
196
|
+
if locations[0][:begin] > 0
|
197
|
+
working << text[0..locations[0][:begin]-1]
|
198
|
+
end
|
199
|
+
|
200
|
+
locations.each_with_index do |l,i|
|
201
|
+
is_last = locations.length - 1 - i == 0
|
202
|
+
end_pos = -1
|
203
|
+
if !is_last
|
204
|
+
end_pos = locations[i+1][:begin] - 1
|
205
|
+
end
|
206
|
+
|
207
|
+
working << l[:symbol]
|
208
|
+
working << text[l[:continue_pos]..end_pos]
|
209
|
+
end
|
210
|
+
working
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|
214
|
+
end
|
data/lib/term_color.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require_relative './term_color/rule.rb'
|
2
|
+
require_relative './term_color/rule_set.rb'
|
3
|
+
##
|
4
|
+
# Main TermColor module
|
5
|
+
# @author Wade H. <vdtdev.prod@gmail.com>
|
6
|
+
module TermColor
|
7
|
+
extend self
|
8
|
+
|
9
|
+
##
|
10
|
+
# Alias for constructing a new RuleSet
|
11
|
+
# @see TermColor::RuleSet
|
12
|
+
def create_rule_set(rules={})
|
13
|
+
TermColor::RuleSet.new(rules)
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: term_color
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Wade H.
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-02-25 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 3.7.0
|
20
|
+
- - "~>"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '3.7'
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 3.7.0
|
30
|
+
- - "~>"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '3.7'
|
33
|
+
description: " Rule-based tool for easily applying color and styling to terminal
|
34
|
+
text output.\n"
|
35
|
+
email: vdtdev.prod@gmail.com
|
36
|
+
executables: []
|
37
|
+
extensions: []
|
38
|
+
extra_rdoc_files: []
|
39
|
+
files:
|
40
|
+
- README.md
|
41
|
+
- lib/term_color.rb
|
42
|
+
- lib/term_color/rule.rb
|
43
|
+
- lib/term_color/rule_set.rb
|
44
|
+
homepage: https://github.com/vdtdev/term_color
|
45
|
+
licenses:
|
46
|
+
- MIT
|
47
|
+
metadata:
|
48
|
+
source_code_uri: https://github.com/vdtdev/term_color
|
49
|
+
post_install_message:
|
50
|
+
rdoc_options: []
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
requirements: []
|
64
|
+
rubygems_version: 3.0.1
|
65
|
+
signing_key:
|
66
|
+
specification_version: 4
|
67
|
+
summary: Terminal Colors
|
68
|
+
test_files: []
|