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.
- checksums.yaml +7 -0
- data/lib/jmadlibs.rb +288 -0
- 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: []
|