poefy-pg 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 562ca9a5d0e1e8fc84fce3f1d2cc1191f67b72b8
4
+ data.tar.gz: 532339860c2f1ad84288c8232162b31de8854154
5
+ SHA512:
6
+ metadata.gz: 8a95524ade873f8ff08785a8d4abdb94bb808f15c92fb73bf5fb6daa11f6c9f62f288ef86d44fc305f186a31eb6ea2369927f9471a769b00bb88c6a713cfa270
7
+ data.tar.gz: 1f7f41a631894cb7191805f0d8024290dc2757c0f1fdcd2c10ba4d3c6a69dbf4f5341407d5174262ce1f8ec00c0ef146339dea24cf540d688d713b35580863b6
data/.gitignore ADDED
@@ -0,0 +1,77 @@
1
+
2
+ ################################################################################
3
+ # Ruby specific files
4
+
5
+ *.gem
6
+ *.rbc
7
+ /.config
8
+ /coverage/
9
+ /InstalledFiles
10
+ /pkg/
11
+ /spec/reports/
12
+ /test/tmp/
13
+ /test/version_tmp/
14
+ /tmp/
15
+
16
+ ## Specific to RubyMotion:
17
+ .dat*
18
+ .repl_history
19
+ build/
20
+
21
+ ## Documentation cache and generated files:
22
+ /.yardoc/
23
+ /_yardoc/
24
+ /doc/
25
+ /rdoc/
26
+
27
+ ## Environment normalisation:
28
+ /.bundle/
29
+ /vendor/bundle
30
+ /lib/bundler/man/
31
+
32
+ # for a library or gem, you might want to ignore these files since the code is
33
+ # intended to run in multiple environments; otherwise, check them in:
34
+ Gemfile.lock
35
+ .ruby-version
36
+ .ruby-gemset
37
+
38
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
39
+ .rvmrc
40
+
41
+ ################################################################################
42
+ # Rails stuff
43
+
44
+ # Ignore all logfiles and tempfiles.
45
+ /log/*
46
+ /tmp/*
47
+ !/log/.keep
48
+ !/tmp/.keep
49
+
50
+ # Ignore Byebug command history file.
51
+ .byebug_history
52
+
53
+ # Ignore application configuration
54
+ /config/application.yml
55
+
56
+ ################################################################################
57
+ # System and config files
58
+ desktop.ini
59
+ .agignore
60
+ .ignore
61
+ *.lnk
62
+
63
+ ################################################################################
64
+ # App specific files
65
+
66
+ # Development files
67
+ work*.rb
68
+ /~/
69
+
70
+ # Config files
71
+ settings.yml
72
+
73
+ # Data files
74
+ /data/
75
+
76
+ # Output files
77
+ /output/
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright (C) 2017 Paul Thompson
2
+
3
+ This program is free software: you can redistribute it and/or modify
4
+ it under the terms of the GNU General Public License as published by
5
+ the Free Software Foundation, either version 3 of the License, or
6
+ (at your option) any later version.
7
+
8
+ This program is distributed in the hope that it will be useful,
9
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ GNU General Public License for more details.
12
+
13
+ Full text of this licence: <https://www.gnu.org/licenses/gpl.html>
data/README.md ADDED
@@ -0,0 +1,7 @@
1
+ # poefy-pg
2
+
3
+ by Paul Thompson - nossidge@gmail.com
4
+
5
+ PostgreSQL interface for the 'poefy' gem.
6
+
7
+ Please see the [poefy](https://github.com/nossidge/poefy) documentation.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec) do |t|
5
+ t.verbose = false
6
+ end
7
+ task :default => :spec
8
+ task :test => :spec
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env ruby
2
+ # Encoding: UTF-8
3
+
4
+ module Poefy
5
+
6
+ module Pg
7
+
8
+ def self.version_number
9
+ major = 0
10
+ minor = 1
11
+ tiny = 0
12
+ pre = nil
13
+
14
+ string = [major, minor, tiny, pre].compact.join('.')
15
+ Gem::Version.new string
16
+ end
17
+
18
+ def self.version_date
19
+ '2017-10-02'
20
+ end
21
+
22
+ end
23
+
24
+ end
data/lib/poefy/pg.rb ADDED
@@ -0,0 +1,295 @@
1
+ #!/usr/bin/env ruby
2
+ # Encoding: UTF-8
3
+
4
+ ################################################################################
5
+ # Extend 'Database' class for connecting to a PostgreSQL database.
6
+ # These methods are specific to PostgreSQL.
7
+ # Other databases should be implemented in separate gems.
8
+ ################################################################################
9
+
10
+ require 'pg'
11
+
12
+ ################################################################################
13
+
14
+ module Poefy
15
+
16
+ class Database
17
+
18
+ # Open a connection, execute a query, close the connection.
19
+ def self.single_exec! sql, sql_args = nil
20
+ output = nil
21
+ begin
22
+ con = PG.connect(
23
+ :dbname => 'poefy',
24
+ :user => 'poefy',
25
+ :password => 'poefy'
26
+ )
27
+ output = if sql_args
28
+ con.exec(sql, [*sql_args]).values
29
+ else
30
+ con.exec(sql).values
31
+ end
32
+ ensure
33
+ con.close if con
34
+ end
35
+ output
36
+ end
37
+
38
+ # List all tables in the database.
39
+ # Does not include tables used for testing.
40
+ def self.list
41
+ rs = Database::single_exec! <<-SQL
42
+ SELECT table_name
43
+ FROM information_schema.tables
44
+ WHERE table_schema = 'public';
45
+ SQL
46
+ rs.flatten.reject do |i|
47
+ i.start_with?('spec_')
48
+ end.sort - ['test']
49
+ end
50
+
51
+ # Get the description of a table.
52
+ def self.desc table_name
53
+ begin
54
+ sql = "SELECT obj_description($1::regclass, 'pg_class');"
55
+ single_exec!(sql, [table_name]).flatten.first.to_s
56
+ rescue
57
+ ''
58
+ end
59
+ end
60
+
61
+ # List all database files and their descriptions.
62
+ def self.list_with_desc
63
+ Database::list.map do |i|
64
+ begin
65
+ [i, Database::desc(i)]
66
+ rescue
67
+ [i, '']
68
+ end
69
+ end.to_h
70
+ end
71
+
72
+ ############################################################################
73
+
74
+ # This is the type of database that is being used.
75
+ # It is also used as a signifier that a database has been specified.
76
+ def type
77
+ 'pg'
78
+ end
79
+
80
+ # Get/set the description of the table.
81
+ def desc
82
+ Database::desc table
83
+ end
84
+ def desc=(description)
85
+ safe_desc = description.to_s.gsub("'","''")
86
+ execute! "COMMENT ON TABLE #{table} IS '#{safe_desc}';"
87
+ end
88
+
89
+ # The number of lines in the table.
90
+ def count
91
+ return 0 if not exists?
92
+ sql = "SELECT COUNT(*) AS num FROM #{table};"
93
+ execute!(sql).first['num'].to_i
94
+ end
95
+
96
+ # See if the table exists or not.
97
+ # Attempt to access table, and return false on error.
98
+ def exists?
99
+ open_connection
100
+ @db.exec("SELECT $1::regclass", [*table])
101
+ true
102
+ rescue PG::UndefinedTable
103
+ false
104
+ end
105
+
106
+ # Get all rhyming lines for the word.
107
+ def rhymes word, key = nil
108
+ return nil if word.nil?
109
+
110
+ sql = <<-SQL
111
+ SELECT rhyme, final_word, syllables, line
112
+ FROM #{table}
113
+ WHERE rhyme = $1
114
+ ORDER BY rhyme, final_word, syllables, line
115
+ SQL
116
+ output = word.to_phrase.rhymes.keys.map do |rhyme|
117
+ execute!(sql, [rhyme]).to_a
118
+ end.flatten
119
+
120
+ if !key.nil? and %w[rhyme final_word syllables line].include?(key)
121
+ output.map!{ |i| i[key] }
122
+ end
123
+ output
124
+ end
125
+
126
+ private
127
+
128
+ # The name of the table.
129
+ def table
130
+ @name
131
+ end
132
+
133
+ # Create a new table.
134
+ def new_connection
135
+ open_connection
136
+ end
137
+
138
+ # Open a connection to the database.
139
+ def open_connection
140
+ @db ||= PG.connect(
141
+ :dbname => 'poefy',
142
+ :user => 'poefy',
143
+ :password => 'poefy'
144
+ )
145
+ end
146
+
147
+ # Execute a query.
148
+ def execute! sql, *args
149
+ db.exec sql, *args
150
+ end
151
+
152
+ # Insert an array of lines.
153
+ def insert_lines table_name, rows
154
+ sql = "INSERT INTO #{table_name} VALUES ( $1, $2, $3, $4 )"
155
+ db.transaction do |db_tr|
156
+ rows.each do |line|
157
+ db_tr.exec sql, line
158
+ end
159
+ end
160
+ end
161
+
162
+ ##########################################################################
163
+
164
+ # Create the table and the index.
165
+ def create_table table_name, description = nil
166
+ index_name = 'idx_' + table_name
167
+ execute! <<-SQL
168
+ SET client_min_messages TO WARNING;
169
+ DROP INDEX IF EXISTS #{index_name};
170
+ DROP TABLE IF EXISTS #{table_name};
171
+ CREATE TABLE #{table_name} (
172
+ line TEXT,
173
+ syllables SMALLINT,
174
+ final_word TEXT,
175
+ rhyme TEXT
176
+ );
177
+ CREATE INDEX #{index_name} ON #{table_name} (
178
+ rhyme, final_word, line
179
+ );
180
+ SQL
181
+ self.desc = description
182
+ end
183
+
184
+ ##########################################################################
185
+
186
+ # Define SQL of the stored procedures.
187
+ def sprocs_sql_hash
188
+ sql = {}
189
+ sql[:rbc] = <<-SQL
190
+ SELECT rhyme, COUNT(rhyme) AS count
191
+ FROM (
192
+ SELECT rhyme, final_word, COUNT(final_word) AS wc
193
+ FROM #{table}
194
+ GROUP BY rhyme, final_word
195
+ ) AS sub_table
196
+ GROUP BY rhyme
197
+ HAVING COUNT(rhyme) >= $1
198
+ SQL
199
+ sql[:rbcs] = <<-SQL
200
+ SELECT rhyme, COUNT(rhyme) AS count
201
+ FROM (
202
+ SELECT rhyme, final_word, COUNT(final_word) AS wc
203
+ FROM #{table}
204
+ WHERE syllables BETWEEN $1 AND $2
205
+ GROUP BY rhyme, final_word
206
+ ) AS sub_table
207
+ GROUP BY rhyme
208
+ HAVING COUNT(rhyme) >= $3
209
+ SQL
210
+ sql[:la] = <<-SQL
211
+ SELECT line, syllables, final_word, rhyme
212
+ FROM #{table} WHERE rhyme = $1
213
+ SQL
214
+ sql[:las] = <<-SQL
215
+ SELECT line, syllables, final_word, rhyme
216
+ FROM #{table} WHERE rhyme = $1
217
+ AND syllables BETWEEN $2 AND $3
218
+ SQL
219
+ sql
220
+ end
221
+
222
+ # Create the stored procedures in the database.
223
+ def create_sprocs
224
+ sprocs_sql_hash.each do |key, value|
225
+ db.prepare key.to_s, value
226
+ end
227
+ rescue
228
+ handle_error \
229
+ "ERROR: Database table structure is invalid.\n" +
230
+ " Please manually DROP the corrupt table and recreate it."
231
+ end
232
+
233
+ # Find rhymes and counts greater than a certain length.
234
+ def sproc_rhymes_by_count rhyme_count
235
+ rs = db.exec_prepared 'rbc', [rhyme_count]
236
+ rs.values.map do |row|
237
+ {
238
+ 'rhyme' => row[0],
239
+ 'count' => row[1].to_i
240
+ }
241
+ end
242
+ end
243
+
244
+ # Also adds syllable selection.
245
+ def sproc_rhymes_by_count_syllables rhyme_count, syllable_min_max
246
+ arg_array = [
247
+ syllable_min_max[:min],
248
+ syllable_min_max[:max],
249
+ rhyme_count
250
+ ]
251
+ rs = db.exec_prepared 'rbcs', arg_array
252
+ rs.values.map do |row|
253
+ {
254
+ 'rhyme' => row[0],
255
+ 'count' => row[1].to_i
256
+ }
257
+ end
258
+ end
259
+
260
+ # Find all lines for a certain rhyme.
261
+ def sproc_lines_by_rhyme rhyme
262
+ rs = db.exec_prepared 'la', [rhyme]
263
+ rs.values.map do |row|
264
+ {
265
+ 'line' => row[0],
266
+ 'syllables' => row[1].to_i,
267
+ 'final_word' => row[2],
268
+ 'rhyme' => row[3]
269
+ }
270
+ end
271
+ end
272
+
273
+ # Also adds syllable selection.
274
+ def sproc_lines_by_rhyme_syllables rhyme, syllable_min_max
275
+ arg_array = [
276
+ rhyme,
277
+ syllable_min_max[:min],
278
+ syllable_min_max[:max]
279
+ ]
280
+ rs = db.exec_prepared 'las', arg_array
281
+ rs.values.map do |row|
282
+ {
283
+ 'line' => row[0],
284
+ 'syllables' => row[1].to_i,
285
+ 'final_word' => row[2],
286
+ 'rhyme' => row[3]
287
+ }
288
+ end
289
+ end
290
+
291
+ end
292
+
293
+ end
294
+
295
+ ################################################################################
data/poefy-pg.gemspec ADDED
@@ -0,0 +1,31 @@
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/pg/version.rb'
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = 'poefy-pg'
9
+ s.authors = ['Paul Thompson']
10
+ s.email = ['nossidge@gmail.com']
11
+
12
+ s.summary = %q{PostgreSQL interface for the 'poefy' gem}
13
+ s.description = %q{PostgreSQL interface for the 'poefy' gem}
14
+ s.homepage = 'https://github.com/nossidge/poefy-pg'
15
+
16
+ s.version = Poefy::Pg.version_number
17
+ s.date = Poefy::Pg.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.require_paths = ['lib']
23
+
24
+ s.add_development_dependency('bundler', '~> 1.13')
25
+ s.add_development_dependency('rake', '~> 10.0')
26
+ s.add_development_dependency('rspec', '~> 3.0')
27
+ s.add_development_dependency('ruby_rhymes', '~> 0.1')
28
+
29
+ s.add_runtime_dependency('poefy', '~> 1.0', '>= 1.0.0')
30
+ s.add_runtime_dependency('pg', '~> 0.21', '>= 0.21.0')
31
+ end
@@ -0,0 +1,682 @@
1
+ #!/usr/bin/env ruby
2
+ # Encoding: UTF-8
3
+
4
+ ################################################################################
5
+
6
+ describe Poefy::Poem, "-- Postgres" do
7
+
8
+ before(:all) do
9
+ require 'poefy/pg'
10
+ @root = Poefy.root
11
+ dbs = %w{spec_test_tiny spec_shakespeare spec_whitman}
12
+ dbs.each do |db_name|
13
+ Poefy::Database.single_exec! <<-SQL
14
+ SET client_min_messages TO WARNING;
15
+ DROP TABLE IF EXISTS #{db_name};
16
+ SQL
17
+ end
18
+ end
19
+
20
+ after(:all) do
21
+ dbs = %w{spec_test_tiny spec_shakespeare spec_whitman}
22
+ dbs.each do |db_name|
23
+ Poefy::Database.single_exec! <<-SQL
24
+ SET client_min_messages TO WARNING;
25
+ DROP TABLE IF EXISTS #{db_name};
26
+ SQL
27
+ end
28
+ end
29
+
30
+ ##############################################################################
31
+
32
+ describe "using tiny dataset 'spec_test_tiny'" do
33
+ corpus = "spec_test_tiny"
34
+
35
+ # Create a small corpus of a few rhymes.
36
+ text_array = %w{man plan flan can dan fish dish wish bee sea tree flea}
37
+ text_array.map!{ |i| 'a ' + i }
38
+ text_string = text_array.join("\n")
39
+ row_count = text_array.count
40
+
41
+ before(:each) do
42
+ @poefy = Poefy::Poem.new(corpus, { proper: false })
43
+ end
44
+ after(:each) do
45
+ @poefy.close
46
+ end
47
+ it "initialised object not nil" do
48
+ expect(@poefy).to_not be_nil
49
+ end
50
+
51
+ # Create corpora in the three different ways.
52
+ describe "@poefy#make_database!" do
53
+
54
+ it "Use array of strings" do
55
+ @poefy.make_database! text_array
56
+ expect(@poefy.corpus.exists?).to be true
57
+ expect(@poefy.corpus.count).to be row_count
58
+ poem = @poefy.poem({ rhyme: 'aabb' })
59
+ expect(poem.count).to be 4
60
+ end
61
+
62
+ it "Use one long newline delimited string" do
63
+ @poefy.make_database! text_string
64
+ expect(@poefy.corpus.exists?).to be true
65
+ expect(@poefy.corpus.count).to be row_count
66
+ poem = @poefy.poem({ rhyme: 'aabb' })
67
+ expect(poem.count).to be 4
68
+ end
69
+
70
+ it "Use text lines from a file" do
71
+
72
+ # Create a temp file.
73
+ tmp = Tempfile.new('spec-', Poefy.root + '/spec')
74
+ text_path = tmp.path
75
+ tmp.write text_string
76
+ tmp.close
77
+
78
+ @poefy.make_database! text_path
79
+ expect(@poefy.corpus.exists?).to be true
80
+ expect(@poefy.corpus.count).to be row_count
81
+ poem = @poefy.poem({ rhyme: 'aabb' })
82
+ expect(poem.count).to be 4
83
+
84
+ # Delete the temp file.
85
+ tmp.delete
86
+ end
87
+ end
88
+
89
+ # Make sure that the description can be updated as specified
90
+ # and that it doesn't cause SQL injection.
91
+ describe "corpus description using #desc=" do
92
+ it "@poefy.corpus.desc is initially empty" do
93
+ expect(@poefy.corpus.desc).to eq ''
94
+ end
95
+
96
+ values = [
97
+ "test",
98
+ " -- test",
99
+ "; -- test",
100
+ "test' -- ",
101
+ "test'' -- ",
102
+ "'test' -- ",
103
+ "'test'' -- ",
104
+ "Shakespeare's sonnets",
105
+ "Shakespeare's -- sonnets",
106
+ "Shakespeare's; -- sonnets",
107
+ "test' ; INSERT INTO spec_test_tiny VALUES('foo') -- ",
108
+ "105 OR 1=1",
109
+ "' or ''='"
110
+ ]
111
+ values.each do |value|
112
+ it "@poefy.corpus.desc = #{value}" do
113
+ @poefy.corpus.desc = value
114
+ expect(@poefy.corpus.desc).to eq value
115
+ expect(@poefy.corpus.count).to be row_count
116
+ end
117
+ end
118
+ end
119
+
120
+ describe ":rhyme option" do
121
+
122
+ describe "should return nil" do
123
+ it "blank, no argument" do
124
+ poem = @poefy.poem
125
+ expect(poem).to be_nil
126
+ end
127
+ it "({ })" do
128
+ poem = @poefy.poem ({ })
129
+ expect(poem).to be_nil
130
+ end
131
+ it "({ rhyme: nil })" do
132
+ poem = @poefy.poem ({ rhyme: nil })
133
+ expect(poem).to be_nil
134
+ end
135
+ it "({ rhyme: ' ' })" do
136
+ poem = @poefy.poem ({ rhyme: ' ' })
137
+ expect(poem).to be_nil
138
+ end
139
+ it "({ rhyme: '' })" do
140
+ poem = @poefy.poem ({ rhyme: '' })
141
+ expect(poem).to be_nil
142
+ end
143
+ end
144
+
145
+ describe "should return correct number of lines" do
146
+ rhymes = %w{a b z A aa ab zz AA AB AA1 A1 B1 Z1 AB1 A1A1A1A1B1B1B1B1B1}
147
+ rhymes += ['A1A1A1 A1A1A1 B1B1B1B1B1B1','a b c a b c']
148
+ rhymes += [' abc','abc ',' abc ']
149
+ rhymes += ['n aaa n','n aXXXa N1']
150
+ rhymes.each do |i|
151
+ it "({ rhyme: '#{i}' })" do
152
+ poem = @poefy.poem ({ rhyme: i })
153
+ expect(poem.count).to be i.gsub(/[0-9]/,'').length
154
+ end
155
+ end
156
+ end
157
+
158
+ describe "should accept characters other than number" do
159
+ rhymes = %w{. , : .. ., ,, :: (()) @ ~ <<>< A1A1A1...a;}
160
+ rhymes.each do |i|
161
+ it "({ rhyme: '#{i}' })" do
162
+ poem = @poefy.poem ({ rhyme: i })
163
+ expect(poem.count).to be i.gsub(/[0-9]/,'').length
164
+ end
165
+ end
166
+ end
167
+
168
+ describe "should be nil if can't parse rhyme string" do
169
+ rhymes = %w{a1 b1 ab1 Ab1 AAAAABb1 1 1111 1122 11221 ;;::1. }
170
+ rhymes += ['AA Bb1','11 11','11 1 1','..1.']
171
+ rhymes.each do |i|
172
+ it "({ rhyme: '#{i}' })" do
173
+ poem = @poefy.poem ({ rhyme: i })
174
+ expect(poem).to be_nil
175
+ end
176
+ end
177
+ end
178
+
179
+ describe "should be nil if can't complete rhyme string" do
180
+ rhymes = %w{aaaaaa abcd aaaaabbbbb}
181
+ rhymes.each do |i|
182
+ it "({ rhyme: '#{i}' })" do
183
+ poem = @poefy.poem ({ rhyme: i })
184
+ expect(poem).to be_nil
185
+ end
186
+ end
187
+ end
188
+
189
+ describe "should correctly repeat uppercase lines" do
190
+ lines = 200
191
+ it "({ rhyme: 'A' * #{lines} })" do
192
+ poem = @poefy.poem ({ rhyme: 'A' * lines })
193
+ expect(poem.count).to be lines
194
+ expect(poem.uniq.count).to be 1
195
+ end
196
+ it "({ rhyme: ('A'..'C').to_a.map { |i| i * #{lines} }.join })" do
197
+ rhyme = ('A'..'C').to_a.map { |i| i * lines }.join
198
+ poem = @poefy.poem ({ rhyme: rhyme })
199
+ expect(poem.count).to be lines * 3
200
+ expect(poem.uniq.count).to be 3
201
+ end
202
+ end
203
+
204
+ describe "should be nil if can't complete repeating rhyme string" do
205
+ lines = 200
206
+ it "({ rhyme: ('A'..'D').to_a.map { |i| i * #{lines} }.join })" do
207
+ rhyme = ('A'..'D').to_a.map { |i| i * lines }.join
208
+ poem = @poefy.poem ({ rhyme: rhyme })
209
+ expect(poem).to be_nil
210
+ end
211
+ end
212
+
213
+ end
214
+
215
+ describe ":form option" do
216
+
217
+ describe "should return correct number of lines" do
218
+ it "({ form: :default })" do
219
+ poem = @poefy.poem ({ form: :default })
220
+ expect(poem.count).to be 1
221
+ end
222
+ end
223
+
224
+ describe "should be nil if given a named form it can't fulfil" do
225
+ it "({ form: 'sonnet' })" do
226
+ poem = @poefy.poem ({ form: 'sonnet' })
227
+ expect(poem).to be_nil
228
+ end
229
+ it "({ form: :villanelle })" do
230
+ poem = @poefy.poem ({ form: :villanelle })
231
+ expect(poem).to be_nil
232
+ end
233
+ end
234
+
235
+ describe "should be nil if given a junk named form" do
236
+ it "({ form: 'sonnet_junk' })" do
237
+ poem = @poefy.poem ({ form: 'sonnet_junk' })
238
+ expect(poem).to be_nil
239
+ end
240
+ it "({ form: :not_a_form })" do
241
+ poem = @poefy.poem ({ form: :not_a_form })
242
+ expect(poem).to be_nil
243
+ end
244
+ it "({ form: :not_a_form, indent: '0010' })" do
245
+ poem = @poefy.poem ({ form: :not_a_form, indent: '0010' })
246
+ expect(poem).to be_nil
247
+ end
248
+ end
249
+
250
+ describe "should be valid if given a junk named form, and a rhyme" do
251
+ it "({ form: :not_a_form, rhyme: 'abcb' })" do
252
+ poem = @poefy.poem ({ form: :not_a_form, rhyme: 'abcb' })
253
+ expect(poem.count).to be 4
254
+ end
255
+ end
256
+
257
+ describe "should overwrite a named form if another option is specified" do
258
+ it "({ form: 'default', rhyme: 'ab' })" do
259
+ poem = @poefy.poem ({ form: 'default', rhyme: 'ab' })
260
+ expect(poem.count).to be 2
261
+ end
262
+ it "({ form: :villanelle, rhyme: 'abcb' })" do
263
+ poem = @poefy.poem ({ form: :villanelle, rhyme: 'abcb' })
264
+ expect(poem.count).to be 4
265
+ end
266
+ end
267
+ end
268
+ end
269
+
270
+ ##############################################################################
271
+
272
+ describe "using dataset 'spec_shakespeare'" do
273
+
274
+ file_txt = "shakespeare_sonnets.txt"
275
+ file_db = "spec_shakespeare"
276
+
277
+ # All the Shakespeare lines are pentameter, so some forms should fail.
278
+ forms = Poefy::PoeticForms::POETIC_FORMS
279
+ forms_fail = [:limerick, :haiku, :common, :ballad, :double_dactyl]
280
+ forms_pass = forms.keys - forms_fail
281
+
282
+ before(:each) do
283
+ @poefy = Poefy::Poem.new(file_db, { proper: false })
284
+ end
285
+ after(:each) do
286
+ @poefy.close
287
+ end
288
+
289
+ it "initialised object not nil" do
290
+ expect(@poefy).to_not be_nil
291
+ end
292
+
293
+ describe "#make_database( '#{@root}/data/#{file_txt}', true )" do
294
+ it "should make the database '#{@root}/data/#{file_db}" do
295
+ input = `sed '/[a-z]/!d' #{@root}/data/#{file_txt}`
296
+ @poefy.make_database! input
297
+ expect(@poefy.corpus.exists?).to be true
298
+ end
299
+ end
300
+
301
+ describe "using acrostic option" do
302
+ describe "should return correct number of lines" do
303
+ it "({ form: :sonnet, acrostic: 'pauldpthompson' })" do
304
+ poem = @poefy.poem ({ form: :sonnet,
305
+ acrostic: 'pauldpthompson' })
306
+ expect(poem.count).to be 14
307
+ end
308
+ end
309
+ describe "should fail to be created" do
310
+ it "({ form: :sonnet, acrostic: 'qqqqqqqqqqqqqq' })" do
311
+ poem = @poefy.poem ({ form: :sonnet,
312
+ acrostic: 'qqqqqqqqqqqqqq' })
313
+ expect(poem).to be_nil
314
+ end
315
+ end
316
+ end
317
+
318
+ describe "using form string" do
319
+ describe "should return correct number of lines" do
320
+
321
+ # Make sure each form's lines match the expected output.
322
+ # Generate a few to be sure.
323
+ forms_pass.each do |form|
324
+ it "({ form: #{form} })" do
325
+ 10.times do
326
+ poem = @poefy.poem ({ form: form })
327
+ expect(poem.count).to satisfy do |c|
328
+ [*forms[form][:rhyme]].map do |r|
329
+ r.gsub(/[0-9]/,'').length
330
+ end.include?(c)
331
+ end
332
+ end
333
+ end
334
+ end
335
+ end
336
+
337
+ describe "should fail to be created" do
338
+ forms_fail.each do |form|
339
+ it "({ form: #{form} })" do
340
+ 4.times do
341
+ poem = @poefy.poem ({ form: form })
342
+ expect(poem).to be_nil
343
+ end
344
+ end
345
+ end
346
+ end
347
+ end
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
+ end
372
+
373
+ ##############################################################################
374
+
375
+ describe "using dataset 'spec_whitman'" do
376
+
377
+ file_txt = "whitman_leaves.txt"
378
+ file_db = "spec_whitman"
379
+
380
+ # There's a good mix of syllable count, so all forms should pass.
381
+ forms = Poefy::PoeticForms::POETIC_FORMS
382
+ forms_pass = forms.keys
383
+
384
+ before(:each) do
385
+ @poefy = Poefy::Poem.new(file_db, { proper: false })
386
+ end
387
+ after(:each) do
388
+ @poefy.close
389
+ end
390
+
391
+ it "initialised object not nil" do
392
+ expect(@poefy).to_not be_nil
393
+ end
394
+
395
+ describe "#make_database( '#{@root}/data/#{file_txt}', true )" do
396
+ it "should make the database '#{@root}/data/#{file_db}" do
397
+ input = `sed '/[a-z]/!d' #{@root}/data/#{file_txt}`
398
+ @poefy.make_database! input
399
+ expect(@poefy.corpus.exists?).to be true
400
+ end
401
+ end
402
+
403
+ describe "using form string" do
404
+ describe "should return correct number of lines" do
405
+
406
+ # Make sure each form's lines match the expected output.
407
+ # Generate a few to be sure.
408
+ forms_pass.each do |form|
409
+ it "({ form: #{form} })" do
410
+ 10.times do
411
+ poem = @poefy.poem ({ form: form })
412
+ expect(poem.count).to satisfy do |c|
413
+ [*forms[form][:rhyme]].map do |r|
414
+ r.gsub(/[0-9]/,'').length
415
+ end.include?(c)
416
+ end
417
+ end
418
+ end
419
+ end
420
+ end
421
+ end
422
+
423
+ describe "make sonnets" do
424
+ sonnet_options = [
425
+ { rhyme: 'ababcdcdefefgg' },
426
+ { rhyme: 'abab cdcd efef gg', indent: '0101 0101 0011 01' },
427
+ { form: 'sonnet' },
428
+ { form: :sonnet, syllable: 0 },
429
+ { form: :sonnet, syllable: 10 },
430
+ { form: :sonnet, regex: /^[A-Z].*$/ },
431
+ { form: :sonnet, regex: '^[A-Z].*$' },
432
+ { form: :sonnet, acrostic: 'pauldpthompson' },
433
+ { form: 'sonnet', indent: '01010101001101' },
434
+ { form: 'sonnet', proper: false }
435
+ ]
436
+ sonnet_options.each do |option|
437
+ it "#{option}" do
438
+ 4.times do
439
+ poem = @poefy.poem(option)
440
+ expect(poem).to_not be_nil
441
+ end
442
+ end
443
+ end
444
+ end
445
+
446
+ describe "using syllable string" do
447
+
448
+ it "({ rhyme: 'abcb defe', syllable: '[8,6,8,6,0,8,6,8,6]' })" do
449
+ options = {
450
+ rhyme: 'abcb defe',
451
+ syllable: '[8,6,8,6,0,8,6,8,6]'
452
+ }
453
+ poem = @poefy.poem (options)
454
+ expect(poem.count).to be options[:rhyme].length
455
+ end
456
+
457
+ it "({ rhyme: 'abcb defe', syllable: '[8,6,8,6,8,6,8,6]' })" do
458
+ options = {
459
+ rhyme: 'abcb defe',
460
+ syllable: '[8,6,8,6,8,6,8,6]'
461
+ }
462
+ poem = @poefy.poem (options)
463
+ expect(poem.count).to be options[:rhyme].length
464
+ end
465
+ end
466
+ end
467
+
468
+ ##############################################################################
469
+
470
+ describe "reusing the same Poem instance" do
471
+ it "should correctly merge the option hashes" do
472
+
473
+ # Default to use rondeau poetic form, and proper sentence validation
474
+ poefy = Poefy::Poem.new(
475
+ 'spec_shakespeare',
476
+ { form: 'rondeau', proper: true }
477
+ )
478
+
479
+ # Generate a properly sentenced rondeau
480
+ poem = poefy.poem
481
+ expect(poem.count).to be 17
482
+
483
+ # Generate a rondeau without proper validation
484
+ poem = poefy.poem ({ proper: false })
485
+ expect(poem.count).to be 17
486
+
487
+ # Generate a proper rondeau with a certain indentation
488
+ poem = poefy.poem ({ indent: '01012 0012 010112' })
489
+ expect(poem.count).to be 17
490
+
491
+ # Generate other forms
492
+ poem = poefy.poem ({ rhyme: 'abbaabbacdecde' })
493
+ expect(poem.count).to be 14
494
+ poem = poefy.poem ({ form: 'sonnet' })
495
+ expect(poem.count).to be 14
496
+ poem = poefy.poem ({ form: 'ballade' })
497
+ expect(poem.count).to be 31
498
+
499
+ # Generate a default rondeau again
500
+ poem = poefy.poem
501
+ expect(poem.count).to be 17
502
+
503
+ poefy.close
504
+ end
505
+ end
506
+
507
+ ##############################################################################
508
+
509
+ describe "using the transform option" do
510
+
511
+ it "should correctly transform the output 1" do
512
+ poefy = Poefy::Poem.new :spec_shakespeare
513
+ transform_hash = {
514
+ 4 => proc { |line, num, poem| line.upcase },
515
+ 12 => proc { |line, num, poem| line.upcase }
516
+ }
517
+ poem = poefy.poem({ form: :sonnet, transform: transform_hash })
518
+ expect(poem.count).to be 14
519
+ expect(poem[3]).to eq poem[3].upcase
520
+ expect(poem[11]).to eq poem[11].upcase
521
+ poefy.close
522
+ end
523
+
524
+ it "should correctly transform the output 2" do
525
+ poefy = Poefy::Poem.new :spec_shakespeare
526
+ transform_hash = {
527
+ 4 => proc { |line, num, poem| poem.count },
528
+ -3 => proc { |line, num, poem| poem.count },
529
+ 7 => proc { |line, num, poem| 'test string' }
530
+ }
531
+ poem = poefy.poem({ form: :sonnet, transform: transform_hash })
532
+ expect(poem.count).to be 14
533
+ expect(poem[3]).to eq '14'
534
+ expect(poem[11]).to eq '14'
535
+ expect(poem[6]).to eq 'test string'
536
+ poefy.close
537
+ end
538
+
539
+ it "should correctly transform the output 3" do
540
+ poefy = Poefy::Poem.new :spec_shakespeare
541
+ transform_proc = proc { |line, num, poem| line.downcase }
542
+ poem = poefy.poem({ form: :sonnet, transform: transform_proc })
543
+ expect(poem.count).to be 14
544
+ poem.each do |i|
545
+ expect(i).to eq i.downcase
546
+ end
547
+ poefy.close
548
+ end
549
+
550
+ it "should correctly transform the output 4" do
551
+ poefy = Poefy::Poem.new :spec_shakespeare
552
+ transform_proc = proc { |line, num, poem| "#{num} #{line.downcase}" }
553
+ poem = poefy.poem({ form: :sonnet, transform: transform_proc })
554
+ expect(poem.count).to be 14
555
+ poem.each.with_index do |line, index|
556
+ expect(line).to eq line.downcase
557
+ first_word = line.split(' ').first
558
+ expect(first_word).to eq (index + 1).to_s
559
+ end
560
+ poefy.close
561
+ end
562
+ end
563
+
564
+ ##############################################################################
565
+
566
+ describe "using the form_from_text option" do
567
+ before(:all) do
568
+ @text = <<-TEXT
569
+ [Chorus 1]
570
+ Oh yeah, I'll tell you something
571
+ I think you'll understand
572
+ When I'll say that something
573
+ I want to hold your hand
574
+ I want to hold your hand
575
+ I want to hold your hand
576
+
577
+ [Verse 1]
578
+ Oh please, say to me
579
+ You'll let me be your man
580
+ And please, say to me
581
+ You'll let me hold your hand
582
+ I'll let me hold your hand
583
+ I want to hold your hand
584
+ TEXT
585
+ @line_count = @text.split("\n").count
586
+ end
587
+
588
+ it "should use the exact poetic form 1" do
589
+ poefy = Poefy::Poem.new(:spec_whitman, {
590
+ form_from_text: @text
591
+ })
592
+ poem = poefy.poem
593
+ poem.map!(&:strip!)
594
+ expect(poem.count).to be @line_count
595
+ expect(poem[0]).to eq "[Chorus 1]"
596
+ expect(poem[8]).to eq "[Verse 1]"
597
+ expect(poem[5]).to eq poem[4]
598
+ expect(poem[6]).to eq poem[4]
599
+ poefy.close
600
+ end
601
+
602
+ it "should use the exact poetic form 2" do
603
+ poefy = Poefy::Poem.new :spec_whitman
604
+ poem = poefy.poem({
605
+ form_from_text: @text
606
+ })
607
+ poem.map!(&:strip!)
608
+ expect(poem.count).to be @line_count
609
+ expect(poem[0]).to eq "[Chorus 1]"
610
+ expect(poem[8]).to eq "[Verse 1]"
611
+ expect(poem[5]).to eq poem[4]
612
+ expect(poem[6]).to eq poem[4]
613
+ poefy.close
614
+ end
615
+
616
+ it "should correctly modify the poetic form 1" do
617
+ poefy = Poefy::Poem.new(:spec_whitman, {
618
+ form_from_text: @text,
619
+ syllable: 6
620
+ })
621
+ poem = poefy.poem
622
+ poem.map!(&:strip!)
623
+ expect(poem.count).to be @line_count
624
+ expect(poem[0]).to eq "[Chorus 1]"
625
+ expect(poem[8]).to eq "[Verse 1]"
626
+ expect(poem[5]).to eq poem[4]
627
+ expect(poem[6]).to eq poem[4]
628
+ poefy.close
629
+ end
630
+
631
+ it "should correctly modify the poetic form 2" do
632
+ poefy = Poefy::Poem.new :spec_whitman
633
+ poem = poefy.poem({
634
+ form_from_text: @text,
635
+ syllable: 6
636
+ })
637
+ poem.map!(&:strip!)
638
+ expect(poem.count).to be @line_count
639
+ expect(poem[0]).to eq "[Chorus 1]"
640
+ expect(poem[8]).to eq "[Verse 1]"
641
+ expect(poem[5]).to eq poem[4]
642
+ expect(poem[6]).to eq poem[4]
643
+ poefy.close
644
+ end
645
+
646
+ it "should correctly modify the poetic form 3" do
647
+ poefy = Poefy::Poem.new(:spec_whitman, {
648
+ form_from_text: @text
649
+ })
650
+ poem = poefy.poem({
651
+ syllable: 6
652
+ })
653
+ poem.map!(&:strip!)
654
+ expect(poem.count).to be @line_count
655
+ expect(poem[0]).to eq "[Chorus 1]"
656
+ expect(poem[8]).to eq "[Verse 1]"
657
+ expect(poem[5]).to eq poem[4]
658
+ expect(poem[6]).to eq poem[4]
659
+ poefy.close
660
+ end
661
+
662
+ it "should correctly replace the poetic form" do
663
+ poefy = Poefy::Poem.new(:spec_whitman, {
664
+ syllable: 6
665
+ })
666
+ poem = poefy.poem({
667
+ form_from_text: @text
668
+ })
669
+ poem.map!(&:strip!)
670
+ expect(poem.count).to be @line_count
671
+ expect(poem[0]).to eq "[Chorus 1]"
672
+ expect(poem[8]).to eq "[Verse 1]"
673
+ expect(poem[5]).to eq poem[4]
674
+ expect(poem[6]).to eq poem[4]
675
+ poefy.close
676
+ end
677
+
678
+ end
679
+
680
+ end
681
+
682
+ ################################################################################
@@ -0,0 +1,11 @@
1
+ require 'bundler/setup'
2
+ Bundler.setup
3
+
4
+ require 'tempfile'
5
+ require 'poefy'
6
+
7
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
8
+
9
+ RSpec.configure do |config|
10
+ # some (optional) config here
11
+ end
metadata ADDED
@@ -0,0 +1,151 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: poefy-pg
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Paul Thompson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-10-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.13'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.13'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: ruby_rhymes
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.1'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: poefy
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.0'
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: 1.0.0
79
+ type: :runtime
80
+ prerelease: false
81
+ version_requirements: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - "~>"
84
+ - !ruby/object:Gem::Version
85
+ version: '1.0'
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: 1.0.0
89
+ - !ruby/object:Gem::Dependency
90
+ name: pg
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '0.21'
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: 0.21.0
99
+ type: :runtime
100
+ prerelease: false
101
+ version_requirements: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - "~>"
104
+ - !ruby/object:Gem::Version
105
+ version: '0.21'
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: 0.21.0
109
+ description: PostgreSQL interface for the 'poefy' gem
110
+ email:
111
+ - nossidge@gmail.com
112
+ executables: []
113
+ extensions: []
114
+ extra_rdoc_files: []
115
+ files:
116
+ - ".gitignore"
117
+ - ".rspec"
118
+ - Gemfile
119
+ - LICENSE
120
+ - README.md
121
+ - Rakefile
122
+ - lib/poefy/pg.rb
123
+ - lib/poefy/pg/version.rb
124
+ - poefy-pg.gemspec
125
+ - spec/poefy_pg_spec.rb
126
+ - spec/spec_helper.rb
127
+ homepage: https://github.com/nossidge/poefy-pg
128
+ licenses:
129
+ - GPL-3.0
130
+ metadata: {}
131
+ post_install_message:
132
+ rdoc_options: []
133
+ require_paths:
134
+ - lib
135
+ required_ruby_version: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ required_rubygems_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ requirements: []
146
+ rubyforge_project:
147
+ rubygems_version: 2.5.2
148
+ signing_key:
149
+ specification_version: 4
150
+ summary: PostgreSQL interface for the 'poefy' gem
151
+ test_files: []