poefy 0.5.1 → 0.5.2
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/README.md +11 -3
- data/bin/poefy +15 -16
- data/lib/poefy.rb +0 -1
- data/lib/poefy/conditional_satisfaction.rb +33 -15
- data/lib/poefy/database.rb +88 -72
- data/lib/poefy/generation.rb +82 -55
- data/lib/poefy/poefy_gen_base.rb +4 -6
- data/lib/poefy/poetic_forms.rb +7 -1
- data/lib/poefy/string_manipulation.rb +15 -0
- data/lib/poefy/version.rb +2 -2
- data/spec/poefy_spec.rb +77 -5
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e04a9ee24cb090de963b812bcf8ec3b8109b2587
|
4
|
+
data.tar.gz: '028bc8ed0525d9bcc77a358162f904aef19d6556'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: de3b67a5f31522c5e3741fdf821a56cc155a03e7c49a13a4387d87c11de3dbb220be6d74263fd8743d3c17042fc41150baec3efb4726fd0e010c10899b701a82
|
7
|
+
data.tar.gz: 117a747d04707cad5062d07fa1ed3eabc173783129de31fef102f426484bf17ad5f2b5b841f497233982130175bb2b8d500567245747b82874557f35bfa617bf
|
data/README.md
CHANGED
@@ -25,7 +25,7 @@ Or install it yourself as:
|
|
25
25
|
|
26
26
|
$ gem install poefy
|
27
27
|
|
28
|
-
The repo comes with some text files included. To generate databases for these files, execute special `make_dbs` command:
|
28
|
+
The repo comes with some text files included. To generate databases for these files, execute the special `make_dbs` command:
|
29
29
|
|
30
30
|
$ poefy make_dbs
|
31
31
|
|
@@ -100,6 +100,12 @@ To view and amend these definitions, the code is in `lib/poefy/poetic_forms.rb`.
|
|
100
100
|
rhyme: 'aabba',
|
101
101
|
indent: '',
|
102
102
|
syllable: '{1:[8],2:[8],3:[4,5],4:[4,5],5:[8]}'
|
103
|
+
},
|
104
|
+
double_dactyl: {
|
105
|
+
rhyme: 'abcd efgd',
|
106
|
+
indent: '',
|
107
|
+
syllable: '[6,6,6,4,0,6,6,6,4]',
|
108
|
+
regex: '{7=>/^\S+$/}'
|
103
109
|
}
|
104
110
|
}
|
105
111
|
```
|
@@ -267,7 +273,9 @@ puts poefy.poem ({ rhyme: 'abab cdcd efef gg', indent: '0101 0101 0011 01' })
|
|
267
273
|
puts poefy.poem ({ form: 'sonnet' })
|
268
274
|
puts poefy.poem ({ form: :sonnet, syllable: 0 })
|
269
275
|
puts poefy.poem ({ form: :sonnet, syllable: 10 })
|
270
|
-
puts poefy.poem ({ form: :sonnet, regex:
|
276
|
+
puts poefy.poem ({ form: :sonnet, regex: /^[A-Z].*$/ })
|
277
|
+
puts poefy.poem ({ form: :sonnet, regex: '^[A-Z].*$' })
|
278
|
+
puts poefy.poem ({ form: :sonnet, acrostic: 'pauldpthompson' })
|
271
279
|
puts poefy.poem ({ form: 'sonnet', indent: '01010101001101' })
|
272
280
|
puts poefy.poem ({ form: 'sonnet', proper: false })
|
273
281
|
```
|
@@ -309,7 +317,7 @@ Use sed to filter out lines that are too short:
|
|
309
317
|
|
310
318
|
### Make a database, ignoring uppercase lines
|
311
319
|
|
312
|
-
Use sed to filter out lines that
|
320
|
+
Use sed to filter out lines that do not contain lowercase letters. For example, the sonnets file contains lines with the number of the sonnet, e.g. "CXLVII."
|
313
321
|
|
314
322
|
$ sed -r 'sed '/[a-z]/!d' shakespeare_sonnets.txt | poefy -o shakespeare
|
315
323
|
|
data/bin/poefy
CHANGED
@@ -57,7 +57,7 @@ def parse_options
|
|
57
57
|
' ' * 37 + "Specifies rhyme, indent and syllable\n" +
|
58
58
|
' ' * 37 + "One of:\n" +
|
59
59
|
' ' * 39 + forms_by_4.join("\n" + ' ' * 39)) do |s|
|
60
|
-
options[:form] = s
|
60
|
+
options[:form] = s
|
61
61
|
end
|
62
62
|
opts.on('-r', '--rhyme STRING', "See big block of text below") do |s|
|
63
63
|
options[:rhyme] = s
|
@@ -76,9 +76,7 @@ def parse_options
|
|
76
76
|
|
77
77
|
opts.on('-a', '--acrostic STRING',
|
78
78
|
"Overwrite 'regex' option to generate an acrostic") do |s|
|
79
|
-
|
80
|
-
obj.extend(Poefy::PoeticForms)
|
81
|
-
options[:regex] = obj.acrostic s
|
79
|
+
options[:acrostic] = s
|
82
80
|
end
|
83
81
|
|
84
82
|
# Handle proper sentence structure.
|
@@ -146,26 +144,28 @@ if first_arg.nil?
|
|
146
144
|
exit 0
|
147
145
|
end
|
148
146
|
|
149
|
-
# If the first argument is '
|
147
|
+
# If the first argument is 'make_dbs', then make
|
150
148
|
# databases from the included text files.
|
151
149
|
if first_arg == 'make_dbs'
|
152
|
-
|
150
|
+
path = File.expand_path('../../data', __FILE__)
|
151
|
+
|
152
|
+
input = `sed '/[a-z]/!d' #{path}/shakespeare_sonnets.txt`
|
153
153
|
poefy = Poefy::PoefyGen.new 'shakespeare'
|
154
154
|
poefy.make_database input, false
|
155
155
|
|
156
|
-
input = `sed '/[a-z]/!d'
|
156
|
+
input = `sed '/[a-z]/!d' #{path}/st_therese_of_lisieux.txt`
|
157
157
|
poefy = Poefy::PoefyGen.new 'therese'
|
158
158
|
poefy.make_database input, false
|
159
159
|
|
160
|
-
input = `sed '/[a-z]/!d'
|
160
|
+
input = `sed '/[a-z]/!d' #{path}/whitman_leaves.txt`
|
161
161
|
poefy = Poefy::PoefyGen.new 'whitman'
|
162
162
|
poefy.make_database input, false
|
163
163
|
|
164
|
-
input = `sed '/[a-z]/!d'
|
164
|
+
input = `sed '/[a-z]/!d' #{path}/emily_dickinson.txt`
|
165
165
|
poefy = Poefy::PoefyGen.new 'dickinson'
|
166
166
|
poefy.make_database input, false
|
167
167
|
|
168
|
-
input = `sed '/[a-z]/!d'
|
168
|
+
input = `sed '/[a-z]/!d' #{path}/english_as_she_is_spoke.txt`
|
169
169
|
poefy = Poefy::PoefyGen.new 'spoke'
|
170
170
|
poefy.make_database input, false
|
171
171
|
|
@@ -174,7 +174,7 @@ end
|
|
174
174
|
|
175
175
|
# Poetic form name is the second argument, if it exists.
|
176
176
|
second_arg = (ARGV.length > 1) ? ARGV[1] : ''
|
177
|
-
options[:form] = second_arg
|
177
|
+
options[:form] = second_arg
|
178
178
|
|
179
179
|
# Create poefy object.
|
180
180
|
poefy = Poefy::PoefyGen.new first_arg, options
|
@@ -190,11 +190,7 @@ end
|
|
190
190
|
|
191
191
|
# Create a database using input.
|
192
192
|
if data
|
193
|
-
|
194
|
-
poefy.make_database! data
|
195
|
-
else
|
196
|
-
poefy.make_database data
|
197
|
-
end
|
193
|
+
poefy.make_database data, options[:overwrite]
|
198
194
|
|
199
195
|
# Make a new poem, and output it.
|
200
196
|
else
|
@@ -202,4 +198,7 @@ else
|
|
202
198
|
puts poem if poem
|
203
199
|
end
|
204
200
|
|
201
|
+
# Close the database connection.
|
202
|
+
poefy.close
|
203
|
+
|
205
204
|
################################################################################
|
data/lib/poefy.rb
CHANGED
@@ -6,6 +6,22 @@
|
|
6
6
|
# array of conditions for each element.
|
7
7
|
# Both methods return an output array consisting of samples from an
|
8
8
|
# input array, for which output[0] satisfies condition[0], etc.
|
9
|
+
# Both methods may take a whole lot of time, depending on how lenient the
|
10
|
+
# conditions are. It is better for the stricter conditions to be at the
|
11
|
+
# start of the array, due to the way the code is written.
|
12
|
+
# If none of the conditions match, then it will run in factorial time,
|
13
|
+
# which will get exponentially longer the more elements there are in the
|
14
|
+
# input array.
|
15
|
+
# I would recommend wrapping inside a Timeout block to assuage this. If it
|
16
|
+
# fails to resolve in, say, two seconds, then it's probably not possible
|
17
|
+
# to fit the conditions to the lines:
|
18
|
+
# begin
|
19
|
+
# Timeout::timeout(2) do
|
20
|
+
# output = conditional_selection(lines.shuffle, conditions)
|
21
|
+
# end
|
22
|
+
# rescue
|
23
|
+
# output = []
|
24
|
+
# end
|
9
25
|
################################################################################
|
10
26
|
# '#conditional_permutation' returns a complete permutation of an array.
|
11
27
|
# i.e. output length == array length
|
@@ -18,11 +34,6 @@
|
|
18
34
|
# proc { |arr, elem| elem > 1}
|
19
35
|
# ]
|
20
36
|
# possible output = [1,3,4,5,2]
|
21
|
-
#
|
22
|
-
#
|
23
|
-
# ToDo: This is now not used! Need to add 'current_array' argument.
|
24
|
-
#
|
25
|
-
#
|
26
37
|
################################################################################
|
27
38
|
# '#conditional_selection' returns an array that satisfies only the conditions.
|
28
39
|
# i.e. output length == conditions length
|
@@ -63,7 +74,13 @@ module Poefy
|
|
63
74
|
valid = valid && [*poetic_form[:syllable]].include?(line['syllables'])
|
64
75
|
end
|
65
76
|
if poetic_form[:regex]
|
66
|
-
|
77
|
+
if poetic_form[:regex].respond_to?(:each)
|
78
|
+
poetic_form[:regex].each do |i|
|
79
|
+
valid = valid && !!(line['line'].match(i))
|
80
|
+
end
|
81
|
+
else
|
82
|
+
valid = valid && !!(line['line'].match(poetic_form[:regex]))
|
83
|
+
end
|
67
84
|
end
|
68
85
|
valid
|
69
86
|
end
|
@@ -113,10 +130,9 @@ module Poefy
|
|
113
130
|
# Return a permutation of 'array' where each element validates to the
|
114
131
|
# same index in a 'conditions' array of procs that return Boolean.
|
115
132
|
# Will not work on arrays that contain nil values.
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
def conditional_permutation array, conditions, current_iter = 0
|
133
|
+
def conditional_permutation array, conditions,
|
134
|
+
current_iter = 0,
|
135
|
+
current_array = []
|
120
136
|
output = []
|
121
137
|
|
122
138
|
# Get the current conditional.
|
@@ -128,7 +144,7 @@ module Poefy
|
|
128
144
|
|
129
145
|
# Test the condition. If we've run out of elements
|
130
146
|
# in the condition array, then allow any value.
|
131
|
-
valid = cond ? cond.call(elem) : true
|
147
|
+
valid = cond ? cond.call(current_array, elem) : true
|
132
148
|
if valid
|
133
149
|
|
134
150
|
# Remove this element from the array, and recurse.
|
@@ -138,8 +154,9 @@ module Poefy
|
|
138
154
|
# If the remaining array is empty, no need to recurse.
|
139
155
|
new_val = nil
|
140
156
|
if !remain.empty?
|
141
|
-
new_val = conditional_permutation(remain,
|
142
|
-
|
157
|
+
new_val = conditional_permutation(remain, conditions,
|
158
|
+
current_iter + 1,
|
159
|
+
current_array + [elem])
|
143
160
|
end
|
144
161
|
|
145
162
|
# If we cannot use this value, because it breaks future conditions.
|
@@ -184,8 +201,9 @@ module Poefy
|
|
184
201
|
delete_first(remain, elem)
|
185
202
|
|
186
203
|
# If the remaining array is empty, no need to recurse.
|
187
|
-
new_val = conditional_selection(remain,
|
188
|
-
|
204
|
+
new_val = conditional_selection(remain, conditions,
|
205
|
+
current_iter + 1,
|
206
|
+
current_array + [elem])
|
189
207
|
|
190
208
|
# If we cannot use this value, because it breaks future conditions.
|
191
209
|
if new_val and new_val.empty?
|
data/lib/poefy/database.rb
CHANGED
@@ -23,17 +23,17 @@ module Poefy
|
|
23
23
|
attr_reader :console, :db_file
|
24
24
|
|
25
25
|
# Finalizer must be a class variable.
|
26
|
-
@@final = proc { |dbase| proc {
|
27
|
-
|
28
|
-
@sproc_rhymes_by_count.close if @sproc_rhymes_by_count
|
29
|
-
@sproc_rhymes_by_count_syllables.close if @sproc_rhymes_by_count_syllables
|
26
|
+
@@final = proc { |dbase, sproc| proc {
|
27
|
+
sproc.each { |k, v| v.close }
|
30
28
|
dbase.close if dbase
|
31
29
|
} }
|
32
30
|
|
33
31
|
def initialize db_file, console = false
|
34
32
|
@db_file = db_file
|
35
33
|
@console = console
|
36
|
-
|
34
|
+
@sproc = {}
|
35
|
+
db
|
36
|
+
ObjectSpace.define_finalizer(self, @@final.call(@db, @sproc))
|
37
37
|
end
|
38
38
|
|
39
39
|
# Open global database session, if not already existing.
|
@@ -41,11 +41,16 @@ module Poefy
|
|
41
41
|
# execute it before any calling code.
|
42
42
|
def db
|
43
43
|
if not @db
|
44
|
-
|
45
|
-
open
|
46
|
-
rescue
|
44
|
+
if !exists?
|
47
45
|
@db = nil
|
48
|
-
|
46
|
+
else
|
47
|
+
begin
|
48
|
+
open
|
49
|
+
create_sprocs
|
50
|
+
rescue
|
51
|
+
@db = nil
|
52
|
+
return handle_error 'ERROR: Database contains invalid structure'
|
53
|
+
end
|
49
54
|
end
|
50
55
|
end
|
51
56
|
@db
|
@@ -66,10 +71,9 @@ module Poefy
|
|
66
71
|
|
67
72
|
# Close the database file.
|
68
73
|
def close
|
69
|
-
@
|
70
|
-
@
|
71
|
-
@
|
72
|
-
db.close
|
74
|
+
@sproc.each { |k, v| v.close rescue nil }
|
75
|
+
@db.close if @db
|
76
|
+
@db = nil
|
73
77
|
end
|
74
78
|
|
75
79
|
# See if the database file exists or not.
|
@@ -93,7 +97,7 @@ module Poefy
|
|
93
97
|
sql_import_file = save_sql_import_file lines
|
94
98
|
|
95
99
|
# Delete any existing database.
|
96
|
-
File.delete(@db_file)
|
100
|
+
File.delete(@db_file) if File.exists?(@db_file)
|
97
101
|
|
98
102
|
# Write SQL and SQLite instructions to temp file,
|
99
103
|
# import to database, delete temp file.
|
@@ -133,14 +137,20 @@ module Poefy
|
|
133
137
|
|
134
138
|
# Public interfaces for private stored procedure methods.
|
135
139
|
def sproc_rhymes_all! rhyme_count, syllable_min_max = nil
|
140
|
+
db
|
136
141
|
if syllable_min_max
|
137
142
|
sproc_rhymes_by_count_syllables rhyme_count, syllable_min_max
|
138
143
|
else
|
139
144
|
sproc_rhymes_by_count rhyme_count
|
140
145
|
end
|
141
146
|
end
|
142
|
-
def sproc_lines_all! rhyme
|
143
|
-
|
147
|
+
def sproc_lines_all! rhyme, syllable_min_max = nil
|
148
|
+
db
|
149
|
+
if syllable_min_max
|
150
|
+
sproc_lines_all_syllables rhyme, syllable_min_max
|
151
|
+
else
|
152
|
+
sproc_lines_all rhyme
|
153
|
+
end
|
144
154
|
end
|
145
155
|
|
146
156
|
private
|
@@ -174,77 +184,83 @@ module Poefy
|
|
174
184
|
|
175
185
|
##########################################################################
|
176
186
|
|
177
|
-
#
|
178
|
-
def
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
187
|
+
# Define all stored procedures.
|
188
|
+
def create_sprocs
|
189
|
+
sql = {}
|
190
|
+
sql[:rbc] = %Q[
|
191
|
+
SELECT rhyme, COUNT(rhyme) AS rc
|
192
|
+
FROM (
|
193
|
+
SELECT rhyme, final_word, COUNT(final_word) AS wc
|
194
|
+
FROM lines
|
195
|
+
GROUP BY rhyme, final_word
|
196
|
+
)
|
197
|
+
GROUP BY rhyme
|
198
|
+
HAVING rc >= ?
|
199
|
+
]
|
200
|
+
sql[:rbcs] = %Q[
|
201
|
+
SELECT rhyme, COUNT(rhyme) AS rc
|
202
|
+
FROM (
|
203
|
+
SELECT rhyme, final_word, COUNT(final_word) AS wc
|
204
|
+
FROM lines
|
205
|
+
WHERE syllables BETWEEN ? AND ?
|
206
|
+
GROUP BY rhyme, final_word
|
207
|
+
)
|
208
|
+
GROUP BY rhyme
|
209
|
+
HAVING rc >= ?
|
210
|
+
]
|
211
|
+
sql[:la] = %Q[
|
212
|
+
SELECT line, syllables, final_word, rhyme
|
213
|
+
FROM lines WHERE rhyme = ?
|
214
|
+
]
|
215
|
+
sql[:las] = %Q[
|
216
|
+
SELECT line, syllables, final_word, rhyme
|
217
|
+
FROM lines WHERE rhyme = ?
|
218
|
+
AND syllables BETWEEN ? AND ?
|
219
|
+
]
|
220
|
+
sql.each do |key, value|
|
190
221
|
begin
|
191
|
-
@
|
222
|
+
@sproc[key] = db.prepare value
|
192
223
|
rescue
|
224
|
+
raise 'ERROR: Database table structure is invalid'
|
193
225
|
return handle_error 'ERROR: Database table structure is invalid'
|
194
226
|
end
|
195
227
|
end
|
196
|
-
|
197
|
-
|
198
|
-
|
228
|
+
end
|
229
|
+
|
230
|
+
# Find rhymes and counts greater than a certain length.
|
231
|
+
def sproc_rhymes_by_count rhyme_count
|
232
|
+
@sproc[:rbc].reset!
|
233
|
+
@sproc[:rbc].bind_param(1, rhyme_count)
|
234
|
+
@sproc[:rbc].execute.to_a
|
199
235
|
end
|
200
236
|
|
201
237
|
# Also adds syllable selection.
|
202
238
|
def sproc_rhymes_by_count_syllables rhyme_count, syllable_min_max
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
FROM lines
|
209
|
-
WHERE syllables BETWEEN ? AND ?
|
210
|
-
GROUP BY rhyme, final_word
|
211
|
-
)
|
212
|
-
GROUP BY rhyme
|
213
|
-
HAVING rc >= ?
|
214
|
-
]
|
215
|
-
begin
|
216
|
-
@sproc_rhymes_by_count_syllables = db.prepare sql
|
217
|
-
rescue
|
218
|
-
return handle_error 'ERROR: Database table structure is invalid'
|
219
|
-
end
|
220
|
-
end
|
221
|
-
@sproc_rhymes_by_count_syllables.reset!
|
222
|
-
@sproc_rhymes_by_count_syllables.bind_param(1, syllable_min_max[:min])
|
223
|
-
@sproc_rhymes_by_count_syllables.bind_param(2, syllable_min_max[:max])
|
224
|
-
@sproc_rhymes_by_count_syllables.bind_param(3, rhyme_count)
|
225
|
-
@sproc_rhymes_by_count_syllables.execute.to_a
|
239
|
+
@sproc[:rbcs].reset!
|
240
|
+
@sproc[:rbcs].bind_param(1, syllable_min_max[:min])
|
241
|
+
@sproc[:rbcs].bind_param(2, syllable_min_max[:max])
|
242
|
+
@sproc[:rbcs].bind_param(3, rhyme_count)
|
243
|
+
@sproc[:rbcs].execute.to_a
|
226
244
|
end
|
227
245
|
|
228
|
-
##########################################################################
|
229
|
-
|
230
246
|
# Find all lines for a certain rhyme.
|
231
247
|
def sproc_lines_all rhyme
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
FROM lines WHERE rhyme = ?
|
236
|
-
]
|
237
|
-
begin
|
238
|
-
@sproc_lines_all = db.prepare sql
|
239
|
-
rescue
|
240
|
-
return handle_error 'ERROR: Database table structure is invalid'
|
241
|
-
end
|
242
|
-
end
|
243
|
-
@sproc_lines_all.reset!
|
244
|
-
@sproc_lines_all.bind_param(1, rhyme)
|
245
|
-
@sproc_lines_all.execute.to_a
|
248
|
+
@sproc[:la].reset!
|
249
|
+
@sproc[:la].bind_param(1, rhyme)
|
250
|
+
@sproc[:la].execute.to_a
|
246
251
|
end
|
247
252
|
|
253
|
+
# Also adds syllable selection.
|
254
|
+
def sproc_lines_all_syllables rhyme, syllable_min_max
|
255
|
+
@sproc[:las].reset!
|
256
|
+
@sproc[:las].bind_param(1, rhyme)
|
257
|
+
@sproc[:las].bind_param(2, syllable_min_max[:min])
|
258
|
+
@sproc[:las].bind_param(3, syllable_min_max[:max])
|
259
|
+
@sproc[:las].execute.to_a
|
260
|
+
end
|
261
|
+
|
262
|
+
##########################################################################
|
263
|
+
|
248
264
|
end
|
249
265
|
|
250
266
|
end
|
data/lib/poefy/generation.rb
CHANGED
@@ -29,11 +29,12 @@ module Poefy
|
|
29
29
|
if !(poetic_form[:form] or poetic_form[:rhyme])
|
30
30
|
return handle_error \
|
31
31
|
"ERROR: No valid rhyme or form option specified.\n" +
|
32
|
-
" Try again using the -f or -r option.\n"
|
32
|
+
" Try again using the -f or -r option.\n" +
|
33
|
+
" Use -h or --help to view valid forms."
|
33
34
|
end
|
34
35
|
|
35
36
|
# Loop until we find a valid poem.
|
36
|
-
output =
|
37
|
+
output = gen_poem_using_conditions poetic_form
|
37
38
|
|
38
39
|
# Return nil if poem could not be created.
|
39
40
|
return nil if (output.nil? or output == [] or output == [''])
|
@@ -41,7 +42,7 @@ module Poefy
|
|
41
42
|
# Indent the output using the :indent string.
|
42
43
|
output = do_indent(output, get_poetic_form_indent(poetic_form))
|
43
44
|
|
44
|
-
# Append
|
45
|
+
# Append blank lines to the end if the :rhyme demands it.
|
45
46
|
rhyme = tokenise_rhyme get_poetic_form_rhyme poetic_form
|
46
47
|
(output.length...rhyme.length).each do |i|
|
47
48
|
output[i] = ''
|
@@ -74,39 +75,6 @@ module Poefy
|
|
74
75
|
|
75
76
|
private
|
76
77
|
|
77
|
-
# Generate a poem using the database and a poem format.
|
78
|
-
# If ':proper' then loop to find a valid poem.
|
79
|
-
def poem_proper_sentence poetic_form = @poetic_form
|
80
|
-
poetic_form = validate_poetic_form poetic_form
|
81
|
-
output, count_down = [], 500
|
82
|
-
|
83
|
-
# Final line must close with sentence-end punctuation.
|
84
|
-
# Don't start poems with these words.
|
85
|
-
banned_first_words = poetic_form[:proper] ? %w{and but or nor yet} : []
|
86
|
-
|
87
|
-
loop do
|
88
|
-
output = gen_poem_using_conditions poetic_form
|
89
|
-
break if output.nil? or output.empty?
|
90
|
-
if !banned_first_words.include?(first_word(output[0]).downcase) and
|
91
|
-
( !poetic_form[:proper] or has_stop_punctuation?(output[-1]) )
|
92
|
-
break
|
93
|
-
end
|
94
|
-
|
95
|
-
# Fail after some number of failures.
|
96
|
-
if (count_down -= 1) == 0 and poetic_form[:proper]
|
97
|
-
return handle_error \
|
98
|
-
"ERROR: Proper sentence structure not able to be honoured.\n" +
|
99
|
-
" Ensure the input has closing punctuation or\n" +
|
100
|
-
" try again using the -p option."
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
# (previous way of dealing with closing full stops)
|
105
|
-
# (leaving the code commented out for now)
|
106
|
-
# output[-1] = end_the_sentence(output[-1])
|
107
|
-
output
|
108
|
-
end
|
109
|
-
|
110
78
|
# Use the constraints in 'poetic_form' to generate a poem.
|
111
79
|
def gen_poem_using_conditions poetic_form = @poetic_form
|
112
80
|
poetic_form = poetic_form_full poetic_form
|
@@ -118,9 +86,60 @@ module Poefy
|
|
118
86
|
return handle_error 'ERROR: Rhyme string is not valid', []
|
119
87
|
end
|
120
88
|
|
121
|
-
# Add
|
89
|
+
# Add acrostic to the regex, if necessary.
|
90
|
+
if poetic_form[:acrostic]
|
91
|
+
poetic_form[:regex] =
|
92
|
+
merge_hashes poetic_form[:regex], acrostic(poetic_form[:acrostic])
|
93
|
+
end
|
94
|
+
|
95
|
+
# Add line number as ':line' in each element's hash.
|
122
96
|
by_line = conditions_by_line(tokenised_rhyme, poetic_form)
|
123
97
|
|
98
|
+
# If the poetic_form[:proper] option is true, we're going to
|
99
|
+
# need to add additional regex conditions to the first and
|
100
|
+
# last lines.
|
101
|
+
# This is pretty easy for non-repeating lines, but for refrains
|
102
|
+
# we need to apply the regex for all occurrences.
|
103
|
+
if poetic_form[:proper]
|
104
|
+
|
105
|
+
# Turn the regex into an array, if it isn't already.
|
106
|
+
# Then add the banned starting words.
|
107
|
+
line_conds = [*by_line[0][:regex]]
|
108
|
+
line_conds += [/^((?!and).)/i]
|
109
|
+
line_conds += [/^((?!but).)/i]
|
110
|
+
line_conds += [/^((?!or).)/i]
|
111
|
+
line_conds += [/^((?!nor).)/i]
|
112
|
+
line_conds += [/^((?!yet).)/i]
|
113
|
+
by_line[0][:regex] = line_conds
|
114
|
+
|
115
|
+
# Same for the last line.
|
116
|
+
line_conds = [*by_line[tokenised_rhyme.count-1][:regex]]
|
117
|
+
line_conds += [/[\.?!]$/]
|
118
|
+
by_line[tokenised_rhyme.count-1][:regex] = line_conds
|
119
|
+
|
120
|
+
# Get all refrains by uppercase rhyme token.
|
121
|
+
refrains = by_line.reject do |i|
|
122
|
+
i[:rhyme] == i[:rhyme].downcase
|
123
|
+
end.group_by do |i|
|
124
|
+
i[:rhyme]
|
125
|
+
end
|
126
|
+
|
127
|
+
# Now make each refrain :regex be an array of all.
|
128
|
+
refrain_regex = Hash.new { |h,k| h[k] = [] }
|
129
|
+
refrains.each do |key, value|
|
130
|
+
refrain_regex[key] = value.map do |i|
|
131
|
+
i[:regex]
|
132
|
+
end.flatten.compact
|
133
|
+
end
|
134
|
+
|
135
|
+
# Go through [by_line] and update each :regex.
|
136
|
+
by_line.each do |i|
|
137
|
+
if not refrain_regex[i[:rhyme]].empty?
|
138
|
+
i[:regex] = refrain_regex[i[:rhyme]]
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
124
143
|
# Now we have ':line', so we can break the array order.
|
125
144
|
# Let's get rid of empty lines, and group by the rhyme letter.
|
126
145
|
conditions_by_rhyme = by_line.reject do |i|
|
@@ -179,19 +198,8 @@ module Poefy
|
|
179
198
|
|
180
199
|
# If all the lines include a 'syllable' condition,
|
181
200
|
# then we can specify to only query for matching lines.
|
182
|
-
|
183
|
-
|
184
|
-
[*a[:syllable]].min <=> [*b[:syllable]].min
|
185
|
-
end[:syllable]
|
186
|
-
max = line_conds.max do |a, b|
|
187
|
-
[*a[:syllable]].max <=> [*b[:syllable]].max
|
188
|
-
end[:syllable]
|
189
|
-
min_max = { min: [*min].min, max: [*max].max }
|
190
|
-
min_max = nil if min_max[:max] == 0
|
191
|
-
rhymes = @db.sproc_rhymes_all!(line_conds.count, min_max)
|
192
|
-
else
|
193
|
-
rhymes = @db.sproc_rhymes_all!(line_conds.count)
|
194
|
-
end
|
201
|
+
min_max = syllable_min_max line_conds
|
202
|
+
rhymes = @db.sproc_rhymes_all!(line_conds.count, min_max)
|
195
203
|
|
196
204
|
# Get just the rhyme part of the hash.
|
197
205
|
rhymes = rhymes.map{ |i| i['rhyme'] }
|
@@ -200,11 +208,15 @@ module Poefy
|
|
200
208
|
# For each rhyme, get all lines and try to sastify all conditions.
|
201
209
|
out = []
|
202
210
|
rhymes.shuffle.each do |rhyme|
|
203
|
-
out = try_rhyme(conditions, rhyme)
|
211
|
+
out = try_rhyme(conditions, rhyme, min_max)
|
204
212
|
break if !out.empty?
|
205
213
|
end
|
206
214
|
if out.empty?
|
207
|
-
|
215
|
+
msg = 'ERROR: Not enough rhyming lines in the input.'
|
216
|
+
if poetic_form[:proper]
|
217
|
+
msg += "\n Perhaps try again using the -p option."
|
218
|
+
end
|
219
|
+
return handle_error msg
|
208
220
|
end
|
209
221
|
rhymes_already_used << out.first['rhyme']
|
210
222
|
|
@@ -247,13 +259,12 @@ module Poefy
|
|
247
259
|
|
248
260
|
# Loop through the rhymes until we find one that works.
|
249
261
|
# (In a reasonable time-frame)
|
250
|
-
def try_rhyme conditions, rhyme
|
262
|
+
def try_rhyme conditions, rhyme, syllable_min_max = nil
|
251
263
|
output = []
|
252
|
-
lines = @db.sproc_lines_all!(rhyme)
|
264
|
+
lines = @db.sproc_lines_all!(rhyme, syllable_min_max)
|
253
265
|
begin
|
254
266
|
Timeout::timeout(2) do
|
255
267
|
output = conditional_selection(lines.shuffle, conditions)
|
256
|
-
break
|
257
268
|
end
|
258
269
|
rescue
|
259
270
|
output = []
|
@@ -261,6 +272,22 @@ module Poefy
|
|
261
272
|
output
|
262
273
|
end
|
263
274
|
|
275
|
+
# Find min and max syllable count from the conditions.
|
276
|
+
def syllable_min_max line_conds
|
277
|
+
min_max = nil
|
278
|
+
if line_conds.all?{ |i| i[:syllable] }
|
279
|
+
min = line_conds.min do |a, b|
|
280
|
+
[*a[:syllable]].min <=> [*b[:syllable]].min
|
281
|
+
end[:syllable]
|
282
|
+
max = line_conds.max do |a, b|
|
283
|
+
[*a[:syllable]].max <=> [*b[:syllable]].max
|
284
|
+
end[:syllable]
|
285
|
+
min_max = { min: [*min].min, max: [*max].max }
|
286
|
+
min_max = nil if min_max[:max] == 0
|
287
|
+
end
|
288
|
+
min_max
|
289
|
+
end
|
290
|
+
|
264
291
|
end
|
265
292
|
|
266
293
|
end
|
data/lib/poefy/poefy_gen_base.rb
CHANGED
@@ -5,10 +5,6 @@
|
|
5
5
|
# Base internals for the PoefyGen class.
|
6
6
|
################################################################################
|
7
7
|
|
8
|
-
require 'json'
|
9
|
-
|
10
|
-
################################################################################
|
11
|
-
|
12
8
|
module Poefy
|
13
9
|
|
14
10
|
module PoefyGenBase
|
@@ -22,14 +18,15 @@ module Poefy
|
|
22
18
|
|
23
19
|
# Make a database using the given lines.
|
24
20
|
def make_database input, overwrite = @overwrite
|
21
|
+
@db.close if @db
|
25
22
|
if overwrite
|
26
|
-
|
23
|
+
@db.make_new! validate_lines input
|
27
24
|
else
|
28
25
|
@db.make_new validate_lines input
|
29
26
|
end
|
30
27
|
end
|
31
28
|
def make_database! input
|
32
|
-
|
29
|
+
make_database input, true
|
33
30
|
end
|
34
31
|
|
35
32
|
# Close the database.
|
@@ -86,6 +83,7 @@ module Poefy
|
|
86
83
|
output[:indent] = input[:indent] if input[:indent]
|
87
84
|
output[:syllable] = input[:syllable] if input[:syllable]
|
88
85
|
output[:regex] = input[:regex] if input[:regex]
|
86
|
+
output[:acrostic] = input[:acrostic] if input[:acrostic]
|
89
87
|
|
90
88
|
# Tokenise string to arrays and hashes.
|
91
89
|
rhyme = get_poetic_form_rhyme(output)
|
data/lib/poefy/poetic_forms.rb
CHANGED
@@ -106,6 +106,12 @@ module Poefy
|
|
106
106
|
rhyme: 'abab',
|
107
107
|
indent: '0101',
|
108
108
|
syllable: '[8,6,8,6]'
|
109
|
+
},
|
110
|
+
double_dactyl: {
|
111
|
+
rhyme: 'abcd efgd',
|
112
|
+
indent: '',
|
113
|
+
syllable: '[6,6,6,4,0,6,6,6,4]',
|
114
|
+
regex: '{7=>/^\S+$/}'
|
109
115
|
}
|
110
116
|
}
|
111
117
|
|
@@ -231,7 +237,7 @@ module Poefy
|
|
231
237
|
result
|
232
238
|
end
|
233
239
|
|
234
|
-
# Sort by keys, to make
|
240
|
+
# Sort by keys, to make it more human-readable.
|
235
241
|
def sort_hash input
|
236
242
|
output = {}
|
237
243
|
input.keys.sort.each do |k|
|
@@ -74,6 +74,21 @@ module Poefy
|
|
74
74
|
output_array
|
75
75
|
end
|
76
76
|
|
77
|
+
# Combine two hashes together, transforming all values to array.
|
78
|
+
# These arrays are then flattened.
|
79
|
+
def merge_hashes one, two
|
80
|
+
one ||= {}
|
81
|
+
two ||= {}
|
82
|
+
new_hash = Hash.new { |h,k| h[k] = [] }
|
83
|
+
keys = (one.keys + two.keys).sort.uniq
|
84
|
+
keys.each do |key|
|
85
|
+
new_hash[key] << one[key] if one[key]
|
86
|
+
new_hash[key] << two[key] if two[key]
|
87
|
+
new_hash[key].flatten!
|
88
|
+
end
|
89
|
+
new_hash
|
90
|
+
end
|
91
|
+
|
77
92
|
end
|
78
93
|
|
79
94
|
end
|
data/lib/poefy/version.rb
CHANGED
data/spec/poefy_spec.rb
CHANGED
@@ -7,6 +7,8 @@ describe Poefy::PoefyGen do
|
|
7
7
|
|
8
8
|
before(:all) do
|
9
9
|
@root = File.expand_path('../../', __FILE__)
|
10
|
+
db_file = "#{@root}/data/spec_test_tiny.db"
|
11
|
+
File.delete(db_file) if File.exists?(db_file)
|
10
12
|
end
|
11
13
|
after(:all) do
|
12
14
|
db_file = "#{@root}/data/spec_test_tiny.db"
|
@@ -31,7 +33,6 @@ describe Poefy::PoefyGen do
|
|
31
33
|
describe "#make_database( '#{@root}/data/#{file_txt}', true )" do
|
32
34
|
it "should make the database '#{@root}/data/#{file_db}" do
|
33
35
|
db_file = "#{@root}/data/#{file_db}"
|
34
|
-
File.delete(db_file) if File.exists?(db_file)
|
35
36
|
@poefy.make_database "#{@root}/data/#{file_txt}", true
|
36
37
|
expect(@poefy.db.exists?).to be true
|
37
38
|
expect(File.exists?(db_file)).to be true
|
@@ -41,10 +42,14 @@ describe Poefy::PoefyGen do
|
|
41
42
|
describe ":rhyme option" do
|
42
43
|
|
43
44
|
describe "should return nil" do
|
44
|
-
it "
|
45
|
+
it "blank, no argument" do
|
45
46
|
poem = @poefy.poem
|
46
47
|
expect(poem).to be_nil
|
47
48
|
end
|
49
|
+
it "({ })" do
|
50
|
+
poem = @poefy.poem ({ })
|
51
|
+
expect(poem).to be_nil
|
52
|
+
end
|
48
53
|
it "({ rhyme: nil })" do
|
49
54
|
poem = @poefy.poem ({ rhyme: nil })
|
50
55
|
expect(poem).to be_nil
|
@@ -193,12 +198,15 @@ describe Poefy::PoefyGen do
|
|
193
198
|
|
194
199
|
# All the Shakespeare lines are pentameter, so some forms should fail.
|
195
200
|
forms = Poefy::PoeticForms::POETIC_FORMS
|
196
|
-
forms_fail = [:limerick, :haiku, :common, :ballad]
|
201
|
+
forms_fail = [:limerick, :haiku, :common, :ballad, :double_dactyl]
|
197
202
|
forms_pass = forms.keys - forms_fail
|
198
203
|
|
199
204
|
before(:each) do
|
200
205
|
@poefy = Poefy::PoefyGen.new(file_db, { proper: false })
|
201
206
|
end
|
207
|
+
after(:each) do
|
208
|
+
@poefy.close
|
209
|
+
end
|
202
210
|
|
203
211
|
it "initialised object not nil" do
|
204
212
|
expect(@poefy).to_not be_nil
|
@@ -207,7 +215,6 @@ describe Poefy::PoefyGen do
|
|
207
215
|
describe "#make_database( '#{@root}/data/#{file_txt}', true )" do
|
208
216
|
it "should make the database '#{@root}/data/#{file_db}" do
|
209
217
|
db_file = "#{@root}/data/#{file_db}"
|
210
|
-
# File.delete(db_file) if File.exists?(db_file)
|
211
218
|
input = `sed '/[a-z]/!d' #{@root}/data/#{file_txt}`
|
212
219
|
@poefy.make_database input
|
213
220
|
expect(@poefy.db.exists?).to be true
|
@@ -215,6 +222,23 @@ describe Poefy::PoefyGen do
|
|
215
222
|
end
|
216
223
|
end
|
217
224
|
|
225
|
+
describe "using acrostic option" do
|
226
|
+
describe "should return correct number of lines" do
|
227
|
+
it "({ form: :sonnet, acrostic: 'pauldpthompson' })" do
|
228
|
+
poem = @poefy.poem ({ form: :sonnet,
|
229
|
+
acrostic: 'pauldpthompson' })
|
230
|
+
expect(poem.count).to be 14
|
231
|
+
end
|
232
|
+
end
|
233
|
+
describe "should fail to be created" do
|
234
|
+
it "({ form: :sonnet, acrostic: 'qqqqqqqqqqqqqq' })" do
|
235
|
+
poem = @poefy.poem ({ form: :sonnet,
|
236
|
+
acrostic: 'qqqqqqqqqqqqqq' })
|
237
|
+
expect(poem).to be_nil
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
218
242
|
describe "using form string" do
|
219
243
|
describe "should return correct number of lines" do
|
220
244
|
|
@@ -245,6 +269,29 @@ describe Poefy::PoefyGen do
|
|
245
269
|
end
|
246
270
|
end
|
247
271
|
end
|
272
|
+
|
273
|
+
describe "make sonnets" do
|
274
|
+
sonnet_options = [
|
275
|
+
{ rhyme: 'ababcdcdefefgg' },
|
276
|
+
{ rhyme: 'abab cdcd efef gg', indent: '0101 0101 0011 01' },
|
277
|
+
{ form: 'sonnet' },
|
278
|
+
{ form: :sonnet, syllable: 0 },
|
279
|
+
{ form: :sonnet, syllable: 10 },
|
280
|
+
{ form: :sonnet, regex: /^[A-Z].*$/ },
|
281
|
+
{ form: :sonnet, regex: '^[A-Z].*$' },
|
282
|
+
{ form: :sonnet, acrostic: 'pauldpthompson' },
|
283
|
+
{ form: 'sonnet', indent: '01010101001101' },
|
284
|
+
{ form: 'sonnet', proper: false }
|
285
|
+
]
|
286
|
+
sonnet_options.each do |option|
|
287
|
+
it "#{option}" do
|
288
|
+
4.times do
|
289
|
+
poem = @poefy.poem(option)
|
290
|
+
expect(poem).to_not be_nil
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
248
295
|
end
|
249
296
|
|
250
297
|
##############################################################################
|
@@ -261,6 +308,9 @@ describe Poefy::PoefyGen do
|
|
261
308
|
before(:each) do
|
262
309
|
@poefy = Poefy::PoefyGen.new(file_db, { proper: false })
|
263
310
|
end
|
311
|
+
after(:each) do
|
312
|
+
@poefy.close
|
313
|
+
end
|
264
314
|
|
265
315
|
it "initialised object not nil" do
|
266
316
|
expect(@poefy).to_not be_nil
|
@@ -269,7 +319,6 @@ describe Poefy::PoefyGen do
|
|
269
319
|
describe "#make_database( '#{@root}/data/#{file_txt}', true )" do
|
270
320
|
it "should make the database '#{@root}/data/#{file_db}" do
|
271
321
|
db_file = "#{@root}/data/#{file_db}"
|
272
|
-
# File.delete(db_file) if File.exists?(db_file)
|
273
322
|
input = `sed '/[a-z]/!d' #{@root}/data/#{file_txt}`
|
274
323
|
@poefy.make_database input
|
275
324
|
expect(@poefy.db.exists?).to be true
|
@@ -297,6 +346,29 @@ describe Poefy::PoefyGen do
|
|
297
346
|
end
|
298
347
|
end
|
299
348
|
|
349
|
+
describe "make sonnets" do
|
350
|
+
sonnet_options = [
|
351
|
+
{ rhyme: 'ababcdcdefefgg' },
|
352
|
+
{ rhyme: 'abab cdcd efef gg', indent: '0101 0101 0011 01' },
|
353
|
+
{ form: 'sonnet' },
|
354
|
+
{ form: :sonnet, syllable: 0 },
|
355
|
+
{ form: :sonnet, syllable: 10 },
|
356
|
+
{ form: :sonnet, regex: /^[A-Z].*$/ },
|
357
|
+
{ form: :sonnet, regex: '^[A-Z].*$' },
|
358
|
+
{ form: :sonnet, acrostic: 'pauldpthompson' },
|
359
|
+
{ form: 'sonnet', indent: '01010101001101' },
|
360
|
+
{ form: 'sonnet', proper: false }
|
361
|
+
]
|
362
|
+
sonnet_options.each do |option|
|
363
|
+
it "#{option}" do
|
364
|
+
4.times do
|
365
|
+
poem = @poefy.poem(option)
|
366
|
+
expect(poem).to_not be_nil
|
367
|
+
end
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
300
372
|
describe "using syllable string" do
|
301
373
|
|
302
374
|
it "({ rhyme: 'abcb defe', syllable: '[8,6,8,6,0,8,6,8,6]' })" do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: poefy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Paul Thompson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-05-
|
11
|
+
date: 2017-05-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|