libis-tools 0.9.42 → 0.9.43

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ccaa2ba1a94d6b88f3b45135a81b62ea9a0a76ac
4
- data.tar.gz: 74d858081b5baf3a1f3181f9a3ff790a70e942e8
3
+ metadata.gz: 637fd6ebde184f6dcedfb30f78b4b47ec1a7aa35
4
+ data.tar.gz: 446600a929178ec33509865ae9b069e26b508475
5
5
  SHA512:
6
- metadata.gz: add083b5399e12b82820d9674e8568a921d8e0a81473c092512c104d28c2ffd1a73b584eb9fb09f4bec359c71f2440ed69dd8a1ad061f9594bb68600e9a7705b
7
- data.tar.gz: f65e1a15ef0b042a4ad13c386ce4fcc87f4dcc60db6de59987f621474803d7cd120f91fe0d410288d3446fbbf0d8fec92981a62b0314b19947770847f00d24d8
6
+ metadata.gz: f1de8197f9df452ccfac937d3973df91fb2c76baed18858bfcbc4314d1a55800a42bd23a56299e65bfba4f10e3aaf963350cff7ad36f4a24497ec25e4bcc48af
7
+ data.tar.gz: 5d6c903ad244e87b0bd1301b2127deff90da0037a2ac6bb52493c3b8d69af49def55e68c049c1cf9968e9e9d8c8c86a8217f6731314474be22e4329543efc024
data/lib/libis/tools.rb CHANGED
@@ -7,11 +7,12 @@ module Libis
7
7
  autoload :Command, 'libis/tools/command'
8
8
  autoload :Config, 'libis/tools/config'
9
9
  autoload :ConfigFile, 'libis/tools/config_file'
10
- autoload :DCRecord, 'libis/tools/dc_record'
10
+ autoload :Csv, 'libis/tools/csv'
11
11
  autoload :DeepStruct, 'libis/tools/deep_struct'
12
12
  autoload :Logger, 'libis/tools/logger'
13
13
  autoload :MetsFile, 'libis/tools/mets_file'
14
14
  autoload :Parameter, 'libis/tools/parameter'
15
+ autoload :Spreadsheet, 'libis/tools/spreadsheet'
15
16
  autoload :ThreadSafe, 'libis/tools/thread_safe'
16
17
  autoload :XmlDocument, 'libis/tools/xml_document'
17
18
 
@@ -0,0 +1,86 @@
1
+ require 'roo'
2
+ require 'roo-xls'
3
+ require 'roo-google'
4
+
5
+ module Roo
6
+ class HeaderRowIncompleteError < Error;
7
+ end
8
+ class Base
9
+
10
+ def each(options = {})
11
+ return to_enum(:each, options) unless block_given?
12
+
13
+ if options.empty?
14
+ 1.upto(last_row) do |line|
15
+ yield row(line)
16
+ end
17
+ else
18
+ clean_sheet_if_need(options)
19
+ search_or_set_header(options)
20
+ headers = @headers ||
21
+ Hash[(first_column..last_column).map do |col|
22
+ [cell(@header_line, col), col]
23
+ end]
24
+
25
+ start_line = @header_line || 1
26
+ start_line = (@header_line || 0) + 1 if @options[:skip_headers]
27
+ start_line.upto(last_row) do |line|
28
+ yield(Hash[headers.map { |k, v| [k, cell(line, v)] }])
29
+ end
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def row_with(query, return_headers = false)
36
+ line_no = 0
37
+ each do |row|
38
+ line_no += 1
39
+ headers = query.map { |q| row.grep(q)[0] }.compact
40
+
41
+ if headers.length == query.length
42
+ @header_line = line_no
43
+ return return_headers ? headers : line_no
44
+ elsif line_no > 100
45
+ raise Roo::HeaderRowNotFoundError
46
+ elsif headers.length > 0
47
+ # partial match
48
+ @header_line = line_no
49
+ raise Roo::HeaderRowIncompleteError unless @options[:force_headers]
50
+ return return_headers ? headers : line_no
51
+ end
52
+ end
53
+ raise Roo::HeaderRowNotFoundError
54
+ end
55
+
56
+ def search_or_set_header(options)
57
+ if options[:header_search]
58
+ @headers = nil
59
+ @header_line = row_with(options[:header_search])
60
+ elsif [:first_row, true].include?(options[:headers])
61
+ @headers = Hash[row(first_row).map.with_index{ |x, i| [x, i + first_column] }]
62
+ @header_line = first_row
63
+ else
64
+ set_headers(options)
65
+ end
66
+ end
67
+
68
+ def set_headers(hash = {})
69
+ # try to find header row with all values or give an error
70
+ # then create new hash by indexing strings and keeping integers for header array
71
+ @headers = row_with(hash.values, true)
72
+ @headers = Hash[hash.keys.zip(@headers.map { |x| header_index(x) })]
73
+ rescue Roo::HeaderRowNotFoundError => e
74
+ if @options[:force_headers]
75
+ # Finding headers failed. Force the headers in the order they are given, but up to the last column
76
+ @headers = {}
77
+ hash.keys.each.with_index { |k, i| @headers[k] = i + first_column if i + first_column <= last_column }
78
+ @header_line = first_row
79
+ @header_line -= 1 unless hash.values.any? { |v| row(1).include? v } # partial match
80
+ else
81
+ raise e
82
+ end
83
+ end
84
+
85
+ end
86
+ end
@@ -0,0 +1,170 @@
1
+ require 'libis/tools/extend/roo'
2
+ require 'libis/tools/extend/hash'
3
+ require 'awesome_print'
4
+
5
+ def PUTS(*args)
6
+ puts *args
7
+ end
8
+
9
+ module Libis
10
+ module Tools
11
+
12
+ class Spreadsheet
13
+
14
+ # Spreadsheet reader.
15
+ #
16
+ # This class supports CSV, Excel 2007-2016, Excel (pre-2007), LibreOffice/OpenOffice Calc and Google spreadsheets
17
+ # thanks to the Roo (http://github.com/roo-rb/roo) project.
18
+ #
19
+ # The first argument is the file name to read. For spreadsheets, append '|' and the sheet name to specify the
20
+ # sheet to read.
21
+ #
22
+ # The second argument is a Hash with options. The options can be:
23
+ # - required: a list of headers that need to be present. The list can be an Array containing the litteral header
24
+ # values expected. Alternatively, a Hash is also allowed with alternative header names as keys and litteral
25
+ # names as values. If a :headers keys is present in the Hash with a value of true or :first, whatever is on the
26
+ # first row, will be used as header values, ignoring the rest of the Hash. A key of :header_search
27
+ # Default is empty array, meaning to use whatever is on the first row as header.
28
+ # - optional: a list of headers that may be present, but are not required. Similar format as above. Default is
29
+ # empty array
30
+ # - extension: :csv, :xlsx, :xlsm, :ods, :xls, :google to help the library in deciding what format the file is in.
31
+ #
32
+ # The following options are only applicable to CSV input files and are ignored otherwise.
33
+ # - encoding: the encoding of the CSV file. e.g. 'windows-1252:UTF-8' to convert the input from windows code page
34
+ # 1252 to UTF-8 during file reading
35
+ # - col_sep: column separator. Default is ',', but can be set to "\t" for TSV files.
36
+ # - quote_char: character for quoting.
37
+ #
38
+ # Resources are created during initialisation and should be freed by calling the #close method.
39
+ #
40
+ # @param [String] file_name
41
+ # @param [Hash] opts
42
+ def initialize(file_name, opts = {})
43
+ options = {
44
+ csv_options: [:encoding, :col_sep, :quote_char].inject({}) do |h, k|
45
+ h[k] = opts.delete(k) if opts[k]
46
+ h
47
+ end.merge(
48
+ encoding: 'UTF-8',
49
+ col_sep: ',',
50
+ quote_char: '"',
51
+ ),
52
+ skip_headers: true,
53
+ force_headers: true,
54
+ }.merge(opts)
55
+
56
+ required_headers = options.delete(:required) || []
57
+ optional_headers = options.delete(:optional) || []
58
+
59
+ file, sheet = file_name.split('|')
60
+ @ss = ::Roo::Spreadsheet.open(file, options)
61
+ @ss.default_sheet = sheet if sheet
62
+
63
+ check_headers(required_headers, optional_headers)
64
+
65
+ end
66
+
67
+ # Iterate over sheet content.
68
+ #
69
+ # The options Hash can contain the following keys:
70
+ # - :sheet - overwrites default sheet name
71
+ # - :required - Array or Hash of required headers
72
+ # - :optional - Array or Hash of optional headers
73
+ #
74
+ # Each iteration, a Hash will be passed with the key names as specified in the header options and the
75
+ # corresponding cell values.
76
+ #
77
+ # @param [Hash] options
78
+ def each(options = {}, &block)
79
+ @ss.default_sheet = options[:sheet] if options[:sheet]
80
+ @ss.each(check_headers(options[:required], options[:optional]), &block)
81
+ end
82
+
83
+ def parse(options = {})
84
+ @ss.default_sheet = options[:sheet] if options[:sheet]
85
+ @ss.parse(check_headers(options[:required], options[:optional]))
86
+ end
87
+
88
+ def shift
89
+ return nil unless @current_row < @ss.last_row
90
+ @current_row += 1
91
+ Hash[@ss.row(@current_row).map.with_index { |v, i| [headers[i], v] }]
92
+ end
93
+
94
+ # Open and iterate over sheet content.
95
+ #
96
+ # @param @see #initialize
97
+ def self.foreach(file_name, opts = {}, &block)
98
+ Libis::Tools::Spreadsheet.new(file_name, opts).each(&block)
99
+ end
100
+
101
+ def headers
102
+ (@ss.headers || {}).keys + @extra_headers
103
+ end
104
+
105
+ private
106
+
107
+ def check_headers(required_headers, optional_headers)
108
+ return @header_options unless required_headers || optional_headers
109
+ header_options = {}
110
+ required_headers ||= []
111
+ optional_headers ||= []
112
+ unless required_headers.is_a?(Hash) || required_headers.is_a?(Array)
113
+ raise RuntimeError, 'Required headers should be either a Hash or an Array.'
114
+ end
115
+ unless optional_headers.is_a?(Hash) || optional_headers.is_a?(Array)
116
+ raise RuntimeError, 'Optional headers should be either a Hash or an Array.'
117
+ end
118
+ if required_headers.empty?
119
+ if optional_headers.empty?
120
+ header_options[:headers] = :first_row
121
+ else
122
+ header_options[:header_search] =
123
+ (optional_headers.is_a?(Hash) ? optional_headers.values : optional_headers)
124
+ end
125
+ else
126
+ header_options =
127
+ required_headers.is_a?(Hash) ? required_headers : Hash[required_headers.map { |x| [x] * 2 }]
128
+ header_options.merge!(
129
+ optional_headers.is_a?(Hash) ? optional_headers : Hash[optional_headers.map { |x| [x] * 2 }]
130
+ )
131
+ end
132
+
133
+ required_headers = required_headers.values if required_headers.is_a?(Hash)
134
+
135
+ @ss.each(header_options) { break }
136
+ @current_row = @ss.header_line
137
+
138
+ # checks
139
+ PUTS 'required_headers:', required_headers.ai
140
+ PUTS 'header_line: ', @ss.header_line
141
+ PUTS 'header_row:', @ss.row([@ss.header_line, 1].max).ai
142
+ found_headers = required_headers & @ss.row([@current_row, 1].max)
143
+ if found_headers.empty?
144
+ # No headers found - check if there are enough columns to satisfy the required headers
145
+ if required_headers.size > (@ss.last_column - @ss.first_column) + 1
146
+ raise RuntimeError, 'Sheet does not contain enough columns.'
147
+ end
148
+ elsif found_headers.size < required_headers.size
149
+ # Some, but not all headers found
150
+ raise RuntimeError, "Headers not found: #{required_headers - found_headers}."
151
+ else
152
+ # All required headers found
153
+ end
154
+
155
+ PUTS 'sheet headers: ', @ss.headers.ai
156
+ PUTS 'header_line: ', @ss.header_line.ai
157
+ PUTS 'header_options: ', header_options.ai
158
+ PUTS 'header row:', @ss.row(@ss.header_line).ai
159
+ @extra_headers = (required_headers.empty? && optional_headers.empty?) ? [] :
160
+ @ss.row(@ss.header_line).keep_if { |x| x && !header_options.values.include?(x) }
161
+ PUTS '@extra_headers:', @extra_headers.ai
162
+ PUTS 'headers:', headers.ai
163
+
164
+ @header_options = header_options.merge(Hash[@extra_headers.map { |v| [v] * 2 }])
165
+ end
166
+
167
+ end
168
+
169
+ end
170
+ end
@@ -1,5 +1,5 @@
1
1
  module Libis
2
2
  module Tools
3
- VERSION = '0.9.42'
3
+ VERSION = '0.9.43'
4
4
  end
5
5
  end
data/libis-tools.gemspec CHANGED
@@ -44,4 +44,7 @@ Gem::Specification.new do |spec|
44
44
  spec.add_runtime_dependency 'logging', '~> 2.0'
45
45
  spec.add_runtime_dependency 'concurrent-ruby', '~> 1.0'
46
46
  spec.add_runtime_dependency 'yard', '~> 0.8'
47
+ spec.add_runtime_dependency 'roo', '~> 2.5'
48
+ spec.add_runtime_dependency 'roo-xls', '~> 1.0'
49
+ spec.add_runtime_dependency 'roo-google', '~> 1.0'
47
50
  end
Binary file
@@ -21,7 +21,6 @@ describe 'ScopeMapper' do
21
21
  converted_dc = input_dc.to_dc
22
22
  expect(converted_dc).to be_a Libis::Tools::Metadata::DublinCoreRecord
23
23
 
24
- expect(converted_dc.root).to be_equivalent_to output_dc.root
25
24
  converted_dc.root.elements.each_with_index do |element, i|
26
25
  expect(element).to be_equivalent_to(output_dc.root.elements[i])
27
26
  end
@@ -0,0 +1,1335 @@
1
+ # encoding: utf-8
2
+ require_relative 'spec_helper'
3
+ require 'rspec/matchers'
4
+ require 'libis/tools/spreadsheet'
5
+
6
+ describe 'Libis::Tools::Spreadsheet' do
7
+
8
+ let(:path) { File.absolute_path('data', File.dirname(__FILE__)) }
9
+ let(:ss) {
10
+ Libis::Tools::Spreadsheet.new(
11
+ File.join(path, file_name),
12
+ required: required_headers,
13
+ optional: optional_headers
14
+ )
15
+ }
16
+
17
+ let(:optional_headers) { [] }
18
+
19
+ context 'CSV file' do
20
+ context 'with headers' do
21
+ let(:file_name) { 'test-headers.csv' }
22
+
23
+ context 'well-formed' do
24
+
25
+ let(:required_headers) { %w'FirstName LastName' }
26
+
27
+ it 'opens correctly' do
28
+ expect{ ss }.not_to raise_error
29
+ end
30
+
31
+ it 'contains expected headers' do
32
+ required_headers.each do |header|
33
+ expect(ss.headers).to include header
34
+ end
35
+ expect(ss.headers).to eq %w'FirstName LastName address'
36
+ end
37
+
38
+ it '#shift returns Hash object' do
39
+ row = ss.shift
40
+ expect(row).to be_a Hash
41
+ expect(row['FirstName']).to eq 'John'
42
+ expect(row['LastName']).to eq 'Smith'
43
+ expect(row['address']).to eq 'mystreet 1, myplace'
44
+ expect(row['phone']).to be_nil
45
+ end
46
+
47
+ it '#parse returns Array of Hash objects' do
48
+ rows = ss.parse
49
+ expect(rows).to be_a Array
50
+ expect(rows.size).to eq 1
51
+ row = rows[0]
52
+ expect(row).to be_a Hash
53
+ expect(row['FirstName']).to eq 'John'
54
+ expect(row['LastName']).to eq 'Smith'
55
+ expect(row['address']).to eq 'mystreet 1, myplace'
56
+ expect(row['phone']).to be_nil
57
+ end
58
+
59
+ end
60
+
61
+ context 'not specified' do
62
+
63
+ let(:required_headers) { [] }
64
+
65
+ it 'opens correctly' do
66
+ expect{ ss }.not_to raise_error
67
+ end
68
+
69
+ it 'contains expected headers' do
70
+ expect(ss.headers).to eq %w'FirstName LastName address'
71
+ end
72
+
73
+ it '#shift returns Hash object' do
74
+ row = ss.shift
75
+ expect(row).to be_a Hash
76
+ expect(row['FirstName']).to eq 'John'
77
+ expect(row['LastName']).to eq 'Smith'
78
+ expect(row['address']).to eq 'mystreet 1, myplace'
79
+ expect(row['phone']).to be_nil
80
+ end
81
+
82
+ it '#parse returns Array of Hash objects' do
83
+ rows = ss.parse
84
+ expect(rows).to be_a Array
85
+ expect(rows.size).to eq 1
86
+ row = rows[0]
87
+ expect(row).to be_a Hash
88
+ expect(row['FirstName']).to eq 'John'
89
+ expect(row['LastName']).to eq 'Smith'
90
+ expect(row['address']).to eq 'mystreet 1, myplace'
91
+ expect(row['phone']).to be_nil
92
+ end
93
+
94
+ end
95
+
96
+ context 'not well-formed' do
97
+
98
+ let(:required_headers) { %w'FirstName LastName address phone'}
99
+
100
+ it 'throws error when opened' do
101
+ expect { ss }.to raise_error(RuntimeError, 'Headers not found: ["phone"].')
102
+ end
103
+ end
104
+
105
+ end
106
+
107
+ context 'without headers' do
108
+ let(:file_name) { 'test-noheaders.csv' }
109
+
110
+ context 'well-formed and strict' do
111
+ let(:required_headers) { %w'FirstName LastName' }
112
+
113
+ it 'opens correctly' do
114
+ expect { ss }.not_to raise_error
115
+ end
116
+
117
+ it 'contains only required headers' do
118
+ required_headers.each do |header|
119
+ expect(ss.headers).to include header
120
+ end
121
+ expect(ss.headers).to eq %w'FirstName LastName'
122
+ end
123
+
124
+ it '#shift returns Hash object' do
125
+ row = ss.shift
126
+ expect(row).to be_a Hash
127
+ expect(row['FirstName']).to eq 'John'
128
+ expect(row['LastName']).to eq 'Smith'
129
+ expect(row['address']).to be_nil
130
+ expect(row['phone']).to be_nil
131
+ end
132
+
133
+ it '#parse returns Array of Hash objects' do
134
+ rows = ss.parse
135
+ expect(rows).to be_a Array
136
+ expect(rows.size).to eq 1
137
+ row = rows[0]
138
+ expect(row).to be_a Hash
139
+ expect(row['FirstName']).to eq 'John'
140
+ expect(row['LastName']).to eq 'Smith'
141
+ expect(row['address']).to be_nil
142
+ expect(row['phone']).to be_nil
143
+ end
144
+
145
+ end
146
+
147
+ context 'well-formed with optional headers' do
148
+ let(:required_headers) { %w'FirstName LastName' }
149
+ let(:optional_headers) { %w'address' }
150
+
151
+ it 'opens correctly' do
152
+ expect { ss }.not_to raise_error
153
+ end
154
+
155
+ it 'contains required and optional headers' do
156
+ required_headers.each do |header|
157
+ expect(ss.headers).to include header
158
+ end
159
+ optional_headers.each do |header|
160
+ expect(ss.headers).to include header
161
+ end
162
+ expect(ss.headers).to eq %w'FirstName LastName address'
163
+ end
164
+
165
+ it '#shift returns Hash object' do
166
+ row = ss.shift
167
+ expect(row).to be_a Hash
168
+ expect(row['FirstName']).to eq 'John'
169
+ expect(row['LastName']).to eq 'Smith'
170
+ expect(row['address']).to eq 'mystreet 1, myplace'
171
+ expect(row['phone']).to be_nil
172
+ end
173
+
174
+ it '#parse returns Array of Hash objects' do
175
+ rows = ss.parse
176
+ expect(rows).to be_a Array
177
+ expect(rows.size).to eq 1
178
+ row = rows[0]
179
+ expect(row).to be_a Hash
180
+ expect(row['FirstName']).to eq 'John'
181
+ expect(row['LastName']).to eq 'Smith'
182
+ expect(row['address']).to eq 'mystreet 1, myplace'
183
+ expect(row['phone']).to be_nil
184
+ end
185
+
186
+ end
187
+
188
+ context 'missing optional headers' do
189
+
190
+ let(:required_headers) { %w'FirstName LastName address' }
191
+ let(:optional_headers) { %w'phone' }
192
+
193
+ it 'opens correctly' do
194
+ expect { ss }.not_to raise_error
195
+ end
196
+
197
+ it 'contains only required headers' do
198
+ required_headers.each do |header|
199
+ expect(ss.headers).to include header
200
+ end
201
+ optional_headers.each do |header|
202
+ expect(ss.headers).not_to include header
203
+ end
204
+ expect(ss.headers).to eq %w'FirstName LastName address'
205
+ end
206
+
207
+ it '#shift returns Hash object' do
208
+ row = ss.shift
209
+ expect(row).to be_a Hash
210
+ expect(row['FirstName']).to eq 'John'
211
+ expect(row['LastName']).to eq 'Smith'
212
+ expect(row['address']).to eq 'mystreet 1, myplace'
213
+ expect(row['phone']).to be_nil
214
+ end
215
+
216
+ it '#parse returns Array of Hash objects' do
217
+ rows = ss.parse
218
+ expect(rows).to be_a Array
219
+ expect(rows.size).to eq 1
220
+ row = rows[0]
221
+ expect(row).to be_a Hash
222
+ expect(row['FirstName']).to eq 'John'
223
+ expect(row['LastName']).to eq 'Smith'
224
+ expect(row['address']).to eq 'mystreet 1, myplace'
225
+ expect(row['phone']).to be_nil
226
+ end
227
+
228
+ end
229
+
230
+ context 'missing required header' do
231
+ let(:required_headers) { %w'FirstName LastName address phone'}
232
+
233
+ it 'throws error when opened' do
234
+ expect { ss }.to raise_error(RuntimeError, 'Sheet does not contain enough columns.')
235
+ end
236
+
237
+ end
238
+
239
+ end
240
+
241
+ end
242
+
243
+ context 'XLSX file' do
244
+ # let(:ss) {
245
+ # Libis::Tools::Spreadsheet.new(
246
+ # File.join(path, file_name),
247
+ # required: required_headers,
248
+ # optional: optional_headers,
249
+ # extension: :xlsx
250
+ # )
251
+ # }
252
+
253
+ context 'with headers' do
254
+ let(:file_name) { 'test.xlsx|Expenses' }
255
+
256
+ context 'well-formed' do
257
+
258
+ let(:required_headers) { %w'Date Amount' }
259
+
260
+ it 'opens correctly' do
261
+ expect{ ss }.not_to raise_error
262
+ end
263
+
264
+ it 'contains expected headers' do
265
+ required_headers.each do |header|
266
+ expect(ss.headers).to include header
267
+ end
268
+ expect(ss.headers).to eq %w'Date Amount Code Remark'
269
+ end
270
+
271
+ it '#shift returns Hash object' do
272
+ row = ss.shift
273
+ expect(row).to be_a Hash
274
+ expect(row['Date']).to eq Date.new(2016, 05, 10)
275
+ expect(row['Amount']).to eq 1270.0
276
+ expect(row['Code']).to eq 1
277
+ expect(row['Remark']).to eq 'a'
278
+ expect(row['dummy']).to be_nil
279
+ end
280
+
281
+ it '#parse returns Array of Hash objects' do
282
+ rows = ss.parse
283
+ expect(rows).to be_a Array
284
+ expect(rows.size).to eq 17
285
+ row = rows[0]
286
+ expect(row).to be_a Hash
287
+ expect(row['Date']).to eq Date.new(2016,5,10)
288
+ expect(row['Amount']).to eq 1270.0
289
+ expect(row['Code']).to eq 1
290
+ expect(row['Remark']).to eq 'a'
291
+ expect(row['dummy']).to be_nil
292
+ row = rows[13]
293
+ expect(row).to be_a Hash
294
+ expect(row['Date']).to eq Date.new(2016,7,1)
295
+ expect(row['Amount']).to eq 3705.0
296
+ expect(row['Code']).to eq 3
297
+ expect(row['Remark']).to eq 'b'
298
+ expect(row['dummy']).to be_nil
299
+ end
300
+
301
+ end
302
+
303
+ context 'not specified' do
304
+
305
+ let(:required_headers) { [] }
306
+
307
+ it 'opens correctly' do
308
+ expect{ ss }.not_to raise_error
309
+ end
310
+
311
+ it 'contains expected headers' do
312
+ required_headers.each do |header|
313
+ expect(ss.headers).to include header
314
+ end
315
+ expect(ss.headers).to eq %w'Date Amount Code Remark'
316
+ end
317
+
318
+ it '#shift returns Hash object' do
319
+ row = ss.shift
320
+ expect(row).to be_a Hash
321
+ expect(row['Date']).to eq Date.new(2016, 05, 10)
322
+ expect(row['Amount']).to eq 1270.0
323
+ expect(row['Code']).to eq 1
324
+ expect(row['Remark']).to eq 'a'
325
+ expect(row['dummy']).to be_nil
326
+ end
327
+
328
+ it '#parse returns Array of Hash objects' do
329
+ rows = ss.parse
330
+ expect(rows).to be_a Array
331
+ expect(rows.size).to eq 17
332
+ row = rows[0]
333
+ expect(row).to be_a Hash
334
+ expect(row['Date']).to eq Date.new(2016,5,10)
335
+ expect(row['Amount']).to eq 1270.0
336
+ expect(row['Code']).to eq 1
337
+ expect(row['Remark']).to eq 'a'
338
+ expect(row['dummy']).to be_nil
339
+ row = rows[13]
340
+ expect(row).to be_a Hash
341
+ expect(row['Date']).to eq Date.new(2016,7,1)
342
+ expect(row['Amount']).to eq 3705.0
343
+ expect(row['Code']).to eq 3
344
+ expect(row['Remark']).to eq 'b'
345
+ expect(row['dummy']).to be_nil
346
+ end
347
+
348
+ end
349
+
350
+ context 'not well-formed' do
351
+
352
+ let(:required_headers) { %w'Date dummy1 Amount dummy2'}
353
+
354
+ it 'throws error when opened' do
355
+ expect { ss }.to raise_error(RuntimeError, 'Headers not found: ["dummy1", "dummy2"].')
356
+ end
357
+ end
358
+
359
+ end
360
+
361
+ context 'without headers' do
362
+ let(:file_name) { 'test.xlsx|ExpensesNoHeaders' }
363
+
364
+ context 'well-formed and strict' do
365
+ let(:required_headers) { %w'Date Amount' }
366
+
367
+ it 'opens correctly' do
368
+ expect { ss }.not_to raise_error
369
+ end
370
+
371
+ it 'contains only required headers' do
372
+ required_headers.each do |header|
373
+ expect(ss.headers).to include header
374
+ end
375
+ expect(ss.headers).to eq %w'Date Amount'
376
+ end
377
+
378
+ it '#shift returns Hash object' do
379
+ row = ss.shift
380
+ expect(row).to be_a Hash
381
+ expect(row['Date']).to eq Date.new(2016, 05, 10)
382
+ expect(row['Amount']).to eq 1270.0
383
+ expect(row['Code']).to be_nil
384
+ expect(row['Remark']).to be_nil
385
+ expect(row['dummy']).to be_nil
386
+ end
387
+
388
+ it '#parse returns Array of Hash objects' do
389
+ rows = ss.parse
390
+ expect(rows).to be_a Array
391
+ expect(rows.size).to eq 17
392
+ row = rows[0]
393
+ expect(row).to be_a Hash
394
+ expect(row['Date']).to eq Date.new(2016,5,10)
395
+ expect(row['Amount']).to eq 1270.0
396
+ expect(row['Code']).to be_nil
397
+ expect(row['Remark']).to be_nil
398
+ expect(row['dummy']).to be_nil
399
+ row = rows[13]
400
+ expect(row).to be_a Hash
401
+ expect(row['Date']).to eq Date.new(2016,7,1)
402
+ expect(row['Amount']).to eq 3705.0
403
+ expect(row['Code']).to be_nil
404
+ expect(row['Remark']).to be_nil
405
+ expect(row['dummy']).to be_nil
406
+ end
407
+
408
+ end
409
+
410
+ context 'well-formed with optional headers' do
411
+ let(:required_headers) { %w'Date Amount' }
412
+ let(:optional_headers) { %w'Code' }
413
+
414
+ it 'opens correctly' do
415
+ expect { ss }.not_to raise_error
416
+ end
417
+
418
+ it 'contains required and optional headers' do
419
+ required_headers.each do |header|
420
+ expect(ss.headers).to include header
421
+ end
422
+ optional_headers.each do |header|
423
+ expect(ss.headers).to include header
424
+ end
425
+ expect(ss.headers).to eq %w'Date Amount Code'
426
+ end
427
+
428
+ it '#shift returns Hash object' do
429
+ row = ss.shift
430
+ expect(row).to be_a Hash
431
+ expect(row['Date']).to eq Date.new(2016, 05, 10)
432
+ expect(row['Amount']).to eq 1270.0
433
+ expect(row['Code']).to eq 1
434
+ expect(row['Remark']).to be_nil
435
+ expect(row['dummy']).to be_nil
436
+ end
437
+
438
+ it '#parse returns Array of Hash objects' do
439
+ rows = ss.parse
440
+ expect(rows).to be_a Array
441
+ expect(rows.size).to eq 17
442
+ row = rows[0]
443
+ expect(row).to be_a Hash
444
+ expect(row['Date']).to eq Date.new(2016,5,10)
445
+ expect(row['Amount']).to eq 1270.0
446
+ expect(row['Code']).to eq 1
447
+ expect(row['Remark']).to be_nil
448
+ expect(row['dummy']).to be_nil
449
+ row = rows[13]
450
+ expect(row).to be_a Hash
451
+ expect(row['Date']).to eq Date.new(2016,7,1)
452
+ expect(row['Amount']).to eq 3705.0
453
+ expect(row['Code']).to eq 3
454
+ expect(row['Remark']).to be_nil
455
+ expect(row['dummy']).to be_nil
456
+ end
457
+
458
+ end
459
+
460
+ context 'missing optional headers' do
461
+
462
+ let(:required_headers) { %w'Date Amount Code Remark' }
463
+ let(:optional_headers) { %w'dummy' }
464
+
465
+ it 'opens correctly' do
466
+ expect { ss }.not_to raise_error
467
+ end
468
+
469
+ it 'contains only required headers' do
470
+ required_headers.each do |header|
471
+ expect(ss.headers).to include header
472
+ end
473
+ optional_headers.each do |header|
474
+ expect(ss.headers).not_to include header
475
+ end
476
+ expect(ss.headers).to eq %w'Date Amount Code Remark'
477
+ end
478
+
479
+ it '#shift returns Hash object' do
480
+ row = ss.shift
481
+ expect(row).to be_a Hash
482
+ expect(row['Date']).to eq Date.new(2016, 05, 10)
483
+ expect(row['Amount']).to eq 1270.0
484
+ expect(row['Code']).to eq 1
485
+ expect(row['Remark']).to eq 'a'
486
+ expect(row['dummy']).to be_nil
487
+ end
488
+
489
+ it '#parse returns Array of Hash objects' do
490
+ rows = ss.parse
491
+ expect(rows).to be_a Array
492
+ expect(rows.size).to eq 17
493
+ row = rows[0]
494
+ expect(row).to be_a Hash
495
+ expect(row['Date']).to eq Date.new(2016,5,10)
496
+ expect(row['Amount']).to eq 1270.0
497
+ expect(row['Code']).to eq 1
498
+ expect(row['Remark']).to eq 'a'
499
+ expect(row['dummy']).to be_nil
500
+ row = rows[13]
501
+ expect(row).to be_a Hash
502
+ expect(row['Date']).to eq Date.new(2016,7,1)
503
+ expect(row['Amount']).to eq 3705.0
504
+ expect(row['Code']).to eq 3
505
+ expect(row['Remark']).to eq 'b'
506
+ expect(row['dummy']).to be_nil
507
+ end
508
+
509
+ end
510
+
511
+ context 'missing required header' do
512
+ let(:required_headers) { %w'Date Amount Code Remark dummy' }
513
+
514
+ it 'throws error when opened' do
515
+ expect { ss }.to raise_error(RuntimeError, 'Sheet does not contain enough columns.')
516
+ end
517
+
518
+ end
519
+
520
+ end
521
+
522
+ context 'blank rows with headers' do
523
+ let(:file_name) { 'test.xlsx|ExpensesBlankRows' }
524
+
525
+ context 'well-formed' do
526
+
527
+ let(:required_headers) { %w'Date Amount' }
528
+
529
+ it 'opens correctly' do
530
+ expect{ ss }.not_to raise_error
531
+ end
532
+
533
+ it 'contains expected headers' do
534
+ required_headers.each do |header|
535
+ expect(ss.headers).to include header
536
+ end
537
+ expect(ss.headers).to eq %w'Date Amount Code Remark'
538
+ end
539
+
540
+ it '#shift returns Hash object' do
541
+ row = ss.shift
542
+ expect(row).to be_a Hash
543
+ expect(row['Date']).to eq Date.new(2016, 05, 10)
544
+ expect(row['Amount']).to eq 1270.0
545
+ expect(row['Code']).to eq 1
546
+ expect(row['Remark']).to eq 'a'
547
+ expect(row['dummy']).to be_nil
548
+ end
549
+
550
+ it '#parse returns Array of Hash objects' do
551
+ rows = ss.parse
552
+ expect(rows).to be_a Array
553
+ expect(rows.size).to eq 17
554
+ row = rows[0]
555
+ expect(row).to be_a Hash
556
+ expect(row['Date']).to eq Date.new(2016,5,10)
557
+ expect(row['Amount']).to eq 1270.0
558
+ expect(row['Code']).to eq 1
559
+ expect(row['Remark']).to eq 'a'
560
+ expect(row['dummy']).to be_nil
561
+ row = rows[13]
562
+ expect(row).to be_a Hash
563
+ expect(row['Date']).to eq Date.new(2016,7,1)
564
+ expect(row['Amount']).to eq 3705.0
565
+ expect(row['Code']).to eq 3
566
+ expect(row['Remark']).to eq 'b'
567
+ expect(row['dummy']).to be_nil
568
+ end
569
+
570
+ end
571
+
572
+ context 'not specified' do
573
+
574
+ let(:required_headers) { [] }
575
+
576
+ it 'opens correctly' do
577
+ expect{ ss }.not_to raise_error
578
+ end
579
+
580
+ it 'contains expected headers' do
581
+ required_headers.each do |header|
582
+ expect(ss.headers).to include header
583
+ end
584
+ expect(ss.headers).to eq %w'Date Amount Code Remark'
585
+ end
586
+
587
+ it '#shift returns Hash object' do
588
+ row = ss.shift
589
+ expect(row).to be_a Hash
590
+ expect(row['Date']).to eq Date.new(2016, 05, 10)
591
+ expect(row['Amount']).to eq 1270.0
592
+ expect(row['Code']).to eq 1
593
+ expect(row['Remark']).to eq 'a'
594
+ expect(row['dummy']).to be_nil
595
+ end
596
+
597
+ it '#parse returns Array of Hash objects' do
598
+ rows = ss.parse
599
+ expect(rows).to be_a Array
600
+ expect(rows.size).to eq 17
601
+ row = rows[0]
602
+ expect(row).to be_a Hash
603
+ expect(row['Date']).to eq Date.new(2016,5,10)
604
+ expect(row['Amount']).to eq 1270.0
605
+ expect(row['Code']).to eq 1
606
+ expect(row['Remark']).to eq 'a'
607
+ expect(row['dummy']).to be_nil
608
+ row = rows[13]
609
+ expect(row).to be_a Hash
610
+ expect(row['Date']).to eq Date.new(2016,7,1)
611
+ expect(row['Amount']).to eq 3705.0
612
+ expect(row['Code']).to eq 3
613
+ expect(row['Remark']).to eq 'b'
614
+ expect(row['dummy']).to be_nil
615
+ end
616
+
617
+ end
618
+
619
+ context 'not well-formed' do
620
+
621
+ let(:required_headers) { %w'Date dummy1 Amount dummy2'}
622
+
623
+ it 'throws error when opened' do
624
+ expect { ss }.to raise_error(RuntimeError, 'Headers not found: ["dummy1", "dummy2"].')
625
+ end
626
+ end
627
+
628
+ end
629
+
630
+ context 'blank rows without headers' do
631
+ let(:file_name) { 'test.xlsx|ExpensesBlankRowsNoHeaders' }
632
+
633
+ context 'well-formed and strict' do
634
+ let(:required_headers) { %w'Date Amount' }
635
+
636
+ it 'opens correctly' do
637
+ expect { ss }.not_to raise_error
638
+ end
639
+
640
+ it 'contains only required headers' do
641
+ required_headers.each do |header|
642
+ expect(ss.headers).to include header
643
+ end
644
+ expect(ss.headers).to eq %w'Date Amount'
645
+ end
646
+
647
+ it '#shift returns Hash object' do
648
+ row = ss.shift
649
+ expect(row).to be_a Hash
650
+ expect(row['Date']).to eq Date.new(2016, 05, 10)
651
+ expect(row['Amount']).to eq 1270.0
652
+ expect(row['Code']).to be_nil
653
+ expect(row['Remark']).to be_nil
654
+ expect(row['dummy']).to be_nil
655
+ end
656
+
657
+ it '#parse returns Array of Hash objects' do
658
+ rows = ss.parse
659
+ expect(rows).to be_a Array
660
+ expect(rows.size).to eq 17
661
+ row = rows[0]
662
+ expect(row).to be_a Hash
663
+ expect(row['Date']).to eq Date.new(2016,5,10)
664
+ expect(row['Amount']).to eq 1270.0
665
+ expect(row['Code']).to be_nil
666
+ expect(row['Remark']).to be_nil
667
+ expect(row['dummy']).to be_nil
668
+ row = rows[13]
669
+ expect(row).to be_a Hash
670
+ expect(row['Date']).to eq Date.new(2016,7,1)
671
+ expect(row['Amount']).to eq 3705.0
672
+ expect(row['Code']).to be_nil
673
+ expect(row['Remark']).to be_nil
674
+ expect(row['dummy']).to be_nil
675
+ end
676
+
677
+ end
678
+
679
+ context 'well-formed with optional headers' do
680
+ let(:required_headers) { %w'Date Amount' }
681
+ let(:optional_headers) { %w'Code' }
682
+
683
+ it 'opens correctly' do
684
+ expect { ss }.not_to raise_error
685
+ end
686
+
687
+ it 'contains required and optional headers' do
688
+ required_headers.each do |header|
689
+ expect(ss.headers).to include header
690
+ end
691
+ optional_headers.each do |header|
692
+ expect(ss.headers).to include header
693
+ end
694
+ expect(ss.headers).to eq %w'Date Amount Code'
695
+ end
696
+
697
+ it '#shift returns Hash object' do
698
+ row = ss.shift
699
+ expect(row).to be_a Hash
700
+ expect(row['Date']).to eq Date.new(2016, 05, 10)
701
+ expect(row['Amount']).to eq 1270.0
702
+ expect(row['Code']).to eq 1
703
+ expect(row['Remark']).to be_nil
704
+ expect(row['dummy']).to be_nil
705
+ end
706
+
707
+ it '#parse returns Array of Hash objects' do
708
+ rows = ss.parse
709
+ expect(rows).to be_a Array
710
+ expect(rows.size).to eq 17
711
+ row = rows[0]
712
+ expect(row).to be_a Hash
713
+ expect(row['Date']).to eq Date.new(2016,5,10)
714
+ expect(row['Amount']).to eq 1270.0
715
+ expect(row['Code']).to eq 1
716
+ expect(row['Remark']).to be_nil
717
+ expect(row['dummy']).to be_nil
718
+ row = rows[13]
719
+ expect(row).to be_a Hash
720
+ expect(row['Date']).to eq Date.new(2016,7,1)
721
+ expect(row['Amount']).to eq 3705.0
722
+ expect(row['Code']).to eq 3
723
+ expect(row['Remark']).to be_nil
724
+ expect(row['dummy']).to be_nil
725
+ end
726
+
727
+ end
728
+
729
+ context 'missing optional headers' do
730
+
731
+ let(:required_headers) { %w'Date Amount Code Remark' }
732
+ let(:optional_headers) { %w'dummy' }
733
+
734
+ it 'opens correctly' do
735
+ expect { ss }.not_to raise_error
736
+ end
737
+
738
+ it 'contains only required headers' do
739
+ required_headers.each do |header|
740
+ expect(ss.headers).to include header
741
+ end
742
+ optional_headers.each do |header|
743
+ expect(ss.headers).not_to include header
744
+ end
745
+ expect(ss.headers).to eq %w'Date Amount Code Remark'
746
+ end
747
+
748
+ it '#shift returns Hash object' do
749
+ row = ss.shift
750
+ expect(row).to be_a Hash
751
+ expect(row['Date']).to eq Date.new(2016, 05, 10)
752
+ expect(row['Amount']).to eq 1270.0
753
+ expect(row['Code']).to eq 1
754
+ expect(row['Remark']).to eq 'a'
755
+ expect(row['dummy']).to be_nil
756
+ end
757
+
758
+ it '#parse returns Array of Hash objects' do
759
+ rows = ss.parse
760
+ expect(rows).to be_a Array
761
+ expect(rows.size).to eq 17
762
+ row = rows[0]
763
+ expect(row).to be_a Hash
764
+ expect(row['Date']).to eq Date.new(2016,5,10)
765
+ expect(row['Amount']).to eq 1270.0
766
+ expect(row['Code']).to eq 1
767
+ expect(row['Remark']).to eq 'a'
768
+ expect(row['dummy']).to be_nil
769
+ row = rows[13]
770
+ expect(row).to be_a Hash
771
+ expect(row['Date']).to eq Date.new(2016,7,1)
772
+ expect(row['Amount']).to eq 3705.0
773
+ expect(row['Code']).to eq 3
774
+ expect(row['Remark']).to eq 'b'
775
+ expect(row['dummy']).to be_nil
776
+ end
777
+
778
+ end
779
+
780
+ context 'missing required header' do
781
+ let(:required_headers) { %w'Date Amount Code Remark dummy' }
782
+
783
+ it 'throws error when opened' do
784
+ expect { ss }.to raise_error(RuntimeError, 'Sheet does not contain enough columns.')
785
+ end
786
+
787
+ end
788
+
789
+ end
790
+
791
+ context 'blank columns with headers' do
792
+ let(:file_name) { 'test.xlsx|ExpensesBlankColumns' }
793
+
794
+ context 'well-formed' do
795
+
796
+ let(:required_headers) { %w'Date Amount' }
797
+
798
+ it 'opens correctly' do
799
+ expect{ ss }.not_to raise_error
800
+ end
801
+
802
+ it 'contains expected headers' do
803
+ required_headers.each do |header|
804
+ expect(ss.headers).to include header
805
+ end
806
+ expect(ss.headers).to eq %w'Date Amount Code Remark'
807
+ end
808
+
809
+ it '#shift returns Hash object' do
810
+ row = ss.shift
811
+ expect(row).to be_a Hash
812
+ puts row
813
+ expect(row['Date']).to eq Date.new(2016, 05, 10)
814
+ expect(row['Amount']).to eq 1270.0
815
+ expect(row['Code']).to eq 1
816
+ expect(row['Remark']).to eq 'a'
817
+ expect(row['dummy']).to be_nil
818
+ end
819
+
820
+ it '#parse returns Array of Hash objects' do
821
+ rows = ss.parse
822
+ expect(rows).to be_a Array
823
+ expect(rows.size).to eq 17
824
+ row = rows[0]
825
+ expect(row).to be_a Hash
826
+ expect(row['Date']).to eq Date.new(2016,5,10)
827
+ expect(row['Amount']).to eq 1270.0
828
+ expect(row['Code']).to eq 1
829
+ expect(row['Remark']).to eq 'a'
830
+ expect(row['dummy']).to be_nil
831
+ row = rows[13]
832
+ expect(row).to be_a Hash
833
+ expect(row['Date']).to eq Date.new(2016,7,1)
834
+ expect(row['Amount']).to eq 3705.0
835
+ expect(row['Code']).to eq 3
836
+ expect(row['Remark']).to eq 'b'
837
+ expect(row['dummy']).to be_nil
838
+ end
839
+
840
+ end
841
+
842
+ context 'not specified' do
843
+
844
+ let(:required_headers) { [] }
845
+
846
+ it 'opens correctly' do
847
+ expect{ ss }.not_to raise_error
848
+ end
849
+
850
+ it 'contains expected headers' do
851
+ required_headers.each do |header|
852
+ expect(ss.headers).to include header
853
+ end
854
+ expect(ss.headers).to eq %w'Date Amount Code Remark'
855
+ end
856
+
857
+ it '#shift returns Hash object' do
858
+ row = ss.shift
859
+ expect(row).to be_a Hash
860
+ puts row
861
+ expect(row['Date']).to eq Date.new(2016, 05, 10)
862
+ expect(row['Amount']).to eq 1270.0
863
+ expect(row['Code']).to eq 1
864
+ expect(row['Remark']).to eq 'a'
865
+ expect(row['dummy']).to be_nil
866
+ end
867
+
868
+ it '#parse returns Array of Hash objects' do
869
+ rows = ss.parse
870
+ expect(rows).to be_a Array
871
+ expect(rows.size).to eq 17
872
+ row = rows[0]
873
+ expect(row).to be_a Hash
874
+ expect(row['Date']).to eq Date.new(2016,5,10)
875
+ expect(row['Amount']).to eq 1270.0
876
+ expect(row['Code']).to eq 1
877
+ expect(row['Remark']).to eq 'a'
878
+ expect(row['dummy']).to be_nil
879
+ row = rows[13]
880
+ expect(row).to be_a Hash
881
+ expect(row['Date']).to eq Date.new(2016,7,1)
882
+ expect(row['Amount']).to eq 3705.0
883
+ expect(row['Code']).to eq 3
884
+ expect(row['Remark']).to eq 'b'
885
+ expect(row['dummy']).to be_nil
886
+ end
887
+
888
+ end
889
+
890
+ context 'not well-formed' do
891
+
892
+ let(:required_headers) { %w'Date dummy1 Amount dummy2'}
893
+
894
+ it 'throws error when opened' do
895
+ expect { ss }.to raise_error(RuntimeError, 'Headers not found: ["dummy1", "dummy2"].')
896
+ end
897
+ end
898
+
899
+ end
900
+
901
+ context 'blank columns without headers' do
902
+ let(:file_name) { 'test.xlsx|ExpensesBlankColumnsNoHeaders' }
903
+
904
+ context 'well-formed and strict' do
905
+ let(:required_headers) { %w'Date Amount' }
906
+
907
+ it 'opens correctly' do
908
+ expect { ss }.not_to raise_error
909
+ end
910
+
911
+ it 'contains only required headers' do
912
+ required_headers.each do |header|
913
+ expect(ss.headers).to include header
914
+ end
915
+ expect(ss.headers).to eq %w'Date Amount'
916
+ end
917
+
918
+ it '#shift returns Hash object' do
919
+ row = ss.shift
920
+ expect(row).to be_a Hash
921
+ expect(row['Date']).to eq Date.new(2016, 05, 10)
922
+ expect(row['Amount']).to eq 1270.0
923
+ expect(row['Code']).to be_nil
924
+ expect(row['Remark']).to be_nil
925
+ expect(row['dummy']).to be_nil
926
+ end
927
+
928
+ it '#parse returns Array of Hash objects' do
929
+ rows = ss.parse
930
+ expect(rows).to be_a Array
931
+ expect(rows.size).to eq 17
932
+ row = rows[0]
933
+ expect(row).to be_a Hash
934
+ expect(row['Date']).to eq Date.new(2016,5,10)
935
+ expect(row['Amount']).to eq 1270.0
936
+ expect(row['Code']).to be_nil
937
+ expect(row['Remark']).to be_nil
938
+ expect(row['dummy']).to be_nil
939
+ row = rows[13]
940
+ expect(row).to be_a Hash
941
+ expect(row['Date']).to eq Date.new(2016,7,1)
942
+ expect(row['Amount']).to eq 3705.0
943
+ expect(row['Code']).to be_nil
944
+ expect(row['Remark']).to be_nil
945
+ expect(row['dummy']).to be_nil
946
+ end
947
+
948
+ end
949
+
950
+ context 'well-formed with optional headers' do
951
+ let(:required_headers) { %w'Date Amount' }
952
+ let(:optional_headers) { %w'Code' }
953
+
954
+ it 'opens correctly' do
955
+ expect { ss }.not_to raise_error
956
+ end
957
+
958
+ it 'contains required and optional headers' do
959
+ required_headers.each do |header|
960
+ expect(ss.headers).to include header
961
+ end
962
+ optional_headers.each do |header|
963
+ expect(ss.headers).to include header
964
+ end
965
+ expect(ss.headers).to eq %w'Date Amount Code'
966
+ end
967
+
968
+ it '#shift returns Hash object' do
969
+ row = ss.shift
970
+ expect(row).to be_a Hash
971
+ expect(row['Date']).to eq Date.new(2016, 05, 10)
972
+ expect(row['Amount']).to eq 1270.0
973
+ expect(row['Code']).to eq 1
974
+ expect(row['Remark']).to be_nil
975
+ expect(row['dummy']).to be_nil
976
+ end
977
+
978
+ it '#parse returns Array of Hash objects' do
979
+ rows = ss.parse
980
+ expect(rows).to be_a Array
981
+ expect(rows.size).to eq 17
982
+ row = rows[0]
983
+ expect(row).to be_a Hash
984
+ expect(row['Date']).to eq Date.new(2016,5,10)
985
+ expect(row['Amount']).to eq 1270.0
986
+ expect(row['Code']).to eq 1
987
+ expect(row['Remark']).to be_nil
988
+ expect(row['dummy']).to be_nil
989
+ row = rows[13]
990
+ expect(row).to be_a Hash
991
+ expect(row['Date']).to eq Date.new(2016,7,1)
992
+ expect(row['Amount']).to eq 3705.0
993
+ expect(row['Code']).to eq 3
994
+ expect(row['Remark']).to be_nil
995
+ expect(row['dummy']).to be_nil
996
+ end
997
+
998
+ end
999
+
1000
+ context 'missing optional headers' do
1001
+
1002
+ let(:required_headers) { %w'Date Amount Code Remark' }
1003
+ let(:optional_headers) { %w'dummy' }
1004
+
1005
+ it 'opens correctly' do
1006
+ expect { ss }.not_to raise_error
1007
+ end
1008
+
1009
+ it 'contains only required headers' do
1010
+ required_headers.each do |header|
1011
+ expect(ss.headers).to include header
1012
+ end
1013
+ optional_headers.each do |header|
1014
+ expect(ss.headers).not_to include header
1015
+ end
1016
+ expect(ss.headers).to eq %w'Date Amount Code Remark'
1017
+ end
1018
+
1019
+ it '#shift returns Hash object' do
1020
+ row = ss.shift
1021
+ expect(row).to be_a Hash
1022
+ expect(row['Date']).to eq Date.new(2016, 05, 10)
1023
+ expect(row['Amount']).to eq 1270.0
1024
+ expect(row['Code']).to eq 1
1025
+ expect(row['Remark']).to eq 'a'
1026
+ expect(row['dummy']).to be_nil
1027
+ end
1028
+
1029
+ it '#parse returns Array of Hash objects' do
1030
+ rows = ss.parse
1031
+ expect(rows).to be_a Array
1032
+ expect(rows.size).to eq 17
1033
+ row = rows[0]
1034
+ expect(row).to be_a Hash
1035
+ expect(row['Date']).to eq Date.new(2016,5,10)
1036
+ expect(row['Amount']).to eq 1270.0
1037
+ expect(row['Code']).to eq 1
1038
+ expect(row['Remark']).to eq 'a'
1039
+ expect(row['dummy']).to be_nil
1040
+ row = rows[13]
1041
+ expect(row).to be_a Hash
1042
+ expect(row['Date']).to eq Date.new(2016,7,1)
1043
+ expect(row['Amount']).to eq 3705.0
1044
+ expect(row['Code']).to eq 3
1045
+ expect(row['Remark']).to eq 'b'
1046
+ expect(row['dummy']).to be_nil
1047
+ end
1048
+
1049
+ end
1050
+
1051
+ context 'missing required header' do
1052
+ let(:required_headers) { %w'Date Amount Code Remark dummy' }
1053
+
1054
+ it 'throws error when opened' do
1055
+ expect { ss }.to raise_error(RuntimeError, 'Sheet does not contain enough columns.')
1056
+ end
1057
+
1058
+ end
1059
+
1060
+ end
1061
+
1062
+ context 'blank row and columns with headers' do
1063
+ let(:file_name) { 'test.xlsx|ExpensesBlankRowsAndColumns' }
1064
+
1065
+ context 'well-formed' do
1066
+
1067
+ let(:required_headers) { %w'Date Amount' }
1068
+
1069
+ it 'opens correctly' do
1070
+ expect{ ss }.not_to raise_error
1071
+ end
1072
+
1073
+ it 'contains expected headers' do
1074
+ required_headers.each do |header|
1075
+ expect(ss.headers).to include header
1076
+ end
1077
+ expect(ss.headers).to eq %w'Date Amount Code Remark'
1078
+ end
1079
+
1080
+ it '#shift returns Hash object' do
1081
+ row = ss.shift
1082
+ expect(row).to be_a Hash
1083
+ puts row
1084
+ expect(row['Date']).to eq Date.new(2016, 05, 10)
1085
+ expect(row['Amount']).to eq 1270.0
1086
+ expect(row['Code']).to eq 1
1087
+ expect(row['Remark']).to eq 'a'
1088
+ expect(row['dummy']).to be_nil
1089
+ end
1090
+
1091
+ it '#parse returns Array of Hash objects' do
1092
+ rows = ss.parse
1093
+ expect(rows).to be_a Array
1094
+ expect(rows.size).to eq 17
1095
+ row = rows[0]
1096
+ expect(row).to be_a Hash
1097
+ expect(row['Date']).to eq Date.new(2016,5,10)
1098
+ expect(row['Amount']).to eq 1270.0
1099
+ expect(row['Code']).to eq 1
1100
+ expect(row['Remark']).to eq 'a'
1101
+ expect(row['dummy']).to be_nil
1102
+ row = rows[13]
1103
+ expect(row).to be_a Hash
1104
+ expect(row['Date']).to eq Date.new(2016,7,1)
1105
+ expect(row['Amount']).to eq 3705.0
1106
+ expect(row['Code']).to eq 3
1107
+ expect(row['Remark']).to eq 'b'
1108
+ expect(row['dummy']).to be_nil
1109
+ end
1110
+
1111
+ end
1112
+
1113
+ context 'not specified' do
1114
+
1115
+ let(:required_headers) { [] }
1116
+
1117
+ it 'opens correctly' do
1118
+ expect{ ss }.not_to raise_error
1119
+ end
1120
+
1121
+ it 'contains expected headers' do
1122
+ required_headers.each do |header|
1123
+ expect(ss.headers).to include header
1124
+ end
1125
+ expect(ss.headers).to eq %w'Date Amount Code Remark'
1126
+ end
1127
+
1128
+ it '#shift returns Hash object' do
1129
+ row = ss.shift
1130
+ expect(row).to be_a Hash
1131
+ puts row
1132
+ expect(row['Date']).to eq Date.new(2016, 05, 10)
1133
+ expect(row['Amount']).to eq 1270.0
1134
+ expect(row['Code']).to eq 1
1135
+ expect(row['Remark']).to eq 'a'
1136
+ expect(row['dummy']).to be_nil
1137
+ end
1138
+
1139
+ it '#parse returns Array of Hash objects' do
1140
+ rows = ss.parse
1141
+ expect(rows).to be_a Array
1142
+ expect(rows.size).to eq 17
1143
+ row = rows[0]
1144
+ expect(row).to be_a Hash
1145
+ expect(row['Date']).to eq Date.new(2016,5,10)
1146
+ expect(row['Amount']).to eq 1270.0
1147
+ expect(row['Code']).to eq 1
1148
+ expect(row['Remark']).to eq 'a'
1149
+ expect(row['dummy']).to be_nil
1150
+ row = rows[13]
1151
+ expect(row).to be_a Hash
1152
+ expect(row['Date']).to eq Date.new(2016,7,1)
1153
+ expect(row['Amount']).to eq 3705.0
1154
+ expect(row['Code']).to eq 3
1155
+ expect(row['Remark']).to eq 'b'
1156
+ expect(row['dummy']).to be_nil
1157
+ end
1158
+
1159
+ end
1160
+
1161
+ context 'not well-formed' do
1162
+
1163
+ let(:required_headers) { %w'Date dummy1 Amount dummy2'}
1164
+
1165
+ it 'throws error when opened' do
1166
+ expect { ss }.to raise_error(RuntimeError, 'Headers not found: ["dummy1", "dummy2"].')
1167
+ end
1168
+ end
1169
+
1170
+ end
1171
+
1172
+ context 'blank row and columns without headers' do
1173
+ let(:file_name) { 'test.xlsx|ExpensesBlankRowsAndColumnsNoH' }
1174
+
1175
+ context 'well-formed and strict' do
1176
+ let(:required_headers) { %w'Date Amount' }
1177
+
1178
+ it 'opens correctly' do
1179
+ expect { ss }.not_to raise_error
1180
+ end
1181
+
1182
+ it 'contains only required headers' do
1183
+ required_headers.each do |header|
1184
+ expect(ss.headers).to include header
1185
+ end
1186
+ expect(ss.headers).to eq %w'Date Amount'
1187
+ end
1188
+
1189
+ it '#shift returns Hash object' do
1190
+ row = ss.shift
1191
+ expect(row).to be_a Hash
1192
+ expect(row['Date']).to eq Date.new(2016, 05, 10)
1193
+ expect(row['Amount']).to eq 1270.0
1194
+ expect(row['Code']).to be_nil
1195
+ expect(row['Remark']).to be_nil
1196
+ expect(row['dummy']).to be_nil
1197
+ end
1198
+
1199
+ it '#parse returns Array of Hash objects' do
1200
+ rows = ss.parse
1201
+ expect(rows).to be_a Array
1202
+ expect(rows.size).to eq 17
1203
+ row = rows[0]
1204
+ expect(row).to be_a Hash
1205
+ expect(row['Date']).to eq Date.new(2016,5,10)
1206
+ expect(row['Amount']).to eq 1270.0
1207
+ expect(row['Code']).to be_nil
1208
+ expect(row['Remark']).to be_nil
1209
+ expect(row['dummy']).to be_nil
1210
+ row = rows[13]
1211
+ expect(row).to be_a Hash
1212
+ expect(row['Date']).to eq Date.new(2016,7,1)
1213
+ expect(row['Amount']).to eq 3705.0
1214
+ expect(row['Code']).to be_nil
1215
+ expect(row['Remark']).to be_nil
1216
+ expect(row['dummy']).to be_nil
1217
+ end
1218
+
1219
+ end
1220
+
1221
+ context 'well-formed with optional headers' do
1222
+ let(:required_headers) { %w'Date Amount' }
1223
+ let(:optional_headers) { %w'Code' }
1224
+
1225
+ it 'opens correctly' do
1226
+ expect { ss }.not_to raise_error
1227
+ end
1228
+
1229
+ it 'contains required and optional headers' do
1230
+ required_headers.each do |header|
1231
+ expect(ss.headers).to include header
1232
+ end
1233
+ optional_headers.each do |header|
1234
+ expect(ss.headers).to include header
1235
+ end
1236
+ expect(ss.headers).to eq %w'Date Amount Code'
1237
+ end
1238
+
1239
+ it '#shift returns Hash object' do
1240
+ row = ss.shift
1241
+ expect(row).to be_a Hash
1242
+ expect(row['Date']).to eq Date.new(2016, 05, 10)
1243
+ expect(row['Amount']).to eq 1270.0
1244
+ expect(row['Code']).to eq 1
1245
+ expect(row['Remark']).to be_nil
1246
+ expect(row['dummy']).to be_nil
1247
+ end
1248
+
1249
+ it '#parse returns Array of Hash objects' do
1250
+ rows = ss.parse
1251
+ expect(rows).to be_a Array
1252
+ expect(rows.size).to eq 17
1253
+ row = rows[0]
1254
+ expect(row).to be_a Hash
1255
+ expect(row['Date']).to eq Date.new(2016,5,10)
1256
+ expect(row['Amount']).to eq 1270.0
1257
+ expect(row['Code']).to eq 1
1258
+ expect(row['Remark']).to be_nil
1259
+ expect(row['dummy']).to be_nil
1260
+ row = rows[13]
1261
+ expect(row).to be_a Hash
1262
+ expect(row['Date']).to eq Date.new(2016,7,1)
1263
+ expect(row['Amount']).to eq 3705.0
1264
+ expect(row['Code']).to eq 3
1265
+ expect(row['Remark']).to be_nil
1266
+ expect(row['dummy']).to be_nil
1267
+ end
1268
+
1269
+ end
1270
+
1271
+ context 'missing optional headers' do
1272
+
1273
+ let(:required_headers) { %w'Date Amount Code Remark' }
1274
+ let(:optional_headers) { %w'dummy' }
1275
+
1276
+ it 'opens correctly' do
1277
+ expect { ss }.not_to raise_error
1278
+ end
1279
+
1280
+ it 'contains only required headers' do
1281
+ required_headers.each do |header|
1282
+ expect(ss.headers).to include header
1283
+ end
1284
+ optional_headers.each do |header|
1285
+ expect(ss.headers).not_to include header
1286
+ end
1287
+ expect(ss.headers).to eq %w'Date Amount Code Remark'
1288
+ end
1289
+
1290
+ it '#shift returns Hash object' do
1291
+ row = ss.shift
1292
+ expect(row).to be_a Hash
1293
+ expect(row['Date']).to eq Date.new(2016, 05, 10)
1294
+ expect(row['Amount']).to eq 1270.0
1295
+ expect(row['Code']).to eq 1
1296
+ expect(row['Remark']).to eq 'a'
1297
+ expect(row['dummy']).to be_nil
1298
+ end
1299
+
1300
+ it '#parse returns Array of Hash objects' do
1301
+ rows = ss.parse
1302
+ expect(rows).to be_a Array
1303
+ expect(rows.size).to eq 17
1304
+ row = rows[0]
1305
+ expect(row).to be_a Hash
1306
+ expect(row['Date']).to eq Date.new(2016,5,10)
1307
+ expect(row['Amount']).to eq 1270.0
1308
+ expect(row['Code']).to eq 1
1309
+ expect(row['Remark']).to eq 'a'
1310
+ expect(row['dummy']).to be_nil
1311
+ row = rows[13]
1312
+ expect(row).to be_a Hash
1313
+ expect(row['Date']).to eq Date.new(2016,7,1)
1314
+ expect(row['Amount']).to eq 3705.0
1315
+ expect(row['Code']).to eq 3
1316
+ expect(row['Remark']).to eq 'b'
1317
+ expect(row['dummy']).to be_nil
1318
+ end
1319
+
1320
+ end
1321
+
1322
+ context 'missing required header' do
1323
+ let(:required_headers) { %w'Date Amount Code Remark dummy' }
1324
+
1325
+ it 'throws error when opened' do
1326
+ expect { ss }.to raise_error(RuntimeError, 'Sheet does not contain enough columns.')
1327
+ end
1328
+
1329
+ end
1330
+
1331
+ end
1332
+
1333
+ end
1334
+
1335
+ end