poefy 0.5.1 → 0.5.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|