goodsheet 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/goodsheet.rb +1 -0
- data/lib/goodsheet/read_result.rb +88 -21
- data/lib/goodsheet/row.rb +78 -19
- data/lib/goodsheet/spreadsheet.rb +27 -40
- data/lib/goodsheet/validation_error.rb +3 -3
- data/lib/goodsheet/validation_errors.rb +9 -31
- data/lib/goodsheet/version.rb +1 -1
- data/notes.txt +13 -0
- data/test/fixtures/example.xls +0 -0
- data/test/test_row.rb +6 -4
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aef27ae57a44343de4b43b8118c3a4dba7940021
|
4
|
+
data.tar.gz: ccd478aad49271a61e1e4278cbfd95ea9a226b17
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cf0b9d857e4eb5a66cbf72a6c205e1ab4f517fd93dbd40e42382e1b1f34d0b0e74813bdf8bb5e3e48c554933297ccc31160d39e83e2d4b311825ddf73c32d897
|
7
|
+
data.tar.gz: f866ccb2f33ffa9618f12c597a1ab4c9d88282eaf6ffd0485d0e262f78cc506c424a58cc97f2de569e385aa88e2f517b3ee85fd443a955c35e9fe4c808bd7e6e
|
data/lib/goodsheet.rb
CHANGED
@@ -1,11 +1,30 @@
|
|
1
1
|
module Goodsheet
|
2
|
-
|
3
2
|
class ReadResult
|
4
|
-
attr_reader :
|
3
|
+
attr_reader :errors
|
5
4
|
|
6
|
-
def initialize(
|
7
|
-
@
|
8
|
-
|
5
|
+
def initialize(row_attributes, max_errors, collector=:a_arr)
|
6
|
+
@row_attributes = row_attributes
|
7
|
+
case collector
|
8
|
+
when :hash
|
9
|
+
@hash = {}
|
10
|
+
when :a_arr
|
11
|
+
@a_arr = []
|
12
|
+
when :h_arr
|
13
|
+
@h_arr = []
|
14
|
+
end
|
15
|
+
@errors = ValidationErrors.new(max_errors)
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def add(row_number, row)
|
20
|
+
if defined? @hash
|
21
|
+
row.to_hash.each_pair do |k, v|
|
22
|
+
(@hash[k]||=[]) << v # @hash = {:a => ["dog, "cat", ...], :b => [3, 7, ...]}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
@a_arr << row.to_a if defined? @a_arr # @a_arr = [["dog", 3], ["cat", 7], ...]
|
26
|
+
@h_arr << row.to_hash if defined? @h_arr # @h_arr = [{:a => "dog", :b => 3}, {:a => "cat", :b => 7}, ...]
|
27
|
+
@errors.add(row_number, row)
|
9
28
|
end
|
10
29
|
|
11
30
|
def valid?
|
@@ -16,33 +35,81 @@ module Goodsheet
|
|
16
35
|
!valid?
|
17
36
|
end
|
18
37
|
|
19
|
-
def add(attribute, row)
|
20
|
-
attribute = attribute.to_sym
|
21
|
-
(@vv[attribute] ||= []) << row.send(attribute)
|
22
|
-
end
|
23
|
-
|
24
38
|
def values(format=:columns)
|
25
|
-
|
26
|
-
|
39
|
+
if defined? @hash
|
40
|
+
return [] if @hash.empty?
|
41
|
+
values_for_hash(format)
|
42
|
+
|
43
|
+
elsif defined? @h_arr
|
44
|
+
return [] if @h_arr.empty?
|
45
|
+
values_for_h_arr(format)
|
27
46
|
|
47
|
+
elsif defined? @a_arr # @a_arr = [["dog", 3], ["cat", 7], ...]
|
48
|
+
return [] if @a_arr.empty?
|
49
|
+
values_for_a_arr(format)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def values_for_hash(format)
|
28
54
|
case format
|
29
55
|
when :columns
|
30
|
-
@
|
56
|
+
@hash
|
31
57
|
|
32
58
|
when :rows_array
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
59
|
+
a1 = []
|
60
|
+
@hash.values.first.size.times do |i|
|
61
|
+
a1 << @row_attributes.collect{|a| @hash[a][i]}
|
62
|
+
end
|
63
|
+
a1
|
64
|
+
|
65
|
+
when :rows_hash
|
66
|
+
a1 = []
|
67
|
+
@hash.values.first.size.times do |i|
|
68
|
+
a2 = {}
|
69
|
+
@row_attributes.each{|a| a2[a] = @hash[a][i]}
|
70
|
+
a1 << a2
|
71
|
+
end
|
72
|
+
a1
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def values_for_a_arr(format)
|
77
|
+
case format
|
78
|
+
when :columns
|
79
|
+
h = {}
|
80
|
+
@row_attributes.each_with_index do |attrib, i|
|
81
|
+
h[attrib] = @a_arr.map{|e| e[i]}
|
37
82
|
end
|
83
|
+
h
|
84
|
+
|
85
|
+
when :rows_array
|
86
|
+
@a_arr
|
38
87
|
|
39
88
|
when :rows_hash
|
40
|
-
|
41
|
-
|
42
|
-
@vv.keys.each{|k| hh[k] = @vv[k][i1] }
|
43
|
-
hh
|
89
|
+
@a_arr.map do |arr|
|
90
|
+
Hash[@row_attributes.map.with_index{|attrib,i| [attrib, arr[i]]}]
|
44
91
|
end
|
45
92
|
end
|
46
93
|
end
|
94
|
+
|
95
|
+
def values_for_h_arr(format)
|
96
|
+
case format
|
97
|
+
when :columns
|
98
|
+
keys = @h_arr.first.keys
|
99
|
+
h = {}
|
100
|
+
@h_arr.each do |e|
|
101
|
+
keys.each do |key|
|
102
|
+
(h[key] ||= []) << e[key]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
h
|
106
|
+
|
107
|
+
when :rows_array
|
108
|
+
@h_arr.map(&:values)
|
109
|
+
|
110
|
+
when :rows_hash
|
111
|
+
@h_arr
|
112
|
+
end
|
113
|
+
end
|
47
114
|
end
|
48
115
|
end
|
data/lib/goodsheet/row.rb
CHANGED
@@ -4,18 +4,21 @@ module Goodsheet
|
|
4
4
|
|
5
5
|
class Row
|
6
6
|
include ActiveModel::Validations
|
7
|
-
include ActiveModel::Conversion
|
8
|
-
extend ActiveModel::Naming
|
9
7
|
|
10
8
|
class << self
|
11
|
-
attr_accessor :keys
|
9
|
+
attr_accessor :keys, :defaults
|
12
10
|
end
|
13
|
-
@keys = {} # idx => key
|
11
|
+
# @keys = {} # idx => key: {0=>:name, 1=>:quantity, 2=>:price, 3=>:total, 6=>:password}
|
12
|
+
# @defaults = {} # name => default_value
|
14
13
|
|
15
14
|
def initialize(arr, nil_value=nil)
|
15
|
+
# puts "--- arr: #{arr.inspect}"
|
16
|
+
if (diff=self.class.keys.size-arr.size)>0
|
17
|
+
arr = arr + Array.new(diff, nil)
|
18
|
+
end
|
16
19
|
arr.each_with_index do |v, idx|
|
17
20
|
if k = self.class.keys[idx]
|
18
|
-
send
|
21
|
+
send "#{k}=", v || self.class.defaults[k] || nil_value
|
19
22
|
end
|
20
23
|
end
|
21
24
|
super()
|
@@ -23,12 +26,30 @@ module Goodsheet
|
|
23
26
|
|
24
27
|
def self.inherit(block)
|
25
28
|
c = Class.new(self) do
|
26
|
-
@keys = {} # idx => key
|
29
|
+
@keys = {} # idx => key: {0=>:name, 1=>:quantity, 2=>:price, 3=>:total, 6=>:password}
|
30
|
+
@defaults = {} # name => default_value
|
27
31
|
end
|
28
32
|
c.class_eval(&block)
|
29
33
|
c
|
30
34
|
end
|
31
35
|
|
36
|
+
# using indexes: defaults 1 => 0.0, 2 => ""
|
37
|
+
# using names: defaults :qty => 0.0, :name => ""
|
38
|
+
|
39
|
+
def self.column_defaults(*attr)
|
40
|
+
raise ArgumentError, 'You have to pass at least one attribute' if attr.empty?
|
41
|
+
if attr[0].is_a? Array
|
42
|
+
@defaults = Hash[attr[0].map.with_index{|v, i| [self.keys[i], v]}]
|
43
|
+
|
44
|
+
elsif attr[0].is_a? Hash
|
45
|
+
@defaults = Hash[attr[0].to_a.collect{|a| [(a[0].is_a?(Fixnum)) ? (self.keys[a[0]]) : a[0], a[1]]}]
|
46
|
+
|
47
|
+
else
|
48
|
+
@defaults = Hash[attr.map.with_index{|v, i| [self.keys[i], v]}]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
|
32
53
|
# Define the position (or index) and the name of columns.
|
33
54
|
# You have four ways to define them:
|
34
55
|
# using an hash index-to-name (like { 0 => :year, 2 => :day })
|
@@ -42,22 +63,25 @@ module Goodsheet
|
|
42
63
|
raise ArgumentError, 'You have to pass at least one attribute' if attr.empty?
|
43
64
|
if attr[0].is_a? Array
|
44
65
|
attr[0].each_with_index do |name, idx|
|
45
|
-
if name
|
46
|
-
|
47
|
-
|
48
|
-
|
66
|
+
self.set_key_pair(idx, name) if name
|
67
|
+
# if name
|
68
|
+
# self.keys[idx] = name
|
69
|
+
# attr_accessor name
|
70
|
+
# end
|
49
71
|
end
|
50
72
|
|
51
73
|
elsif attr[0].is_a? Hash
|
52
74
|
if attr[0].first[0].is_a? Integer
|
53
75
|
attr[0].each do |idx, name|
|
54
|
-
self.
|
55
|
-
|
76
|
+
self.set_key_pair(idx, name)
|
77
|
+
# self.keys[idx] = name
|
78
|
+
# attr_accessor name
|
56
79
|
end
|
57
80
|
else
|
58
81
|
attr[0].each do |name, idx|
|
59
|
-
self.
|
60
|
-
|
82
|
+
self.set_key_pair(idx, name)
|
83
|
+
# self.keys[idx] = name
|
84
|
+
# attr_accessor name
|
61
85
|
end
|
62
86
|
end
|
63
87
|
|
@@ -65,8 +89,9 @@ module Goodsheet
|
|
65
89
|
attr.each_with_index do |name, idx|
|
66
90
|
if name
|
67
91
|
name = name.to_s.gsub(" ", "_").to_sym unless name.is_a? Symbol
|
68
|
-
self.
|
69
|
-
|
92
|
+
self.set_key_pair(idx, name)
|
93
|
+
# self.keys[idx] = name
|
94
|
+
# attr_accessor name
|
70
95
|
end
|
71
96
|
end
|
72
97
|
end
|
@@ -74,13 +99,47 @@ module Goodsheet
|
|
74
99
|
end
|
75
100
|
|
76
101
|
|
77
|
-
def
|
78
|
-
|
102
|
+
def self.set_key_pair(idx, name)
|
103
|
+
self.keys[idx] = name
|
104
|
+
attr_accessor name
|
79
105
|
end
|
80
106
|
|
107
|
+
|
108
|
+
# def persisted?
|
109
|
+
# false
|
110
|
+
# end
|
111
|
+
|
81
112
|
# Get the list of attributes (the columns to import)
|
82
|
-
def
|
113
|
+
def Row.attributes
|
83
114
|
@keys.values
|
84
115
|
end
|
116
|
+
|
117
|
+
def attributes
|
118
|
+
self.class.attributes
|
119
|
+
end
|
120
|
+
|
121
|
+
def Row.extend_with(block)
|
122
|
+
class_name = "CustRow_#{(Time.now.to_f*(10**10)).to_i}"
|
123
|
+
Object.const_set class_name, Row.inherit(block)
|
124
|
+
end
|
125
|
+
|
126
|
+
def to_hash
|
127
|
+
Hash[self.class.attributes.map{|a| [a, self.send(a)]}]
|
128
|
+
end
|
129
|
+
|
130
|
+
def to_a
|
131
|
+
self.class.attributes.map{|a| self.send(a)}
|
132
|
+
end
|
85
133
|
end
|
86
134
|
end
|
135
|
+
|
136
|
+
# class Row01 < Goodsheet::Row
|
137
|
+
# column_names :filename => 0, :size => 1
|
138
|
+
# validates :size, :numericality => true
|
139
|
+
# end
|
140
|
+
|
141
|
+
# r = Row01.new(["pippo", "e"])
|
142
|
+
# p r.valid?
|
143
|
+
# puts r.class.attributes.inspect
|
144
|
+
# puts r.to_hash.inspect
|
145
|
+
# puts r.to_a.inspect
|
@@ -110,23 +110,15 @@ module Goodsheet
|
|
110
110
|
# @yield Column settings and validation rules
|
111
111
|
# @return [ValidationErrors] Validation errors
|
112
112
|
def validate(options={}, &block)
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
my_class = options[:my_custom_row_class] || build_my_class(block)
|
121
|
-
|
122
|
-
line = @s_opts[index][:skip] # 0-based, from the top
|
123
|
-
@ss.parse[@s_opts[index][:skip]..-1].each do |row| # row is an array of elements
|
124
|
-
validation_errors.add(line, my_class.new(row, force_nil))
|
125
|
-
break if max_errors>0 && validation_errors.size >= max_errors
|
126
|
-
break if row_limit && row_limit>0 && line>=(row_limit+@s_opts[index][:skip]-1)
|
127
|
-
line +=1
|
113
|
+
set_variables(options)
|
114
|
+
errors = ValidationErrors.new(@max_errors)
|
115
|
+
row_class = Row.extend_with(block)
|
116
|
+
|
117
|
+
last_row = @row_limit.zero? ? @ss.last_row : min(@ss.last_row, @row_limit+@skip)
|
118
|
+
(@skip+1).upto(last_row) do |r|
|
119
|
+
break unless errors.add(r, row_class.new(@ss.row(r), @force_nil))
|
128
120
|
end
|
129
|
-
|
121
|
+
errors
|
130
122
|
end
|
131
123
|
|
132
124
|
|
@@ -141,24 +133,13 @@ module Goodsheet
|
|
141
133
|
# @yield Column settings and validation rules
|
142
134
|
# @return [ReadResult] The result
|
143
135
|
def read(options={}, &block)
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
options[:my_custom_row_class] = my_class
|
152
|
-
read_result = ReadResult.new(validate(options){ block })
|
153
|
-
return read_result if read_result.invalid?
|
154
|
-
|
155
|
-
line = skip # 0-based, from the top
|
156
|
-
@ss.parse[skip..-1].each do |row| # row is an array of elements
|
157
|
-
my_class.row_attributes.each do |attribute|
|
158
|
-
read_result.add(attribute, my_class.new(row, force_nil))
|
159
|
-
end
|
160
|
-
break if row_limit && row_limit>0 && line>=(row_limit + skip - 1)
|
161
|
-
line +=1
|
136
|
+
set_variables(options)
|
137
|
+
row_class = Row.extend_with(block)
|
138
|
+
read_result = ReadResult.new(row_class.attributes, @max_errors, options[:collector]||:a_arr)
|
139
|
+
|
140
|
+
last_row = @row_limit.zero? ? @ss.last_row : min(@ss.last_row, @row_limit+@skip)
|
141
|
+
(@skip+1).upto(last_row) do |r|
|
142
|
+
break unless read_result.add(r, row_class.new(@ss.row(r), @force_nil))
|
162
143
|
end
|
163
144
|
read_result
|
164
145
|
end
|
@@ -166,9 +147,14 @@ module Goodsheet
|
|
166
147
|
|
167
148
|
private
|
168
149
|
|
169
|
-
|
170
|
-
|
171
|
-
|
150
|
+
|
151
|
+
|
152
|
+
def set_variables(options)
|
153
|
+
@skip = options[:skip] || @s_opts[index][:skip]
|
154
|
+
@header_row = options[:header_row] || @s_opts[index][:header_row]
|
155
|
+
@max_errors = options[:max_errors] || @s_opts[index][:max_errors]
|
156
|
+
@row_limit = options[:row_limit] || @s_opts[index][:row_limit] || 0
|
157
|
+
@force_nil = options[:force_nil] || @s_opts[index][:force_nil]
|
172
158
|
end
|
173
159
|
|
174
160
|
def select_sheet_options(idx)
|
@@ -189,9 +175,6 @@ module Goodsheet
|
|
189
175
|
end
|
190
176
|
end
|
191
177
|
|
192
|
-
def get_custom_row_class_name
|
193
|
-
"CustRow_#{(Time.now.to_f*(10**10)).to_i}"
|
194
|
-
end
|
195
178
|
|
196
179
|
def set_sheet_options(idx, options)
|
197
180
|
i = idx.is_a?(Integer) ? idx : @ss.sheets.index(idx)
|
@@ -203,6 +186,10 @@ module Goodsheet
|
|
203
186
|
:force_nil => options[:force_nil] || @s_opts[i][:force_nil] || nil
|
204
187
|
}
|
205
188
|
end
|
189
|
+
|
190
|
+
def min(a,b)
|
191
|
+
a<b ? a : b
|
192
|
+
end
|
206
193
|
end
|
207
194
|
end
|
208
195
|
|
@@ -1,14 +1,14 @@
|
|
1
1
|
module Goodsheet
|
2
2
|
|
3
3
|
class ValidationError
|
4
|
+
|
4
5
|
def initialize(line, val_err)
|
5
6
|
@line = line
|
6
|
-
@val_err = val_err
|
7
|
+
@val_err = val_err.full_messages.join(', ')
|
7
8
|
end
|
8
9
|
|
9
10
|
def to_s
|
10
|
-
|
11
|
-
"Row #{@line} is invalid: #{@val_err.full_messages.join(', ')}"
|
11
|
+
"Row #{@line} is not valid: #{@val_err}"
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
@@ -1,40 +1,18 @@
|
|
1
1
|
module Goodsheet
|
2
2
|
|
3
|
-
class ValidationErrors
|
4
|
-
attr_reader :array
|
3
|
+
class ValidationErrors < Array
|
5
4
|
|
6
|
-
def initialize
|
7
|
-
@
|
5
|
+
def initialize(limit=0)
|
6
|
+
@max_size = (limit==0 || limit.nil?) ? Float::INFINITY : limit
|
8
7
|
end
|
9
8
|
|
9
|
+
# Add a potential error (will be added only if the row is not valid)
|
10
|
+
#
|
11
|
+
# @param line_number [Fixnum] Line number (0-based).
|
12
|
+
# @return [boolean] Return false if the limit has been reached, true otherwise.
|
10
13
|
def add(line_number, row)
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
def empty?
|
15
|
-
@array.empty?
|
16
|
-
end
|
17
|
-
|
18
|
-
def size
|
19
|
-
@array.size
|
20
|
-
end
|
21
|
-
|
22
|
-
def to_s
|
23
|
-
@array.to_s
|
24
|
-
end
|
25
|
-
|
26
|
-
def [](i)
|
27
|
-
@array[i]
|
28
|
-
end
|
29
|
-
|
30
|
-
def to_a
|
31
|
-
@array
|
32
|
-
end
|
33
|
-
|
34
|
-
def each(&block)
|
35
|
-
@array.each do |i|
|
36
|
-
yield(i)
|
37
|
-
end
|
14
|
+
self << ValidationError.new(line_number+1, row.errors) if row.invalid?
|
15
|
+
self.size < @max_size
|
38
16
|
end
|
39
17
|
|
40
18
|
def valid?
|
data/lib/goodsheet/version.rb
CHANGED
data/notes.txt
CHANGED
@@ -66,3 +66,16 @@ values = @ss.read(:force_nil => 0.0, :skip => 6) do |row|
|
|
66
66
|
MyRow.new(:year => row[0])
|
67
67
|
end
|
68
68
|
|
69
|
+
|
70
|
+
Test a single test file:
|
71
|
+
rake test TEST=test/test_defaults.rb
|
72
|
+
L3lIrj
|
73
|
+
|
74
|
+
|
75
|
+
Tests:
|
76
|
+
rake test TEST=test/test_defaults.rb # OOOKK! committa
|
77
|
+
rake test TEST=test/test_enel.rb # OOOKK!
|
78
|
+
rake test TEST=test/test_example_2.rb # OOOKK!
|
79
|
+
rake test TEST=test/test_roo.rb # OOOKK!
|
80
|
+
|
81
|
+
|
data/test/fixtures/example.xls
CHANGED
Binary file
|
data/test/test_row.rb
CHANGED
@@ -3,6 +3,7 @@ require 'goodsheet'
|
|
3
3
|
|
4
4
|
class TestRow < Test::Unit::TestCase
|
5
5
|
|
6
|
+
# Test various style of column_names option
|
6
7
|
def test_column_names
|
7
8
|
assert_raise ArgumentError do
|
8
9
|
Goodsheet::Row.column_names
|
@@ -11,17 +12,18 @@ class TestRow < Test::Unit::TestCase
|
|
11
12
|
Goodsheet::Row.column_names(6)
|
12
13
|
end
|
13
14
|
|
15
|
+
result = {0 => :a, 2 => :b, 3 => :c}
|
14
16
|
Goodsheet::Row.column_names(:a, nil, :b, :c)
|
15
|
-
assert_equal(Goodsheet::Row.keys,
|
17
|
+
assert_equal(Goodsheet::Row.keys, result)
|
16
18
|
|
17
19
|
Goodsheet::Row.column_names([:a, nil, :b, :c])
|
18
|
-
assert_equal(Goodsheet::Row.keys,
|
20
|
+
assert_equal(Goodsheet::Row.keys, result)
|
19
21
|
|
20
22
|
Goodsheet::Row.column_names(:a => 0, :b => 2, :c => 3)
|
21
|
-
assert_equal(Goodsheet::Row.keys,
|
23
|
+
assert_equal(Goodsheet::Row.keys, result)
|
22
24
|
|
23
25
|
Goodsheet::Row.column_names(0 => :a, 2 => :b, 3 => :c)
|
24
|
-
assert_equal(Goodsheet::Row.keys,
|
26
|
+
assert_equal(Goodsheet::Row.keys, result)
|
25
27
|
end
|
26
28
|
|
27
29
|
|