text-gen 0.11.2 → 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.
- checksums.yaml +4 -4
- data/lib/text/gen/context.rb +119 -0
- data/lib/text/gen/filter/base.rb +16 -13
- data/lib/text/gen/filter/capitalize.rb +19 -0
- data/lib/text/gen/filter/censor.rb +52 -0
- data/lib/text/gen/filter/clear.rb +19 -0
- data/lib/text/gen/filter/distinct.rb +19 -0
- data/lib/text/gen/filter/downcase.rb +19 -0
- data/lib/text/gen/filter/exclude.rb +19 -0
- data/lib/text/gen/filter/locale.rb +44 -0
- data/lib/text/gen/filter/match.rb +19 -0
- data/lib/text/gen/filter/meta.rb +35 -0
- data/lib/text/gen/filter/pluralize.rb +96 -0
- data/lib/text/gen/filter/reject.rb +24 -0
- data/lib/text/gen/filter/remember.rb +18 -0
- data/lib/text/gen/filter/replace.rb +15 -0
- data/lib/text/gen/filter/select.rb +24 -0
- data/lib/text/gen/filter/swap.rb +50 -0
- data/lib/text/gen/filter/titleize.rb +29 -0
- data/lib/text/gen/filter/upcase.rb +19 -0
- data/lib/text/gen/filter.rb +10 -168
- data/lib/text/gen/investigator.rb +38 -0
- data/lib/text/gen/meta.rb +46 -0
- data/lib/text/gen/result.rb +7 -22
- data/lib/text/gen/result_accumulator.rb +6 -11
- data/lib/text/gen/runner.rb +113 -174
- data/lib/text/gen/store.rb +29 -1
- data/lib/text/gen/version.rb +1 -1
- data/lib/text/gen.rb +4 -0
- metadata +21 -17
- data/lib/text/gen/filter/item/locale.rb +0 -29
- data/lib/text/gen/filter/item/reject.rb +0 -28
- data/lib/text/gen/filter/item/select.rb +0 -28
- data/lib/text/gen/filter/item_filter.rb +0 -17
- data/lib/text/gen/filter/result/capitalize.rb +0 -17
- data/lib/text/gen/filter/result/censor.rb +0 -55
- data/lib/text/gen/filter/result/clear.rb +0 -19
- data/lib/text/gen/filter/result/downcase.rb +0 -17
- data/lib/text/gen/filter/result/exclude.rb +0 -22
- data/lib/text/gen/filter/result/match.rb +0 -22
- data/lib/text/gen/filter/result/meta.rb +0 -31
- data/lib/text/gen/filter/result/pluralize.rb +0 -91
- data/lib/text/gen/filter/result/swap.rb +0 -48
- data/lib/text/gen/filter/result/titleize.rb +0 -28
- data/lib/text/gen/filter/result/upcase.rb +0 -17
- data/lib/text/gen/filter/result_filter.rb +0 -35
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a96a06698a3f29093011dac3d0f9311741ffa92707ee42d685837480efaf458e
|
|
4
|
+
data.tar.gz: 3d6a85c17d38b8e2f13c203744e088efe7ff12d1f8d0eebc26551d9b94a0d9c7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bbcf087725fc5fa33da6427ddd0ff10e0482083a1e8193c37a2a609e63bf2a1932c4763bae0811ff29d489e70736a6e47e46503b0010f1a90db84dbd18f5fc0f
|
|
7
|
+
data.tar.gz: c4cc7a812869a52bab81fcd78dfcebea2c64951b09299ccdb9c7f1cda7cc05b65635082cb15c03d0ff61ae248eb031f166edce39c3da52cbe1e4ce379a3d47b9
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Text
|
|
4
|
+
module Gen
|
|
5
|
+
# Keep the context for one run.
|
|
6
|
+
class Context
|
|
7
|
+
attr_reader :store, :depth
|
|
8
|
+
|
|
9
|
+
def initialize(store:, filters: nil, meta: nil, max_recursion: 10)
|
|
10
|
+
@depth = 0
|
|
11
|
+
@store = store
|
|
12
|
+
|
|
13
|
+
# Request filters are added to the top level builder
|
|
14
|
+
@filters = (filters || []).map { |hsh| Filter.build(hsh, 1) }.compact
|
|
15
|
+
@meta = meta
|
|
16
|
+
@keys = []
|
|
17
|
+
@strategy_stack = []
|
|
18
|
+
@remember = Hash.new { |h, k| h[k] = [] }
|
|
19
|
+
@max_recursion = max_recursion
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def descend!(builder)
|
|
23
|
+
@depth += 1
|
|
24
|
+
@keys << builder["key"]
|
|
25
|
+
@strategy_stack << (builder["strategy"] || "random").split(":", 2)
|
|
26
|
+
raise MaxRecursionError if @depth > @max_recursion
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def ascend!
|
|
30
|
+
@depth -= 1
|
|
31
|
+
@keys.pop
|
|
32
|
+
@strategy_stack.pop
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def with_filters(filters, offset: 0)
|
|
36
|
+
count = 0
|
|
37
|
+
(filters || []).each do |hsh|
|
|
38
|
+
filter = Filter.build(hsh, depth + offset)
|
|
39
|
+
next unless filter
|
|
40
|
+
|
|
41
|
+
count += 1
|
|
42
|
+
@filters << filter
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
result = yield
|
|
46
|
+
|
|
47
|
+
@filters.pop(count)
|
|
48
|
+
result
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def current_key
|
|
52
|
+
@keys.last
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def current_strategy
|
|
56
|
+
@strategy_stack.last&.first || "random"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def current_modifier
|
|
60
|
+
@strategy_stack.last&.[](1)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def remember(result)
|
|
64
|
+
@remember[current_key] << result
|
|
65
|
+
result
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def remembered(key = nil)
|
|
69
|
+
@remember[key || current_key]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def apply_builder_filters(builder)
|
|
73
|
+
@filters.each do |filter|
|
|
74
|
+
builder = filter.builder(self, builder) if filter.respond_to?(:builder)
|
|
75
|
+
return unless builder
|
|
76
|
+
return builder if builder.is_a?(Text::Gen::Result)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
builder
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def apply_item_list_filters(items)
|
|
83
|
+
@filters.each do |filter|
|
|
84
|
+
items = filter.items(self, items) if filter.respond_to?(:items)
|
|
85
|
+
return [] if items.nil? || items.empty?
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
items
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def apply_item_filters(item)
|
|
92
|
+
@filters.each do |filter|
|
|
93
|
+
item = filter.item(self, item) if filter.respond_to?(:item)
|
|
94
|
+
return unless item
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
item
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def apply_segment_filters(segment)
|
|
101
|
+
@filters.each do |filter|
|
|
102
|
+
segment = filter.segment(self, segment) if filter.respond_to?(:segment)
|
|
103
|
+
return if segment.nil? || segment.empty?
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
[segment]
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def apply_result_filters(result)
|
|
110
|
+
@filters.each do |filter|
|
|
111
|
+
result = filter.result(self, result) if filter.respond_to?(:result)
|
|
112
|
+
return unless result
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
result
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
data/lib/text/gen/filter/base.rb
CHANGED
|
@@ -6,33 +6,36 @@ module Text
|
|
|
6
6
|
# Base class for all filters
|
|
7
7
|
# Provides common utilities and template method pattern
|
|
8
8
|
class Base
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def initialize(filter = {}, lookup: nil)
|
|
9
|
+
def initialize(filter, depth)
|
|
12
10
|
@filter = filter
|
|
13
|
-
@
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
# Template method - must be implemented by subclasses
|
|
17
|
-
def apply(input)
|
|
18
|
-
raise NotImplementedError, "Subclasses must implement #apply"
|
|
11
|
+
@depth = depth
|
|
19
12
|
end
|
|
20
13
|
|
|
21
14
|
def component_key
|
|
22
|
-
"function:#{
|
|
15
|
+
"function:#{type}"
|
|
23
16
|
end
|
|
24
17
|
|
|
25
18
|
# Shared utilities
|
|
26
19
|
def key
|
|
27
|
-
|
|
20
|
+
return @key if defined?(@key)
|
|
21
|
+
|
|
22
|
+
@key = @filter["key"].nil? || @filter["key"].empty? ? nil : @filter["key"]
|
|
28
23
|
end
|
|
29
24
|
|
|
30
25
|
def value
|
|
31
|
-
|
|
26
|
+
return @value if defined?(@value)
|
|
27
|
+
|
|
28
|
+
@value = @filter["value"].nil? || @filter["value"].empty? ? nil : @filter["value"]
|
|
32
29
|
end
|
|
33
30
|
|
|
34
31
|
def type
|
|
35
|
-
filter["type"]
|
|
32
|
+
@type ||= @filter["type"]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class << self
|
|
36
|
+
def filter_name
|
|
37
|
+
@filter_name ||= name.split("::").last.downcase
|
|
38
|
+
end
|
|
36
39
|
end
|
|
37
40
|
end
|
|
38
41
|
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Text
|
|
4
|
+
module Gen
|
|
5
|
+
module Filter
|
|
6
|
+
class Capitalize < Base
|
|
7
|
+
def result(context, result)
|
|
8
|
+
return result if @depth && context.depth != @depth
|
|
9
|
+
|
|
10
|
+
text = result.text
|
|
11
|
+
text = key == "force" ? text.capitalize : text.sub(/\S/, &:upcase)
|
|
12
|
+
return result if text == result.text
|
|
13
|
+
|
|
14
|
+
Result.from(result, text:, type: component_key)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Text
|
|
4
|
+
module Gen
|
|
5
|
+
module Filter
|
|
6
|
+
class Censor < Base
|
|
7
|
+
def result(context, result)
|
|
8
|
+
return result if @depth && context.depth != @depth
|
|
9
|
+
return result if key.nil?
|
|
10
|
+
|
|
11
|
+
builder = context.store.fetch(key)
|
|
12
|
+
return result unless builder
|
|
13
|
+
|
|
14
|
+
# Build a list of censor texts
|
|
15
|
+
runner = Text::Gen::Runner.new(key:, lookup: nil)
|
|
16
|
+
censor = Context.new(context.store)
|
|
17
|
+
list = builder["items"].map do |item|
|
|
18
|
+
runner.run_item(censor, item)&.text
|
|
19
|
+
end.compact.uniq
|
|
20
|
+
|
|
21
|
+
# Get the text to compare
|
|
22
|
+
compare_text = apply_function(result.text)
|
|
23
|
+
|
|
24
|
+
# Return nil if result text matches any censor text
|
|
25
|
+
return if list.any? { |s| apply_function(s) == compare_text }
|
|
26
|
+
|
|
27
|
+
result
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def apply_function(text)
|
|
33
|
+
return nil unless text
|
|
34
|
+
|
|
35
|
+
# default is case insensitve comparison
|
|
36
|
+
return text.downcase if value.nil? || value.empty?
|
|
37
|
+
|
|
38
|
+
case value
|
|
39
|
+
when "downcase"
|
|
40
|
+
text.downcase
|
|
41
|
+
when "upcase"
|
|
42
|
+
text.upcase
|
|
43
|
+
when "capitalize"
|
|
44
|
+
text.capitalize
|
|
45
|
+
else
|
|
46
|
+
text
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Text
|
|
4
|
+
module Gen
|
|
5
|
+
module Filter
|
|
6
|
+
# Clears result meta
|
|
7
|
+
class Clear < Base
|
|
8
|
+
def result(context, result)
|
|
9
|
+
return result if @depth && context.depth != @depth
|
|
10
|
+
|
|
11
|
+
new_meta = Text::Gen::Meta.clear_kv(result.meta, key, value)
|
|
12
|
+
return result if new_meta == result.meta
|
|
13
|
+
|
|
14
|
+
Result.from(result, type: component_key, meta: new_meta)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Text
|
|
4
|
+
module Gen
|
|
5
|
+
module Filter
|
|
6
|
+
# Return nil if the text value has already been generated
|
|
7
|
+
class Distinct < Base
|
|
8
|
+
def result(context, result)
|
|
9
|
+
return result if @depth && context.depth != @depth
|
|
10
|
+
|
|
11
|
+
previous = context.remembered(key)
|
|
12
|
+
return nil if previous.any? { |r| r.text.strip.downcase == result.text.strip.downcase }
|
|
13
|
+
|
|
14
|
+
result
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Text
|
|
4
|
+
module Gen
|
|
5
|
+
module Filter
|
|
6
|
+
class Downcase < Base
|
|
7
|
+
def result(context, result)
|
|
8
|
+
return result if @depth && context.depth != @depth
|
|
9
|
+
|
|
10
|
+
text = result.text
|
|
11
|
+
text = text.downcase
|
|
12
|
+
return result if text == result.text
|
|
13
|
+
|
|
14
|
+
Result.from(result, text:, type: component_key)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Text
|
|
4
|
+
module Gen
|
|
5
|
+
module Filter
|
|
6
|
+
class Exclude < Base
|
|
7
|
+
def result(context, result)
|
|
8
|
+
return result if @depth && context.depth != @depth
|
|
9
|
+
return nil if result.nil?
|
|
10
|
+
return nil if value == "*" && result.meta.key?(key)
|
|
11
|
+
return nil if key == "*" && result.meta.values.any? { |arr| arr.include?(value) }
|
|
12
|
+
return nil if result.meta[key]&.include?(value)
|
|
13
|
+
|
|
14
|
+
result
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Text
|
|
4
|
+
module Gen
|
|
5
|
+
module Filter
|
|
6
|
+
# Locale if the key on the filter matches a meta key, then
|
|
7
|
+
# the value of the key becomes a replacement item
|
|
8
|
+
#
|
|
9
|
+
# For example if a filter with the type and key of "locale:en"
|
|
10
|
+
# were run against an item with segments of `["gracias"]` but with a meta value
|
|
11
|
+
# of `{"en": ["thanks", "thank you"]}` then a new item would replace
|
|
12
|
+
# the existing item with a randomly selected segment from the meta.
|
|
13
|
+
class Locale < Base
|
|
14
|
+
def items(_context, items)
|
|
15
|
+
items.map { |item| locale_item(item) || item }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def locale_item(item)
|
|
21
|
+
meta = item["meta"]
|
|
22
|
+
return if meta.nil? || meta.empty?
|
|
23
|
+
|
|
24
|
+
locale_text = meta[key.downcase]&.sample
|
|
25
|
+
return unless locale_text
|
|
26
|
+
|
|
27
|
+
constant_item(locale_text, item)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def constant_item(text, item)
|
|
31
|
+
segments = Segment::Parser.parse(text)
|
|
32
|
+
citem = { "segments" => segments }
|
|
33
|
+
if item
|
|
34
|
+
citem["value"] = item["value"] if item["value"]
|
|
35
|
+
citem["multiplier"] = item["multiplier"] if item["multiplier"]
|
|
36
|
+
citem["filters"] = item["filters"] if item["filters"]
|
|
37
|
+
citem["meta"] = item["meta"] if item["meta"]
|
|
38
|
+
end
|
|
39
|
+
citem
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Text
|
|
4
|
+
module Gen
|
|
5
|
+
module Filter
|
|
6
|
+
class Match < Base
|
|
7
|
+
def result(context, result)
|
|
8
|
+
return result if @depth && context.depth != @depth
|
|
9
|
+
return nil if result.nil?
|
|
10
|
+
return result if value == "*" && result.meta.key?(key)
|
|
11
|
+
return result if key == "*" && result.meta.values.any? { |arr| arr.include?(value) }
|
|
12
|
+
return result if result.meta[key]&.include?(value)
|
|
13
|
+
|
|
14
|
+
nil
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Text
|
|
4
|
+
module Gen
|
|
5
|
+
module Filter
|
|
6
|
+
class Meta < Base
|
|
7
|
+
def result(context, result)
|
|
8
|
+
return result if @depth && context.depth != @depth
|
|
9
|
+
|
|
10
|
+
new_meta = Text::Gen::Meta.append_kv(result.meta.dup, key, meta_value(result))
|
|
11
|
+
return result if new_meta == result.meta
|
|
12
|
+
|
|
13
|
+
Result.from(result, type: component_key, meta: new_meta)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def meta_value(result)
|
|
17
|
+
case value
|
|
18
|
+
when "_text_"
|
|
19
|
+
result.text
|
|
20
|
+
when "_value_"
|
|
21
|
+
result.value.to_s
|
|
22
|
+
when "_multiplier_"
|
|
23
|
+
result.multiplier.to_s
|
|
24
|
+
when "_words_"
|
|
25
|
+
result.text.split(/\s+/).length.to_s
|
|
26
|
+
when "_length_"
|
|
27
|
+
result.text.length.to_s
|
|
28
|
+
else
|
|
29
|
+
value
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Text
|
|
4
|
+
module Gen
|
|
5
|
+
module Filter
|
|
6
|
+
class Pluralize < Base
|
|
7
|
+
EXCEPTIONS = {
|
|
8
|
+
"foot" => "feet",
|
|
9
|
+
"axis" => "axes",
|
|
10
|
+
"child" => "children",
|
|
11
|
+
"codex" => "codices",
|
|
12
|
+
"die" => "dice",
|
|
13
|
+
"dwarf" => "dwarves",
|
|
14
|
+
"goose" => "geese",
|
|
15
|
+
"elf" => "elves",
|
|
16
|
+
"man" => "men",
|
|
17
|
+
"ox" => "oxen",
|
|
18
|
+
"cactus" => "cacti",
|
|
19
|
+
"thesis" => "theses",
|
|
20
|
+
"criterion" => "criteria",
|
|
21
|
+
"thief" => "thieves",
|
|
22
|
+
"tooth" => "teeth",
|
|
23
|
+
"wolf" => "wolves",
|
|
24
|
+
"woman" => "women"
|
|
25
|
+
}.freeze
|
|
26
|
+
|
|
27
|
+
SINGLE = Set.new(%w[a an the this his her its my your our that their])
|
|
28
|
+
|
|
29
|
+
def result(context, result)
|
|
30
|
+
return result if @depth && context.depth != @depth
|
|
31
|
+
|
|
32
|
+
text = result.text
|
|
33
|
+
text = pluralize(text, result.multiplier)
|
|
34
|
+
return result if text == result.text
|
|
35
|
+
|
|
36
|
+
Result.from(result, text:, type: component_key)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def pluralize(str, multiplier = 1)
|
|
42
|
+
return str if str.empty?
|
|
43
|
+
|
|
44
|
+
arr = str.split(/\s+/)
|
|
45
|
+
return str if arr.length < 2
|
|
46
|
+
|
|
47
|
+
idx = arr.rindex { |s| to_num(s) }
|
|
48
|
+
|
|
49
|
+
# Use the multiplier if available, otherwise parse from text
|
|
50
|
+
num = if multiplier > 1
|
|
51
|
+
multiplier
|
|
52
|
+
else
|
|
53
|
+
(idx ? to_num(arr[idx]) : nil)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
return str if num.nil? || num <= 1
|
|
57
|
+
return str if idx && idx >= arr.length - 1
|
|
58
|
+
|
|
59
|
+
dc = arr[-1].downcase
|
|
60
|
+
arr[-1] = exceptions(dc) || single_letter(dc) || others(dc) || ends_with_y(dc) || simple(dc)
|
|
61
|
+
arr.join(" ")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def to_num(str)
|
|
65
|
+
return 1 if SINGLE.include?(str.downcase)
|
|
66
|
+
|
|
67
|
+
str =~ /\d+/ ? str.to_i : nil
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def exceptions(str)
|
|
71
|
+
EXCEPTIONS[str]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def single_letter(str)
|
|
75
|
+
return unless str =~ /^[a-zA-Z0-9]$/
|
|
76
|
+
|
|
77
|
+
"#{str}'s"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def others(str)
|
|
81
|
+
if str.end_with?("s") || str.end_with?("x") || str.end_with?("z") || str.end_with?("ch") || str.end_with?("sh")
|
|
82
|
+
"#{str}es"
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def ends_with_y(str)
|
|
87
|
+
"#{str[0..-2]}ies" if str.end_with?("y")
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def simple(str)
|
|
91
|
+
"#{str}s"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Text
|
|
4
|
+
module Gen
|
|
5
|
+
module Filter
|
|
6
|
+
class Reject < Base
|
|
7
|
+
def items(_context, items)
|
|
8
|
+
items.select do |item|
|
|
9
|
+
pass_reject?(item["meta"])
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def pass_reject?(meta)
|
|
16
|
+
return false if value == "*" && meta.key?(key)
|
|
17
|
+
return false if key == "*" && meta.values.any? { |arr| arr.include?(value) }
|
|
18
|
+
|
|
19
|
+
!meta[key]&.include?(value)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Text
|
|
4
|
+
module Gen
|
|
5
|
+
module Filter
|
|
6
|
+
class Remember < Base
|
|
7
|
+
def result(context, result)
|
|
8
|
+
return result if @depth && context.depth != @depth
|
|
9
|
+
|
|
10
|
+
previous = context.remembered(key)
|
|
11
|
+
return previous.sample unless previous.nil? || previous.empty?
|
|
12
|
+
|
|
13
|
+
result
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Text
|
|
4
|
+
module Gen
|
|
5
|
+
module Filter
|
|
6
|
+
class Replace < Base
|
|
7
|
+
def builder(context, builder)
|
|
8
|
+
return builder unless builder["key"] == key
|
|
9
|
+
|
|
10
|
+
Result.new(text: value, type: "replace")
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Text
|
|
4
|
+
module Gen
|
|
5
|
+
module Filter
|
|
6
|
+
class Select < Base
|
|
7
|
+
def apply(items)
|
|
8
|
+
items.select do |item|
|
|
9
|
+
pass_select?(item["meta"])
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def pass_select?(meta)
|
|
16
|
+
return true if value == "*" && meta.key?(key)
|
|
17
|
+
return true if key == "*" && meta.values.any? { |arr| arr.include?(value) }
|
|
18
|
+
|
|
19
|
+
meta[key]&.include?(value)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Text
|
|
4
|
+
module Gen
|
|
5
|
+
module Filter
|
|
6
|
+
class Swap < Base
|
|
7
|
+
DIGIT_WORDS = {
|
|
8
|
+
"1" => "one", "2" => "two", "3" => "three",
|
|
9
|
+
"4" => "four", "5" => "five", "6" => "six",
|
|
10
|
+
"7" => "seven", "8" => "eight", "9" => "nine"
|
|
11
|
+
}.freeze
|
|
12
|
+
|
|
13
|
+
def result(context, result)
|
|
14
|
+
return result if @depth && context.depth != @depth
|
|
15
|
+
|
|
16
|
+
text = result.text
|
|
17
|
+
text = swap(text, key, value)
|
|
18
|
+
return result if text == result.text
|
|
19
|
+
|
|
20
|
+
Result.from(result, text:, type: component_key)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def swap(str, key, val)
|
|
26
|
+
return str if key.nil? || key.empty?
|
|
27
|
+
|
|
28
|
+
arr = str.split(/\s+/)
|
|
29
|
+
arr = arr.map do |s|
|
|
30
|
+
case key
|
|
31
|
+
when "_digit_"
|
|
32
|
+
swap_digit(s)
|
|
33
|
+
else
|
|
34
|
+
swap_any(s, key, val.to_s)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
arr.join(" ")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def swap_any(str, key, val)
|
|
41
|
+
str == key ? val : str
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def swap_digit(str)
|
|
45
|
+
DIGIT_WORDS.fetch(str, str)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|