csv_party 0.0.1.pre3 → 0.0.1.pre7
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/csv_party.rb +139 -57
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4a03ef93214f6d5974e16e881c4ccab9b2543cfe
|
4
|
+
data.tar.gz: 3179789242dc149e98129bf06e6a18dafb1b25cf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8fd9938a9dfe1fe7cf0fd2f52cb94c2bf210c4f3fab1e7789e14e0ad5a10b999895489193ba32d6df97327c475c6ce37b3f116f30990f91c8d0d27e3b271cd3e
|
7
|
+
data.tar.gz: 58b12e98902a2e676dfb32475922f6a36eb1b4535900b782d601d248cab785bbd5c8a4bd5fbd63eed527649870a4b5947b4dcbf06ac5fb2f96d2fe0fb3b1a919
|
data/lib/csv_party.rb
CHANGED
@@ -3,65 +3,59 @@ require 'bigdecimal'
|
|
3
3
|
require 'ostruct'
|
4
4
|
|
5
5
|
class CSVParty
|
6
|
+
attr_accessor :columns, :row_importer, :importer, :error_processor
|
7
|
+
|
8
|
+
attr_reader :imported_rows, :skipped_rows, :aborted_rows,
|
9
|
+
:abort_message
|
10
|
+
|
6
11
|
def initialize(csv_path, options = {})
|
12
|
+
initialize_import_settings
|
13
|
+
initialize_counters_and_statuses
|
14
|
+
|
7
15
|
options[:headers] = true
|
16
|
+
dependencies = options.delete(:dependencies)
|
8
17
|
@headers = CSV.new(File.open(csv_path)).shift
|
9
18
|
@csv = CSV.new(File.open(csv_path), options)
|
10
19
|
|
20
|
+
setup_dependencies(dependencies)
|
11
21
|
raise_unless_named_parsers_are_valid
|
12
22
|
raise_unless_csv_has_all_headers
|
13
23
|
end
|
14
24
|
|
15
25
|
def import!
|
26
|
+
if importer
|
27
|
+
instance_exec(&importer)
|
28
|
+
else
|
29
|
+
import_rows!
|
30
|
+
end
|
31
|
+
rescue AbortedImportError => error
|
32
|
+
@aborted = true
|
33
|
+
@abort_message = error.message
|
34
|
+
end
|
35
|
+
|
36
|
+
def import_rows!
|
16
37
|
loop do
|
17
38
|
begin
|
18
39
|
row = @csv.shift
|
19
40
|
break unless row
|
20
|
-
import_row(row)
|
41
|
+
import_row!(row)
|
42
|
+
imported_rows << @csv.lineno
|
43
|
+
rescue SkippedRowError
|
44
|
+
skipped_rows << @csv.lineno
|
45
|
+
next
|
46
|
+
rescue AbortedImportError => error
|
47
|
+
raise AbortedImportError, error.message
|
21
48
|
rescue StandardError => error
|
22
49
|
process_error(error, @csv.lineno + 1)
|
50
|
+
aborted_rows << @csv.lineno
|
23
51
|
next
|
24
52
|
end
|
25
53
|
end
|
26
54
|
end
|
27
55
|
|
28
|
-
def
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
columns.each do |name, options|
|
33
|
-
header = options[:header]
|
34
|
-
unparsed_value = row[header]
|
35
|
-
parser = options[:parser]
|
36
|
-
|
37
|
-
unparsed_row[name] = unparsed_value
|
38
|
-
parsed_row[name] = if options[:blanks_as_nil] && is_blank?(unparsed_value)
|
39
|
-
nil
|
40
|
-
elsif parser.is_a? Symbol
|
41
|
-
send(parser, unparsed_value)
|
42
|
-
else
|
43
|
-
instance_exec(unparsed_value, &parser)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
parsed_row['unparsed'] = unparsed_row
|
48
|
-
parsed_row['csv_string'] = row.to_csv
|
49
|
-
|
50
|
-
return parsed_row
|
51
|
-
end
|
52
|
-
|
53
|
-
def import_row(row)
|
54
|
-
parsed_row = parse_row(row)
|
55
|
-
instance_exec(parsed_row, &importer)
|
56
|
-
end
|
57
|
-
|
58
|
-
def process_error(error, line_number)
|
59
|
-
instance_exec(error, line_number, &error_handler)
|
60
|
-
end
|
61
|
-
|
62
|
-
def self.column(name, options, &block)
|
63
|
-
raise_if_duplicate_column(name)
|
64
|
-
raise_if_missing_header(name, options)
|
56
|
+
def self.column(column, options, &block)
|
57
|
+
raise_if_duplicate_column(column)
|
58
|
+
raise_if_missing_header(column, options)
|
65
59
|
|
66
60
|
options = {
|
67
61
|
blanks_as_nil: (options[:as] == :raw ? false : true),
|
@@ -74,60 +68,116 @@ class CSVParty
|
|
74
68
|
"#{options[:as]}_parser".to_sym
|
75
69
|
end
|
76
70
|
|
77
|
-
columns[
|
71
|
+
columns[column] = {
|
78
72
|
header: options[:header],
|
79
73
|
parser: parser,
|
80
74
|
blanks_as_nil: options[:blanks_as_nil]
|
81
75
|
}
|
82
76
|
end
|
83
77
|
|
84
|
-
def self.
|
85
|
-
@
|
86
|
-
end
|
87
|
-
|
88
|
-
def columns
|
89
|
-
self.class.columns
|
78
|
+
def self.rows(&block)
|
79
|
+
@row_importer = block
|
90
80
|
end
|
91
81
|
|
92
82
|
def self.import(&block)
|
93
83
|
@importer = block
|
94
84
|
end
|
95
85
|
|
96
|
-
def self.
|
97
|
-
@
|
86
|
+
def self.errors(&block)
|
87
|
+
@error_processor = block
|
98
88
|
end
|
99
89
|
|
100
|
-
def
|
101
|
-
|
90
|
+
def self.columns
|
91
|
+
@columns ||= {}
|
102
92
|
end
|
103
93
|
|
104
|
-
def self.
|
105
|
-
@
|
94
|
+
def self.row_importer
|
95
|
+
@row_importer ||= nil
|
106
96
|
end
|
107
97
|
|
108
|
-
def self.
|
109
|
-
@
|
98
|
+
def self.importer
|
99
|
+
@importer ||= nil
|
110
100
|
end
|
111
101
|
|
112
|
-
def
|
113
|
-
|
102
|
+
def self.error_processor
|
103
|
+
@error_processor ||= nil
|
104
|
+
end
|
105
|
+
|
106
|
+
def aborted?
|
107
|
+
@aborted
|
114
108
|
end
|
115
109
|
|
116
110
|
def self.raise_if_duplicate_column(name)
|
117
111
|
return unless columns.has_key?(name)
|
118
112
|
|
119
113
|
raise DuplicateColumnError, "A column named :#{name} has already been \
|
120
|
-
|
114
|
+
defined, choose a different name"
|
121
115
|
end
|
116
|
+
private_class_method :raise_if_duplicate_column
|
122
117
|
|
123
118
|
def self.raise_if_missing_header(name, options)
|
124
119
|
return if options.has_key?(:header)
|
125
120
|
|
126
121
|
raise MissingHeaderError, "A header must be specified for #{name}"
|
127
122
|
end
|
123
|
+
private_class_method :raise_if_missing_header
|
128
124
|
|
129
125
|
private
|
130
126
|
|
127
|
+
def import_row!(row)
|
128
|
+
parsed_row = parse_row(row)
|
129
|
+
instance_exec(parsed_row, &row_importer)
|
130
|
+
end
|
131
|
+
|
132
|
+
def parse_row(row)
|
133
|
+
unparsed_row = OpenStruct.new
|
134
|
+
columns.each do |column, options|
|
135
|
+
header = options[:header]
|
136
|
+
unparsed_row[column] = row[header]
|
137
|
+
end
|
138
|
+
|
139
|
+
parsed_row = OpenStruct.new
|
140
|
+
columns.each do |column, options|
|
141
|
+
value = row[options[:header]]
|
142
|
+
parsed_row[column] = parse_column(
|
143
|
+
value,
|
144
|
+
options[:parser],
|
145
|
+
options[:blanks_as_nil]
|
146
|
+
)
|
147
|
+
end
|
148
|
+
|
149
|
+
parsed_row[:unparsed] = unparsed_row
|
150
|
+
parsed_row[:csv_string] = row.to_csv
|
151
|
+
|
152
|
+
return parsed_row
|
153
|
+
end
|
154
|
+
|
155
|
+
def parse_column(value, parser, blanks_as_nil)
|
156
|
+
if blanks_as_nil && is_blank?(value)
|
157
|
+
nil
|
158
|
+
elsif parser.is_a? Symbol
|
159
|
+
send(parser, value)
|
160
|
+
else
|
161
|
+
instance_exec(value, &parser)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def process_error(error, line_number)
|
166
|
+
instance_exec(error, line_number, &error_processor)
|
167
|
+
end
|
168
|
+
|
169
|
+
def skip_row
|
170
|
+
raise SkippedRowError
|
171
|
+
end
|
172
|
+
|
173
|
+
def abort_row(message)
|
174
|
+
raise AbortedRowError, message
|
175
|
+
end
|
176
|
+
|
177
|
+
def abort_import(message)
|
178
|
+
raise AbortedImportError, message
|
179
|
+
end
|
180
|
+
|
131
181
|
def is_blank?(value)
|
132
182
|
value.nil? || value.strip.empty?
|
133
183
|
end
|
@@ -190,7 +240,30 @@ class CSVParty
|
|
190
240
|
columns = missing_columns.join("', '")
|
191
241
|
raise MissingColumnError,
|
192
242
|
"CSV file is missing column(s) with header(s) '#{columns}'. \
|
193
|
-
|
243
|
+
File has these headers: #{@headers.join(', ')}."
|
244
|
+
end
|
245
|
+
|
246
|
+
def setup_dependencies(dependencies)
|
247
|
+
return unless dependencies
|
248
|
+
|
249
|
+
dependencies.each do |dependency, value|
|
250
|
+
self.class.class_eval { attr_accessor dependency }
|
251
|
+
send("#{dependency}=", value)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def initialize_import_settings
|
256
|
+
@columns = self.class.columns
|
257
|
+
@row_importer = self.class.row_importer
|
258
|
+
@importer = self.class.importer
|
259
|
+
@error_processor = self.class.error_processor
|
260
|
+
end
|
261
|
+
|
262
|
+
def initialize_counters_and_statuses
|
263
|
+
@imported_rows = []
|
264
|
+
@skipped_rows = []
|
265
|
+
@aborted_rows = []
|
266
|
+
@aborted = false
|
194
267
|
end
|
195
268
|
end
|
196
269
|
|
@@ -205,3 +278,12 @@ end
|
|
205
278
|
|
206
279
|
class MissingColumnError < ArgumentError
|
207
280
|
end
|
281
|
+
|
282
|
+
class SkippedRowError < RuntimeError
|
283
|
+
end
|
284
|
+
|
285
|
+
class AbortedRowError < RuntimeError
|
286
|
+
end
|
287
|
+
|
288
|
+
class AbortedImportError < RuntimeError
|
289
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: csv_party
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.1.
|
4
|
+
version: 0.0.1.pre7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rico Jones
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-05-01 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: A gem for making CSV imports a little more fun.
|
14
14
|
email: rico@toasterlovin.com
|
@@ -29,7 +29,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
30
30
|
- - ">="
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: '0'
|
32
|
+
version: '2.0'
|
33
33
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
34
34
|
requirements:
|
35
35
|
- - ">"
|