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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 42c9b9aed17a520bca45c074ceb06f6e797dd082
4
- data.tar.gz: 3920e82bb6324eaf83f61091602e3c2398db4d42
3
+ metadata.gz: e04a9ee24cb090de963b812bcf8ec3b8109b2587
4
+ data.tar.gz: '028bc8ed0525d9bcc77a358162f904aef19d6556'
5
5
  SHA512:
6
- metadata.gz: be56e85cacb37a40c48995c7f032f1f5d12c396a69f317db12a17e401bcde8d3bbcc1f37c375aebf3b7e2aa164184d40c12e6d8481ff4ff70a634ab0bbf67e59
7
- data.tar.gz: 6508356de1969dcfafa22b3f141f280c23c872bc9ca89ba4d9f066fb68981e24232d592cc412e593243489256b1265eb3e2347f47cf04b97009ba0676adb5ceb
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: poefy.acrostic('pauldpthompson') })
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 only contain uppercase lines:
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.to_sym
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
- obj = nil
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 'makedbs', then make
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
- input = `sed '/[a-z]/!d' data/shakespeare_sonnets.txt`
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' data/st_therese_of_lisieux.txt`
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' data/whitman_leaves.txt`
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' data/emily_dickinson.txt`
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' data/english_as_she_is_spoke.txt`
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.to_sym
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
- if options[:overwrite]
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
@@ -15,7 +15,6 @@ require 'wordfilter'
15
15
  require 'tempfile'
16
16
  require 'sqlite3'
17
17
  require 'timeout'
18
- require 'json'
19
18
 
20
19
  require_relative 'poefy/version.rb'
21
20
  require_relative 'poefy/self.rb'
@@ -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
- valid = valid && !!(line['line'].match(poetic_form[:regex]))
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
- # This may take a whole lot of time, depending on how lenient the
117
- # conditions are. It is better for the stricter conditions to be
118
- # at the start of the array, due to the way the code is written.
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
- conditions, current_iter + 1)
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
- conditions, current_iter + 1, current_array + [elem])
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?
@@ -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
- @sproc_lines_all.close if @sproc_lines_all
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
- ObjectSpace.define_finalizer(self, @@final.call(@db))
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
- begin
45
- open
46
- rescue
44
+ if !exists?
47
45
  @db = nil
48
- return handle_error 'ERROR: Database does not yet exist'
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
- @sproc_lines_all.close if @sproc_lines_all
70
- @sproc_rhymes_by_count.close if @sproc_rhymes_by_count
71
- @sproc_rhymes_by_count_syllables.close if @sproc_rhymes_by_count_syllables
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) rescue nil
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
- sproc_lines_all rhyme
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
- # Find rhymes and counts greater than a certain length.
178
- def sproc_rhymes_by_count rhyme_count
179
- if not @sproc_rhymes_by_count
180
- sql = %Q[
181
- SELECT rhyme, COUNT(rhyme) AS rc
182
- FROM (
183
- SELECT rhyme, final_word, COUNT(final_word) AS wc
184
- FROM lines
185
- GROUP BY rhyme, final_word
186
- )
187
- GROUP BY rhyme
188
- HAVING rc >= ?
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
- @sproc_rhymes_by_count = db.prepare sql
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
- @sproc_rhymes_by_count.reset!
197
- @sproc_rhymes_by_count.bind_param(1, rhyme_count)
198
- @sproc_rhymes_by_count.execute.to_a
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
- if not @sproc_rhymes_by_count_syllables
204
- sql = %Q[
205
- SELECT rhyme, COUNT(rhyme) AS rc
206
- FROM (
207
- SELECT rhyme, final_word, COUNT(final_word) AS wc
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
- if not @sproc_lines_all
233
- sql = %Q[
234
- SELECT line, syllables, final_word, rhyme
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
@@ -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", nil
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 = poem_proper_sentence poetic_form
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 nil lines to the end if the :rhyme demands it.
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 lines number as ':line' in each element's hash.
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
- if line_conds.all?{ |i| i[:syllable] }
183
- min = line_conds.min do |a, b|
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
- return handle_error 'ERROR: Not enough rhyming lines in the input'
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
@@ -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
- make_database! input
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
- @db.make_new! validate_lines input
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)
@@ -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 JSON more human-readable.
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
@@ -12,13 +12,13 @@ module Poefy
12
12
  end
13
13
 
14
14
  def self.version_date
15
- '2017-05-17'
15
+ '2017-05-22'
16
16
  end
17
17
 
18
18
  module VERSION
19
19
  MAJOR = 0
20
20
  MINOR = 5
21
- TINY = 1
21
+ TINY = 2
22
22
  PRE = nil
23
23
 
24
24
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
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 "({ })" do
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.1
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-17 00:00:00.000000000 Z
11
+ date: 2017-05-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler