poefy 1.1.1 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.rspec +1 -1
- data/LICENSE +13 -13
- data/README.md +844 -844
- data/Rakefile +6 -6
- data/bin/poefy +349 -310
- data/bin/poefy_make +91 -91
- data/data/st_therese_of_lisieux.txt +3700 -3700
- data/data/whitman_leaves.txt +17815 -17815
- data/lib/poefy/core_extensions/array.rb +121 -121
- data/lib/poefy/database.rb +151 -151
- data/lib/poefy/db_type.rb +57 -57
- data/lib/poefy/generation.rb +1 -1
- data/lib/poefy/poem_base.rb +114 -112
- data/lib/poefy/poetic_forms.rb +529 -529
- data/lib/poefy/self.rb +39 -39
- data/lib/poefy/version.rb +26 -26
- data/lib/poefy.rb +46 -46
- data/poefy.gemspec +5 -3
- data/settings.yml +2 -2
- data/spec/poefy_unit_spec.rb +621 -621
- data/spec/spec_helper.rb +11 -11
- metadata +9 -12
data/lib/poefy/poetic_forms.rb
CHANGED
@@ -1,529 +1,529 @@
|
|
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
|
-
require 'yaml'
|
49
|
-
|
50
|
-
################################################################################
|
51
|
-
|
52
|
-
module Poefy
|
53
|
-
|
54
|
-
module PoeticForms
|
55
|
-
|
56
|
-
# If the token is an array, then a random sample will be used.
|
57
|
-
POETIC_FORMS = {
|
58
|
-
default: {
|
59
|
-
rhyme: 'a',
|
60
|
-
indent: '0',
|
61
|
-
syllable: ''
|
62
|
-
},
|
63
|
-
rondeau: {
|
64
|
-
rhyme: 'aabba aabR aabbaR',
|
65
|
-
indent: '',
|
66
|
-
syllable: ''
|
67
|
-
},
|
68
|
-
villanelle: {
|
69
|
-
rhyme: 'A1bA2 abA1 abA2 abA1 abA2 abA1A2',
|
70
|
-
indent: '010 001 001 001 001 0011',
|
71
|
-
syllable: ''
|
72
|
-
},
|
73
|
-
ballade: {
|
74
|
-
rhyme: 'ababbcbC ababbcbC ababbcbC bcbC',
|
75
|
-
indent: '',
|
76
|
-
syllable: ''
|
77
|
-
},
|
78
|
-
ballata: {
|
79
|
-
rhyme: ['AbbaA','AbbaAbbaA','AbbaAbbaAbbaA'],
|
80
|
-
indent: '',
|
81
|
-
syllable: ''
|
82
|
-
},
|
83
|
-
sonnet: {
|
84
|
-
rhyme: 'ababcdcdefefgg',
|
85
|
-
indent: '',
|
86
|
-
syllable: ''
|
87
|
-
},
|
88
|
-
petrarchan: {
|
89
|
-
rhyme: ['abbaabbacdecde','abbaabbacdccdc','abbaabbacdcddc',
|
90
|
-
'abbaabbacddcdd','abbaabbacddece','abbaabbacdcdcd'],
|
91
|
-
indent: ['01100110010010','10001000100100'],
|
92
|
-
syllable: ''
|
93
|
-
},
|
94
|
-
limerick: {
|
95
|
-
rhyme: 'aabba',
|
96
|
-
indent: '',
|
97
|
-
syllable: '{1:[8],2:[8],3:[4,5],4:[4,5],5:[8]}'
|
98
|
-
},
|
99
|
-
haiku: {
|
100
|
-
rhyme: 'abc',
|
101
|
-
indent: '',
|
102
|
-
syllable: '[5,7,5]'
|
103
|
-
},
|
104
|
-
common: {
|
105
|
-
rhyme: 'abcb',
|
106
|
-
indent: '0101',
|
107
|
-
syllable: '{o:8,e:6}'
|
108
|
-
},
|
109
|
-
ballad: {
|
110
|
-
rhyme: 'abab',
|
111
|
-
indent: '0101',
|
112
|
-
syllable: '{o:8,e:6}'
|
113
|
-
},
|
114
|
-
double_dactyl: {
|
115
|
-
rhyme: 'abcd efgd',
|
116
|
-
indent: '',
|
117
|
-
syllable: '{0:6, 4m0:4}',
|
118
|
-
regex: '{7: ^\S+$}'
|
119
|
-
}
|
120
|
-
}
|
121
|
-
|
122
|
-
# Create a regex specification for acrostics.
|
123
|
-
# acrostic('unintelligible')
|
124
|
-
# acrostic('unin tell igib le')
|
125
|
-
def acrostic word
|
126
|
-
output = {}
|
127
|
-
word.split('').each.with_index do |char, i|
|
128
|
-
output[i + 1] = /^[#{char.downcase}]/i if char != ' '
|
129
|
-
end
|
130
|
-
output
|
131
|
-
end
|
132
|
-
|
133
|
-
# Create a regex specification for acrostics.
|
134
|
-
# Uses special logic for 'X'.
|
135
|
-
# Match words starting 'ex' and then change case to 'eX'.
|
136
|
-
def acrostic_x word
|
137
|
-
regex = {}
|
138
|
-
transform = {}
|
139
|
-
word.split('').each.with_index do |char, i|
|
140
|
-
if char.downcase == 'x'
|
141
|
-
regex[i + 1] = /^ex/i
|
142
|
-
transform[i + 1] = proc do |line|
|
143
|
-
line[0..1] = 'eX'
|
144
|
-
' ' + line
|
145
|
-
end
|
146
|
-
elsif char != ' '
|
147
|
-
regex[i + 1] = /^[#{char.downcase}]/i
|
148
|
-
transform[i + 1] = proc do |line|
|
149
|
-
' ' + line
|
150
|
-
end
|
151
|
-
end
|
152
|
-
end
|
153
|
-
{ regex: regex, transform: transform }
|
154
|
-
end
|
155
|
-
|
156
|
-
private
|
157
|
-
|
158
|
-
# Can the string be converted to integer?
|
159
|
-
def is_int? str
|
160
|
-
!(Integer(str) rescue nil).nil?
|
161
|
-
end
|
162
|
-
|
163
|
-
# Make sure the form name is in the list.
|
164
|
-
def get_valid_form form_name
|
165
|
-
return nil if form_name.nil?
|
166
|
-
POETIC_FORMS[form_name.to_sym] ? form_name.to_sym : nil
|
167
|
-
end
|
168
|
-
|
169
|
-
# Get full form, from either the user-specified options,
|
170
|
-
# or the default poetic form.
|
171
|
-
def poetic_form_full poetic_form = @poetic_form
|
172
|
-
rhyme = get_poetic_form_token :rhyme, poetic_form
|
173
|
-
indent = get_poetic_form_token :indent, poetic_form
|
174
|
-
syllable = get_poetic_form_token :syllable, poetic_form
|
175
|
-
regex = get_poetic_form_token :regex, poetic_form
|
176
|
-
transform = get_poetic_form_token :transform, poetic_form
|
177
|
-
poetic_form[:rhyme] = rhyme
|
178
|
-
poetic_form[:indent] = indent if indent != ''
|
179
|
-
poetic_form[:syllable] = syllable if syllable != ''
|
180
|
-
poetic_form[:regex] = regex if regex
|
181
|
-
poetic_form[:transform] = transform if transform != ' '
|
182
|
-
poetic_form
|
183
|
-
end
|
184
|
-
|
185
|
-
# If the token is specified in the hash, return it,
|
186
|
-
# else get the token for the named form.
|
187
|
-
def get_poetic_form_rhyme_longest poetic_form = @poetic_form
|
188
|
-
get_poetic_form_token :rhyme, poetic_form, true
|
189
|
-
end
|
190
|
-
def get_poetic_form_rhyme poetic_form = @poetic_form
|
191
|
-
get_poetic_form_token :rhyme, poetic_form
|
192
|
-
end
|
193
|
-
def get_poetic_form_indent poetic_form = @poetic_form
|
194
|
-
get_poetic_form_token :indent, poetic_form
|
195
|
-
end
|
196
|
-
def get_poetic_form_token token,
|
197
|
-
poetic_form = @poetic_form,
|
198
|
-
longest = false
|
199
|
-
if poetic_form.empty?
|
200
|
-
' '
|
201
|
-
elsif poetic_form[token]
|
202
|
-
poetic_form[token]
|
203
|
-
elsif poetic_form[:form].nil?
|
204
|
-
' '
|
205
|
-
elsif POETIC_FORMS[poetic_form[:form].to_sym].nil?
|
206
|
-
' '
|
207
|
-
else
|
208
|
-
token = POETIC_FORMS[poetic_form[:form].to_sym][token]
|
209
|
-
if token.is_a?(Array)
|
210
|
-
token = longest ? token.max_by(&:length) : token.sample
|
211
|
-
end
|
212
|
-
token
|
213
|
-
end
|
214
|
-
end
|
215
|
-
|
216
|
-
# Turn a rhyme format string into a usable array of tokens.
|
217
|
-
# Example formats:
|
218
|
-
# sonnet_form = 'abab cdcd efef gg'
|
219
|
-
# villanelle_form = 'A1bA2 abA1 abA2 abA1 abA2 abA1A2'
|
220
|
-
def tokenise_rhyme rhyme_string
|
221
|
-
return rhyme_string if rhyme_string.is_a? Array
|
222
|
-
|
223
|
-
tokens = []
|
224
|
-
buffer = ''
|
225
|
-
rhyme_string.split('').each do |char|
|
226
|
-
if !numeric?(char) and buffer != ''
|
227
|
-
tokens << buffer
|
228
|
-
buffer = ''
|
229
|
-
end
|
230
|
-
buffer += char
|
231
|
-
end
|
232
|
-
tokens << buffer
|
233
|
-
|
234
|
-
# Handle invalid tokens.
|
235
|
-
# ["a1"] ["1"] ["1122"] [" 1"] [" 11"] [":1"]
|
236
|
-
boolean_array = tokens.map do |i|
|
237
|
-
keep = i.gsub(/[^A-Z,0-9]/,'')
|
238
|
-
(keep == '' or !is_int?(keep))
|
239
|
-
end
|
240
|
-
valid = boolean_array.reduce{ |sum, i| sum && i }
|
241
|
-
raise Poefy::RhymeError unless valid
|
242
|
-
tokens = [' '] if tokens == ['']
|
243
|
-
|
244
|
-
# Output as a hash.
|
245
|
-
tokens.map do |i|
|
246
|
-
hash = {
|
247
|
-
token: i,
|
248
|
-
rhyme_letter: i[0].downcase
|
249
|
-
}
|
250
|
-
hash[:refrain] = i if i[0] == i[0].upcase
|
251
|
-
hash
|
252
|
-
end
|
253
|
-
end
|
254
|
-
|
255
|
-
# Indent an array of lines using a string of numbers.
|
256
|
-
def do_indent lines, str
|
257
|
-
return lines if str.nil? or lines.nil? or lines.empty?
|
258
|
-
|
259
|
-
# Convert the indent string into an array.
|
260
|
-
indent_arr = (str + '0' * lines.length).split('')
|
261
|
-
indent_arr = indent_arr.each_slice(lines.length).to_a[0]
|
262
|
-
|
263
|
-
# Convert to integers. Spaces should be zero.
|
264
|
-
indent_arr.map! { |i| Integer(i) rescue 0 }
|
265
|
-
|
266
|
-
# Zip, iterate, and prepend indent.
|
267
|
-
indent_arr.zip(lines).map do |line|
|
268
|
-
' ' * line[0] + (line[1] ? line[1] : '')
|
269
|
-
end
|
270
|
-
end
|
271
|
-
|
272
|
-
# Sort by keys, to make it more human-readable.
|
273
|
-
def sort_hash input
|
274
|
-
output = {}
|
275
|
-
input.keys.sort.each do |k|
|
276
|
-
output[k] = input[k]
|
277
|
-
end
|
278
|
-
output
|
279
|
-
end
|
280
|
-
|
281
|
-
# Convert a range in the string form "1-6" to an array.
|
282
|
-
# Assumes elements are integers.
|
283
|
-
def range_to_array input
|
284
|
-
return input if input.is_a?(Numeric) || !input.include?('-')
|
285
|
-
vals = input.split('-').map(&:to_i).sort
|
286
|
-
(vals.first..vals.last).to_a
|
287
|
-
end
|
288
|
-
|
289
|
-
# Convert an array in the string form "4,6,8-10,12" to an array.
|
290
|
-
# Assumes elements are positive integers.
|
291
|
-
def string_to_array input
|
292
|
-
return [input.to_i.abs] if input.is_a?(Numeric)
|
293
|
-
arr = input.is_a?(Array) ? input : input.split(',')
|
294
|
-
|
295
|
-
# Convert to positive integers, and remove duplicates.
|
296
|
-
output = arr.map do |i|
|
297
|
-
range_to_array(i)
|
298
|
-
end.flatten.map do |i|
|
299
|
-
i.to_i.abs
|
300
|
-
end.sort.uniq.select do |i|
|
301
|
-
i != 0
|
302
|
-
end
|
303
|
-
|
304
|
-
# This cannot be an empty array []. It will fail anyway when we
|
305
|
-
# come to do the poem generation, but it's better to fail now.
|
306
|
-
raise Poefy::SyllableError.new if output.empty?
|
307
|
-
output
|
308
|
-
end
|
309
|
-
|
310
|
-
# '10'
|
311
|
-
# '9,10,11'
|
312
|
-
# '[8,8,5,5,8]'
|
313
|
-
# '[[8,9],[8,9],[4,5,6],[4,5,6],[8,9]]'
|
314
|
-
# '{1:8,2:8,3:5,4:5,5:8}'
|
315
|
-
# '{1:[8,9],2:[8,9],3:[4,5,6],4:[4,5,6],5:[8,9]}'
|
316
|
-
# '{0:[8,9],3:[4,5,6],4:[4,5,6]}'
|
317
|
-
# '{1:8,5:8}'
|
318
|
-
# '{1:8,2:8,3:5,-2:5,-1:8}'
|
319
|
-
# Use the rhyme string as base for the number of lines in total.
|
320
|
-
def transform_input_syllable input, rhyme
|
321
|
-
tokens = tokenise_rhyme rhyme
|
322
|
-
hash = transform_input_to_hash :syllable, input
|
323
|
-
hash = validate_hash_values :syllable, hash
|
324
|
-
hash = expand_hash_keys :syllable, hash, tokens, 0
|
325
|
-
end
|
326
|
-
|
327
|
-
# Do the same for regular expression strings.
|
328
|
-
def transform_input_regex input, rhyme
|
329
|
-
tokens = tokenise_rhyme rhyme
|
330
|
-
hash = transform_input_to_hash :regex, input
|
331
|
-
hash = validate_hash_values :regex, hash
|
332
|
-
hash = expand_hash_keys :regex, hash, tokens, //
|
333
|
-
end
|
334
|
-
|
335
|
-
# This should work for both syllable and regex strings.
|
336
|
-
# It should also be fine for Integer and Regexp 'input' values.
|
337
|
-
def transform_input_to_hash type, input
|
338
|
-
return input if input.is_a? Hash
|
339
|
-
|
340
|
-
# Don't go any further if we've got an invalid type.
|
341
|
-
valid_non_string =
|
342
|
-
input.is_a?(Array) ||
|
343
|
-
(type == :syllable and input.is_a?(Numeric)) ||
|
344
|
-
(type == :regex and input.is_a?(Regexp))
|
345
|
-
valid_string_like = !valid_non_string && input.respond_to?(:to_s)
|
346
|
-
raise TypeError unless valid_non_string || valid_string_like
|
347
|
-
|
348
|
-
# Perform different tasks depending on type.
|
349
|
-
input.strip! if input.is_a? String
|
350
|
-
input = input.to_i if input.is_a? Numeric
|
351
|
-
input = input.to_s if valid_string_like
|
352
|
-
return {} if input == ''
|
353
|
-
|
354
|
-
# This will be built up over the course of the method.
|
355
|
-
output = {}
|
356
|
-
|
357
|
-
# Figure out datatype.
|
358
|
-
# Regex string input cannot be an array, but syllable can.
|
359
|
-
datatype = :string
|
360
|
-
if !input.is_a?(Regexp)
|
361
|
-
if input.is_a?(Array)
|
362
|
-
datatype = :array
|
363
|
-
elsif type == :syllable and input[0] == '[' and input[-1] == ']'
|
364
|
-
datatype = :array
|
365
|
-
elsif input[0] == '{' and input[-1] == '}'
|
366
|
-
datatype = :hash
|
367
|
-
end
|
368
|
-
end
|
369
|
-
|
370
|
-
# If it's a basic string format, convert it to hash.
|
371
|
-
if datatype == :string
|
372
|
-
|
373
|
-
# Regex cannot be an array or range, but syllable can.
|
374
|
-
if type == :regex
|
375
|
-
arr = (input == []) ? [] : [Regexp.new(input)]
|
376
|
-
|
377
|
-
# Special case for if a user explicitly states only '0'.
|
378
|
-
elsif type == :syllable
|
379
|
-
arr = input == '0' ? [0] : string_to_array(input)
|
380
|
-
end
|
381
|
-
|
382
|
-
# Set this to be the default '0' hash value.
|
383
|
-
arr = arr.first if arr.count == 1
|
384
|
-
output = { 0 => arr }
|
385
|
-
datatype = :hash
|
386
|
-
|
387
|
-
# If it's wrapped in [] or {}, then evaluate it using YAML.
|
388
|
-
else
|
389
|
-
|
390
|
-
# Don't need to evaluate if it's already an Array.
|
391
|
-
if input.is_a?(Array)
|
392
|
-
output = input
|
393
|
-
else
|
394
|
-
begin
|
395
|
-
# If it's a regex, mandate the ': ' key separator.
|
396
|
-
# (This is so the string substitutions don't mess up the regex.)
|
397
|
-
# If it's a syllable, we can be more flexible with gsubs.
|
398
|
-
as_yaml = input
|
399
|
-
if type == :syllable
|
400
|
-
as_yaml = input.gsub(':', ': ').gsub('=>', ': ')
|
401
|
-
end
|
402
|
-
output = YAML.load(as_yaml)
|
403
|
-
rescue
|
404
|
-
# Raise a SyllableError or RegexError.
|
405
|
-
msg = "#{type.capitalize} hash is not valid YAML"
|
406
|
-
e = Object.const_get("Poefy::#{type.capitalize}Error")
|
407
|
-
raise e.new(msg)
|
408
|
-
end
|
409
|
-
end
|
410
|
-
end
|
411
|
-
|
412
|
-
# Convert array to positioned hash.
|
413
|
-
if datatype == :array
|
414
|
-
output = output.map.with_index do |e, i|
|
415
|
-
[i+1, e]
|
416
|
-
end.to_h
|
417
|
-
end
|
418
|
-
|
419
|
-
output
|
420
|
-
end
|
421
|
-
|
422
|
-
# Run different methods on each value depending on the type.
|
423
|
-
# If it's a syllable, convert all values to int arrays.
|
424
|
-
# If it's a regex, convert all values to regexp.
|
425
|
-
def validate_hash_values type, input
|
426
|
-
format_value = if type == :syllable
|
427
|
-
Proc.new do |x|
|
428
|
-
arr = string_to_array(x)
|
429
|
-
arr.count == 1 ? arr.first : arr
|
430
|
-
end
|
431
|
-
elsif type == :regex
|
432
|
-
Proc.new do |x|
|
433
|
-
x.is_a?(Regexp) ? x : Regexp.new(x.to_s)
|
434
|
-
end
|
435
|
-
end
|
436
|
-
|
437
|
-
# Validate values.
|
438
|
-
if input.is_a?(Hash)
|
439
|
-
input.each do |k, v|
|
440
|
-
begin
|
441
|
-
input[k] = format_value.call(v)
|
442
|
-
rescue
|
443
|
-
# Raise a SyllableError or RegexError.
|
444
|
-
msg = "#{type.capitalize} hash invalid, key='#{k}' value='#{v}'"
|
445
|
-
e = Object.const_get("Poefy::#{type.capitalize}Error")
|
446
|
-
raise e.new(msg)
|
447
|
-
end
|
448
|
-
end
|
449
|
-
elsif input.is_a?(Array)
|
450
|
-
input.map! do |i|
|
451
|
-
i = format_value.call(i)
|
452
|
-
end
|
453
|
-
end
|
454
|
-
input
|
455
|
-
end
|
456
|
-
|
457
|
-
# Convert non-positive-integer keys into the correct position.
|
458
|
-
def expand_hash_keys type, input, tokens, default
|
459
|
-
output = input.dup
|
460
|
-
line_count = tokens.length
|
461
|
-
|
462
|
-
# Handle negative keys.
|
463
|
-
output.keys.each do |k|
|
464
|
-
if k.is_a?(Numeric) and k < 0
|
465
|
-
line = line_count + 1 + k
|
466
|
-
output[line] = output[k]
|
467
|
-
end
|
468
|
-
end
|
469
|
-
|
470
|
-
# Find all lines that are not empty.
|
471
|
-
content_lines = tokens.map.with_index do |v, i|
|
472
|
-
i + 1 if (v[:token].strip != '')
|
473
|
-
end.compact
|
474
|
-
|
475
|
-
# Handle modulo lines.
|
476
|
-
# Handle 'e' even and 'o' odd lines.
|
477
|
-
modulo_lines = {}
|
478
|
-
output.keys.each do |k|
|
479
|
-
is_modulo = k.respond_to?(:include?) && k.include?('m')
|
480
|
-
is_even_odd = %w[e o].include?(k)
|
481
|
-
if is_modulo or is_even_odd
|
482
|
-
if is_modulo
|
483
|
-
vals = k.split('m').map(&:to_i)
|
484
|
-
divider = vals.first.to_i.abs
|
485
|
-
remainder = vals.last.to_i.abs
|
486
|
-
if divider == 0
|
487
|
-
# Raise a SyllableError or RegexError.
|
488
|
-
msg = "#{type.capitalize} hash invalid,"
|
489
|
-
msg += " key='#{k}', modulo='#{divider}m#{remainder}'"
|
490
|
-
e = Object.const_get("Poefy::#{type.capitalize}Error")
|
491
|
-
raise e.new(msg)
|
492
|
-
end
|
493
|
-
elsif is_even_odd
|
494
|
-
divider = 2
|
495
|
-
remainder = (k == 'e') ? 0 : 1
|
496
|
-
end
|
497
|
-
content_lines.modulo_index(divider, remainder, 1).each do |i|
|
498
|
-
modulo_lines[i] = output[k]
|
499
|
-
end
|
500
|
-
end
|
501
|
-
end
|
502
|
-
|
503
|
-
# Take {modulo_lines} as the base and overwrite it with specified keys.
|
504
|
-
if modulo_lines
|
505
|
-
output.keys.each do |k|
|
506
|
-
modulo_lines[k] = output[k]
|
507
|
-
end
|
508
|
-
output = modulo_lines
|
509
|
-
end
|
510
|
-
|
511
|
-
# Go through each line and make sure there is a value for each.
|
512
|
-
# Use default if there is no specific value.
|
513
|
-
default_value = output[0] ? output[0] : default
|
514
|
-
(1..line_count).each do |i|
|
515
|
-
output[i] = default_value if output[i].nil?
|
516
|
-
end
|
517
|
-
|
518
|
-
# Remove keys that are not numeric, or are less than or equal to zero.
|
519
|
-
output.reject!{ |k| !k.is_a?(Numeric) or k <= 0 }
|
520
|
-
|
521
|
-
# Return sorted hash.
|
522
|
-
sort_hash output
|
523
|
-
end
|
524
|
-
|
525
|
-
end
|
526
|
-
|
527
|
-
end
|
528
|
-
|
529
|
-
################################################################################
|
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
|
+
require 'yaml'
|
49
|
+
|
50
|
+
################################################################################
|
51
|
+
|
52
|
+
module Poefy
|
53
|
+
|
54
|
+
module PoeticForms
|
55
|
+
|
56
|
+
# If the token is an array, then a random sample will be used.
|
57
|
+
POETIC_FORMS = {
|
58
|
+
default: {
|
59
|
+
rhyme: 'a',
|
60
|
+
indent: '0',
|
61
|
+
syllable: ''
|
62
|
+
},
|
63
|
+
rondeau: {
|
64
|
+
rhyme: 'aabba aabR aabbaR',
|
65
|
+
indent: '',
|
66
|
+
syllable: ''
|
67
|
+
},
|
68
|
+
villanelle: {
|
69
|
+
rhyme: 'A1bA2 abA1 abA2 abA1 abA2 abA1A2',
|
70
|
+
indent: '010 001 001 001 001 0011',
|
71
|
+
syllable: ''
|
72
|
+
},
|
73
|
+
ballade: {
|
74
|
+
rhyme: 'ababbcbC ababbcbC ababbcbC bcbC',
|
75
|
+
indent: '',
|
76
|
+
syllable: ''
|
77
|
+
},
|
78
|
+
ballata: {
|
79
|
+
rhyme: ['AbbaA','AbbaAbbaA','AbbaAbbaAbbaA'],
|
80
|
+
indent: '',
|
81
|
+
syllable: ''
|
82
|
+
},
|
83
|
+
sonnet: {
|
84
|
+
rhyme: 'ababcdcdefefgg',
|
85
|
+
indent: '',
|
86
|
+
syllable: ''
|
87
|
+
},
|
88
|
+
petrarchan: {
|
89
|
+
rhyme: ['abbaabbacdecde','abbaabbacdccdc','abbaabbacdcddc',
|
90
|
+
'abbaabbacddcdd','abbaabbacddece','abbaabbacdcdcd'],
|
91
|
+
indent: ['01100110010010','10001000100100'],
|
92
|
+
syllable: ''
|
93
|
+
},
|
94
|
+
limerick: {
|
95
|
+
rhyme: 'aabba',
|
96
|
+
indent: '',
|
97
|
+
syllable: '{1:[8],2:[8],3:[4,5],4:[4,5],5:[8]}'
|
98
|
+
},
|
99
|
+
haiku: {
|
100
|
+
rhyme: 'abc',
|
101
|
+
indent: '',
|
102
|
+
syllable: '[5,7,5]'
|
103
|
+
},
|
104
|
+
common: {
|
105
|
+
rhyme: 'abcb',
|
106
|
+
indent: '0101',
|
107
|
+
syllable: '{o:8,e:6}'
|
108
|
+
},
|
109
|
+
ballad: {
|
110
|
+
rhyme: 'abab',
|
111
|
+
indent: '0101',
|
112
|
+
syllable: '{o:8,e:6}'
|
113
|
+
},
|
114
|
+
double_dactyl: {
|
115
|
+
rhyme: 'abcd efgd',
|
116
|
+
indent: '',
|
117
|
+
syllable: '{0:6, 4m0:4}',
|
118
|
+
regex: '{7: ^\S+$}'
|
119
|
+
}
|
120
|
+
}
|
121
|
+
|
122
|
+
# Create a regex specification for acrostics.
|
123
|
+
# acrostic('unintelligible')
|
124
|
+
# acrostic('unin tell igib le')
|
125
|
+
def acrostic word
|
126
|
+
output = {}
|
127
|
+
word.split('').each.with_index do |char, i|
|
128
|
+
output[i + 1] = /^[#{char.downcase}]/i if char != ' '
|
129
|
+
end
|
130
|
+
output
|
131
|
+
end
|
132
|
+
|
133
|
+
# Create a regex specification for acrostics.
|
134
|
+
# Uses special logic for 'X'.
|
135
|
+
# Match words starting 'ex' and then change case to 'eX'.
|
136
|
+
def acrostic_x word
|
137
|
+
regex = {}
|
138
|
+
transform = {}
|
139
|
+
word.split('').each.with_index do |char, i|
|
140
|
+
if char.downcase == 'x'
|
141
|
+
regex[i + 1] = /^ex/i
|
142
|
+
transform[i + 1] = proc do |line|
|
143
|
+
line[0..1] = 'eX'
|
144
|
+
' ' + line
|
145
|
+
end
|
146
|
+
elsif char != ' '
|
147
|
+
regex[i + 1] = /^[#{char.downcase}]/i
|
148
|
+
transform[i + 1] = proc do |line|
|
149
|
+
' ' + line
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
{ regex: regex, transform: transform }
|
154
|
+
end
|
155
|
+
|
156
|
+
private
|
157
|
+
|
158
|
+
# Can the string be converted to integer?
|
159
|
+
def is_int? str
|
160
|
+
!(Integer(str) rescue nil).nil?
|
161
|
+
end
|
162
|
+
|
163
|
+
# Make sure the form name is in the list.
|
164
|
+
def get_valid_form form_name
|
165
|
+
return nil if form_name.nil?
|
166
|
+
POETIC_FORMS[form_name.to_sym] ? form_name.to_sym : nil
|
167
|
+
end
|
168
|
+
|
169
|
+
# Get full form, from either the user-specified options,
|
170
|
+
# or the default poetic form.
|
171
|
+
def poetic_form_full poetic_form = @poetic_form
|
172
|
+
rhyme = get_poetic_form_token :rhyme, poetic_form
|
173
|
+
indent = get_poetic_form_token :indent, poetic_form
|
174
|
+
syllable = get_poetic_form_token :syllable, poetic_form
|
175
|
+
regex = get_poetic_form_token :regex, poetic_form
|
176
|
+
transform = get_poetic_form_token :transform, poetic_form
|
177
|
+
poetic_form[:rhyme] = rhyme
|
178
|
+
poetic_form[:indent] = indent if indent != ''
|
179
|
+
poetic_form[:syllable] = syllable if syllable != ''
|
180
|
+
poetic_form[:regex] = regex if regex
|
181
|
+
poetic_form[:transform] = transform if transform != ' '
|
182
|
+
poetic_form
|
183
|
+
end
|
184
|
+
|
185
|
+
# If the token is specified in the hash, return it,
|
186
|
+
# else get the token for the named form.
|
187
|
+
def get_poetic_form_rhyme_longest poetic_form = @poetic_form
|
188
|
+
get_poetic_form_token :rhyme, poetic_form, true
|
189
|
+
end
|
190
|
+
def get_poetic_form_rhyme poetic_form = @poetic_form
|
191
|
+
get_poetic_form_token :rhyme, poetic_form
|
192
|
+
end
|
193
|
+
def get_poetic_form_indent poetic_form = @poetic_form
|
194
|
+
get_poetic_form_token :indent, poetic_form
|
195
|
+
end
|
196
|
+
def get_poetic_form_token token,
|
197
|
+
poetic_form = @poetic_form,
|
198
|
+
longest = false
|
199
|
+
if poetic_form.empty?
|
200
|
+
' '
|
201
|
+
elsif poetic_form[token]
|
202
|
+
poetic_form[token]
|
203
|
+
elsif poetic_form[:form].nil?
|
204
|
+
' '
|
205
|
+
elsif POETIC_FORMS[poetic_form[:form].to_sym].nil?
|
206
|
+
' '
|
207
|
+
else
|
208
|
+
token = POETIC_FORMS[poetic_form[:form].to_sym][token]
|
209
|
+
if token.is_a?(Array)
|
210
|
+
token = longest ? token.max_by(&:length) : token.sample
|
211
|
+
end
|
212
|
+
token
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
# Turn a rhyme format string into a usable array of tokens.
|
217
|
+
# Example formats:
|
218
|
+
# sonnet_form = 'abab cdcd efef gg'
|
219
|
+
# villanelle_form = 'A1bA2 abA1 abA2 abA1 abA2 abA1A2'
|
220
|
+
def tokenise_rhyme rhyme_string
|
221
|
+
return rhyme_string if rhyme_string.is_a? Array
|
222
|
+
|
223
|
+
tokens = []
|
224
|
+
buffer = ''
|
225
|
+
rhyme_string.split('').each do |char|
|
226
|
+
if !numeric?(char) and buffer != ''
|
227
|
+
tokens << buffer
|
228
|
+
buffer = ''
|
229
|
+
end
|
230
|
+
buffer += char
|
231
|
+
end
|
232
|
+
tokens << buffer
|
233
|
+
|
234
|
+
# Handle invalid tokens.
|
235
|
+
# ["a1"] ["1"] ["1122"] [" 1"] [" 11"] [":1"]
|
236
|
+
boolean_array = tokens.map do |i|
|
237
|
+
keep = i.gsub(/[^A-Z,0-9]/,'')
|
238
|
+
(keep == '' or !is_int?(keep))
|
239
|
+
end
|
240
|
+
valid = boolean_array.reduce{ |sum, i| sum && i }
|
241
|
+
raise Poefy::RhymeError unless valid
|
242
|
+
tokens = [' '] if tokens == ['']
|
243
|
+
|
244
|
+
# Output as a hash.
|
245
|
+
tokens.map do |i|
|
246
|
+
hash = {
|
247
|
+
token: i,
|
248
|
+
rhyme_letter: i[0].downcase
|
249
|
+
}
|
250
|
+
hash[:refrain] = i if i[0] == i[0].upcase
|
251
|
+
hash
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
# Indent an array of lines using a string of numbers.
|
256
|
+
def do_indent lines, str
|
257
|
+
return lines if str.nil? or lines.nil? or lines.empty?
|
258
|
+
|
259
|
+
# Convert the indent string into an array.
|
260
|
+
indent_arr = (str + '0' * lines.length).split('')
|
261
|
+
indent_arr = indent_arr.each_slice(lines.length).to_a[0]
|
262
|
+
|
263
|
+
# Convert to integers. Spaces should be zero.
|
264
|
+
indent_arr.map! { |i| Integer(i) rescue 0 }
|
265
|
+
|
266
|
+
# Zip, iterate, and prepend indent.
|
267
|
+
indent_arr.zip(lines).map do |line|
|
268
|
+
' ' * line[0] + (line[1] ? line[1] : '')
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
# Sort by keys, to make it more human-readable.
|
273
|
+
def sort_hash input
|
274
|
+
output = {}
|
275
|
+
input.keys.sort.each do |k|
|
276
|
+
output[k] = input[k]
|
277
|
+
end
|
278
|
+
output
|
279
|
+
end
|
280
|
+
|
281
|
+
# Convert a range in the string form "1-6" to an array.
|
282
|
+
# Assumes elements are integers.
|
283
|
+
def range_to_array input
|
284
|
+
return input if input.is_a?(Numeric) || !input.include?('-')
|
285
|
+
vals = input.split('-').map(&:to_i).sort
|
286
|
+
(vals.first..vals.last).to_a
|
287
|
+
end
|
288
|
+
|
289
|
+
# Convert an array in the string form "4,6,8-10,12" to an array.
|
290
|
+
# Assumes elements are positive integers.
|
291
|
+
def string_to_array input
|
292
|
+
return [input.to_i.abs] if input.is_a?(Numeric)
|
293
|
+
arr = input.is_a?(Array) ? input : input.split(',')
|
294
|
+
|
295
|
+
# Convert to positive integers, and remove duplicates.
|
296
|
+
output = arr.map do |i|
|
297
|
+
range_to_array(i)
|
298
|
+
end.flatten.map do |i|
|
299
|
+
i.to_i.abs
|
300
|
+
end.sort.uniq.select do |i|
|
301
|
+
i != 0
|
302
|
+
end
|
303
|
+
|
304
|
+
# This cannot be an empty array []. It will fail anyway when we
|
305
|
+
# come to do the poem generation, but it's better to fail now.
|
306
|
+
raise Poefy::SyllableError.new if output.empty?
|
307
|
+
output
|
308
|
+
end
|
309
|
+
|
310
|
+
# '10'
|
311
|
+
# '9,10,11'
|
312
|
+
# '[8,8,5,5,8]'
|
313
|
+
# '[[8,9],[8,9],[4,5,6],[4,5,6],[8,9]]'
|
314
|
+
# '{1:8,2:8,3:5,4:5,5:8}'
|
315
|
+
# '{1:[8,9],2:[8,9],3:[4,5,6],4:[4,5,6],5:[8,9]}'
|
316
|
+
# '{0:[8,9],3:[4,5,6],4:[4,5,6]}'
|
317
|
+
# '{1:8,5:8}'
|
318
|
+
# '{1:8,2:8,3:5,-2:5,-1:8}'
|
319
|
+
# Use the rhyme string as base for the number of lines in total.
|
320
|
+
def transform_input_syllable input, rhyme
|
321
|
+
tokens = tokenise_rhyme rhyme
|
322
|
+
hash = transform_input_to_hash :syllable, input
|
323
|
+
hash = validate_hash_values :syllable, hash
|
324
|
+
hash = expand_hash_keys :syllable, hash, tokens, 0
|
325
|
+
end
|
326
|
+
|
327
|
+
# Do the same for regular expression strings.
|
328
|
+
def transform_input_regex input, rhyme
|
329
|
+
tokens = tokenise_rhyme rhyme
|
330
|
+
hash = transform_input_to_hash :regex, input
|
331
|
+
hash = validate_hash_values :regex, hash
|
332
|
+
hash = expand_hash_keys :regex, hash, tokens, //
|
333
|
+
end
|
334
|
+
|
335
|
+
# This should work for both syllable and regex strings.
|
336
|
+
# It should also be fine for Integer and Regexp 'input' values.
|
337
|
+
def transform_input_to_hash type, input
|
338
|
+
return input if input.is_a? Hash
|
339
|
+
|
340
|
+
# Don't go any further if we've got an invalid type.
|
341
|
+
valid_non_string =
|
342
|
+
input.is_a?(Array) ||
|
343
|
+
(type == :syllable and input.is_a?(Numeric)) ||
|
344
|
+
(type == :regex and input.is_a?(Regexp))
|
345
|
+
valid_string_like = !valid_non_string && input.respond_to?(:to_s)
|
346
|
+
raise TypeError unless valid_non_string || valid_string_like
|
347
|
+
|
348
|
+
# Perform different tasks depending on type.
|
349
|
+
input.strip! if input.is_a? String
|
350
|
+
input = input.to_i if input.is_a? Numeric
|
351
|
+
input = input.to_s if valid_string_like
|
352
|
+
return {} if input == ''
|
353
|
+
|
354
|
+
# This will be built up over the course of the method.
|
355
|
+
output = {}
|
356
|
+
|
357
|
+
# Figure out datatype.
|
358
|
+
# Regex string input cannot be an array, but syllable can.
|
359
|
+
datatype = :string
|
360
|
+
if !input.is_a?(Regexp)
|
361
|
+
if input.is_a?(Array)
|
362
|
+
datatype = :array
|
363
|
+
elsif type == :syllable and input[0] == '[' and input[-1] == ']'
|
364
|
+
datatype = :array
|
365
|
+
elsif input[0] == '{' and input[-1] == '}'
|
366
|
+
datatype = :hash
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
# If it's a basic string format, convert it to hash.
|
371
|
+
if datatype == :string
|
372
|
+
|
373
|
+
# Regex cannot be an array or range, but syllable can.
|
374
|
+
if type == :regex
|
375
|
+
arr = (input == []) ? [] : [Regexp.new(input)]
|
376
|
+
|
377
|
+
# Special case for if a user explicitly states only '0'.
|
378
|
+
elsif type == :syllable
|
379
|
+
arr = input == '0' ? [0] : string_to_array(input)
|
380
|
+
end
|
381
|
+
|
382
|
+
# Set this to be the default '0' hash value.
|
383
|
+
arr = arr.first if arr.count == 1
|
384
|
+
output = { 0 => arr }
|
385
|
+
datatype = :hash
|
386
|
+
|
387
|
+
# If it's wrapped in [] or {}, then evaluate it using YAML.
|
388
|
+
else
|
389
|
+
|
390
|
+
# Don't need to evaluate if it's already an Array.
|
391
|
+
if input.is_a?(Array)
|
392
|
+
output = input
|
393
|
+
else
|
394
|
+
begin
|
395
|
+
# If it's a regex, mandate the ': ' key separator.
|
396
|
+
# (This is so the string substitutions don't mess up the regex.)
|
397
|
+
# If it's a syllable, we can be more flexible with gsubs.
|
398
|
+
as_yaml = input
|
399
|
+
if type == :syllable
|
400
|
+
as_yaml = input.gsub(':', ': ').gsub('=>', ': ')
|
401
|
+
end
|
402
|
+
output = YAML.load(as_yaml)
|
403
|
+
rescue
|
404
|
+
# Raise a SyllableError or RegexError.
|
405
|
+
msg = "#{type.capitalize} hash is not valid YAML"
|
406
|
+
e = Object.const_get("Poefy::#{type.capitalize}Error")
|
407
|
+
raise e.new(msg)
|
408
|
+
end
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
# Convert array to positioned hash.
|
413
|
+
if datatype == :array
|
414
|
+
output = output.map.with_index do |e, i|
|
415
|
+
[i+1, e]
|
416
|
+
end.to_h
|
417
|
+
end
|
418
|
+
|
419
|
+
output
|
420
|
+
end
|
421
|
+
|
422
|
+
# Run different methods on each value depending on the type.
|
423
|
+
# If it's a syllable, convert all values to int arrays.
|
424
|
+
# If it's a regex, convert all values to regexp.
|
425
|
+
def validate_hash_values type, input
|
426
|
+
format_value = if type == :syllable
|
427
|
+
Proc.new do |x|
|
428
|
+
arr = string_to_array(x)
|
429
|
+
arr.count == 1 ? arr.first : arr
|
430
|
+
end
|
431
|
+
elsif type == :regex
|
432
|
+
Proc.new do |x|
|
433
|
+
x.is_a?(Regexp) ? x : Regexp.new(x.to_s)
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
# Validate values.
|
438
|
+
if input.is_a?(Hash)
|
439
|
+
input.each do |k, v|
|
440
|
+
begin
|
441
|
+
input[k] = format_value.call(v)
|
442
|
+
rescue
|
443
|
+
# Raise a SyllableError or RegexError.
|
444
|
+
msg = "#{type.capitalize} hash invalid, key='#{k}' value='#{v}'"
|
445
|
+
e = Object.const_get("Poefy::#{type.capitalize}Error")
|
446
|
+
raise e.new(msg)
|
447
|
+
end
|
448
|
+
end
|
449
|
+
elsif input.is_a?(Array)
|
450
|
+
input.map! do |i|
|
451
|
+
i = format_value.call(i)
|
452
|
+
end
|
453
|
+
end
|
454
|
+
input
|
455
|
+
end
|
456
|
+
|
457
|
+
# Convert non-positive-integer keys into the correct position.
|
458
|
+
def expand_hash_keys type, input, tokens, default
|
459
|
+
output = input.dup
|
460
|
+
line_count = tokens.length
|
461
|
+
|
462
|
+
# Handle negative keys.
|
463
|
+
output.keys.each do |k|
|
464
|
+
if k.is_a?(Numeric) and k < 0
|
465
|
+
line = line_count + 1 + k
|
466
|
+
output[line] = output[k]
|
467
|
+
end
|
468
|
+
end
|
469
|
+
|
470
|
+
# Find all lines that are not empty.
|
471
|
+
content_lines = tokens.map.with_index do |v, i|
|
472
|
+
i + 1 if (v[:token].strip != '')
|
473
|
+
end.compact
|
474
|
+
|
475
|
+
# Handle modulo lines.
|
476
|
+
# Handle 'e' even and 'o' odd lines.
|
477
|
+
modulo_lines = {}
|
478
|
+
output.keys.each do |k|
|
479
|
+
is_modulo = k.respond_to?(:include?) && k.include?('m')
|
480
|
+
is_even_odd = %w[e o].include?(k)
|
481
|
+
if is_modulo or is_even_odd
|
482
|
+
if is_modulo
|
483
|
+
vals = k.split('m').map(&:to_i)
|
484
|
+
divider = vals.first.to_i.abs
|
485
|
+
remainder = vals.last.to_i.abs
|
486
|
+
if divider == 0
|
487
|
+
# Raise a SyllableError or RegexError.
|
488
|
+
msg = "#{type.capitalize} hash invalid,"
|
489
|
+
msg += " key='#{k}', modulo='#{divider}m#{remainder}'"
|
490
|
+
e = Object.const_get("Poefy::#{type.capitalize}Error")
|
491
|
+
raise e.new(msg)
|
492
|
+
end
|
493
|
+
elsif is_even_odd
|
494
|
+
divider = 2
|
495
|
+
remainder = (k == 'e') ? 0 : 1
|
496
|
+
end
|
497
|
+
content_lines.modulo_index(divider, remainder, 1).each do |i|
|
498
|
+
modulo_lines[i] = output[k]
|
499
|
+
end
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
# Take {modulo_lines} as the base and overwrite it with specified keys.
|
504
|
+
if modulo_lines
|
505
|
+
output.keys.each do |k|
|
506
|
+
modulo_lines[k] = output[k]
|
507
|
+
end
|
508
|
+
output = modulo_lines
|
509
|
+
end
|
510
|
+
|
511
|
+
# Go through each line and make sure there is a value for each.
|
512
|
+
# Use default if there is no specific value.
|
513
|
+
default_value = output[0] ? output[0] : default
|
514
|
+
(1..line_count).each do |i|
|
515
|
+
output[i] = default_value if output[i].nil?
|
516
|
+
end
|
517
|
+
|
518
|
+
# Remove keys that are not numeric, or are less than or equal to zero.
|
519
|
+
output.reject!{ |k| !k.is_a?(Numeric) or k <= 0 }
|
520
|
+
|
521
|
+
# Return sorted hash.
|
522
|
+
sort_hash output
|
523
|
+
end
|
524
|
+
|
525
|
+
end
|
526
|
+
|
527
|
+
end
|
528
|
+
|
529
|
+
################################################################################
|