term_color 0.0.2 → 0.0.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6271321446e52dfcff7f4ed2e996586653731e79103a94504910df3823b1ec48
4
- data.tar.gz: 4bde610a032d25a836096bb389163c66d2dcacb81ad7c605366fbbf0dab491f0
3
+ metadata.gz: 5ca92238dae771cacd8dd2165975e084ac10aac980cdb3ce36f52da3d76742e4
4
+ data.tar.gz: 2e800af8a7aefbc39895d29ac25a8dcc67501b9a48a7827d76fbc017ae6ae9fc
5
5
  SHA512:
6
- metadata.gz: d9d58ee125d900d753fe50574b61c5314bb9d78c5e2ec7a2e350bf9913f3ce3c00e7ffdd5cff49fe7c2075c2e85c3ea24073c9246dcd216c214c3210035e0062
7
- data.tar.gz: 70d3f8cebf1834534a991c8c82ab17975103b1c346f03fa8527ccf58d128a9613c0ff866e95fcd2c234b9008b0d6ddf8bf81d0c586be078032e696e7b5256643
6
+ metadata.gz: 3bc3d2a0e78014e06c21d0be5ed18a5ca081c712ee456080b4eddca3c216b75e5986d2b909807d6e286edbcb3236626183a5f4a9af61465659b3ca6fb2cc376f
7
+ data.tar.gz: dd06e1eba2e21496090f4681b7c4c79dcd80a5211dba25129cf106a181ce0df26cd68c1298c12afac53a20ec05546abac2025d8fc4be9f3751a0ec2e56f92cbf
data/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  Rule-based text coloring/styling library for terminal text
4
4
 
5
+ [![Gem Version](https://badge.fury.io/rb/term_color.svg)](https://badge.fury.io/rb/term_color)
6
+
5
7
  ## Overview
6
8
 
7
9
  ### Justifying Features
@@ -16,11 +18,40 @@ Rule-based text coloring/styling library for terminal text
16
18
 
17
19
  ## Concepts/Syntax
18
20
 
19
- Rules are grouped into `RuleSets`, which are represented by instances of {TermColor::RuleSet}.
21
+ Rules are grouped into `RuleSets`, which are represented by instances of {TermColor::RuleSet}. Rules are applied to text using tag strings indicating start and end of rule application. There is also a tag that can be used outside of rule application to fully reset all styling options to system default.
22
+
23
+ ### Tags
24
+
25
+ _The 'tag' strings used to specify start and end of ranges of text to apply a rule to can be customized in the `RuleSet` constructor. These details assume the defaults are used._
26
+
27
+ - Open: `{%<rule name>`
28
+ - Ex: `{%rule1`
29
+ - Close: `%}`
30
+ - Reset: `%@`
31
+ - Only usable outside of tags. Resets all styling to default even if a previously used rule is configured to keep styling applied after close
32
+
33
+ ```ruby
34
+ # Rules named :a and :b
35
+ "Unstyled {%a Styled with a %} Normal {%bStyled with b%} normal %@ fully reset"
36
+ ```
37
+
38
+ Rules can be nested. When an inner rule tag is closed, the styling of the any outer/unclosed tags will be re-applied from outer most to inner most.
39
+
40
+ ```ruby
41
+ "{%aRule A{%bRule A+B{%cRule A+B+C%}A+B%}A%} Normal"
42
+ ```
43
+
44
+ ### Rule Sets
20
45
 
21
- ### Rule Set
46
+ Rule Sets contain named styling rules, any override options you chose to use (`after` behavior, open/close/reset symbols). Instances of `RuleSet` also provide methods for processing and displaying text with style tag markup via methods like `print` and `printf`
22
47
 
23
- A set is constructed from a Hash of rule, where they keys are rule names and the values are the rule definition hashes.
48
+ Gemeral Usage:
49
+
50
+ ```ruby
51
+ rs = TermColor.create_rule_set(<rules hash>, <named options>)
52
+ ```
53
+
54
+ Constructor:
24
55
 
25
56
  ```ruby
26
57
  rules = {
@@ -33,6 +64,13 @@ rule_set = TermColor.create_rule_set(rules)
33
64
  rule_set = TermColor::RuleSet.new(rules)
34
65
  ```
35
66
 
67
+ #### Constructor Options
68
+
69
+ - `after` - Override default 'after' rule (controlling what gets reset after a style tag is closed). Default is `:auto`, which automatically determines what styling to removed based on the rule being closed
70
+ - `:reset` - Causes all colors and styling to be reset on close
71
+ - `:keep` - Causes nothing to get reset
72
+ - Custom Hash - Allows you to specify your own after rule as a hash containing properties valid for `after` sections (`[:fg,:bg,:reset,:enable,:disable]`)
73
+
36
74
  #### Applying to Text
37
75
 
38
76
  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
@@ -43,63 +81,116 @@ __Methods__
43
81
  - `print` - {TermColor::RuleSet#print}
44
82
  - `printf` - {TermColor::RuleSet#printf}
45
83
 
46
- __Use__
84
+ ### Rule Definitions
47
85
 
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%%%%"`)
86
+ A Rule definition is a hash consisting of two parts, `inside` and `after`. `inside` dictates styling that gets applied to text between the open and close tags for the rule, `after` allows you to override what happens when that tag closes (overrides default `after` rule as specified in constructor).
53
87
 
54
- ### Rule Definitions
88
+ The `after` part, when used, must always be a hash in the rule definition assigned to the key `:after`. Everything else will be automatically grouped into a `inside` key/value if not explicitly specified.
89
+
90
+ ```ruby
91
+ r = { inside: { fg: :red }, after: { keep: :fg }}
92
+ r = { fg: :red, bg: :blue }
93
+ r = { after: { reset: :all} }
94
+ ```
95
+
96
+ ### Rule Options/Actions
55
97
 
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))_
98
+ #### Colors
57
99
 
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: {}`.
100
+ ##### Attributes
101
+
102
+ - `fg` - Change foreground color
103
+ - `bg` - Change background color
104
+
105
+ ##### Values
106
+
107
+ ###### Standard Named Colors
108
+
109
+ Color values can be color-name symbols as defined in {TermColor::Rule::Colors} (`:black, :red, :yellow, :blue, :magenta, :cyan, :white`)
110
+
111
+ ###### XTerm 256 Color Values
112
+
113
+ To use XTerm 256 Color Mode values, include the color code integer inside a single item array. (E.g. for code `208`, use `[208]`)
114
+
115
+ ###### XTerm 16m Color Values
116
+
117
+ To use XTerm 16m Color Mode RGB colors, include the red, green and blue color values in an ordered array (E.g. for 80 red, 80 green, 255 blue, use `[80,80,255]`)
118
+
119
+ #### Styles
120
+
121
+ ##### Actions
122
+
123
+ - `enable` - Style(s) to enable (Can be single item or array)
124
+ - `disable` - Style(s) to disable (Can be single item or array)
125
+
126
+ ##### Values
127
+
128
+ (See symbols in {TermColor::Rule::Styles})
129
+
130
+ - `:bold`/`:intense`
131
+ - `:dim`/`:dark`
132
+ - `:italic`
133
+ - `:underline`
134
+ - `:inverse`
135
+ - `:hidden`
136
+ - `:strikethrough`
137
+
138
+ #### Reset / Keep
139
+
140
+ _(Only valid in `after` section)_
141
+
142
+ Quick way of resetting one or more style rules. `reset`/`keep` can be given a single symbol or an array of symbols.
143
+
144
+ `keep` will be ignored if included in default after rule
59
145
 
60
146
  ```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 } }
147
+ { reset: [options] }
148
+ { keep: [options] }
71
149
  ```
72
150
 
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})
151
+ #### Options
74
152
 
75
- ## Examples
153
+ - `:fg` / `:bg` - Reset foreground / background color
154
+ - `:style` - Reset all styling
155
+ - `:all` - Reset all colors and styling
76
156
 
77
157
  [If example images are missing, view readme on github](https://github.com/vdtdev/term_color/blob/master/README.md)
78
158
 
79
- ### Basic
159
+ ## Examples
160
+
161
+ _If images don't show up, try viewing on [Github](https://github.com/vdtdev/term_color/blob/master/README.md)_
162
+
163
+ ### Basic w/ Nesting (`:auto` after mode)
80
164
 
81
165
  ```ruby
82
166
  rule_set = TermColor.create_rule_set({
83
- opt: { fg: :cyan },
84
- err: { fg: :red }
167
+ yellow: { fg: :yellow },
168
+ ul: { enable: :ul },
169
+ err: { fg: :white, bg: :red, enable: [:italic] }
85
170
  })
86
171
 
87
- rule_set.printf "%errInvalid%% option: %opt%s%%\n", "fruit"
172
+ rule_set.print "Test Score: {%yellow65%}\n"
173
+ rule_set.print "{%ul{%err{%yellowNOTE:%}" +
174
+ "Your score is below a passing grade%}X%}\n"
88
175
  ```
89
176
 
90
177
  ![](./file/docs/example_1.png)
91
178
  ![](./docs/example_1.png)
92
179
 
93
- ### Nested /w XTerm RGB
180
+ ### With `:keep` after mode
94
181
 
95
182
  ```ruby
96
183
  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"
184
+ g: { fg: :green },
185
+ i: { enable: :inverse },
186
+ f: { fg: :yellow, after: { reset: :fg }}
187
+ }, after: :keep)
188
+
189
+ rule_set.print "{%gTitle%}\n"
190
+ rule_set.print "{%i>%}"
191
+ (1..5).each {|i| print "#{i}\n" }
192
+ rule_set.print "{%fThis style reverts after closing%}\n"
193
+ rule_set.print "Before Reset%@After Reset\n"
103
194
  ```
104
195
 
105
196
  ![](./file/docs/example_2.png)
@@ -12,9 +12,9 @@ module TermColor
12
12
  # # Insize: (`a:`) Foreground yellow, bg red, dark style on
13
13
  # # After: Resets all color and style options to default,
14
14
  # # including those set by other rules
15
- # rule = {
15
+ # rule = {
16
16
  # a: {
17
- # fg: :yellow, bg: :red, enable: :dark
17
+ # fg: :yellow, bg: :red, enable: :dark
18
18
  # },
19
19
  # z: {
20
20
  # reset: :all
@@ -45,23 +45,23 @@ module TermColor
45
45
  ##
46
46
  # Numerical modifiers used with Color Values
47
47
  # to target foreground or background.
48
- #
48
+ #
49
49
  # - For {Colors Named Standard Colors}, value is added to given
50
50
  # color's numerical value
51
51
  # - For XTerm 256/16m color codes, value is added to mode base
52
- #
52
+ #
53
53
  # @example Named Standard Color Background
54
54
  # { bg: :red } #=> 40 + 1 = 41
55
55
  # @example XTerm 256 Foreground
56
56
  # { fg: [208] } #=> 8 + 30 = 38
57
- ColorTargets = {
57
+ ColorTargets = {
58
58
  fg: 30, # Foreground target
59
59
  bg: 40 # Background target
60
60
  }.freeze
61
61
 
62
62
  ##
63
63
  # Style option constants
64
- # (Values that can be included in style `enable` and `disable`
64
+ # (Values that can be included in style `enable` and `disable`
65
65
  # rule option attributes)
66
66
  Styles = {
67
67
  bold: 1,
@@ -75,6 +75,7 @@ module TermColor
75
75
  italic: 3,
76
76
  underline: 4,
77
77
  inverse: 7,
78
+ hidden: 8,
78
79
  strikethrough: 9
79
80
  }.freeze
80
81
 
@@ -85,12 +86,12 @@ module TermColor
85
86
  # was used)
86
87
  # @example Disable italic
87
88
  # (:disable) + (:italic) #=> 20 + 3 = 23
88
- StyleActions = {
89
+ StyleActions = {
89
90
  # Enable style(s) action
90
91
  enable: 0,
91
92
  # Disable style(s) action
92
- disable: 20
93
- }
93
+ disable: 20
94
+ }.freeze
94
95
 
95
96
  ##
96
97
  # Reset option constants
@@ -101,9 +102,17 @@ module TermColor
101
102
  # Reset foreground color only
102
103
  fg: 39,
103
104
  # Reset background color only
104
- bg: 49
105
+ bg: 49,
106
+ # Reset style
107
+ style: StyleActions[:disable]
105
108
  }.freeze
106
109
 
110
+ ##
111
+ # Operations associated with reset
112
+ ResetOps = [ :reset, :keep ].freeze
113
+
114
+ ResetsExtra = [ :style ].freeze
115
+
107
116
  ##
108
117
  # Descriptive aliases for part names
109
118
  Parts = {
@@ -111,7 +120,7 @@ module TermColor
111
120
  inside: :inside,
112
121
  # Style appled when rule close is given
113
122
  after: :after
114
- }
123
+ }.freeze
115
124
 
116
125
  ##
117
126
  # Valid rule operations mapped to accepted const values
@@ -126,13 +135,38 @@ module TermColor
126
135
  # Disable style(s) action
127
136
  disable: Styles.keys,
128
137
  # 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
- }
138
+ reset: Resets.keys,
139
+ # Keep action (opposite of reset)
140
+ keep: Resets.keys
141
+ }.freeze
142
+
143
+ ##
144
+ # Normalize rules for ops; either `:keep` to
145
+ # not change original value, or `:array` to
146
+ # wrap single value inside array
147
+ OpNormalize = {
148
+ fg: :keep,
149
+ bg: :keep,
150
+ enable: :array,
151
+ disable: :array,
152
+ reset: :array,
153
+ keep: :array
154
+ }.freeze
155
+
156
+ ##
157
+ # Allowed ops by part
158
+ PartOps = {
159
+ inside: [:fg, :bg, :enable, :disable],
160
+ after: [:fg, :bg, :enable, :disable, :reset, :keep]
161
+ }.freeze
162
+
163
+ ##
164
+ # Operations allowed within 'after'
165
+ AfterOps = (Ops.filter{|k,v| PartOps[:after].include?(k)}).freeze
166
+
167
+ ##
168
+ # Operations allowed within 'inside'
169
+ InsideOps = (Ops.filter {|k,v| PartOps[:inside].include?(k)}).freeze
136
170
 
137
171
  ##
138
172
  # Value added to ColorTarget when using XTerm colors
@@ -167,10 +201,13 @@ module TermColor
167
201
  ##
168
202
  # Compile rule into frozen instance of `Compiled` struct
169
203
  # @param [Hash] rule Rule hash
204
+ # @param [RuleSet] rs Rule set
205
+ # @param [Boolean] is_reset Set to true to indicate rule is for reset
206
+ # operation, and should ignore default after resolution
170
207
  # @return [Compiled] Frozen instance of `Compiled` struct
171
208
  # containing compiled rule
172
- def compile(rule)
173
- evaluated = evaluate(rule)
209
+ def compile(rule, rs, is_reset=false)
210
+ evaluated = evaluate(rule,rs,is_reset)
174
211
  return Compiled.new(
175
212
  rule,
176
213
  evaluated,
@@ -201,16 +238,16 @@ module TermColor
201
238
  end
202
239
  end
203
240
  codes = codes.flatten.compact.uniq
204
- end
241
+ end
205
242
 
206
243
  def resolve_color(color, target = :fg)
207
244
  if color.is_a?(Array)
208
245
  color = color[0..2]
209
246
  return xterm_color(color, target)
210
247
  end
211
-
248
+
212
249
  if !color.is_a?(Integer)
213
-
250
+
214
251
  if color.is_a?(Hash) && ColorsAdvanced.keys.include?(color.keys[0])
215
252
  return self.method(ColorsAdvanced[color.keys[0]]).call(color.values[0])
216
253
  end
@@ -223,7 +260,10 @@ module TermColor
223
260
  if !style.is_a?(Integer)
224
261
  style = Styles[style.to_sym].to_i
225
262
  end
226
- (style + StyleActions[state.to_sym].to_i)
263
+ s_code = (style + StyleActions[state.to_sym].to_i)
264
+ # adjust so bold and dim both get 22
265
+ s_code = [s_code, StyleActions[:disable] + 2].max if state == :disable
266
+ return s_code
227
267
  end
228
268
 
229
269
  def resolve_reset(target)
@@ -257,15 +297,16 @@ module TermColor
257
297
  # Evaluate rule, returning new hash containing list of numerical
258
298
  # codes to use for inside (`:inside`) and after (`:after`)
259
299
  # @param [Hash] rule Rule hash to evaluate
300
+ # @param [RuleSet] rs Rule set
260
301
  # @return [Hash] evaluated version of rule, containing code numbers
261
- def evaluate(rule)
302
+ def evaluate(rule, rs, is_reset)
262
303
  # error if not hash
263
304
  return nil if !rule.is_a?(Hash)
264
305
 
265
306
  inside_part_key = Parts[:inside]
266
307
  after_part_key = Parts[:after]
267
308
  rule_keys = rule.keys.map{|k|k.to_sym}
268
-
309
+
269
310
  # Find 'inside' rule options
270
311
  if rule_keys.include?(inside_part_key)
271
312
  # 'inside' key explicitly defined, so pull from that
@@ -279,15 +320,15 @@ module TermColor
279
320
  # Find 'after' rule options, using nil if not present
280
321
  # This means that if it is defined but as an empty hash,
281
322
  # 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
323
+ after_part = rule.fetch(after_part_key, {})
324
+
325
+ # Resolve after, either from template mixed with any
326
+ # overrides or an automatically generated version mixed with
327
+ # overrides
328
+ if rs.default_after == :auto
329
+ after_part = build_auto_after(inside_part, after_part)
330
+ else
331
+ after_part = override_after(inside_part, after_part, rs.default_after)
291
332
  end
292
333
 
293
334
  parts = {}
@@ -296,9 +337,100 @@ module TermColor
296
337
  parts[after_part_key] = evaluate_ops(after_part)
297
338
 
298
339
  return parts.merge({evaluated: true})
340
+ end
341
+
342
+ def normalize_part(hash,part,clean=false)
343
+ h = hash.dup
344
+ PartOps[part].each do |o|
345
+ if OpNormalize[o] == :array
346
+ h[o] = [h.fetch(o,[])].flatten
347
+ else
348
+ h[o] = h.fetch(o,nil)
349
+ end
350
+ end
351
+
352
+ if clean
353
+ h = h.filter {|k,v| (v.is_a?(Array))? v.length > 0 : !v.nil? }
354
+ end
355
+
356
+ return h
357
+ end
358
+
359
+ def override_after(inside, override, after)
360
+ c_ovr = normalize_part(override, :after)
361
+ c_aft = normalize_part(after, :after)
362
+ c_inside = normalize_part(inside, :inside)
363
+
364
+ # change reset :all to specific resets
365
+ if c_aft[:reset].include?(:all)
366
+ c_aft[:reset] = Resets.keys - [:all]
367
+ end
368
+ # change keep :all to specific resets
369
+ if c_ovr[:keep].include?(:all)
370
+ c_ovr[:keep] = Resets.keys - [:all]
371
+ end
372
+
373
+ c_aft[:reset] = (c_aft[:reset] + c_ovr[:reset]).uniq
374
+ # remove keeps from resets
375
+ c_aft[:reset] -= c_ovr[:keep]
376
+
377
+ # clear disable if keep :style
378
+ if c_ovr[:keep].include?(:style) ||
379
+ c_aft[:disable] = []
380
+ end
381
+
382
+ # if override disables styles, remove blanket style reset
383
+ if c_ovr[:disable].length >= 1
384
+ c_aft[:reset] -= [:style]
385
+ end
386
+
387
+ # replace reset :style with style specific targets
388
+ if c_inside[:enable].length > 0 && c_aft[:reset].include?(:style)
389
+ c_aft[:reset] -= [:style]
390
+ c_aft[:disable] += c_inside[:enable]
391
+ c_aft[:enable] -= c_inside[:enable]
392
+ end
299
393
 
394
+ # If we've reached this point with reset :style, change it into
395
+ # disables
396
+ if c_aft[:reset].include?(:style)
397
+ c_aft[:reset] -= [:style]
398
+ c_aft[:disable] += Styles.keys
399
+ end
400
+
401
+ # prevent enables from conflicting with disables
402
+ en = (c_aft[:enable] + c_ovr[:enable] - c_ovr[:disable]).uniq
403
+ di = (c_aft[:disable] + c_ovr[:disable] - c_ovr[:enable]).uniq
404
+
405
+ en -= di
406
+ di -= en
407
+
408
+ result = { enable: en, disable: di, reset: c_aft[:reset] }
409
+ ColorTargets.keys.each do |k|
410
+ val = (c_ovr[k] || c_aft[k])
411
+ result[k] = val unless val.nil?
412
+ end
413
+
414
+ return normalize_part(result, :after, true)
300
415
  end
301
416
 
417
+ def build_auto_after(inside, after={})
418
+ c_inside = normalize_part(inside, :inside)
419
+ n_after = normalize_part({}, :after)
420
+
421
+ if c_inside[:enable].length > 0
422
+ n_after[:reset] += [:style]
423
+ end
424
+
425
+ ColorTargets.keys.each do |k|
426
+ if !c_inside[k].nil?
427
+ n_after[:reset] += [k]
428
+ end
429
+ end
430
+
431
+ override_after(inside, after, n_after)
432
+ end
433
+
302
434
  ##
303
435
  # Return ANSI color codes from evaluated rule
304
436
  # @param [Hash] rule Full rule
@@ -313,4 +445,4 @@ module TermColor
313
445
  }
314
446
  end
315
447
  end
316
- end
448
+ end
@@ -5,24 +5,60 @@ module TermColor
5
5
  # @license MIT
6
6
  class RuleSet
7
7
 
8
+ DBG = false
9
+
8
10
  ##
9
- # Symbol used as prefix for rule name to denote rule start
10
- RULE_SYMBOL='%'
11
+ # Default rule symbols
12
+ DEFAULT_SYMBOLS = {
13
+ open: '{%',
14
+ close: '%}',
15
+ reset: '%@'
16
+ }.freeze
17
+
11
18
  ##
12
- # String used to denote rule close / reset
13
- RESET_SYMBOL='%%'
19
+ # Default reset rule
20
+ DEFAULT_RESET_RULE = {after: {reset: :all} }.freeze
14
21
 
15
- DEFAULT_RESET_RULE = {z: {reset: :all} }
22
+ ##
23
+ # After preset options
24
+ AFTER_PRESETS = {
25
+ # Full reset
26
+ reset: {reset: :all},
27
+ # Automatically determine what to toggle off
28
+ auto: :auto,
29
+ # No reset
30
+ keep: {keep: :all}
31
+ }
16
32
 
17
- attr_reader :rules, :regexs
33
+ ##
34
+ # Struct for rule and reset symbols
35
+ SymbolOptions = Struct.new(:open,:close,:reset)
36
+
37
+ ##
38
+ # Default after preset choice
39
+ DEFAULT_AFTER = :auto
40
+
41
+ attr_reader :rules, :regexs, :default_after, :symbols
18
42
 
19
43
  ##
20
44
  # Construct new rule set
21
45
  # @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:`
46
+ # which can define before rules (`inside:`), after rules (`after:`) or both.
47
+ # - If neither are given, content is treated as though it was inside a `inside:` key.
48
+ # - If `inside:` only is given, {TermColor::Rule#evaluate Rule evaluate method} attempts to
49
+ # auto guess `after:`, resetting any used color or style rules from `inside:`
50
+ # @param [Hash] opts Optional arguments
51
+ # @option opts [Hash|Symbol] :after Override default `:after` rule behavior when rule has no `after:`
52
+ # Options:
53
+ # - `:reset` - Reset all color and styles
54
+ # - `:auto` (default) - Try to automatically determine what to reset based on applied colors/styles
55
+ # - `:keep` - Keel all rule styles intact
56
+ # - (`Hash`) - Custom rule (formatted as Rule `after` prop, e.g. `{ reset: :fg, keep: :style }`)
57
+ # @option opts [Hash] :symbols Override styling symbols
58
+ # Options:
59
+ # - `:open` - Rule open symbol (used as symbolRulename) (default `{%`)
60
+ # - `:close` - Rule close symbol (default `%}`)
61
+ # - `:reset` - Symbol that can be used between rule blocks to fully reset everything (default `%@`)
26
62
  # @see TermColor::Rule
27
63
  # @example
28
64
  # rules = RuleSet.new({
@@ -33,10 +69,10 @@ module TermColor
33
69
  # quote: { enable: :italic },
34
70
  # # A weird rule that will make fg red inside rule,
35
71
  # # and change fg to blue after rule block ends
36
- # weird: { a: { fg: :red }, z: { fg: :blue }}
72
+ # weird: { inside: { fg: :red }, after: { fg: :blue }}
37
73
  # })
38
74
  #
39
- # print rules.colorize("%nameJohn%%: '%%quoteRoses are %%weirdRed%% (blue)%%.\n")
75
+ # print rules.colorize("{%nameJohn%}: '{%quoteRoses are {%weirdRed%} (blue)%}.\n")
40
76
  # # Result will be:
41
77
  # # fg green+underline "John"
42
78
  # # regular ": "
@@ -44,9 +80,23 @@ module TermColor
44
80
  # # fg red (still italic) "Red"
45
81
  # # (fg blue)(still italic) "(blue)"
46
82
  # # (regular) "."
47
- def initialize(rules)
83
+ def initialize(rules={}, **opts)
84
+ if rules.nil?
85
+ rules = opts
86
+ opts = {}
87
+ end
48
88
  @base_rules = rules
49
- @base_rules[:default] = @base_rules.fetch(:default, DEFAULT_RESET_RULE)
89
+ @base_rules[:reset] = @base_rules.fetch(:reset, DEFAULT_RESET_RULE)
90
+ # binding.pry
91
+ after = opts.fetch(:after, nil)
92
+ after = DEFAULT_AFTER if after.nil? || (after.is_a?(Symbol) && !AFTER_PRESETS.has_key?(after))
93
+ @default_after = (after.is_a?(Hash))? after : AFTER_PRESETS[after]
94
+ sym_opts = opts.fetch(:symbols,{})
95
+ @symbols = SymbolOptions.new(
96
+ sym_opts.fetch(:open, DEFAULT_SYMBOLS[:open]),
97
+ sym_opts.fetch(:close, DEFAULT_SYMBOLS[:close]),
98
+ sym_opts.fetch(:reset, DEFAULT_SYMBOLS[:reset])
99
+ )
50
100
  evaluate_rules
51
101
  build_regexs
52
102
  end
@@ -57,33 +107,54 @@ module TermColor
57
107
  # @return [String] Text with ANSI style codes injected
58
108
  def apply(text)
59
109
  raw = process_text(text)
60
- last_rule = nil
110
+ rule_stack = []
61
111
  str = ''
112
+ rule_names = @rules.keys
62
113
  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]))
114
+ if r.is_a?(Symbol)
115
+ # Part is a rule
116
+ dprint "\tRule Symbol #{r}\n"
117
+ if r == :close && rule_stack.length >= 1
118
+ # Rule close with 1+ opened rules
119
+ opened = rule_stack.pop
120
+ opened_after = @rules[opened].codes(Rule::Parts[:after])
121
+ dprint "\t\tClose, opened rule '#{opened}'\n"
122
+ dprint "\t\t\tClosing rule '#{opened}' with After\n"
123
+ dprint 4,"After: #{opened_after.inspect}\n"
124
+ str.concat(opened_after)
125
+ unless rule_stack.length == 0
126
+ rule_stack.each do |outer|
127
+ outer_inside = @rules[outer].codes(Rule::Parts[:inside])
128
+ # Closed rule was nested in another open rule
129
+ dprint 3, "Outer rule '#{outer}' still open. Restoring Inside\n"
130
+ dprint 4, "Inside: #{outer_inside.inspect}\n}"
131
+ str.concat(outer_inside)
83
132
  end
84
- else
85
- str.concat(r)
133
+ end
134
+ # binding.pry
135
+ # outer = rule_stack[-1]
136
+ # outer_inside = @rules[outer].codes(Rule::Parts[:inside])
137
+ # # Closed rule was nested in another open rule
138
+ # dprint 3, "Outer rule '#{outer}' still open. Restoring Inside\n"
139
+ # dprint 4, "Inside: #{outer_inside.inspect}\n}"
140
+ # str.concat(outer_inside)
141
+ # # binding.pry
142
+ # end
143
+ elsif r == :reset && rule_stack.length == 0
144
+ # no opened outer rules, reset symbol given
145
+ dprint "\t\tReset, no opened rule\n"
146
+ str.concat(@rules[r].codes(Rule::Parts[:after]))
147
+ elsif rule_names.include?(r)
148
+ # New rule to apply
149
+ dprint "\t\tApplying new rule '#{r}'\n"
150
+ dprint 3, "Previous active rule `#{rule_stack[-1]}`\n"
151
+ rule_stack.push r
152
+ str.concat(@rules[r].codes(Rule::Parts[:inside]))
86
153
  end
154
+ else
155
+ # Part is text
156
+ str.concat(r)
157
+ end
87
158
  end
88
159
  str
89
160
  end
@@ -104,7 +175,7 @@ module TermColor
104
175
  # Wraps STDOUT printf method, passing output of `apply` to `print`
105
176
  # Doesn't actually use `printf`, instead passes result of
106
177
  # `format_string % args` to `print`.
107
- # @param [String] format_string printf format string,
178
+ # @param [String] format_string printf format string,
108
179
  # including TermColor style tags
109
180
  # @param [Array] args printf values to use with format string
110
181
  # @param [Hash] opts Optional params
@@ -115,23 +186,35 @@ module TermColor
115
186
 
116
187
  # Sanitize rule symbols
117
188
  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
-
189
+ @rules.keys.each { |k| sanitized.gsub!("#{@symbols.rule}#{k.to_s}","#{255.chr}#{k.to_s}") }
190
+ sanitized.gsub!(@symbols.reset, 255.chr*2)
191
+
121
192
  t = sanitized % args
122
193
  # 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
-
194
+ @rules.keys.each { |k| t.gsub!("#{255.chr}#{k.to_s}","#{@symbols.rule}#{k.to_s}") }
195
+ t.gsub!(255.chr*2,@symbols.reset)
196
+
126
197
  stdout.print apply(t)
127
198
  end
128
-
199
+
129
200
  private
130
201
 
202
+ def dprint(*v)
203
+ if DBG
204
+ if v.length == 2
205
+ tc,t=v
206
+ tabs = "\t" * tc
207
+ print "#{tabs}#{t}"
208
+ else
209
+ print v[0]
210
+ end
211
+ end
212
+ end
213
+
131
214
  def evaluate_rules
132
215
  @rules = {}
133
216
  @base_rules.each_pair do |k,v|
134
- @rules[k] = Rule.compile(v)
217
+ @rules[k] = Rule.compile(v, self)
135
218
  end
136
219
  end
137
220
 
@@ -140,11 +223,14 @@ module TermColor
140
223
  src = @rules
141
224
  src.each_pair do |k,v|
142
225
  @regexs[k] = Regexp.compile(
143
- "(?<#{k.to_s}>(#{RULE_SYMBOL}#{k.to_s}))"
226
+ "(?<#{k.to_s}>(#{@symbols.open}#{k.to_s}))"
144
227
  )
145
228
  end
146
- @regexs[:default] = Regexp.compile(
147
- "(?<default>(#{RESET_SYMBOL}))"
229
+ @regexs[:close] = Regexp.compile(
230
+ "(?<default>(#{@symbols.close}))"
231
+ )
232
+ @regexs[:reset] = Regexp.compile(
233
+ "(?<default>(#{@symbols.reset}))"
148
234
  )
149
235
  end
150
236
 
@@ -203,7 +289,7 @@ module TermColor
203
289
  if !is_last
204
290
  end_pos = locations[i+1][:begin] - 1
205
291
  end
206
-
292
+
207
293
  working << l[:symbol]
208
294
  working << text[l[:continue_pos]..end_pos]
209
295
  end
@@ -211,4 +297,4 @@ module TermColor
211
297
  end
212
298
 
213
299
  end
214
- end
300
+ end
data/lib/term_color.rb CHANGED
@@ -9,8 +9,10 @@ module TermColor
9
9
  ##
10
10
  # Alias for constructing a new RuleSet
11
11
  # @see TermColor::RuleSet
12
- def create_rule_set(rules={})
13
- TermColor::RuleSet.new(rules)
12
+ def create_rule_set(rules=nil,**opts)
13
+ TermColor::RuleSet.new(rules,opts)
14
14
  end
15
15
 
16
- end
16
+
17
+
18
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: term_color
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wade H.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-02-26 00:00:00.000000000 Z
11
+ date: 2020-04-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -46,7 +46,7 @@ licenses:
46
46
  - MIT
47
47
  metadata:
48
48
  source_code_uri: https://github.com/vdtdev/term_color
49
- documentation_uri: https://rubydoc.info/gems/term_color/0.0.2
49
+ documentation_uri: https://rubydoc.info/gems/term_color/0.0.3
50
50
  post_install_message:
51
51
  rdoc_options: []
52
52
  require_paths: