jmadlibs 0.8.1

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 (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/jmadlibs.rb +288 -0
  3. metadata +44 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c413d2fa2a1d13a3d8d9d8bf0dc9569a6ba5e64a
4
+ data.tar.gz: 54d44c3e455169e03d6071781fcfd92cc519684c
5
+ SHA512:
6
+ metadata.gz: a2bd5e151781f33f19cf2a7befea70c7e9ef50017537f26548d48bf315d05c65d554e2c649e121eff0e5ce874b701eac2c86190e35396cca9564a4d33f99736b
7
+ data.tar.gz: ebc8adcf8697f8ec0682646fe32b0c5d0f408941fce52ec905e2103bae8a9cd88138ed32667abd2f4fdc9072635747a5876706fdd54b77cf62b1ee2b8b10facd
data/lib/jmadlibs.rb ADDED
@@ -0,0 +1,288 @@
1
+ # Features:
2
+ # * alternatives: {option1|option2}
3
+ #
4
+ # * optionals: [optionaltext]
5
+ # > weighted optionals: [optionaltext%chance]
6
+ #
7
+ # * substitutions: <wordlist>
8
+ # * post-processed substitutions:
9
+ # > <wordlist%a> prepends a/an based on first letter of returned word
10
+ # > capitalised substitutions: <Wordlist>
11
+ #
12
+ # * joins between two lists: <lista+listb>
13
+
14
+ # All of the above can be combined and appear in the results of a parsing,
15
+ # so having any operator appear in a substitution word list is possible.
16
+
17
+ # TODO:
18
+ # * Currently, {{alt aa|alt ab}|{alt ba|alt bb}} will fail. Regex needs work.
19
+ # * add pluralisation: bike->bikes, boss->bosses, fox->foxes, etc. https://en.wikipedia.org/wiki/English_plurals
20
+ # * \ escaping of token signifiers. For instance, \[thing] treated as plaintext
21
+
22
+
23
+ class JMadlibs
24
+ def initialize(pattern=nil, library=nil)
25
+ @loglevels = {0 => "NONE", 1 => "ERROR", 2 => "WARN", 3 => "INFO", 4 => "DEBUG", 5 => "ALL"}
26
+ @loglevel = 3
27
+ @rng = Random.new
28
+ setPattern(pattern)
29
+ setLibrary(library)
30
+ end
31
+
32
+ def log(msg, priority=5)
33
+ if priority.is_a? String then priority = @loglevels.key(priority) end
34
+ if priority <= @loglevel
35
+ puts "JMadlibs: [" + @loglevels[priority] + "] " + msg
36
+ end
37
+ end
38
+
39
+ def addList(name, wordlist)
40
+ if @library.nil? then @library = {} end
41
+ log "Adding list '" + name + "'", "DEBUG"
42
+ @library[name] = wordlist
43
+ end
44
+
45
+ def setPattern(pattern)
46
+ if !pattern.nil? then log "Pattern set to '" + pattern + "'", "DEBUG" end
47
+ @pattern = pattern
48
+ end
49
+
50
+ def setLibrary(library)
51
+ if !library.nil?
52
+ log "Library updated", "DEBUG"
53
+ @library = library
54
+ end
55
+ end
56
+
57
+ def setLogLevel(loglevel)
58
+ if loglevel.is_a? String then loglevel = @loglevels.key(loglevel) end
59
+ @loglevel = loglevel
60
+ log "Loglevel set to '" + @loglevels[loglevel] +"'", "INFO"
61
+ end
62
+
63
+ def loadList(filename)
64
+ if File.file?(filename)
65
+ log "Loading word lists from " + filename, "INFO"
66
+ @library = ()
67
+ currentList = ""
68
+ currentListContents = []
69
+
70
+ File.foreach(filename).with_index do |line|
71
+ line = line.strip
72
+ if line != "" and !line.start_with?('#')
73
+ matched = /==(.+)==/.match(line)
74
+ if !matched.nil? # new list identifier
75
+ if currentList != "" # save old list, if one exists.
76
+ addList(currentList, currentListContents)
77
+ end
78
+ currentList = matched[1] # update working targets for new list
79
+ currentListContents = []
80
+ elsif currentList == "" # we have no current list; this must be a pattern
81
+ setPattern(line)
82
+ else # word to be added to current list
83
+ currentListContents.push(line)
84
+ end
85
+ end
86
+ end
87
+ addList(currentList, currentListContents)
88
+ else
89
+ log "Unable to open file.", "WARN"
90
+ end
91
+ end
92
+
93
+ def anify(word) # If word begins with a vowel, prefix 'an', else prefix 'a'
94
+ if (/^[aeiou]/.match(word).nil?)
95
+ result = "a " + word
96
+ else
97
+ result = "an " + word
98
+ end
99
+
100
+ return result
101
+ end
102
+
103
+ def pluralise(word)
104
+ # add s by default
105
+ # (lf)(fe?) -> ves
106
+ # y -> ies
107
+ # o -> oes
108
+ # (se?)(sh)(ch)
109
+
110
+ # this is a pretty big problem!
111
+
112
+ return word
113
+ end
114
+
115
+ def resolve_substitution(target)
116
+ up = false
117
+ specifier = ""
118
+
119
+ # do we need to do post-processing?
120
+ post = target.rindex(/\$[ap]/)
121
+
122
+ if !post.nil?
123
+ specifier = target.slice(post+1, target.length - post - 1)
124
+ target = target.slice(0, post)
125
+ end
126
+
127
+ # do we need to capitalise?
128
+ if !(/^[A-Z]/.match(target).nil?)
129
+ target = target.downcase
130
+ up = true
131
+ end
132
+
133
+ # are we selecting from multiple lists?
134
+ mult = target.index /\+/
135
+
136
+ if mult.nil? # only one list
137
+ return "MISSING" if @library[target].nil?
138
+ result = parse_pattern(@library[target].sample)
139
+
140
+ else # more than one list
141
+ multlist = []
142
+ listnames = []
143
+
144
+ # as long as we still have alternatives, keep adding them to listnames
145
+ while !mult.nil?
146
+ listnames << target.slice(0, mult)
147
+ target = target.slice(mult+1, target.length - mult - 1)
148
+ mult = target.index /\+/
149
+ end
150
+ listnames << target # append final alternative
151
+
152
+ # combine lists for sampling
153
+ listnames.each do |list|
154
+ if !@library[list].nil? then multlist += @library[list] end
155
+ end
156
+
157
+ return "MISSING" if multlist.length == 0 # no valid options found
158
+ result = parse_pattern(multlist.sample)
159
+ end
160
+
161
+ # do post-processing
162
+ if up
163
+ result[0] = result[0].upcase
164
+ end
165
+ if specifier == "a"
166
+ result = anify(result)
167
+ end
168
+ if specifier == "p"
169
+ result = pluralise(result)
170
+ end
171
+
172
+ return result
173
+ end
174
+
175
+ def resolve_alternative(target)
176
+ options = []
177
+ while target.length > 0 # split into options
178
+ ind = target.index /\|/ # needs rewriting to parse (a|(ba|bb)) and similar
179
+
180
+ if ind.nil? # only one option
181
+ options << target
182
+ target = ""
183
+ else
184
+ options << target.slice(0, ind)
185
+ target = target.slice(ind+1, target.length - ind - 1)
186
+ end
187
+ end
188
+
189
+ result = options.sample
190
+ return parse_pattern(result)
191
+ end
192
+
193
+ def resolve_optional(target)
194
+ chance = 50
195
+
196
+ ind = target.index("%") # specified chance?
197
+
198
+ if !ind.nil?
199
+ chance = target.slice(ind+1, target.length - ind - 1).to_i
200
+ target = target.slice(0, ind)
201
+ end
202
+
203
+ result = ""
204
+
205
+ if rand(100) <= chance
206
+ result = target
207
+ end
208
+
209
+ return parse_pattern(result)
210
+ end
211
+
212
+ def sampledict(target)
213
+ return resolve_substitution(target)
214
+ end
215
+
216
+ def parse_pattern(target)
217
+ tokens = []
218
+ flags = []
219
+ escaped = false
220
+
221
+ while target.length > 0
222
+ ind = target.index /[<{\[]/
223
+
224
+ if ind.nil? # only plain text remains
225
+ tokens << target
226
+ target = ""
227
+ flags << "p"
228
+
229
+ elsif ind > 0 # plain text with some other token following
230
+ tokens << target.slice(0,ind)
231
+ target = target.slice(ind, target.length - ind)
232
+ flags << "p"
233
+
234
+ else # non-plaintext token
235
+ type = ""
236
+
237
+ if target.slice(0) == "<"
238
+ ind = target.index(">")
239
+ type = "s"
240
+ elsif target.slice(0) == "{"
241
+ ind = target.index("}")
242
+ type = "a"
243
+ elsif target.slice(0) == "["
244
+ ind = target.index("]")
245
+ type = "o"
246
+ end
247
+
248
+ if ind.nil?
249
+ log "'" + target +"' escaped or missing terminator, treating as plaintext.", "INFO"
250
+ tokens << target
251
+ target = ""
252
+ flags << "p"
253
+ else
254
+ tokens << target.slice(1, ind-1)
255
+ target = target.slice(ind+1, target.length - ind+1)
256
+ flags << type
257
+ end
258
+ end
259
+ end
260
+
261
+ # parse tokens
262
+ tokens.each_with_index { |token, index|
263
+ if flags[index] == "a"
264
+ tokens[index] = resolve_alternative(token)
265
+ elsif flags[index] == "o"
266
+ tokens[index] = resolve_optional(token)
267
+ elsif flags[index] == "s"
268
+ tokens[index] = resolve_substitution(token)
269
+ end
270
+ }
271
+
272
+ # rebuild string
273
+ result = tokens.join
274
+ return result
275
+ end
276
+
277
+ def generate
278
+ if @library.nil?
279
+ log "No library defined.", "WARN"
280
+ return nil
281
+ elsif @pattern.nil?
282
+ log "No pattern defined.", "WARN"
283
+ return nil
284
+ else
285
+ return parse_pattern(@pattern)
286
+ end
287
+ end
288
+ end
metadata ADDED
@@ -0,0 +1,44 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jmadlibs
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.8.1
5
+ platform: ruby
6
+ authors:
7
+ - Gareth Morgan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-10-28 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A madlibs engine with several powerful features
14
+ email: jacelium@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/jmadlibs.rb
20
+ homepage: http://rubygems.org/gems/jmadlibs
21
+ licenses:
22
+ - MIT
23
+ metadata: {}
24
+ post_install_message:
25
+ rdoc_options: []
26
+ require_paths:
27
+ - lib
28
+ required_ruby_version: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ required_rubygems_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ requirements: []
39
+ rubyforge_project:
40
+ rubygems_version: 2.2.2
41
+ signing_key:
42
+ specification_version: 4
43
+ summary: JMadlibs madlib engine
44
+ test_files: []