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 +4 -4
- data/lib/libis/tools.rb +2 -1
- data/lib/libis/tools/extend/roo.rb +86 -0
- data/lib/libis/tools/spreadsheet.rb +170 -0
- data/lib/libis/tools/version.rb +1 -1
- data/libis-tools.gemspec +3 -0
- data/spec/data/test.xlsx +0 -0
- data/spec/metadata/scope_mapper_spec.rb +0 -1
- data/spec/spreadsheet_spec.rb +1335 -0
- metadata +51 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 637fd6ebde184f6dcedfb30f78b4b47ec1a7aa35
|
|
4
|
+
data.tar.gz: 446600a929178ec33509865ae9b069e26b508475
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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 :
|
|
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
|
data/lib/libis/tools/version.rb
CHANGED
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
|
data/spec/data/test.xlsx
ADDED
|
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
|