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/bin/poefy
CHANGED
@@ -1,310 +1,349 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
# Encoding: UTF-8
|
3
|
-
|
4
|
-
################################################################################
|
5
|
-
# Use Poefy::Poem to make a poem from the command line.
|
6
|
-
################################################################################
|
7
|
-
|
8
|
-
require 'optparse'
|
9
|
-
|
10
|
-
require_relative '../lib/poefy.rb'
|
11
|
-
|
12
|
-
Poefy.console = true
|
13
|
-
Poefy.require_db
|
14
|
-
|
15
|
-
################################################################################
|
16
|
-
|
17
|
-
# List the corpora & descriptions in a nice table format.
|
18
|
-
def corpora
|
19
|
-
output = Poefy.corpora_with_desc
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
(
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
(Example,
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
poefy
|
68
|
-
poefy
|
69
|
-
poefy
|
70
|
-
poefy -
|
71
|
-
poefy -
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
' ' *
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
opts.
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
opts.on('-
|
144
|
-
"
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
opts.
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
options
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
#
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
if
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
poefy.
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
#
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Encoding: UTF-8
|
3
|
+
|
4
|
+
################################################################################
|
5
|
+
# Use Poefy::Poem to make a poem from the command line.
|
6
|
+
################################################################################
|
7
|
+
|
8
|
+
require 'optparse'
|
9
|
+
|
10
|
+
require_relative '../lib/poefy.rb'
|
11
|
+
|
12
|
+
Poefy.console = true
|
13
|
+
Poefy.require_db
|
14
|
+
|
15
|
+
################################################################################
|
16
|
+
|
17
|
+
# List the corpora & descriptions in a nice table format.
|
18
|
+
def corpora
|
19
|
+
output = Poefy.corpora_with_desc
|
20
|
+
return ['(no corpora yet)'] if output.empty?
|
21
|
+
|
22
|
+
width = output.keys.max_by(&:length).length
|
23
|
+
output.map do |key, value|
|
24
|
+
sprintf "%-#{width}s %s", key, value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Create a new Poefy::Poem object, and catch any exceptions thrown.
|
29
|
+
def poefy_new corpus, options = nil
|
30
|
+
Poefy::Poem.new corpus, options
|
31
|
+
rescue Poefy::DatabaseError => e
|
32
|
+
STDERR.puts e.console_msg
|
33
|
+
exit 1
|
34
|
+
end
|
35
|
+
|
36
|
+
################################################################################
|
37
|
+
|
38
|
+
def parse_options
|
39
|
+
options = {}
|
40
|
+
|
41
|
+
# Set up variables used later.
|
42
|
+
forms = Poefy.poetic_forms
|
43
|
+
forms_by_4 = forms.each_slice(4).to_a.map { |i| i.join ', ' }
|
44
|
+
rhyme_docs = " This is the most important argument.
|
45
|
+
All other form strings are based on this.
|
46
|
+
Each token represents a line.
|
47
|
+
(Token examples: 'a', 'b', 'A1', ' ')
|
48
|
+
Letters indicate rhymes, so all 'a' or 'A' lines have the same rhyme.
|
49
|
+
(Example, limerick: 'aabba')
|
50
|
+
Uppercase letter lines will be duplicated exactly.
|
51
|
+
This is used to create refrain lines.
|
52
|
+
(Example, rondeau: 'aabba aabR aabbaR')
|
53
|
+
Numbers after a capital letter indicate which specific line to repeat.
|
54
|
+
(Example, villanelle: 'A1bA2 abA1 abA2 abA1 abA2 abA1A2'"
|
55
|
+
|
56
|
+
# Get all of the command-line options.
|
57
|
+
optparse = OptionParser.new do |opts|
|
58
|
+
|
59
|
+
# Set a banner, displayed at the top of the help screen.
|
60
|
+
program_info = %[ Poefy, Line-Based Poem Generator
|
61
|
+
Version #{Poefy.version_number} - #{Poefy.version_date}
|
62
|
+
https://github.com/nossidge/poefy
|
63
|
+
Paul Thompson - nossidge@gmail.com
|
64
|
+
].gsub(' ',' ')
|
65
|
+
|
66
|
+
usage = %[Usage: poefy shakespeare -m < shakespeare_sonnets.txt
|
67
|
+
poefy shakespeare -d "The sonnets of Shakespeare"
|
68
|
+
poefy shakespeare sonnet
|
69
|
+
poefy spoke haiku
|
70
|
+
poefy therese -r 'abab cdcd efef gg' -i '0101 0101 0011 01'
|
71
|
+
poefy whitman -r 'A1bA2 abA1 abA2 abA1 abA2 abA1A2'
|
72
|
+
poefy -Lc
|
73
|
+
poefy -f
|
74
|
+
].gsub(' ',' ')
|
75
|
+
|
76
|
+
list = 'Corpora: ' + corpora.join("\n ")
|
77
|
+
|
78
|
+
opts.banner = program_info + "\n" + usage + "\n" + list + "\n\n"
|
79
|
+
|
80
|
+
# These will be further validated within the class.
|
81
|
+
opts.on('-f', '--form [STRING]',
|
82
|
+
"A named poetic form for the output\n" +
|
83
|
+
' ' * 37 + "Specifies rhyme, indent and syllable\n" +
|
84
|
+
' ' * 37 + "One of:\n" +
|
85
|
+
' ' * 39 + forms_by_4.join("\n" + ' ' * 39)) do |s|
|
86
|
+
if s.nil?
|
87
|
+
puts forms
|
88
|
+
exit 0
|
89
|
+
end
|
90
|
+
options[:form] = s
|
91
|
+
end
|
92
|
+
opts.on('-r', '--rhyme STRING', "(See 'Description of rhyme string' below)") do |s|
|
93
|
+
options[:rhyme] = s
|
94
|
+
end
|
95
|
+
opts.on('-i', '--indent STRING', "Indentation of each line") do |s|
|
96
|
+
options[:indent] = s
|
97
|
+
end
|
98
|
+
opts.on('-s', '--syllable STRING',
|
99
|
+
"Apply syllable constraints to certain lines") do |s|
|
100
|
+
options[:syllable] = s
|
101
|
+
end
|
102
|
+
opts.on('-x', '--regex STRING',
|
103
|
+
"Apply regex constraints to certain lines") do |s|
|
104
|
+
options[:regex] = s
|
105
|
+
end
|
106
|
+
|
107
|
+
# Options for acrostic poems.
|
108
|
+
opts.on('-a', '--acrostic STRING',
|
109
|
+
"Generate an acrostic on a certain word") do |s|
|
110
|
+
options[:acrostic] = s
|
111
|
+
end
|
112
|
+
opts.on('-A', '--acrostic_x STRING',
|
113
|
+
"Generate an acrostic with better handling of 'x'") do |s|
|
114
|
+
options[:acrostic_x] = s
|
115
|
+
end
|
116
|
+
|
117
|
+
# Handle proper sentence structure.
|
118
|
+
opts.separator nil
|
119
|
+
opts.on('-p', '--proper',
|
120
|
+
"Ensure first word is not 'and but or nor yet'\n" +
|
121
|
+
' ' * 39 + "and final line ends with closing punctuation'\n" +
|
122
|
+
' ' * 39 + "Defaults to ON -- Use this option to DISABLE") do
|
123
|
+
options[:proper] = false
|
124
|
+
end
|
125
|
+
opts.on('-c', '--capital',
|
126
|
+
"Capitalise the first letter of each line") do
|
127
|
+
options[:transform] = proc do |line|
|
128
|
+
regex = /[a-zA-Z]/
|
129
|
+
line[regex] = line[regex].upcase if line[regex]
|
130
|
+
line
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Poem output options.
|
135
|
+
opts.separator nil
|
136
|
+
opts.on('-n', '--number INTEGER',
|
137
|
+
"Number of poems to generate") do |n|
|
138
|
+
options[:number] = n.to_i
|
139
|
+
end
|
140
|
+
|
141
|
+
# Corpus options.
|
142
|
+
opts.separator nil
|
143
|
+
opts.on('-m', '--make [STRING]',
|
144
|
+
"Make new or overwrite existing corpus with piped input\n" + ' ' * 39 +
|
145
|
+
"Argument is a description of the corpus") do |s|
|
146
|
+
options[:make_corpus] = true
|
147
|
+
options[:corpus_desc] = s
|
148
|
+
end
|
149
|
+
opts.on('-d', '--desc STRING',
|
150
|
+
"Overwrite the description of the corpus") do |s|
|
151
|
+
options[:corpus_desc] = s
|
152
|
+
end
|
153
|
+
opts.on('-l', '--local',
|
154
|
+
"(SQLite only) Default is to use database files from /data/\n" + ' ' * 39 +
|
155
|
+
"With this option, paths are relative to working directory") do
|
156
|
+
options[:local] = true
|
157
|
+
end
|
158
|
+
|
159
|
+
# Database internals.
|
160
|
+
opts.separator nil
|
161
|
+
opts.on('-L', '--list [C|D]',
|
162
|
+
"List all the installed corpora\n" + ' ' * 39 +
|
163
|
+
"Append 'c' or 'd' to list just the corpora or descriptions") do |s|
|
164
|
+
s ||= ' '
|
165
|
+
if s[0].casecmp('c').zero?
|
166
|
+
puts Poefy.corpora
|
167
|
+
elsif s[0].casecmp('d').zero?
|
168
|
+
puts Poefy.corpora_with_desc.values
|
169
|
+
else
|
170
|
+
puts corpora
|
171
|
+
end
|
172
|
+
exit 0
|
173
|
+
end
|
174
|
+
opts.on('-D', '--database [pg|sqlite3]',
|
175
|
+
"Display the database implementation setting\n" + ' ' * 39 +
|
176
|
+
"Append 'pg' or 'sqlite3' to change programs") do |s|
|
177
|
+
s ||= ' '
|
178
|
+
if s[0].casecmp('p').zero?
|
179
|
+
Poefy.database_type = 'pg'
|
180
|
+
elsif s[0].casecmp('s').zero?
|
181
|
+
Poefy.database_type = 'sqlite3'
|
182
|
+
end
|
183
|
+
puts Poefy.database_type
|
184
|
+
exit 0
|
185
|
+
end
|
186
|
+
|
187
|
+
# Help output.
|
188
|
+
opts.separator nil
|
189
|
+
opts.on('-h', '--help', 'Display this help screen' ) do
|
190
|
+
puts opts
|
191
|
+
exit 0
|
192
|
+
end
|
193
|
+
opts.on('-v', '--version', 'Display the version number' ) do
|
194
|
+
puts "poefy #{Poefy.version_number} (#{Poefy.version_date})"
|
195
|
+
exit 0
|
196
|
+
end
|
197
|
+
|
198
|
+
opts.separator nil
|
199
|
+
opts.separator "Description of rhyme string:"
|
200
|
+
opts.separator rhyme_docs
|
201
|
+
opts.separator nil
|
202
|
+
opts.separator "All of this is much better documented in README.md"
|
203
|
+
end
|
204
|
+
|
205
|
+
# Parse the options and show errors on failure.
|
206
|
+
begin
|
207
|
+
optparse.parse! ARGV
|
208
|
+
rescue OptionParser::ParseError => e
|
209
|
+
puts e
|
210
|
+
exit 1
|
211
|
+
end
|
212
|
+
|
213
|
+
options
|
214
|
+
end
|
215
|
+
|
216
|
+
################################################################################
|
217
|
+
|
218
|
+
# Parse the options to shift the ARGV list.
|
219
|
+
options = parse_options
|
220
|
+
|
221
|
+
# Read data lines from STDIN.
|
222
|
+
data = (not STDIN.tty? and not STDIN.closed?) ? STDIN.read : nil
|
223
|
+
|
224
|
+
# Corpus name is the first argument.
|
225
|
+
first_arg = ARGV.first
|
226
|
+
if first_arg.nil?
|
227
|
+
STDERR.puts "ERROR: Please specify a corpus name to read from/to"
|
228
|
+
exit 1
|
229
|
+
end
|
230
|
+
|
231
|
+
# Poetic form name is the second argument, if it exists.
|
232
|
+
second_arg = (ARGV.length > 1) ? ARGV[1] : ''
|
233
|
+
options[:form] = second_arg if second_arg != ''
|
234
|
+
|
235
|
+
# If we need to make a corpus.
|
236
|
+
# Exit the program after corpus is generated.
|
237
|
+
if options[:make_corpus]
|
238
|
+
|
239
|
+
# It's okay if there's an error with an existing database, because
|
240
|
+
# we're creating a new one. So we can swallow any errors here.
|
241
|
+
begin
|
242
|
+
poefy = Poefy::Poem.new first_arg
|
243
|
+
rescue Poefy::DatabaseError
|
244
|
+
end
|
245
|
+
|
246
|
+
if data
|
247
|
+
poefy.make_database data, options[:corpus_desc], true
|
248
|
+
poefy.close
|
249
|
+
exit 0
|
250
|
+
else
|
251
|
+
STDERR.puts 'ERROR: Need text input to generate a corpus'
|
252
|
+
STDERR.puts ' Please pipe some data into the program'
|
253
|
+
exit 1
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
# If we need to update a corpus description.
|
258
|
+
# Exit the program after corpus is generated.
|
259
|
+
if options[:corpus_desc]
|
260
|
+
poefy = poefy_new first_arg
|
261
|
+
begin
|
262
|
+
poefy.corpus.desc = options[:corpus_desc]
|
263
|
+
poefy.close
|
264
|
+
exit 0
|
265
|
+
rescue
|
266
|
+
STDERR.puts "ERROR: Corpus '#{first_arg}' does not yet exist"
|
267
|
+
exit 1
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
# If the second argument is 'rhyme', then output all
|
272
|
+
# lines that rhyme with the word.
|
273
|
+
if second_arg == 'rhyme'
|
274
|
+
poefy = poefy_new first_arg
|
275
|
+
rhyme_word = (ARGV.length > 2) ? ARGV[2] : nil
|
276
|
+
rhyme_key = (ARGV.length > 3) ? ARGV[3] : nil
|
277
|
+
|
278
|
+
# If the value for 'rhyme_key' is instead 'json', then set that bool.
|
279
|
+
is_output_json = (rhyme_key.to_s.downcase == 'json')
|
280
|
+
rhyme_key = nil if is_output_json
|
281
|
+
|
282
|
+
# This will return an array of elements.
|
283
|
+
# If the 'rhyme_key' is nil, it will be an array of hashes.
|
284
|
+
# If the 'rhyme_key' is not, it will be an array of strings.
|
285
|
+
lines = poefy.corpus.rhymes(rhyme_word, rhyme_key)
|
286
|
+
|
287
|
+
if is_output_json or !rhyme_key.nil?
|
288
|
+
puts lines
|
289
|
+
else
|
290
|
+
# Convert the array of hashes to an array of arrays.
|
291
|
+
lines.map! { |i| i.values }
|
292
|
+
|
293
|
+
# Find the max length of each column.
|
294
|
+
max_len = []
|
295
|
+
lines.each do |row|
|
296
|
+
row.each.with_index do |col, index|
|
297
|
+
max_len[index] ||= 0
|
298
|
+
if col.length > max_len[index]
|
299
|
+
max_len[index] = col.length
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
# Output to console as whitespace delimited.
|
305
|
+
lines.each do |i|
|
306
|
+
pform = "%-#{max_len[0]+3}s %-#{max_len[1]+3}s "
|
307
|
+
pform += "%-#{max_len[2]+3}s %-#{max_len[3]}s\n"
|
308
|
+
printf pform, *i
|
309
|
+
end
|
310
|
+
end
|
311
|
+
exit 0
|
312
|
+
end
|
313
|
+
|
314
|
+
# If there is piped data, or the second argument is a file,
|
315
|
+
# then use that as the poetic_form.
|
316
|
+
if data or File.exist?(second_arg)
|
317
|
+
options[:form_from_text] = (data || second_arg)
|
318
|
+
end
|
319
|
+
|
320
|
+
# Create poefy object using the options.
|
321
|
+
begin
|
322
|
+
poefy = poefy_new first_arg, options
|
323
|
+
rescue Poefy::DatabaseError => e
|
324
|
+
STDERR.puts e.console_msg
|
325
|
+
exit 1
|
326
|
+
end
|
327
|
+
|
328
|
+
# Make the correct number of poems, and output them.
|
329
|
+
number = options[:number] || 1
|
330
|
+
number.times do |i|
|
331
|
+
|
332
|
+
# Exit the program if a Poefy error is raised.
|
333
|
+
begin
|
334
|
+
poem = poefy.poem
|
335
|
+
rescue Poefy::Error => e
|
336
|
+
STDERR.puts e.console_msg
|
337
|
+
exit 1
|
338
|
+
end
|
339
|
+
|
340
|
+
if poem
|
341
|
+
puts poem
|
342
|
+
puts nil if i < number - 1
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
# Close the database connection.
|
347
|
+
poefy.close
|
348
|
+
|
349
|
+
################################################################################
|