csv_party 0.0.1.pre → 0.0.1.pre2
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 +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
|