term_color 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
![](./file/docs/example_1.png)
|
91
|
+
![](./docs/example_1.png)
|
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
|
+
![](./file/docs/example_2.png)
|
106
|
+
![](./docs/example_2.png)
|
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: []
|