csv_party 0.0.1.pre → 0.0.1.pre2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/csv_party.rb +85 -38
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c0ddb8c3205a36c4ed2b5dd97baf32e60d1da2bb
|
4
|
+
data.tar.gz: f7bd204ac0a1f74b0f7cd5e90df88fa36edc8e7c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d00c4e83bf4f3564f15c9abf6159e209652a1f04b99df349211a7040b47d8670804a6b4c38c77594e2d75fa1735581b706baa0fa45a1ff449180d5112881e474
|
7
|
+
data.tar.gz: 7778782ffea56a91250478b23fb8ad5634c5b17f1c94b0ef94910c6544e25d242f6c457cbc0f644316eaa1fc28d0827cfeb99d732ba72ea5b27182c8507f456f
|
data/lib/csv_party.rb
CHANGED
@@ -3,66 +3,76 @@ require 'bigdecimal'
|
|
3
3
|
require 'ostruct'
|
4
4
|
|
5
5
|
class CSVParty
|
6
|
-
def initialize(csv_path)
|
6
|
+
def initialize(csv_path, options = {})
|
7
|
+
options[:headers] = true
|
7
8
|
@headers = CSV.new(File.open(csv_path)).shift
|
8
|
-
@csv = CSV.new(File.open(csv_path),
|
9
|
+
@csv = CSV.new(File.open(csv_path), options)
|
10
|
+
|
9
11
|
raise_unless_named_parsers_are_valid
|
10
12
|
raise_unless_csv_has_all_headers
|
11
13
|
end
|
12
14
|
|
13
15
|
def import!
|
14
|
-
|
15
|
-
|
16
|
-
|
16
|
+
loop do
|
17
|
+
begin
|
18
|
+
row = @csv.shift
|
19
|
+
break unless row
|
20
|
+
import_row(row)
|
21
|
+
rescue StandardError => error
|
22
|
+
process_error(error, @csv.lineno + 1)
|
23
|
+
next
|
24
|
+
end
|
17
25
|
end
|
18
26
|
end
|
19
27
|
|
20
28
|
def parse_row(row)
|
29
|
+
unparsed_row = OpenStruct.new
|
21
30
|
parsed_row = OpenStruct.new
|
22
|
-
parsed_row[:values] = OpenStruct.new
|
23
31
|
|
24
32
|
columns.each do |name, options|
|
25
33
|
header = options[:header]
|
34
|
+
unparsed_value = row[header]
|
26
35
|
parser = options[:parser]
|
27
|
-
|
28
|
-
|
36
|
+
|
37
|
+
unparsed_row[name] = unparsed_value
|
38
|
+
parsed_row[name] = instance_exec(unparsed_value, &parser)
|
29
39
|
end
|
30
40
|
|
41
|
+
parsed_row['unparsed'] = unparsed_row
|
42
|
+
parsed_row['csv_string'] = row.to_csv
|
43
|
+
|
31
44
|
return parsed_row
|
32
45
|
end
|
33
46
|
|
34
|
-
def import_row(
|
35
|
-
|
47
|
+
def import_row(row)
|
48
|
+
parsed_row = parse_row(row)
|
49
|
+
instance_exec(parsed_row, &importer)
|
50
|
+
end
|
51
|
+
|
52
|
+
def process_error(error, line_number)
|
53
|
+
instance_exec(error, line_number, &error_handler)
|
36
54
|
end
|
37
55
|
|
38
56
|
def self.column(name, options, &block)
|
39
|
-
|
40
|
-
|
41
|
-
end
|
42
|
-
unless options.has_key?(:header)
|
43
|
-
raise MissingHeaderError, "A header must be specified for #{name}"
|
44
|
-
end
|
57
|
+
raise_if_duplicate_column(name)
|
58
|
+
raise_if_missing_header(name, options)
|
45
59
|
|
46
60
|
if block_given?
|
47
61
|
columns[name] = { header: options[:header], parser: block }
|
48
62
|
else
|
49
|
-
if options.has_key?(:as)
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
63
|
+
parser_method = if options.has_key?(:as)
|
64
|
+
"#{options[:as]}_parser".to_sym
|
65
|
+
else
|
66
|
+
:string_parser
|
67
|
+
end
|
54
68
|
columns[name] = {
|
55
69
|
header: options[:header],
|
56
|
-
parser:
|
70
|
+
parser: proc { |value| send(parser_method, value) },
|
57
71
|
parser_method: parser_method
|
58
72
|
}
|
59
73
|
end
|
60
74
|
end
|
61
75
|
|
62
|
-
def self.import(&block)
|
63
|
-
@importer = block
|
64
|
-
end
|
65
|
-
|
66
76
|
def self.columns
|
67
77
|
@columns ||= {}
|
68
78
|
end
|
@@ -71,6 +81,10 @@ class CSVParty
|
|
71
81
|
self.class.columns
|
72
82
|
end
|
73
83
|
|
84
|
+
def self.import(&block)
|
85
|
+
@importer = block
|
86
|
+
end
|
87
|
+
|
74
88
|
def self.importer
|
75
89
|
@importer
|
76
90
|
end
|
@@ -79,8 +93,32 @@ class CSVParty
|
|
79
93
|
self.class.importer
|
80
94
|
end
|
81
95
|
|
82
|
-
|
96
|
+
def self.error(&block)
|
97
|
+
@error = block
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.error_handler
|
101
|
+
@error
|
102
|
+
end
|
103
|
+
|
104
|
+
def error_handler
|
105
|
+
self.class.error_handler
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.raise_if_duplicate_column(name)
|
109
|
+
return unless columns.has_key?(name)
|
110
|
+
|
111
|
+
raise DuplicateColumnError, "A column named :#{name} has already been \
|
112
|
+
defined, choose a different name"
|
113
|
+
end
|
114
|
+
|
115
|
+
def self.raise_if_missing_header(name, options)
|
116
|
+
return if options.has_key?(:header)
|
117
|
+
|
118
|
+
raise MissingHeaderError, "A header must be specified for #{name}"
|
119
|
+
end
|
83
120
|
|
121
|
+
private
|
84
122
|
|
85
123
|
def raw_parser(value)
|
86
124
|
value
|
@@ -91,15 +129,16 @@ class CSVParty
|
|
91
129
|
end
|
92
130
|
|
93
131
|
def boolean_parser(value)
|
94
|
-
[
|
132
|
+
%w[1 t true].include? value.to_s.strip.downcase
|
95
133
|
end
|
96
134
|
|
97
135
|
def integer_parser(value)
|
136
|
+
return nil if value.nil? || value.strip.empty?
|
98
137
|
value.to_i
|
99
138
|
end
|
100
139
|
|
101
140
|
def decimal_parser(value)
|
102
|
-
cleaned_value = value.to_s.strip.gsub(/[^0-9.]/,
|
141
|
+
cleaned_value = value.to_s.strip.gsub(/[^0-9.]/, '')
|
103
142
|
BigDecimal.new(cleaned_value)
|
104
143
|
end
|
105
144
|
|
@@ -108,7 +147,7 @@ class CSVParty
|
|
108
147
|
end
|
109
148
|
|
110
149
|
def columns_with_named_parsers
|
111
|
-
columns.select { |
|
150
|
+
columns.select { |_name, options| options.has_key?(:parser_method) }
|
112
151
|
end
|
113
152
|
|
114
153
|
# This error has to be raised at runtime because, when the class body
|
@@ -117,22 +156,30 @@ class CSVParty
|
|
117
156
|
def raise_unless_named_parsers_are_valid
|
118
157
|
columns_with_named_parsers.each do |name, options|
|
119
158
|
parser = options[:parser_method]
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
159
|
+
next if named_parsers.include? parser
|
160
|
+
|
161
|
+
parser = parser.to_s.gsub('_parser', '')
|
162
|
+
parsers = named_parsers
|
163
|
+
.map { |p| p.to_s.gsub('_parser', '') }
|
164
|
+
.join(', :')
|
165
|
+
raise UnknownParserError,
|
166
|
+
"You're trying to use the :#{parser} parser for the :#{name} \
|
167
|
+
column, but it doesn't exist. Available parsers are: :#{parsers}."
|
124
168
|
end
|
125
169
|
end
|
126
170
|
|
127
171
|
def defined_headers
|
128
|
-
columns.map { |
|
172
|
+
columns.map { |_name, options| options[:header] }
|
129
173
|
end
|
130
174
|
|
131
175
|
def raise_unless_csv_has_all_headers
|
132
176
|
missing_columns = defined_headers - @headers
|
133
|
-
|
134
|
-
|
135
|
-
|
177
|
+
return if missing_columns.empty?
|
178
|
+
|
179
|
+
columns = missing_columns.join("', '")
|
180
|
+
raise MissingColumnError,
|
181
|
+
"CSV file is missing column(s) with header(s) '#{columns}'. \
|
182
|
+
File has these headers: #{@headers.join(', ')}."
|
136
183
|
end
|
137
184
|
end
|
138
185
|
|
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.pre2
|
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-04-27 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
|