text-gen 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fd025671968a7f3a6d7fd3dc9cc0cb1d02f06a43054c2aa8395f6db054d86c79
4
- data.tar.gz: 5ff622ee28e61931406c4d3f1d792c1bc3eefde351c2bd8e22f8b285b715158d
3
+ metadata.gz: e32773381d2c8aa30b9c027bf9c5a00f4b76045eff5e6fd36626d94fa33de652
4
+ data.tar.gz: 97e3c3b49b367939e270582fb3c6b2e940d623bfec93fab341af27fe1e3902d6
5
5
  SHA512:
6
- metadata.gz: eafd1508e2cac903b0f81e0c94dd47eee86d8bc8321d863f3258b5d2e730cc016a84b7cbba48e0827ba9fc6545916cd20a521d7927587eedfd017cd6561f4b35
7
- data.tar.gz: 0d82d9c14341a74baea22e17cb1830f3c61559a33ca8049b0ba5fde856552ddde0a5fddb82b67b0d81a4d80505f6e8aa8894b930640d49281127a27e20d1148d
6
+ metadata.gz: 68f26c344a0feb1b1a730d9247e41c8dc93e27f1a52f22f7d16bdb0c1d719ba3ff03b294475f36e1c404c816192a995c9c3341ff845af0b29d6ad0a37e0e5028
7
+ data.tar.gz: 3021702f504892d11d449c2de96ad98d5b5b1d7d514f4bc8f32e91b172e74383a3338faf1256fc03c57dcba34fb6442ccc560ad004e7ddc948c2e709562ce684
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Text
2
4
  module Gen
3
5
  class Filter
@@ -15,7 +17,9 @@ module Text
15
17
  when "upcase"
16
18
  result = Result.from(text: result.text.upcase, type: :function, result:)
17
19
  when "titleize"
18
- result = Result.from(text: Titleizer.titleize(result.text), type: :function, result:)
20
+ result = Result.from(text: Function::Titleizer.titleize(result.text), type: :function, result:)
21
+ when "pluralize"
22
+ result = Result.from(text: Function::Pluralizer.pluralize(result.text), type: :function, result:)
19
23
  when "clear"
20
24
  result = Result.from(text: result.text, type: :function, result:)
21
25
  result.clear_meta(filter["key"], filter["value"])
@@ -66,33 +70,47 @@ module Text
66
70
 
67
71
  # Create a builder that always returns a constant value
68
72
  def constant_builder(key, str)
69
- # TODO: the string should be parsed into segments
70
- segments = Segment::Parser.parse(str)
71
73
  {
72
74
  "key" => key,
73
- "items" => [
74
- {
75
- "segments" => segments,
76
- "value" => 0,
77
- "multiplier" => 1,
78
- "filters" => [],
79
- "meta" => {}
80
- }
81
- ],
75
+ "items" => [constant_item(str)],
82
76
  "meta" => {},
83
77
  "filters" => []
84
78
  }
85
79
  end
86
80
 
81
+ def constant_item(str, item = nil)
82
+ segments = Segment::Parser.parse(str)
83
+ citem = { "segments" => segments }
84
+ if item
85
+ citem["value"] = item["value"] if item["value"]
86
+ citem["multiplier"] = item["multiplier"] if item["multiplier"]
87
+ citem["filters"] = item["filters"] if item["filters"]
88
+ citem["meta"] = item["meta"] if item["meta"]
89
+ end
90
+ citem
91
+ end
92
+
87
93
  # Replace locale is used on an item; if the item is selected and has
88
94
  # associated meta value of "locale" and the request is using that locale value
89
95
  # then replace the item text with the value of the meta. If there is
90
96
  # more than one value, select at random.
91
- def replace_locale(meta, locale)
97
+ # Returns a new item with locale text and preserved value/multiplier, or nil
98
+ def replace_locale(item, locale)
92
99
  return if locale.nil?
100
+ return if item.nil?
101
+
102
+ meta = item["meta"]
93
103
  return if meta.nil? || meta.empty?
94
104
 
95
- meta[locale.downcase]&.sample
105
+ locale_text = meta[locale.downcase]&.sample
106
+ return unless locale_text
107
+
108
+ # Create new item with locale text, preserving value and multiplier
109
+ # new_item = constant_item(locale_text)
110
+ # new_item["value"] = item["value"] if item["value"]
111
+ # new_item["multiplier"] = item["multiplier"] if item["multiplier"]
112
+ # new_item
113
+ constant_item(locale_text, item)
96
114
  end
97
115
 
98
116
  def pass_select?(meta, key, value)
@@ -110,6 +128,8 @@ module Text
110
128
  end
111
129
 
112
130
  def separator(filters)
131
+ return "" unless filters
132
+
113
133
  separator_filter = filter_by_type(filters, "separator")
114
134
  return "" unless separator_filter
115
135
  return separator_filter["value"] if separator_filter["key"] == "string"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Text
2
4
  module Gen
3
5
  class FilterError < StandardError; end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Text
4
+ module Gen
5
+ module Function
6
+ class Pluralizer
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
+ "thief" => "thieves",
19
+ "tooth" => "teeth",
20
+ "wolf" => "wolves",
21
+ "woman" => "women"
22
+ }.freeze
23
+
24
+ SINGLE = Set.new(%w[a an the this that my your his her its our their])
25
+
26
+ class << self
27
+ def pluralize(str)
28
+ return str if str.empty?
29
+
30
+ arr = str.split(/\s+/)
31
+ return str if arr.length < 2
32
+
33
+ num = to_num(arr[0])
34
+ if num && num > 1
35
+ dc = arr[-1].downcase
36
+ arr[-1] = exceptions(dc) || others(dc) || ends_with_y(dc) || simple(dc)
37
+ end
38
+ arr.join(" ")
39
+ end
40
+
41
+ def to_num(str)
42
+ return 1 if SINGLE.include?(str.downcase)
43
+
44
+ str =~ /\d+/ ? str.to_i : nil
45
+ end
46
+
47
+ def exceptions(str)
48
+ EXCEPTIONS[str]
49
+ end
50
+
51
+ def others(str)
52
+ if str.end_with?("s") || str.end_with?("x") || str.end_with?("z") || str.end_with?("ch") || str.end_with?("sh")
53
+ "#{str}es"
54
+ end
55
+ end
56
+
57
+ def ends_with_y(str)
58
+ "#{str[0..-2]}ies" if str.end_with?("y")
59
+ end
60
+
61
+ def simple(str)
62
+ "#{str}s"
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Text
4
+ module Gen
5
+ module Function
6
+ class Titleizer
7
+ SKIP_WORDS = Set.new(%w[a an and as at but by for if in of on or the to v via vs])
8
+
9
+ class << self
10
+ def titleize(str)
11
+ arr = str.split(/\s+/)
12
+ idx = 0
13
+ len = arr.length
14
+ while idx < len
15
+ arr[idx] = arr[idx].capitalize if idx.zero? || !SKIP_WORDS.include?(arr[idx])
16
+ idx += 1
17
+ end
18
+ arr.join(" ")
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Text
2
4
  module Gen
3
5
  class MaxAttemptsError < StandardError; end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Text
2
4
  module Gen
3
5
  class MaxRecursionError < StandardError; end
@@ -73,12 +73,15 @@ module Text
73
73
  end
74
74
  end
75
75
 
76
- def merge(results, separator, meta = {}, type: :sequence)
76
+ def merge(results, value: nil, multiplier: nil, filters: [], meta: {}, type: :sequence)
77
+ sep = Filter.separator(filters)
78
+ mul = multiplier || results.reduce(1) { |acc, r| acc * r.multiplier }
79
+ val = value || results.sum(&:value)
77
80
  new(
78
- text: results.map(&:text).join(separator),
81
+ text: results.map(&:text).join(sep),
79
82
  type: type,
80
- value: results.sum(&:value),
81
- multiplier: results.reduce(1) { |acc, r| acc * r.multiplier }
83
+ value: val * mul,
84
+ multiplier: 1
82
85
  ).tap do |result|
83
86
  result.components.append(*results)
84
87
  result.merge_all_meta(results)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Text
2
4
  module Gen
3
5
  # ResultAccumulator handles accumulating results with attempt tracking
@@ -49,7 +49,7 @@ module Text
49
49
  end
50
50
 
51
51
  # A builder is hash with a key field, items, filters, and meta
52
- def run_builder(key, builder, filters, depth)
52
+ def run_builder(_key, builder, filters, depth)
53
53
  depth += 1
54
54
  raise MaxRecursionError if depth > max_recursion
55
55
 
@@ -65,24 +65,36 @@ module Text
65
65
  def run_items(strategy, items, filters, meta, depth)
66
66
  case strategy
67
67
  when "sequence"
68
- sep = Filter.separator(filters)
69
- run_item_sequence(items, sep, meta, depth)
68
+ run_item_sequence(items, filters, meta, depth)
70
69
  when "weighted"
71
70
  run_weighted_items(items, meta, depth)
72
71
  else
73
- run_random_item(items, meta, depth)
72
+ run_random_item(strategy, items, meta, depth)
74
73
  end
75
74
  end
76
75
 
77
- def run_random_item(items, meta, depth)
78
- result = run_item(items.sample, depth)
76
+ def random_item(strategy, items)
77
+ if strategy.nil? || strategy.empty? || strategy == "random"
78
+ items.sample
79
+ else
80
+ total, count = random_from_dice(strategy)
81
+ total -= count # make the roll 0-indexed
82
+ items[total]
83
+ end
84
+ end
85
+
86
+ def run_random_item(strategy, items, meta, depth)
87
+ item = random_item(strategy, items)
88
+ return unless item
89
+
90
+ result = run_item(item, depth)
79
91
  result.merge_meta(meta)
80
92
  result
81
93
  end
82
94
 
83
95
  def run_item_sequence(items, filters, meta, depth)
84
96
  results = items.map { |item| run_item(item, depth) }
85
- Result.merge(results, filters, meta, type: :sequence)
97
+ Result.merge(results, filters:, meta:, type: :sequence)
86
98
  end
87
99
 
88
100
  def run_weighted_items(items, meta, depth)
@@ -99,13 +111,15 @@ module Text
99
111
  end
100
112
 
101
113
  def run_item(item, depth)
102
- locale_result = locale_result_for(item)
103
- return locale_result if locale_result
114
+ locale_item = Filter.replace_locale(item, locale)
115
+ item = locale_item if locale_item
104
116
 
105
117
  results = item["segments"].map { |seg| run_segment(seg, depth) }
106
- sep = Filter.separator(item["filters"])
107
- result = Result.merge(results, sep)
108
- result.merge_meta(item["meta"])
118
+ result = Result.merge(results,
119
+ value: item["value"],
120
+ multiplier: item["multiplier"],
121
+ filters: item["filters"],
122
+ meta: item["meta"])
109
123
  result = apply_result_function(result, item["filters"])
110
124
  apply_result_filters(result, item["filters"])
111
125
  end
@@ -123,16 +137,22 @@ module Text
123
137
  end
124
138
  end
125
139
 
140
+ def random_from_dice(text)
141
+ rolled = DiceNomShim.roll(text)
142
+ parsed = JSON.parse(rolled).first["lhs"]
143
+ count = parsed["values"].size
144
+ total = parsed["total"]
145
+ [total, count]
146
+ end
147
+
126
148
  def run_dice_segment(seg)
127
- rolled = DiceNomShim.roll(seg["text"])
128
- parsed = JSON.parse(rolled)
129
- result = parsed[0].dig("lhs", "total")
130
- Result.new(text: result.to_s, multiplier: result, type: :dice)
149
+ total, = random_from_dice(seg["text"])
150
+ Result.new(text: total.to_s, multiplier: total, type: :dice)
131
151
  end
132
152
 
133
153
  def run_number_segment(seg)
134
154
  num = seg["text"].to_i
135
- Result.new(text: num.to_s, value: num, type: :number)
155
+ Result.new(text: num.to_s, multiplier: num, type: :number)
136
156
  end
137
157
 
138
158
  def run_reference_segment(seg, depth)
@@ -162,13 +182,6 @@ module Text
162
182
  items
163
183
  end
164
184
 
165
- def locale_result_for(item)
166
- text = Filter.replace_locale(item["meta"], locale)
167
- return nil unless text
168
-
169
- Result.new(text:, type: :locale, value: item["value"], multiplier: item["multiplier"])
170
- end
171
-
172
185
  def to_h
173
186
  {
174
187
  "key" => key,
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Text
2
4
  module Gen
3
5
  module Segment
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Text
2
4
  module Gen
3
5
  module Segment
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Text
2
4
  module Gen
3
5
  module Segment
@@ -10,7 +12,9 @@ module Text
10
12
 
11
13
  {
12
14
  "type" => "constant",
13
- "text" => str
15
+ "text" => str,
16
+ "value" => 0,
17
+ "multiplier" => Integer(str)
14
18
  }
15
19
  end
16
20
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative "constant"
2
4
  require_relative "dice"
3
5
  require_relative "number"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Text
2
4
  module Gen
3
5
  module Segment
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Text
4
4
  module Gen
5
- VERSION = "0.3.0"
5
+ VERSION = "0.4.0"
6
6
  end
7
7
  end
data/lib/text/gen.rb CHANGED
@@ -6,8 +6,9 @@ require_relative "gen/lookup_error"
6
6
  require_relative "gen/max_attempts_error"
7
7
  require_relative "gen/max_recursion_error"
8
8
 
9
+ require_relative "gen/function/titleizer"
10
+ require_relative "gen/function/pluralizer"
9
11
  require_relative "gen/result_accumulator"
10
- require_relative "gen/titleizer"
11
12
  require_relative "gen/result"
12
13
  require_relative "gen/segment/parser"
13
14
  require_relative "gen/filter"
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.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - G Palmer
@@ -42,6 +42,8 @@ files:
42
42
  - lib/text/gen.rb
43
43
  - lib/text/gen/filter.rb
44
44
  - lib/text/gen/filter_error.rb
45
+ - lib/text/gen/function/pluralizer.rb
46
+ - lib/text/gen/function/titleizer.rb
45
47
  - lib/text/gen/lookup_error.rb
46
48
  - lib/text/gen/max_attempts_error.rb
47
49
  - lib/text/gen/max_recursion_error.rb
@@ -54,7 +56,6 @@ files:
54
56
  - lib/text/gen/segment/parser.rb
55
57
  - lib/text/gen/segment/reference.rb
56
58
  - lib/text/gen/store.rb
57
- - lib/text/gen/titleizer.rb
58
59
  - lib/text/gen/version.rb
59
60
  - sig/text/gen.rbs
60
61
  homepage: https://github.com/palmergs/text-gen
@@ -1,20 +0,0 @@
1
- module Text
2
- module Gen
3
- class Titleizer
4
- SKIP_WORDS = Set.new(%w[a an and as at but by for if in of on or the to v via vs])
5
-
6
- class << self
7
- def titleize(str)
8
- arr = str.split(/\s+/)
9
- idx = 0
10
- len = arr.length
11
- while idx < len
12
- arr[idx] = arr[idx].capitalize if idx == 0 || !SKIP_WORDS.include?(arr[idx])
13
- idx += 1
14
- end
15
- arr.join(" ")
16
- end
17
- end
18
- end
19
- end
20
- end