text-gen 0.11.3 → 0.12.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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/lib/text/gen/context.rb +119 -0
  3. data/lib/text/gen/filter/base.rb +16 -13
  4. data/lib/text/gen/filter/capitalize.rb +19 -0
  5. data/lib/text/gen/filter/censor.rb +52 -0
  6. data/lib/text/gen/filter/clear.rb +19 -0
  7. data/lib/text/gen/filter/distinct.rb +19 -0
  8. data/lib/text/gen/filter/downcase.rb +19 -0
  9. data/lib/text/gen/filter/exclude.rb +19 -0
  10. data/lib/text/gen/filter/locale.rb +44 -0
  11. data/lib/text/gen/filter/match.rb +19 -0
  12. data/lib/text/gen/filter/meta.rb +35 -0
  13. data/lib/text/gen/filter/pluralize.rb +96 -0
  14. data/lib/text/gen/filter/reject.rb +24 -0
  15. data/lib/text/gen/filter/remember.rb +18 -0
  16. data/lib/text/gen/filter/replace.rb +15 -0
  17. data/lib/text/gen/filter/select.rb +24 -0
  18. data/lib/text/gen/filter/swap.rb +50 -0
  19. data/lib/text/gen/filter/titleize.rb +29 -0
  20. data/lib/text/gen/filter/upcase.rb +19 -0
  21. data/lib/text/gen/filter.rb +10 -168
  22. data/lib/text/gen/investigator.rb +38 -0
  23. data/lib/text/gen/meta.rb +46 -0
  24. data/lib/text/gen/result.rb +7 -22
  25. data/lib/text/gen/result_accumulator.rb +6 -11
  26. data/lib/text/gen/runner.rb +113 -174
  27. data/lib/text/gen/store.rb +29 -1
  28. data/lib/text/gen/version.rb +1 -1
  29. data/lib/text/gen.rb +4 -0
  30. metadata +21 -17
  31. data/lib/text/gen/filter/item/locale.rb +0 -29
  32. data/lib/text/gen/filter/item/reject.rb +0 -28
  33. data/lib/text/gen/filter/item/select.rb +0 -28
  34. data/lib/text/gen/filter/item_filter.rb +0 -17
  35. data/lib/text/gen/filter/result/capitalize.rb +0 -17
  36. data/lib/text/gen/filter/result/censor.rb +0 -55
  37. data/lib/text/gen/filter/result/clear.rb +0 -19
  38. data/lib/text/gen/filter/result/downcase.rb +0 -17
  39. data/lib/text/gen/filter/result/exclude.rb +0 -22
  40. data/lib/text/gen/filter/result/match.rb +0 -22
  41. data/lib/text/gen/filter/result/meta.rb +0 -31
  42. data/lib/text/gen/filter/result/pluralize.rb +0 -91
  43. data/lib/text/gen/filter/result/swap.rb +0 -48
  44. data/lib/text/gen/filter/result/titleize.rb +0 -28
  45. data/lib/text/gen/filter/result/upcase.rb +0 -17
  46. data/lib/text/gen/filter/result_filter.rb +0 -35
@@ -7,149 +7,90 @@ module Text
7
7
  module Gen
8
8
  # Runner generates results from the builder that is found with the given key.
9
9
  class Runner
10
- attr_reader :key, :lookup, :max_attempts, :max_recursion
11
- attr_accessor :unique, :store
10
+ attr_reader :key, :lookup, :max_attempts, :max_recursion, :filters, :meta
12
11
 
13
- def initialize(key:, lookup:, max_recursion: 10, max_attempts: 10, request_filters: [])
12
+ def initialize(key:, lookup:, max_recursion: 10, max_attempts: 10, filters: [], meta: {})
14
13
  @key = key.to_s.downcase
15
14
  @lookup = lookup
16
- @store = Store.new
17
- @unique = false
18
- @request_filters = request_filters
15
+ @filters = filters
16
+ @meta = meta
19
17
  @max_attempts = max_attempts
20
18
  @max_recursion = max_recursion
21
- populate_replace
22
- end
23
-
24
- def unique?
25
- @unique
26
- end
27
-
28
- def populate_replace
29
- @request_filters.each do |f|
30
- store.add(f["key"], Filter.constant_builder(f["key"], f["value"])) if f["type"] == "replace"
31
- end
32
19
  end
33
20
 
34
21
  def run(count: 1)
35
- builder = fetch_builder(key)
36
- return Array.new(count, error_result("{builder `#{key}` not found}")) unless builder
22
+ store = Store.new(lookup)
23
+ builder = store.fetch(key)
24
+ raise NoBuilderMatched, "{builder `#{key}` not found}" unless builder
37
25
 
38
- ResultAccumulator.accumulate(unique: unique?, count: count, max_attempts: max_attempts) do
39
- run_builder(key, builder, @request_filters, 0)
26
+ context = Context.new(store:, filters:, meta:, max_recursion:)
27
+ ResultAccumulator.accumulate(count:, max_attempts:) do
28
+ run_builder(context, builder)
40
29
  end
41
30
  end
42
31
 
43
- def investigate
44
- visit_and_recurse(key, 0).sort.uniq
45
- end
32
+ # A builder is hash with a key field, items, filters, and meta
33
+ def run_builder(context, builder)
34
+ context.descend!(builder)
35
+ context.with_filters(builder["filters"]) do
36
+ builder = context.apply_builder_filters(builder)
37
+ return unless builder
46
38
 
47
- # Returns a hash of all available filters with their parameters.
48
- # Filters requiring explicit parameters will have an array of parameter names.
49
- # Filters without parameters will have an empty array.
50
- def available_filters
51
- Filter.available_filters
52
- end
39
+ # Filter mapping of builder to result is allowed
40
+ return builder if builder.is_a?(Result)
53
41
 
54
- def visit_and_recurse(key, depth)
55
- depth += 1
56
- raise MaxRecursionError if depth > max_recursion
57
-
58
- builder = fetch_builder(key)
59
- keys = [key]
60
- builder["items"].each do |item|
61
- item["segments"].each do |segment|
62
- if segment["type"] == "reference"
63
- keys << segment["text"]
64
- keys += visit_and_recurse(segment["text"], depth)
65
- end
66
- end
67
- end
68
- keys
69
- rescue => e
70
- [key]
71
- end
42
+ result = builder.is_a?(Result) ? builder : run_items(context, builder["items"])
43
+ return unless result
72
44
 
73
- def fetch_builder(key)
74
- builder = @store.find(key) || lookup.call(key)
75
- @store.add(key, builder)
76
- builder
77
- end
45
+ result.merge_meta(builder["meta"])
46
+ result = context.apply_result_filters(result)
47
+ return unless result
78
48
 
79
- # A builder is hash with a key field, items, filters, and meta
80
- def run_builder(key, builder, filters, depth)
81
- depth += 1
82
- raise MaxRecursionError if depth > max_recursion
83
-
84
- current_filters = merge_filters(builder, filters)
85
- current_items = apply_item_filters(builder["items"], current_filters)
86
- result = run_items(key, builder, current_items, current_filters, builder["meta"], depth)
87
- return unless result
88
-
89
- result.merge_meta(builder["meta"])
90
- result = apply_result_function(result, current_filters)
91
- apply_result_filters(result, current_filters)
49
+ context.remember(result)
50
+ end
51
+ ensure
52
+ context.ascend!
92
53
  end
93
54
 
94
- def error_result(text = "{error}")
95
- Result.new(text:, type: :error)
96
- end
55
+ def run_items(context, items)
56
+ items = context.apply_item_list_filters(items)
57
+ return if items.empty?
97
58
 
98
- def run_items(key, builder, items, filters, meta, depth)
99
- strategy, modifier = (builder["strategy"] || "random").split(":")
100
- case strategy
59
+ case context.current_strategy
101
60
  when "sequence"
102
- run_item_sequence(key, modifier, items, filters, meta, depth)
61
+ run_item_sequence(context, items)
103
62
  when "weighted"
104
- run_weighted_items(key, modifier, items, meta, depth)
63
+ run_weighted_items(context, items)
105
64
  else
106
- run_random_item(key, modifier, items, meta, depth)
65
+ run_random_item(context, items)
107
66
  end
108
- rescue StandardError => e
109
- error_result("{#{e.class.name}:#{e.message}}")
110
67
  end
111
68
 
112
- def random_item(dice, items)
113
- return items.sample if dice.nil? || dice.empty? || dice == "*"
114
-
115
- total, count = random_from_dice(dice)
116
- index = total - count # convert to 0-indexed
117
- raise NoItemMatched("roll #{total} exceeds #{items.length}") if index >= items.length
118
-
119
- items[index]
120
- end
121
-
122
- def run_random_item(key, dice, items, meta, depth)
123
- item = random_item(dice, items)
124
- result = run_item(key, item, depth)
125
- return unless result
126
-
127
- result.merge_meta(meta)
128
- result
129
- end
130
-
131
- def run_item_sequence(key, separator, items, filters, meta, depth)
69
+ def run_item_sequence(context, items)
132
70
  results = items.map do |item|
133
71
  weight = item["weight"]&.to_i || 100
134
72
  next unless rand(100) < weight.clamp(1, 100)
135
73
 
136
- run_item(key, item, depth)
74
+ ResultAccumulator.accumulate(count: 1, max_attempts: @max_attempts) do
75
+ run_item(context, item)
76
+ end.first
137
77
  end
138
78
 
139
79
  results = results.compact
140
80
  return if results.empty?
141
81
 
142
- Result.merge(results, separator: separator || "", meta:, type: :sequence)
82
+ Result.merge(results, separator: context.current_modifier, type: :sequence)
143
83
  end
144
84
 
145
- def run_weighted_items(key, dice, items, meta, depth)
85
+ def run_weighted_items(context, items)
146
86
  total_weight = items.sum { |item| [item.fetch("weight", 1).to_i, 1].max }
87
+ dice = context.current_modifier
147
88
  rand_weight = if dice.nil? || dice.empty? || dice == "*"
148
- rand(total_weight)
149
- else
150
- total, count = random_from_dice(dice)
151
- total - count # convert to 0-indexed
152
- end
89
+ rand(total_weight)
90
+ else
91
+ total, count = random_from_dice(dice)
92
+ total - count # convert to 0-indexed
93
+ end
153
94
  return if rand_weight > total_weight
154
95
 
155
96
  current_weight = 0
@@ -157,47 +98,75 @@ module Text
157
98
  current_weight += [item.fetch("weight", 1).to_i, 1].max
158
99
  current_weight > rand_weight
159
100
  end
160
- result = run_item(key, item, depth)
161
- return unless result
162
101
 
163
- result.merge_meta(meta)
164
- result
102
+ run_item(context, item)
103
+ end
104
+
105
+ def run_random_item(context, items)
106
+ item = random_item(items, dice: context.current_modifier)
107
+ return unless item
108
+
109
+ run_item(context, item)
110
+ end
111
+
112
+ def random_item(items, dice: nil)
113
+ return items.sample if dice.nil? || dice.empty? || dice == "*"
114
+
115
+ total, count = random_from_dice(dice)
116
+ index = total - count # convert to 0-indexed
117
+ raise NoItemMatched("roll #{total} exceeds #{items.length}") if index >= items.length
118
+
119
+ items[index]
120
+ end
121
+
122
+ def run_item(context, item)
123
+ item = context.apply_item_filters(item)
124
+ return unless item
125
+
126
+ # Filter mapping of item to result is allowed
127
+ return item if item.is_a?(Result)
128
+
129
+ context.with_filters(item["filters"]) do
130
+ results = item["segments"].map { |seg| run_segment(context, seg) }.compact
131
+ return if results.empty?
132
+
133
+ Result.merge(results,
134
+ value: item["value"],
135
+ multiplier: item["multiplier"],
136
+ meta: item["meta"],
137
+ type: context.current_key)
138
+ end
165
139
  end
166
140
 
167
- def run_item(key, item, depth)
168
- results = item["segments"].map { |seg| run_segment(seg, depth) }
169
- result = Result.merge(results,
170
- value: item["value"],
171
- multiplier: item["multiplier"],
172
- meta: item["meta"],
173
- type: key)
174
- result = apply_result_function(result, item["filters"])
175
- apply_result_filters(result, item["filters"])
141
+ def run_segment(context, seg)
142
+ # Note, a filter may expand a segment into multiple segments
143
+ segments = context.apply_segment_filters(seg)
144
+ return if segments.nil?
145
+
146
+ # Filter mapping of segment to result is allowed
147
+ return segments if segments.is_a?(Result)
148
+
149
+ results = segments.map { |seg| run_simple_segment(context, seg) }.compact
150
+ return if results.empty?
151
+
152
+ return results[0] if results.length == 1
153
+
154
+ Result.merge(results, type: "segment expansion")
176
155
  end
177
156
 
178
- def run_segment(seg, depth)
157
+ def run_simple_segment(context, seg)
179
158
  case seg["type"]
180
159
  when "dice"
181
160
  run_dice_segment(seg)
182
161
  when "number"
183
162
  run_number_segment(seg)
184
163
  when "reference"
185
- run_reference_segment(seg, depth)
164
+ run_reference_segment(context, seg)
186
165
  else
187
166
  Result.new(text: seg["text"], value: seg["value"], type: :constant)
188
167
  end
189
168
  end
190
169
 
191
- def random_from_dice(text)
192
- rolled = DiceNomShim.roll(text)
193
- parsed = JSON.parse(rolled).first["lhs"]
194
-
195
- # Keep tracks the dice that weren't discarded
196
- count = parsed["values"].select {|v| v["keep"] }.count
197
- total = parsed["total"]
198
- [total, count]
199
- end
200
-
201
170
  def run_dice_segment(seg)
202
171
  total, = random_from_dice(seg["text"])
203
172
  Result.new(text: total.to_s, multiplier: total, type: :dice)
@@ -208,56 +177,26 @@ module Text
208
177
  Result.new(text: num.to_s, multiplier: num, type: :number)
209
178
  end
210
179
 
211
- def run_reference_segment(seg, depth)
180
+ def run_reference_segment(context, seg)
212
181
  key = seg["text"]
213
- run_builder(key, fetch_builder(key), seg.fetch("filters", []), depth)
214
- end
215
-
216
- def apply_result_filters(result, filters)
217
- result = Filter.result_select(result, filters)
218
- result = Filter.result_reject(result, filters) if result
219
- result
220
- end
221
-
222
- def apply_result_function(result, filters)
223
- Filter.functions(result, filters, lookup: lookup)
224
- end
225
-
226
- def merge_filters(builder, filters)
227
- builder.key?("filters") ? builder["filters"] + filters : filters
228
- end
229
182
 
230
- def apply_item_filters(items, filters)
231
- items = Filter.filter_locale(items, @request_filters + filters)
232
- items = Filter.filter_select(items, filters)
233
- items = Filter.filter_reject(items, filters)
234
- raise FilterError if items.empty?
235
-
236
- items
237
- end
183
+ builder = context.store.fetch(key)
184
+ return unless builder
238
185
 
239
- def to_h
240
- {
241
- "key" => key,
242
- "request_filters" => request_filters,
243
- "request_meta" => request_meta,
244
- "store" => store.to_h
245
- }
186
+ # Offset by one to ensure the segment filters are applied to the builder
187
+ context.with_filters(seg["filters"], offset: 1) do
188
+ run_builder(context, builder)
189
+ end
246
190
  end
247
191
 
248
- class << self
249
- def from_hash(hash)
250
- runner = Runner.new(
251
- key: hash["key"],
252
- request_filters: hash.fetch("request_filters", [])
253
- )
254
-
255
- hash.fetch("store", {}).each do |k, b|
256
- runner.store.add(k, b)
257
- end
192
+ def random_from_dice(text)
193
+ rolled = DiceNomShim.roll(text)
194
+ parsed = JSON.parse(rolled).first["lhs"]
258
195
 
259
- runner
260
- end
196
+ # Keep tracks the dice that weren't discarded
197
+ count = parsed["values"].select { |v| v["keep"] }.count
198
+ total = parsed["total"]
199
+ [total, count]
261
200
  end
262
201
  end
263
202
  end
@@ -5,7 +5,14 @@ module Text
5
5
  # Store is a local cache of a builder that is persistent per store
6
6
  # to save time on database lookups or transformations.
7
7
  class Store
8
- def initialize
8
+ NOT_FOUND_BUILDER = {
9
+ "filters" => [],
10
+ "meta" => {},
11
+ "items" => []
12
+ }.freeze
13
+
14
+ def initialize(lookup)
15
+ @lookup = lookup
9
16
  @store = {}
10
17
  end
11
18
 
@@ -15,6 +22,27 @@ module Text
15
22
 
16
23
  def add(key, builder)
17
24
  @store[key.to_s.downcase] = builder
25
+ builder
26
+ end
27
+
28
+ def clear(key)
29
+ @store.delete(key.to_s.downcase)
30
+ end
31
+
32
+ def fetch(key)
33
+ builder = find(key)
34
+ return builder if builder
35
+
36
+ builder = @lookup.call(key)
37
+ return not_found(key) unless builder
38
+
39
+ add(key, builder.merge("key" => key.to_s.downcase))
40
+ end
41
+
42
+ def not_found(key)
43
+ hsh = NOT_FOUND_BUILDER.dup
44
+ hsh["key"] = key
45
+ hsh
18
46
  end
19
47
 
20
48
  def to_h
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Text
4
4
  module Gen
5
- VERSION = "0.11.3"
5
+ VERSION = "0.12.0"
6
6
  end
7
7
  end
data/lib/text/gen.rb CHANGED
@@ -6,13 +6,17 @@ require_relative "gen/result_accumulator"
6
6
  require_relative "gen/result"
7
7
  require_relative "gen/segment/parser"
8
8
  require_relative "gen/filter"
9
+ require_relative "gen/context"
10
+ require_relative "gen/meta"
9
11
  require_relative "gen/store"
10
12
  require_relative "gen/runner"
13
+ require_relative "gen/investigator"
11
14
 
12
15
  module Text
13
16
  module Gen
14
17
  class MaxRecursionError < StandardError; end
15
18
  class MaxAttemptsError < StandardError; end
19
+ class NoBuilderMatched < StandardError; end
16
20
  class NoItemMatched < StandardError; end
17
21
  class FilterError < StandardError; end
18
22
  class LookupError < StandardError; end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: text-gen
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.3
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - G Palmer
@@ -41,24 +41,28 @@ files:
41
41
  - README.md
42
42
  - Rakefile
43
43
  - lib/text/gen.rb
44
+ - lib/text/gen/context.rb
44
45
  - lib/text/gen/filter.rb
45
46
  - lib/text/gen/filter/base.rb
46
- - lib/text/gen/filter/item/locale.rb
47
- - lib/text/gen/filter/item/reject.rb
48
- - lib/text/gen/filter/item/select.rb
49
- - lib/text/gen/filter/item_filter.rb
50
- - lib/text/gen/filter/result/capitalize.rb
51
- - lib/text/gen/filter/result/censor.rb
52
- - lib/text/gen/filter/result/clear.rb
53
- - lib/text/gen/filter/result/downcase.rb
54
- - lib/text/gen/filter/result/exclude.rb
55
- - lib/text/gen/filter/result/match.rb
56
- - lib/text/gen/filter/result/meta.rb
57
- - lib/text/gen/filter/result/pluralize.rb
58
- - lib/text/gen/filter/result/swap.rb
59
- - lib/text/gen/filter/result/titleize.rb
60
- - lib/text/gen/filter/result/upcase.rb
61
- - lib/text/gen/filter/result_filter.rb
47
+ - lib/text/gen/filter/capitalize.rb
48
+ - lib/text/gen/filter/censor.rb
49
+ - lib/text/gen/filter/clear.rb
50
+ - lib/text/gen/filter/distinct.rb
51
+ - lib/text/gen/filter/downcase.rb
52
+ - lib/text/gen/filter/exclude.rb
53
+ - lib/text/gen/filter/locale.rb
54
+ - lib/text/gen/filter/match.rb
55
+ - lib/text/gen/filter/meta.rb
56
+ - lib/text/gen/filter/pluralize.rb
57
+ - lib/text/gen/filter/reject.rb
58
+ - lib/text/gen/filter/remember.rb
59
+ - lib/text/gen/filter/replace.rb
60
+ - lib/text/gen/filter/select.rb
61
+ - lib/text/gen/filter/swap.rb
62
+ - lib/text/gen/filter/titleize.rb
63
+ - lib/text/gen/filter/upcase.rb
64
+ - lib/text/gen/investigator.rb
65
+ - lib/text/gen/meta.rb
62
66
  - lib/text/gen/result.rb
63
67
  - lib/text/gen/result_accumulator.rb
64
68
  - lib/text/gen/runner.rb
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../item_filter"
4
-
5
- module Text
6
- module Gen
7
- module Filter
8
- module Item
9
- class Locale < ItemFilter
10
- def apply(items)
11
- items.map { |item| locale_item(item) || item }
12
- end
13
-
14
- private
15
-
16
- def locale_item(item)
17
- meta = item["meta"]
18
- return if meta.nil? || meta.empty?
19
-
20
- locale_text = meta[key.downcase]&.sample
21
- return unless locale_text
22
-
23
- Filter.constant_item(locale_text, item)
24
- end
25
- end
26
- end
27
- end
28
- end
29
- end
@@ -1,28 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../item_filter"
4
-
5
- module Text
6
- module Gen
7
- module Filter
8
- module Item
9
- class Reject < ItemFilter
10
- def apply(items)
11
- items.select do |item|
12
- pass_reject?(item["meta"])
13
- end
14
- end
15
-
16
- private
17
-
18
- def pass_reject?(meta)
19
- return false if value == "*" && meta.key?(key)
20
- return false if key == "*" && meta.values.any? { |arr| arr.include?(value) }
21
-
22
- !meta[key]&.include?(value)
23
- end
24
- end
25
- end
26
- end
27
- end
28
- end
@@ -1,28 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../item_filter"
4
-
5
- module Text
6
- module Gen
7
- module Filter
8
- module Item
9
- class Select < ItemFilter
10
- def apply(items)
11
- items.select do |item|
12
- pass_select?(item["meta"])
13
- end
14
- end
15
-
16
- private
17
-
18
- def pass_select?(meta)
19
- return true if value == "*" && meta.key?(key)
20
- return true if key == "*" && meta.values.any? { |arr| arr.include?(value) }
21
-
22
- meta[key]&.include?(value)
23
- end
24
- end
25
- end
26
- end
27
- end
28
- end
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "base"
4
-
5
- module Text
6
- module Gen
7
- module Filter
8
- # Base class for filters that operate on item arrays
9
- # Subclasses should implement #apply(items) -> Array
10
- class ItemFilter < Base
11
- def apply(items)
12
- raise NotImplementedError, "Subclasses must implement #apply(items)"
13
- end
14
- end
15
- end
16
- end
17
- end
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../result_filter"
4
-
5
- module Text
6
- module Gen
7
- module Filter
8
- module Result
9
- class Capitalize < ResultFilter
10
- def apply(result)
11
- transform_text(result, result.text.sub(/\S/, &:upcase))
12
- end
13
- end
14
- end
15
- end
16
- end
17
- end
@@ -1,55 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../result_filter"
4
-
5
- module Text
6
- module Gen
7
- module Filter
8
- module Result
9
- class Censor < ResultFilter
10
- def apply(result)
11
- return result if lookup.nil? || key.nil?
12
-
13
- # Get the censor builder
14
- builder = lookup.call(key)
15
- return result unless builder
16
-
17
- # Create a runner to evaluate each item
18
- runner = Text::Gen::Runner.new(key: key, lookup: lookup)
19
-
20
- # Run each item in the builder once
21
- censor_texts = builder["items"].map do |item|
22
- item_result = runner.send(:run_item, key, item, 0)
23
- apply_function(item_result.text) if item_result
24
- end.compact.uniq
25
-
26
- # Get the text to compare
27
- compare_text = apply_function(result.text)
28
-
29
- # Check if result text matches any censor text
30
- return nil if censor_texts.include?(compare_text)
31
-
32
- result
33
- end
34
-
35
- private
36
-
37
- def apply_function(text)
38
- return text if value.nil? || value.empty?
39
-
40
- case value
41
- when "downcase"
42
- text.downcase
43
- when "upcase"
44
- text.upcase
45
- when "capitalize"
46
- text.capitalize
47
- else
48
- text
49
- end
50
- end
51
- end
52
- end
53
- end
54
- end
55
- end