libis-tools 0.9.42 → 0.9.43

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 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