poefy 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,330 @@
1
+ #!/usr/bin/env ruby
2
+ # Encoding: UTF-8
3
+
4
+ ################################################################################
5
+ # Description of various poetic forms.
6
+ # Also holds methods for parsing the form strings.
7
+ #
8
+ # All of this is better explained in the README.
9
+ #
10
+ ### Rhyme strings:
11
+ # This is the most important argument.
12
+ # All other form strings are based on this.
13
+ # Each token represents a line.
14
+ # (Token examples: 'a', 'b', 'A1', ' ')
15
+ # Letters indicate rhymes, so all 'a' or 'A' lines have the same rhyme.
16
+ # (Example, limerick: 'aabba')
17
+ # Uppercase letter lines will be duplicated exactly.
18
+ # This is used to create refrain lines.
19
+ # (Example, rondeau: 'aabba aabR aabbaR')
20
+ # Numbers after a capital letter indicate which specific line to repeat.
21
+ # Letters indicate the same rhyme, uppercase or down.
22
+ # (Example, villanelle: 'A1bA2 abA1 abA2 abA1 abA2 abA1A2'
23
+ #
24
+ ### Indent strings:
25
+ # Each character represents a line.
26
+ # The numbers show how many times to repeat ' ' before each line.
27
+ # Any character that doesn't map to an integer defaults to 0.
28
+ # So '0011000101' and '0011 001 1' are the same.
29
+ #
30
+ ### Syllable strings:
31
+ # '10'
32
+ # '9,10,11'
33
+ # '[8,8,5,5,8]'
34
+ # '[[8,9],[8,9],[4,5,6],[4,5,6],[8,9]]'
35
+ # '{1:8,2:8,3:5,4:5,5:8}'
36
+ # '{1:[8,9],2:[8,9],3:[4,5,6],4:[4,5,6],5:[8,9]}'
37
+ # '{0:[8,9],3:[4,5,6],4:[4,5,6]}'
38
+ # '{1:8,5:8}'
39
+ # '{1:8,2:8,3:5,-2:5,-1:8}'
40
+ #
41
+ ### Regex strings:
42
+ # '^[A-Z].*$'
43
+ # '^[^e]*$'
44
+ # '{1=>/^[A-Z].*$/}'
45
+ #
46
+ ################################################################################
47
+
48
+ module Poefy
49
+
50
+ module PoeticForms
51
+
52
+ # If the token is an array, then a random sample will be used.
53
+ POETIC_FORMS = {
54
+ default: {
55
+ rhyme: 'a',
56
+ indent: '0',
57
+ syllable: ''
58
+ },
59
+ rondeau: {
60
+ rhyme: 'aabba aabR aabbaR',
61
+ indent: '',
62
+ syllable: ''
63
+ },
64
+ villanelle: {
65
+ rhyme: 'A1bA2 abA1 abA2 abA1 abA2 abA1A2',
66
+ indent: '010 001 001 001 001 0011',
67
+ syllable: ''
68
+ },
69
+ ballade: {
70
+ rhyme: 'ababbcbC ababbcbC ababbcbC bcbC',
71
+ indent: '',
72
+ syllable: ''
73
+ },
74
+ ballata: {
75
+ rhyme: ['AbbaA','AbbaAbbaA','AbbaAbbaAbbaA'],
76
+ indent: '',
77
+ syllable: ''
78
+ },
79
+ sonnet: {
80
+ rhyme: 'ababcdcdefefgg',
81
+ indent: '',
82
+ syllable: ''
83
+ },
84
+ petrarchan: {
85
+ rhyme: ['abbaabbacdecde','abbaabbacdccdc',
86
+ 'abbaabbacddcdd','abbaabbacddece','abbaabbacdcdcd'],
87
+ indent: ['01100110010010','10001000100100'],
88
+ syllable: ''
89
+ },
90
+ limerick: {
91
+ rhyme: 'aabba',
92
+ indent: '',
93
+ syllable: '{1:[8],2:[8],3:[4,5],4:[4,5],5:[8]}'
94
+ },
95
+ haiku: {
96
+ rhyme: 'abc',
97
+ indent: '',
98
+ syllable: '[5,7,5]'
99
+ },
100
+ common: {
101
+ rhyme: 'abcb',
102
+ indent: '0101',
103
+ syllable: '[8,6,8,6]'
104
+ },
105
+ ballad: {
106
+ rhyme: 'abab',
107
+ indent: '0101',
108
+ syllable: '[8,6,8,6]'
109
+ }
110
+ }
111
+
112
+ # Create a regex specification for acrostics.
113
+ # acrostic('unintelligible')
114
+ # acrostic('unin tell igib le')
115
+ def acrostic word
116
+ output = {}
117
+ counter = 1
118
+ word.split('').each do |i|
119
+ output[counter] = /^[#{i.upcase}#{i.downcase}]/ if i != ' '
120
+ counter += 1
121
+ end
122
+ output
123
+ end
124
+
125
+ private
126
+
127
+ # Can the string be converted to integer?
128
+ def is_int? str
129
+ !(Integer(str) rescue nil).nil?
130
+ end
131
+
132
+ # Make sure the form name is in the list.
133
+ def get_valid_form form_name
134
+ return nil if form_name.nil?
135
+ POETIC_FORMS[form_name.to_sym] ? form_name.to_sym : nil
136
+ end
137
+
138
+ # Get full form, from either the user-specified options,
139
+ # or the default poetic form.
140
+ def poetic_form_full poetic_form = @poetic_form
141
+ rhyme = get_poetic_form_token :rhyme, poetic_form
142
+ indent = get_poetic_form_token :indent, poetic_form
143
+ syllable = get_poetic_form_token :syllable, poetic_form
144
+ regex = get_poetic_form_token :regex, poetic_form
145
+ poetic_form[:rhyme] = rhyme
146
+ poetic_form[:indent] = indent if indent != ''
147
+ poetic_form[:syllable] = syllable if syllable != ''
148
+ poetic_form[:regex] = regex if regex
149
+ poetic_form
150
+ end
151
+
152
+ # If the token is specified in the hash, return it,
153
+ # else get the token for the named form.
154
+ def get_poetic_form_rhyme poetic_form = @poetic_form
155
+ get_poetic_form_token :rhyme, poetic_form
156
+ end
157
+ def get_poetic_form_indent poetic_form = @poetic_form
158
+ get_poetic_form_token :indent, poetic_form
159
+ end
160
+ def get_poetic_form_token token, poetic_form = @poetic_form
161
+ if poetic_form.empty?
162
+ ' '
163
+ elsif poetic_form[token]
164
+ poetic_form[token]
165
+ elsif poetic_form[:form].nil?
166
+ ' '
167
+ elsif POETIC_FORMS[poetic_form[:form].to_sym].nil?
168
+ ' '
169
+ else
170
+ token = POETIC_FORMS[poetic_form[:form].to_sym][token]
171
+ token = token.is_a?(Array) ? token.sample : token
172
+ end
173
+ end
174
+
175
+ # Turn a rhyme format string into a usable array of tokens.
176
+ # Example formats:
177
+ # sonnet_form = 'abab cdcd efef gg'
178
+ # villanelle_form = 'A1bA2 abA1 abA2 abA1 abA2 abA1A2'
179
+ def tokenise_rhyme rhyme_string
180
+ return rhyme_string if rhyme_string.is_a? Array
181
+
182
+ tokens = []
183
+ buffer = ''
184
+ rhyme_string.split('').each do |char|
185
+ if !numeric?(char) and buffer != ''
186
+ tokens << buffer
187
+ buffer = ''
188
+ end
189
+ buffer += char
190
+ end
191
+ tokens << buffer
192
+
193
+ # Handle invalid tokens.
194
+ # ["a1"] ["1"] ["1122"] [" 1"] [" 11"] [":1"]
195
+ boolean_array = tokens.map do |i|
196
+ keep = i.gsub(/[^A-Z,0-9]/,'')
197
+ (keep == '' or !is_int?(keep) or !is_int?(keep))
198
+ end
199
+ valid = boolean_array.reduce{ |sum, i| sum && i }
200
+ if !valid
201
+ return handle_error 'ERROR: Rhyme string is not valid', []
202
+ end
203
+ tokens = [' '] if tokens == ['']
204
+ tokens
205
+ end
206
+
207
+ # Indent an array of lines using a string of numbers.
208
+ def do_indent lines, str
209
+ return lines if str.nil? or lines.nil? or lines.empty?
210
+
211
+ # Convert the indent string into an array.
212
+ indent_arr = (str + '0' * lines.length).split('')
213
+ indent_arr = indent_arr.each_slice(lines.length).to_a[0]
214
+
215
+ # Convert to integers. Spaces should be zero.
216
+ indent_arr.map! { |i| Integer(i) rescue 0 }
217
+
218
+ # Zip, iterate, and prepend indent.
219
+ indent_arr.zip(lines).map do |line|
220
+ ' ' * line[0] + (line[1] ? line[1] : '')
221
+ end
222
+ end
223
+
224
+ # Runs a block of code without warnings.
225
+ # Used for 'eval' calls.
226
+ def silence_warnings &block
227
+ warn_level = $VERBOSE
228
+ $VERBOSE = nil
229
+ result = block.call
230
+ $VERBOSE = warn_level
231
+ result
232
+ end
233
+
234
+ # Sort by keys, to make JSON more human-readable.
235
+ def sort_hash input
236
+ output = {}
237
+ input.keys.sort.each do |k|
238
+ output[k] = input[k]
239
+ end
240
+ output
241
+ end
242
+
243
+ # '10'
244
+ # '9,10,11'
245
+ # '[8,8,5,5,8]'
246
+ # '[[8,9],[8,9],[4,5,6],[4,5,6],[8,9]]'
247
+ # '{1:8,2:8,3:5,4:5,5:8}'
248
+ # '{1:[8,9],2:[8,9],3:[4,5,6],4:[4,5,6],5:[8,9]}'
249
+ # '{0:[8,9],3:[4,5,6],4:[4,5,6]}'
250
+ # '{1:8,5:8}'
251
+ # '{1:8,2:8,3:5,-2:5,-1:8}'
252
+ # Uses #eval, so pretty likely to mess up big time on error.
253
+ # Use the rhyme string as base for the number of lines in total.
254
+ def transform_string_syllable input, rhyme
255
+ return input if input.is_a? Hash
256
+ input = input.to_s
257
+ transform_string_to_hash :syllable, input.gsub(':','=>'), rhyme, 0
258
+ end
259
+
260
+ # Do the same for regular expression strings.
261
+ def transform_string_regex input, rhyme
262
+ transform_string_to_hash :regex, input, rhyme, nil
263
+ end
264
+
265
+ # This should work for both syllable and regex strings.
266
+ def transform_string_to_hash type, string, rhyme, default
267
+ return string if string.is_a? Hash
268
+ return {} if string == ' '
269
+
270
+ output = {}
271
+ line_count = tokenise_rhyme(rhyme).length
272
+
273
+ # Figure out datatype.
274
+ datatype = 'string'
275
+ datatype = 'array' if !string.is_a?(Regexp) and string[0] == '['
276
+ datatype = 'hash' if !string.is_a?(Regexp) and string[0] == '{'
277
+
278
+ # Convert string to array, and eval the others.
279
+ if datatype == 'string'
280
+
281
+ # Regex cannot be an array, but syllable can.
282
+ if type == :syllable
283
+ arr = each_to_int(string.split(','))
284
+ elsif type == :regex
285
+ arr = [Regexp.new(string)]
286
+ end
287
+
288
+ # Set this to be the default '0' hash value.
289
+ arr = arr.first if arr.count == 1
290
+ output = { 0 => arr }
291
+ datatype = 'hash'
292
+ else
293
+ output = silence_warnings { eval string }
294
+ end
295
+
296
+ # Convert array to positioned hash.
297
+ if datatype == 'array'
298
+ output = output.map.with_index do |e, i|
299
+ [i+1, e]
300
+ end.to_h
301
+ end
302
+
303
+ # Go through each line and make sure there is a value for each.
304
+ # Use default if there is no specific value.
305
+ default_value = output[0] ? output[0] : default
306
+ (1..line_count).each do |i|
307
+ output[i] = default_value if output[i].nil?
308
+ end
309
+
310
+ # Handle negative keys.
311
+ output.keys.each do |k|
312
+ if k < 0
313
+ line = line_count + 1 + k
314
+ output[line] = output[k]
315
+ end
316
+ end
317
+
318
+ # Remove keys less than or equal to zero.
319
+ output.reject!{ |k| k <= 0 }
320
+
321
+ # Return sorted hash.
322
+ # ToDo: Doesn't need to be sorted in final code.
323
+ sort_hash output
324
+ end
325
+
326
+ end
327
+
328
+ end
329
+
330
+ ################################################################################
data/lib/poefy/self.rb ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+ # Encoding: UTF-8
3
+
4
+ ################################################################################
5
+ # Class methods for Poefy module.
6
+ ################################################################################
7
+
8
+ module Poefy
9
+
10
+ # Array of all '.db' files in /data/.
11
+ # Do not include databases used for testing.
12
+ def self.all_databases
13
+ path = File.expand_path('../../../data', __FILE__)
14
+ Dir["#{path}/*.db"].map do |i|
15
+ File.basename(i, '.db')
16
+ end.reject{ |i| i.start_with?('spec_') }
17
+ end
18
+
19
+ end
20
+
21
+ ################################################################################
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env ruby
2
+ # Encoding: UTF-8
3
+
4
+ ################################################################################
5
+ # A bunch of string manipulation.
6
+ ################################################################################
7
+
8
+ require 'ruby_rhymes'
9
+ require 'wordfilter'
10
+
11
+ ################################################################################
12
+
13
+ module Poefy
14
+
15
+ module StringManipulation
16
+
17
+ private
18
+
19
+ # True if the whole text string can be expressed as Float.
20
+ def numeric? text
21
+ Float(text) != nil rescue false
22
+ end
23
+
24
+ # The first word in a text string. Relies on space for whitespace.
25
+ # Discards any punctuation.
26
+ def first_word text
27
+ (text.gsub(/[[:punct:]]/,'').scan(/^[^ ]+/).first rescue '') || ''
28
+ end
29
+
30
+ # Uses 'ruby_rhymes' to find the rhyme key for a text.
31
+ def get_rhymes text
32
+ (numeric?(text[-1]) ? [] : text.to_phrase.rhymes.keys) rescue []
33
+ end
34
+
35
+ # The number of syllables in the text.
36
+ def syllables text
37
+ text.to_phrase.syllables rescue 0
38
+ end
39
+
40
+ # Final line must close with sentence-end punctuation.
41
+ def end_the_sentence text
42
+ if find = text.scan(/[[:punct:]]+$/).first
43
+ swap = find.tr(',:;-', '.').delete('—–-')
44
+ text.reverse.sub(find.reverse, swap.reverse).reverse
45
+ else
46
+ text += '.'
47
+ end
48
+ end
49
+
50
+ # Does the sentence end with a .!?
51
+ def has_stop_punctuation? text
52
+ return false if text.nil?
53
+ punct = text.scan(/[[:punct:]]+$/).first
54
+ !!(punct.match(/[\.!?]/) if punct)
55
+ end
56
+
57
+ # Capitalise the first character of a string
58
+ def capitalize_first text
59
+ text[0] = text[0].upcase
60
+ text
61
+ end
62
+
63
+ # Convert each element in an input array to Integer, and raise
64
+ # an error if the conversion is not possible for any element.
65
+ def each_to_int input_array, error_to_raise = TypeError
66
+ output_array = []
67
+ input_array.each do |elem|
68
+ begin
69
+ output_array << Integer(elem)
70
+ rescue ArgumentError => e
71
+ raise error_to_raise
72
+ end
73
+ end
74
+ output_array
75
+ end
76
+
77
+ end
78
+
79
+ end
80
+
81
+ ################################################################################
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # Encoding: UTF-8
3
+
4
+ ################################################################################
5
+ # The current version number and date.
6
+ ################################################################################
7
+
8
+ module Poefy
9
+
10
+ def self.version_number
11
+ Gem::Version.new VERSION::STRING
12
+ end
13
+
14
+ def self.version_date
15
+ '2017-05-17'
16
+ end
17
+
18
+ module VERSION
19
+ MAJOR = 0
20
+ MINOR = 5
21
+ TINY = 1
22
+ PRE = nil
23
+
24
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
25
+ end
26
+
27
+ end
28
+
29
+ ################################################################################
data/lib/poefy.rb ADDED
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env ruby
2
+ # Encoding: UTF-8
3
+
4
+ ################################################################################
5
+ # Line-based, used only to rearrange lines of text, not create new lines.
6
+ # Also uses 'wordfilter' to get rid of yucky words.
7
+ #
8
+ # https://en.wikipedia.org/wiki/Category:Western_medieval_lyric_forms
9
+ # https://en.wikipedia.org/wiki/Virelai
10
+ # https://en.wikipedia.org/wiki/List_of_compositions_by_Guillaume_de_Machaut#Virelais
11
+ ################################################################################
12
+
13
+ require 'ruby_rhymes'
14
+ require 'wordfilter'
15
+ require 'tempfile'
16
+ require 'sqlite3'
17
+ require 'timeout'
18
+ require 'json'
19
+
20
+ require_relative 'poefy/version.rb'
21
+ require_relative 'poefy/self.rb'
22
+ require_relative 'poefy/poefy_gen_base.rb'
23
+ require_relative 'poefy/generation.rb'
24
+ require_relative 'poefy/poetic_forms.rb'
25
+ require_relative 'poefy/string_manipulation.rb'
26
+ require_relative 'poefy/handle_error.rb'
27
+ require_relative 'poefy/database.rb'
28
+ require_relative 'poefy/conditional_satisfaction.rb'
29
+
30
+ ################################################################################
31
+
32
+ # Create a database from text lines.
33
+ # Read the database to generate poetry.
34
+ module Poefy
35
+
36
+ class PoefyGen
37
+
38
+ include Poefy::PoefyGenBase
39
+ include Poefy::Generation
40
+ include Poefy::PoeticForms
41
+ include Poefy::StringManipulation
42
+ include Poefy::ConditionalSatisfaction
43
+ include Poefy::HandleError
44
+
45
+ end
46
+
47
+ end
48
+
49
+ ################################################################################
data/poefy.gemspec ADDED
@@ -0,0 +1,33 @@
1
+ # Encoding: UTF-8
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'poefy/version'
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = 'poefy'
9
+ s.authors = ['Paul Thompson']
10
+ s.email = ['nossidge@gmail.com']
11
+
12
+ s.summary = %q{Create rhyming poetry by rearranging lines of text}
13
+ s.description = %q{Create poems from an input text file, by generating and querying a SQLite database describing each line. Poems are created using a template to select lines from the database, according to closing rhyme, syllable count, and regex matching.}
14
+ s.homepage = 'https://github.com/nossidge/poefy'
15
+
16
+ s.version = Poefy.version_number
17
+ s.date = Poefy.version_date
18
+ s.license = 'GPL-3.0'
19
+
20
+ s.files = `git ls-files`.split("\n")
21
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
22
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
23
+ s.require_paths = ['lib']
24
+ s.bindir = 'bin'
25
+
26
+ s.add_development_dependency('bundler', '~> 1.13')
27
+ s.add_development_dependency('rake', '~> 10.0')
28
+ s.add_development_dependency('rspec', '~> 3.0')
29
+
30
+ s.add_runtime_dependency('sqlite3', '~> 1.3', '>= 1.3.13')
31
+ s.add_runtime_dependency('ruby_rhymes', '~> 0.1', '>= 0.1.2')
32
+ s.add_runtime_dependency('wordfilter', '~> 0.2', '>= 0.2.6')
33
+ end