csv2hash 0.0.2 → 0.1
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/.coveralls.yml +0 -1
- data/Gemfile.lock +1 -1
- data/README.md +149 -61
- data/ReleaseNotes.md +16 -0
- data/bin/publish +1 -1
- data/csv2hash.gemspec +4 -4
- data/lib/csv2hash/csv_array.rb +1 -1
- data/lib/csv2hash/data_wrapper.rb +13 -0
- data/lib/csv2hash/definition.rb +41 -38
- data/lib/csv2hash/extra_validator.rb +5 -0
- data/lib/csv2hash/notifier.rb +7 -0
- data/lib/csv2hash/parser/collection.rb +6 -4
- data/lib/csv2hash/parser/mapping.rb +4 -3
- data/lib/csv2hash/parser.rb +4 -2
- data/lib/csv2hash/validator/collection.rb +5 -4
- data/lib/csv2hash/validator/mapping.rb +3 -3
- data/lib/csv2hash/validator.rb +33 -31
- data/lib/csv2hash/version.rb +1 -1
- data/lib/csv2hash.rb +42 -28
- data/spec/csv2hash/definition_spec.rb +11 -9
- data/spec/csv2hash/parser/collection_spec.rb +15 -8
- data/spec/csv2hash/parser/mapping_spec.rb +3 -5
- data/spec/csv2hash/parser_spec.rb +1 -1
- data/spec/csv2hash/validator/collection_spec.rb +20 -11
- data/spec/csv2hash/validator/mapping_spec.rb +40 -12
- data/spec/csv2hash/validator_spec.rb +6 -6
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a139a39be73ed85cce068c4b1b34c564a7c5a7a7
|
4
|
+
data.tar.gz: 26ad3fe5d1ce458dfcd8dd4269d519f419f79592
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9270e2cf190351a68be08114049fb16392769da65b3fb8eb03206f699a898bf8a99e21d3cf6f6fd28196dfbd3197cb9f0918f3563205d2c1b0d1ddf3064a6b5c
|
7
|
+
data.tar.gz: 55f28e1b266ca939c901d47d61bb6bd58b91d25506e764da2bab3ae62b9022ff190f630b29cb199a7a09f33714d367b7506e51c9d3a5c696095d5b58073de82c
|
data/.coveralls.yml
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -9,7 +9,7 @@
|
|
9
9
|
[](https://coveralls.io/r/joel/csv2hash)
|
10
10
|
|
11
11
|
|
12
|
-
It
|
12
|
+
It is a DSL to validate and map a CSV to a Ruby Hash.
|
13
13
|
|
14
14
|
## Installation
|
15
15
|
|
@@ -27,21 +27,23 @@ Or install it yourself as:
|
|
27
27
|
|
28
28
|
## Usage
|
29
29
|
|
30
|
-
|
30
|
+
Parsing is based on rules, you must defined rules of parsing
|
31
31
|
|
32
|
-
|
32
|
+
### Rules
|
33
|
+
|
34
|
+
You should declare a definition for you CSV, and then define for each cell what you would expect.
|
33
35
|
|
34
36
|
Example :
|
35
37
|
|
36
|
-
You want
|
38
|
+
You want the cell located on first line, first column to be a string with its values to be 'yes' or 'no'. Then you can right the following validation rule :
|
37
39
|
|
38
40
|
{ name: 'aswering', type: 'string', values: ['yes', 'no'], position: [0,0] }
|
39
41
|
|
40
|
-
|
42
|
+
The type is 'string' by default, so can just write:
|
41
43
|
|
42
44
|
{ name: 'aswering', values: ['yes', 'no'], position: [0,0] }
|
43
45
|
|
44
|
-
You can define message, default is 'undefined :key on :position'
|
46
|
+
You can define a message, default is 'undefined :key on :position'
|
45
47
|
|
46
48
|
{ name: 'aswering', values: ['yes', 'no'], position: [0,0], message: 'this value is not supported' }
|
47
49
|
|
@@ -49,37 +51,52 @@ You can also define Range
|
|
49
51
|
|
50
52
|
{ name: 'score', values: 0..5, position: [0,0] }
|
51
53
|
|
52
|
-
|
54
|
+
The message is parsed:
|
53
55
|
|
54
56
|
{ ..., message: 'value of :name is not supported, please you one of :values' }
|
55
57
|
|
56
|
-
|
57
|
-
|
58
|
+
It produces :
|
58
59
|
|
59
60
|
value of aswering is not supported, please you one of [yes, no]
|
60
61
|
|
61
|
-
|
62
|
+
### Default values
|
63
|
+
|
64
|
+
Only position is required:
|
65
|
+
|
66
|
+
* :position
|
67
|
+
|
68
|
+
All remaining keys are optionals:
|
69
|
+
|
70
|
+
* message: 'undefined :key on :position'
|
71
|
+
* mappable: true
|
72
|
+
* type: 'string'
|
73
|
+
* values: nil
|
74
|
+
* nested: nil
|
75
|
+
* allow_blank: false
|
76
|
+
* extra_validator: nil
|
62
77
|
|
63
|
-
|
78
|
+
## Define where your data is expected
|
64
79
|
|
65
|
-
|
80
|
+
**IMPORTANT!** Position mean [Y, X], where Y is rows, X columns
|
66
81
|
|
67
|
-
|
82
|
+
A definition should be provided. There are 2 types of definitions:
|
83
|
+
* search for data at a precise position in the table: `y,x`
|
84
|
+
* or search for data in a column of rows, where all the rows are the same: `x` (column index)
|
68
85
|
|
69
|
-
|
86
|
+
## Samples
|
70
87
|
|
71
|
-
|
88
|
+
### Validation of a cell at a precise position
|
72
89
|
|
73
|
-
Consider
|
90
|
+
Consider the following CSV:
|
74
91
|
|
75
92
|
| Fields | Person Informations | Optional |
|
76
93
|
|-------------|----------------------|----------|
|
77
|
-
| Nickname |
|
94
|
+
| Nickname | jo | no |
|
78
95
|
| First Name | John | yes |
|
79
96
|
| Last Name | Doe | yes |
|
80
97
|
|
81
98
|
|
82
|
-
|
99
|
+
Precise position validation sample:
|
83
100
|
|
84
101
|
class MyParser
|
85
102
|
|
@@ -89,6 +106,12 @@ Mapping sample definition
|
|
89
106
|
@file_path = file_path
|
90
107
|
end
|
91
108
|
|
109
|
+
def data
|
110
|
+
@data_wrapper ||= Csv2hash.new(definition, file_path).parse
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
92
115
|
def rules
|
93
116
|
[].tap do |mapping|
|
94
117
|
mapping << { position: [2,1], key: 'first_name' }
|
@@ -97,27 +120,21 @@ Mapping sample definition
|
|
97
120
|
end
|
98
121
|
|
99
122
|
def definition
|
100
|
-
Definition.new(rules, type = Definition::MAPPING)
|
101
|
-
end
|
102
|
-
|
103
|
-
def data
|
104
|
-
Csv2hash.new(definition, file_path).tap do |csv2hash|
|
105
|
-
csv2hash.parse
|
106
|
-
end.data
|
123
|
+
Csv2Hash::Definition.new(rules, type = Csv2Hash::Definition::MAPPING, 1)
|
107
124
|
end
|
108
125
|
|
109
126
|
end
|
110
127
|
|
111
|
-
|
128
|
+
### Validation of a collection
|
112
129
|
|
113
|
-
Consider
|
130
|
+
Consider the following CSV:
|
114
131
|
|
115
132
|
| Nickname | First Name | Last Name |
|
116
133
|
|----------|------------|-----------|
|
117
|
-
|
|
118
|
-
|
|
134
|
+
| jo | John | Doe |
|
135
|
+
| ja | Jane | Doe |
|
119
136
|
|
120
|
-
|
137
|
+
Collection validation sample:
|
121
138
|
|
122
139
|
class MyParser
|
123
140
|
|
@@ -127,6 +144,12 @@ Mapping sample definition
|
|
127
144
|
@file_path = file_path
|
128
145
|
end
|
129
146
|
|
147
|
+
def data
|
148
|
+
@data_wrapper ||= Csv2hash.new(definition, file_path).parse
|
149
|
+
end
|
150
|
+
|
151
|
+
private
|
152
|
+
|
130
153
|
def rules
|
131
154
|
[].tap do |mapping|
|
132
155
|
mapping << { position: 0, key: 'nickname' }
|
@@ -136,60 +159,125 @@ Mapping sample definition
|
|
136
159
|
end
|
137
160
|
|
138
161
|
def definition
|
139
|
-
Definition.new(rules, type = Definition::
|
140
|
-
end
|
141
|
-
|
142
|
-
def data
|
143
|
-
Csv2hash.new(definition, file_path).tap do |csv2hash|
|
144
|
-
csv2hash.parse
|
145
|
-
end.data
|
162
|
+
Csv2Hash::Definition.new(rules, type = Csv2Hash::Definition::MAPPING, 1)
|
146
163
|
end
|
147
164
|
|
148
165
|
end
|
149
166
|
|
150
|
-
|
167
|
+
### CSV Headers
|
151
168
|
|
152
|
-
You
|
169
|
+
You can define the number of rows to skip in the header of the CSV.
|
153
170
|
|
154
171
|
Definition.new(rules, type, header_size=0)
|
155
172
|
|
156
|
-
|
173
|
+
### Parser and configuration
|
157
174
|
|
158
|
-
|
175
|
+
Pasrer take severale parameters like that :
|
159
176
|
|
177
|
+
initialize definition, file_path, exception_mode=true, data_source=nil, ignore_blank_line=false
|
160
178
|
|
161
|
-
|
179
|
+
you can pass directly Array of data (Array at 2 dimensions) really useful for testing, if you don't care about line blank in your CSV you can ignore them.
|
162
180
|
|
163
|
-
|
164
|
-
result = csv2hash.parse
|
165
|
-
return result if csv2hash.valid?
|
181
|
+
### Response
|
166
182
|
|
167
|
-
|
168
|
-
tempfile = Tempfile.new [filename, File.extname(filename)]
|
169
|
-
File.open(tempfile.path, 'wb') { |file| file.write result }
|
183
|
+
The parser return values wrapper into DataWrapper Object, you can call .valid? method on this Object and grab either data or errors like that :
|
170
184
|
|
171
|
-
|
185
|
+
response = parser.parse
|
186
|
+
if response.valid?
|
187
|
+
response.data
|
188
|
+
else
|
189
|
+
response.errors
|
190
|
+
end
|
172
191
|
|
173
|
-
|
192
|
+
data or errors are Array, but errors can be formatted on csv format with .to_csv call
|
174
193
|
|
194
|
+
response.errors.to_csv
|
175
195
|
|
176
|
-
|
196
|
+
## Exception or Not !
|
177
197
|
|
178
|
-
|
198
|
+
You can choice 2 modes of parsing, either **exception mode** for raise exception in first breaking rules or **csv mode** for get csv original data + errors throwing into added columns.
|
179
199
|
|
180
|
-
|
200
|
+
### On **CSV MODE** you can choose different way for manage errors
|
181
201
|
|
182
|
-
|
202
|
+
`.parse()` return `data_wrapper` if `.parse()` is invalid, you can code your own behavior like that :
|
203
|
+
|
204
|
+
in your code
|
205
|
+
|
206
|
+
parser = Csv2hash.new(definition, 'file_path').new
|
207
|
+
response = parser.parse
|
208
|
+
return response if response.valid?
|
209
|
+
# Whatever
|
210
|
+
|
211
|
+
In the same time Csv2hash call **notify(response)** method when CSV parsing fail, you can add your own Notifier like that
|
212
|
+
|
213
|
+
module Csv2hash
|
214
|
+
module Plugins
|
215
|
+
class Notifier
|
216
|
+
def initialize csv2hash
|
217
|
+
csv2hash.notifier.extend NotifierWithEmail
|
218
|
+
end
|
219
|
+
|
220
|
+
module NotifierWithEmail
|
221
|
+
def notify response
|
222
|
+
filename = 'issues_errors.csv'
|
223
|
+
tempfile = Tempfile.new [filename, File.extname(filename)]
|
224
|
+
File.open(tempfile.path, 'wb') { |file| file.write response.errors.to_csv }
|
225
|
+
# Send mail with csv file + errors and free resource
|
226
|
+
tempfile.unlink
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
Or other implementation
|
234
|
+
|
235
|
+
### Errors Format
|
236
|
+
|
237
|
+
errors is a Array of Hash
|
238
|
+
|
239
|
+
{ y: 1, x: 0, message: 'message', key: 'key', value: '' }
|
240
|
+
|
241
|
+
## Sample
|
242
|
+
|
243
|
+
### Csv data
|
244
|
+
|
245
|
+
| Fields | Person Informations |
|
246
|
+
|-------------|----------------------|
|
247
|
+
| Nickname | nil |
|
248
|
+
|
249
|
+
### Rule
|
250
|
+
|
251
|
+
{ position: [1,1], key: 'nickname', allow_blank: false }
|
252
|
+
|
253
|
+
### Error
|
254
|
+
|
255
|
+
{ y: 1, x: 1, message: 'undefined nikcname on [0, 0]', key: 'nickname', value: nil }
|
256
|
+
## Personal Validator Rule
|
257
|
+
|
258
|
+
You can define your own Validator
|
259
|
+
|
260
|
+
For downcase validation
|
261
|
+
|
262
|
+
class DowncaseValidator < Csv2hash::ExtraValidator
|
263
|
+
def valid? value
|
264
|
+
!!(value.match /^[a-z]+$/)
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
in your rule
|
269
|
+
|
270
|
+
{ position: [0,0], key: 'name', extra_validator: DowncaseValidator.new,
|
271
|
+
message: 'your data should be writting in downcase only' }
|
272
|
+
|
273
|
+
Csv data
|
274
|
+
|
275
|
+
[ [ 'Foo' ] ]
|
183
276
|
|
184
|
-
* message: 'undefined :key on :position'
|
185
|
-
* mappable: true
|
186
|
-
* type: 'string'
|
187
|
-
* values: nil
|
188
|
-
* nested: nil
|
189
|
-
* allow_blank: false
|
190
277
|
|
191
278
|
### Limitations
|
192
279
|
|
280
|
+
* Don't manage number of column you expected on collection mode
|
193
281
|
|
194
282
|
## Contributing
|
195
283
|
|
@@ -197,4 +285,4 @@ all remaind keys are optionals
|
|
197
285
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
198
286
|
3. Commit your changes (`git commit -am 'Added some feature'`)
|
199
287
|
4. Push to the branch (`git push origin my-new-feature`)
|
200
|
-
5. Create new Pull Request
|
288
|
+
5. Create new Pull Request
|
data/ReleaseNotes.md
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
VERSION 0.1
|
2
|
+
|
3
|
+
* Add csv errors mode
|
4
|
+
* Add Extra Validator
|
5
|
+
* Add Notifier Plugin
|
6
|
+
* Add Data Wrapper Response
|
7
|
+
* Add possibility to pass directly data in constructor, for more testing
|
8
|
+
* You can ignore blank line
|
9
|
+
|
10
|
+
VERSION 0.0.2
|
11
|
+
|
12
|
+
* Liitle enhancement
|
13
|
+
|
14
|
+
VERSION 0.0.1
|
15
|
+
|
16
|
+
* Skeleton and first parsing
|
data/bin/publish
CHANGED
data/csv2hash.gemspec
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
Gem::Specification.new do |spec|
|
2
2
|
|
3
3
|
spec.name = 'csv2hash'
|
4
|
-
spec.version = '0.
|
4
|
+
spec.version = '0.1'
|
5
5
|
spec.date = '2013-11-26'
|
6
|
-
spec.summary = %q{Mapping CSV to Ruby Hash}
|
7
|
-
spec.description = %q{DSL
|
6
|
+
spec.summary = %q{Mapping a CSV to a Ruby Hash}
|
7
|
+
spec.description = %q{DSL to map a CSV to a Ruby Hash}
|
8
8
|
spec.authors = ['Joel AZEMAR']
|
9
9
|
spec.email = 'joel.azemar@gmail.com'
|
10
10
|
spec.files = ['lib/csv2hash.rb']
|
@@ -21,4 +21,4 @@ Gem::Specification.new do |spec|
|
|
21
21
|
spec.add_development_dependency 'rspec'
|
22
22
|
|
23
23
|
spec.required_ruby_version = '~> 2.0'
|
24
|
-
end
|
24
|
+
end
|
data/lib/csv2hash/csv_array.rb
CHANGED
data/lib/csv2hash/definition.rb
CHANGED
@@ -1,52 +1,55 @@
|
|
1
|
-
class
|
1
|
+
class Csv2hash
|
2
|
+
class Definition
|
2
3
|
|
3
|
-
|
4
|
-
|
4
|
+
MAPPING = 'mapping'
|
5
|
+
COLLECTION = 'collection'
|
5
6
|
|
6
|
-
|
7
|
+
TYPES = [MAPPING, COLLECTION]
|
7
8
|
|
8
|
-
|
9
|
+
attr_accessor :rules, :type, :header_size
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
11
|
+
def initialize rules, type, header_size=0
|
12
|
+
self.rules, self.type, self.header_size = rules, type, header_size
|
13
|
+
end
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
15
|
+
def validate!
|
16
|
+
unless TYPES.include?(type)
|
17
|
+
raise "not suitable type, please use '#{MAPPING}' or '#{COLLECTION}'"
|
18
|
+
end
|
19
|
+
raise 'rules must be an Array of rules' unless rules.class == Array
|
17
20
|
end
|
18
|
-
raise 'rules must be an Array of rules' unless rules.class == Array
|
19
|
-
end
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
22
|
+
def default!
|
23
|
+
rules.each do |rule|
|
24
|
+
default_position rule
|
25
|
+
unless rule.has_key? :message
|
26
|
+
if rule.has_key? :values
|
27
|
+
rule.merge! message: ':key not supported, please use one of :values'
|
28
|
+
else
|
29
|
+
rule.merge! message: 'undefined :key on :position'
|
30
|
+
end
|
29
31
|
end
|
32
|
+
rule.merge! mappable: true unless rule.has_key? :mappable
|
33
|
+
rule.merge! type: 'string' unless rule.has_key? :type
|
34
|
+
rule.merge! values: nil unless rule.has_key? :values
|
35
|
+
rule.merge! nested: nil unless rule.has_key? :nested
|
36
|
+
rule.merge! allow_blank: false unless rule.has_key? :allow_blank
|
37
|
+
rule.merge! extra_validator: nil unless rule.has_key? :extra_validator
|
30
38
|
end
|
31
|
-
rule.merge! mappable: true unless rule.has_key? :mappable
|
32
|
-
rule.merge! type: 'string' unless rule.has_key? :type
|
33
|
-
rule.merge! values: nil unless rule.has_key? :values
|
34
|
-
rule.merge! nested: nil unless rule.has_key? :nested
|
35
|
-
rule.merge! allow_blank: false unless rule.has_key? :allow_blank
|
36
39
|
end
|
37
|
-
end
|
38
40
|
|
39
|
-
|
41
|
+
private
|
40
42
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
43
|
+
def default_position rule
|
44
|
+
case type
|
45
|
+
when MAPPING
|
46
|
+
y, x = rule.fetch(:position, ['undefined', 'undefined'])
|
47
|
+
rule.merge! key: "key_#{y}_#{x}" unless rule.has_key? :key
|
48
|
+
when COLLECTION
|
49
|
+
x = rule.fetch :position
|
50
|
+
rule.merge! key: "key_undefined_#{x}" unless rule.has_key? :key
|
51
|
+
end
|
49
52
|
end
|
50
|
-
end
|
51
53
|
|
52
|
-
end
|
54
|
+
end
|
55
|
+
end
|
@@ -1,10 +1,12 @@
|
|
1
|
-
module Parser::Collection
|
1
|
+
module Csv2hash::Parser::Collection
|
2
|
+
include Csv2hash::Parser
|
2
3
|
|
3
4
|
def fill!
|
4
|
-
|
5
|
+
self.data = {}.tap do |data_computed|
|
5
6
|
data_computed[:data] ||= []
|
6
|
-
|
7
|
+
self.data_source.each_with_index do |line, y|
|
7
8
|
next if y < definition.header_size
|
9
|
+
next if self.ignore_blank_line and line.compact.empty?
|
8
10
|
data_computed[:data] << {}.tap do |data_parsed|
|
9
11
|
fill_it data_parsed, line
|
10
12
|
end
|
@@ -26,4 +28,4 @@ module Parser::Collection extend Parser
|
|
26
28
|
end
|
27
29
|
end
|
28
30
|
|
29
|
-
end
|
31
|
+
end
|
@@ -1,7 +1,8 @@
|
|
1
|
-
module Parser::Mapping
|
1
|
+
module Csv2hash::Parser::Mapping
|
2
|
+
include Csv2hash::Parser
|
2
3
|
|
3
4
|
def fill!
|
4
|
-
|
5
|
+
self.data = {}.tap do |data_computed|
|
5
6
|
data_computed[:data] ||= []
|
6
7
|
data_computed[:data] << {}.tap do |data_parsed|
|
7
8
|
fill_it data_parsed, data_source
|
@@ -23,4 +24,4 @@ module Parser::Mapping extend Parser
|
|
23
24
|
end
|
24
25
|
end
|
25
26
|
|
26
|
-
end
|
27
|
+
end
|
data/lib/csv2hash/parser.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
|
-
module Validator::Collection
|
2
|
-
include Validator
|
1
|
+
module Csv2hash::Validator::Collection
|
2
|
+
include Csv2hash::Validator
|
3
3
|
|
4
4
|
def validate_data!
|
5
|
-
|
5
|
+
self.data_source.each_with_index do |line, y|
|
6
6
|
next if y < definition.header_size
|
7
|
+
next if self.ignore_blank_line and line.compact.empty?
|
7
8
|
validate_rules y
|
8
9
|
end
|
9
10
|
end
|
@@ -14,4 +15,4 @@ module Validator::Collection
|
|
14
15
|
[nil, _position]
|
15
16
|
end
|
16
17
|
|
17
|
-
end
|
18
|
+
end
|
data/lib/csv2hash/validator.rb
CHANGED
@@ -1,42 +1,44 @@
|
|
1
|
-
|
1
|
+
class Csv2hash
|
2
|
+
module Validator
|
3
|
+
|
4
|
+
def validate_rules y=nil
|
5
|
+
definition.rules.each do |rule|
|
6
|
+
_y, x = position rule.fetch(:position)
|
7
|
+
begin
|
8
|
+
validate_cell (_y||y), x, rule
|
9
|
+
rescue => e
|
10
|
+
self.errors << { y: (_y||y), x: x, message: e.message, key: rule.fetch(:key) }
|
11
|
+
raise if exception_mode
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def valid?() self.errors.empty?; end
|
2
17
|
|
3
|
-
|
18
|
+
protected
|
4
19
|
|
5
|
-
|
6
|
-
|
7
|
-
_y, x = position rule.fetch(:position)
|
20
|
+
def validate_cell y, x, rule
|
21
|
+
value = data_source[y][x] rescue nil
|
8
22
|
begin
|
9
|
-
|
23
|
+
raise unless value unless rule.fetch :allow_blank
|
24
|
+
if (extra_validator = rule.fetch :extra_validator) && extra_validator.kind_of?(Csv2hash::ExtraValidator)
|
25
|
+
raise unless extra_validator.valid? rule, value
|
26
|
+
else
|
27
|
+
if value && (values = rule.fetch :values)
|
28
|
+
raise unless values.include?(value)
|
29
|
+
end
|
30
|
+
end
|
10
31
|
rescue => e
|
11
|
-
|
12
|
-
raise if exception
|
32
|
+
raise message(rule, y, x)
|
13
33
|
end
|
14
34
|
end
|
15
|
-
end
|
16
|
-
|
17
|
-
def valid?() errors.empty?; end
|
18
|
-
|
19
|
-
protected
|
20
35
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
begin
|
26
|
-
raise unless value unless rule.fetch :allow_blank
|
27
|
-
if value && (values = rule.fetch :values)
|
28
|
-
raise unless values.include?(value)
|
36
|
+
def message rule, y, x
|
37
|
+
msg = rule.fetch(:message).tap do |msg|
|
38
|
+
rule.each { |key, value| msg.gsub!(":#{key.to_s}", value.to_s) unless key == :position }
|
29
39
|
end
|
30
|
-
|
31
|
-
raise message(rule, y, x)
|
40
|
+
msg.gsub ':position', "[#{y}, #{x}]"
|
32
41
|
end
|
33
|
-
end
|
34
42
|
|
35
|
-
def message rule, y, x
|
36
|
-
msg = rule.fetch(:message).tap do |msg|
|
37
|
-
rule.each { |key, value| msg.gsub!(":#{key.to_s}", value.to_s) unless key == :position }
|
38
|
-
end
|
39
|
-
msg.gsub ':position', "[#{y}, #{x}]"
|
40
43
|
end
|
41
|
-
|
42
|
-
end
|
44
|
+
end
|
data/lib/csv2hash/version.rb
CHANGED
data/lib/csv2hash.rb
CHANGED
@@ -7,29 +7,52 @@ require 'csv2hash/parser'
|
|
7
7
|
require 'csv2hash/parser/mapping'
|
8
8
|
require 'csv2hash/parser/collection'
|
9
9
|
require 'csv2hash/csv_array'
|
10
|
+
require 'csv2hash/data_wrapper'
|
11
|
+
require 'csv2hash/notifier'
|
12
|
+
require 'csv2hash/extra_validator'
|
13
|
+
|
10
14
|
require 'csv'
|
11
15
|
|
12
16
|
class Csv2hash
|
13
17
|
|
14
|
-
attr_accessor :definition, :file_path, :data, :
|
18
|
+
attr_accessor :definition, :file_path, :data, :notifier, :exception_mode, :errors, :ignore_blank_line
|
19
|
+
|
20
|
+
def initialize definition, file_path, exception_mode=true, data_source=nil, ignore_blank_line=false
|
21
|
+
@data_source = data_source
|
22
|
+
self.definition, self.file_path = definition, file_path
|
23
|
+
dynamic_lib_loading 'Parser'
|
24
|
+
self.exception_mode, self.errors = exception_mode, []
|
25
|
+
dynamic_lib_loading 'Validator'
|
26
|
+
self.notifier = Notifier.new
|
27
|
+
self.ignore_blank_line = ignore_blank_line
|
28
|
+
init_plugins
|
29
|
+
end
|
15
30
|
|
16
|
-
def
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
31
|
+
def init_plugins
|
32
|
+
begin
|
33
|
+
@plugins = []
|
34
|
+
::Csv2hash::Plugins.constants.each do |name|
|
35
|
+
@plugins << ::Csv2hash::Plugins.const_get(name).new(self)
|
36
|
+
end
|
37
|
+
rescue; end
|
21
38
|
end
|
22
39
|
|
23
40
|
def parse
|
24
41
|
load_data_source
|
42
|
+
|
25
43
|
definition.validate!
|
26
44
|
definition.default!
|
27
45
|
validate_data!
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
46
|
+
|
47
|
+
Csv2hash::DataWrapper.new.tap do |response|
|
48
|
+
if valid?
|
49
|
+
fill!
|
50
|
+
response.data = data[:data]
|
51
|
+
else
|
52
|
+
response.valid = false
|
53
|
+
response.errors = csv_with_errors
|
54
|
+
notifier.notify response
|
55
|
+
end
|
33
56
|
end
|
34
57
|
end
|
35
58
|
|
@@ -37,36 +60,27 @@ class Csv2hash
|
|
37
60
|
@csv_with_errors ||= begin
|
38
61
|
CsvArray.new.tap do |rows|
|
39
62
|
errors.each do |error|
|
40
|
-
rows << ((
|
63
|
+
rows << error.merge({ value: (data_source[error[:y]][error[:x]] rescue nil) })
|
41
64
|
end
|
42
|
-
end
|
65
|
+
end #.to_csv
|
43
66
|
end
|
44
67
|
end
|
45
68
|
|
46
69
|
# protected
|
47
70
|
|
48
71
|
def data_source
|
49
|
-
@data_source ||= CSV.read
|
72
|
+
@data_source ||= CSV.read self.file_path
|
50
73
|
end
|
51
74
|
alias_method :load_data_source, :data_source
|
52
75
|
|
53
76
|
private
|
54
77
|
|
55
|
-
def
|
56
|
-
case definition.type
|
57
|
-
when Definition::MAPPING
|
58
|
-
self.extend Validator::Mapping
|
59
|
-
when Definition::COLLECTION
|
60
|
-
self.extend Validator::Collection
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
def dynamic_parser_loading
|
78
|
+
def dynamic_lib_loading type
|
65
79
|
case definition.type
|
66
|
-
when Definition::MAPPING
|
67
|
-
self.extend
|
68
|
-
when Definition::COLLECTION
|
69
|
-
self.extend
|
80
|
+
when Csv2hash::Definition::MAPPING
|
81
|
+
self.extend Module.module_eval("Csv2hash::#{type}::Mapping")
|
82
|
+
when Csv2hash::Definition::COLLECTION
|
83
|
+
self.extend Module.module_eval("Csv2hash::#{type}::Collection")
|
70
84
|
end
|
71
85
|
end
|
72
86
|
|
@@ -1,17 +1,17 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe Definition do
|
3
|
+
describe Csv2hash::Definition do
|
4
4
|
|
5
5
|
context 'regular context' do
|
6
6
|
subject do
|
7
|
-
Definition.new(
|
7
|
+
Csv2hash::Definition.new(
|
8
8
|
[ { position: [0,0], key: 'name' } ],
|
9
|
-
Definition::MAPPING
|
9
|
+
Csv2hash::Definition::MAPPING
|
10
10
|
)
|
11
11
|
end
|
12
12
|
|
13
13
|
it 'variable should be assigned' do
|
14
|
-
subject.type.should eql Definition::MAPPING
|
14
|
+
subject.type.should eql Csv2hash::Definition::MAPPING
|
15
15
|
subject.rules.should eql [ { position: [0,0], key: 'name' } ]
|
16
16
|
end
|
17
17
|
end
|
@@ -19,17 +19,18 @@ describe Definition do
|
|
19
19
|
describe '#validate!' do
|
20
20
|
context 'rules failling validation' do
|
21
21
|
subject do
|
22
|
-
Definition.new nil, 'unsuitable_type'
|
22
|
+
Csv2hash::Definition.new nil, 'unsuitable_type'
|
23
23
|
end
|
24
24
|
it 'should throw exception' do
|
25
25
|
expect {
|
26
26
|
subject.validate!
|
27
|
-
}.to raise_error
|
27
|
+
}.to raise_error("not suitable type, please use '#{Csv2hash::Definition::MAPPING}' " \
|
28
|
+
"or '#{Csv2hash::Definition::COLLECTION}'")
|
28
29
|
end
|
29
30
|
end
|
30
31
|
context 'rules failling validation' do
|
31
32
|
subject do
|
32
|
-
Definition.new 'rules',Definition::MAPPING
|
33
|
+
Csv2hash::Definition.new 'rules',Csv2hash::Definition::MAPPING
|
33
34
|
end
|
34
35
|
it 'should throw exception' do
|
35
36
|
expect { subject.validate! }.to raise_error 'rules must be an Array of rules'
|
@@ -39,7 +40,7 @@ describe Definition do
|
|
39
40
|
|
40
41
|
describe '#default!' do
|
41
42
|
subject do
|
42
|
-
Definition.new [ { position: [0,0], key: 'name' } ], Definition::MAPPING
|
43
|
+
Csv2hash::Definition.new [ { position: [0,0], key: 'name' } ], Csv2hash::Definition::MAPPING
|
43
44
|
end
|
44
45
|
|
45
46
|
before { subject.default! }
|
@@ -52,7 +53,8 @@ describe Definition do
|
|
52
53
|
type: 'string',
|
53
54
|
values: nil,
|
54
55
|
nested: nil,
|
55
|
-
allow_blank: false
|
56
|
+
allow_blank: false,
|
57
|
+
extra_validator: nil }])
|
56
58
|
end
|
57
59
|
end
|
58
60
|
end
|
@@ -1,29 +1,27 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe Parser::Collection do
|
3
|
+
describe Csv2hash::Parser::Collection do
|
4
4
|
|
5
5
|
let(:definition) do
|
6
|
-
Definition.new [ { position: 0, key: 'name' } ], Definition::COLLECTION
|
6
|
+
Csv2hash::Definition.new [ { position: 0, key: 'name' } ], Csv2hash::Definition::COLLECTION
|
7
7
|
end
|
8
8
|
|
9
9
|
let(:data_source) { [ [ 'John Doe' ], [ 'Jane Doe' ] ] }
|
10
10
|
|
11
11
|
subject do
|
12
|
-
Csv2hash.new(definition, 'file_path'
|
13
|
-
csv2hash.data_source = data_source
|
14
|
-
end
|
12
|
+
Csv2hash.new(definition, 'file_path', false, data_source)
|
15
13
|
end
|
16
14
|
|
17
15
|
context 'regular way' do
|
18
16
|
it { expect { subject.parse }.to_not raise_error }
|
19
17
|
it {
|
20
|
-
subject.tap do |
|
21
|
-
|
18
|
+
subject.tap do |parser|
|
19
|
+
parser.parse
|
22
20
|
end.data.should eql({ data: [ { 'name' => 'John Doe' }, { 'name' => 'Jane Doe' } ] })
|
23
21
|
}
|
24
22
|
context 'with header' do
|
25
23
|
before { subject.definition.header_size = 1 }
|
26
|
-
let(:data_source) { [ [ 'Name' ], [ 'John Doe' ], [ 'Jane Doe' ] ]}
|
24
|
+
let(:data_source) { [ [ 'Name' ], [ 'John Doe' ], [ 'Jane Doe' ] ] }
|
27
25
|
it {
|
28
26
|
subject.tap { |c| c.parse }.data.should eql({ data: [ { 'name' => 'John Doe' }, { 'name' => 'Jane Doe' } ] })
|
29
27
|
}
|
@@ -46,4 +44,13 @@ describe Parser::Collection do
|
|
46
44
|
}
|
47
45
|
end
|
48
46
|
|
47
|
+
context '#ignore_blank_line' do
|
48
|
+
let(:data_source) { [ [ 'John Doe' ], [ 'Jane Doe' ], [ nil ] ] }
|
49
|
+
it {
|
50
|
+
subject.tap do |parser|
|
51
|
+
parser.ignore_blank_line = true
|
52
|
+
parser.parse
|
53
|
+
end.data.should eql({ data: [ { 'name' => 'John Doe' }, { 'name' => 'Jane Doe' } ] })
|
54
|
+
}
|
55
|
+
end
|
49
56
|
end
|
@@ -1,17 +1,15 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe Parser::Mapping do
|
3
|
+
describe Csv2hash::Parser::Mapping do
|
4
4
|
|
5
5
|
let(:definition) do
|
6
|
-
Definition.new [ { position: [0,0], key: 'name' } ], Definition::MAPPING
|
6
|
+
Csv2hash::Definition.new [ { position: [0,0], key: 'name' } ], Csv2hash::Definition::MAPPING
|
7
7
|
end
|
8
8
|
|
9
9
|
let(:data_source) { [ [ 'John Doe' ] ] }
|
10
10
|
|
11
11
|
subject do
|
12
|
-
Csv2hash.new(definition, 'file_path'
|
13
|
-
csv2hash.data_source = data_source
|
14
|
-
end
|
12
|
+
Csv2hash.new(definition, 'file_path', false, data_source)
|
15
13
|
end
|
16
14
|
|
17
15
|
context 'regular way' do
|
@@ -1,18 +1,16 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe Validator::Collection do
|
3
|
+
describe Csv2hash::Validator::Collection do
|
4
4
|
|
5
5
|
let(:definition) do
|
6
|
-
Definition.new([ { position: 0, key: 'name' } ], Definition::COLLECTION).tap do |definition|
|
6
|
+
Csv2hash::Definition.new([ { position: 0, key: 'name' } ], Csv2hash::Definition::COLLECTION).tap do |definition|
|
7
7
|
definition.validate!
|
8
8
|
definition.default!
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
12
|
subject do
|
13
|
-
Csv2hash.new(definition, 'file_path'
|
14
|
-
csv2hash.data_source = data_source
|
15
|
-
end
|
13
|
+
Csv2hash.new(definition, 'file_path', true, data_source)
|
16
14
|
end
|
17
15
|
|
18
16
|
context 'with valid data' do
|
@@ -25,8 +23,18 @@ describe Validator::Collection do
|
|
25
23
|
end
|
26
24
|
end
|
27
25
|
|
26
|
+
context '#ignore_blank_line' do
|
27
|
+
let(:data_source) { [ [ ] ] }
|
28
|
+
before { subject.ignore_blank_line = true }
|
29
|
+
it { expect { subject.validate_data! }.to_not raise_error }
|
30
|
+
context 'csv mode' do
|
31
|
+
before { subject.exception_mode = false }
|
32
|
+
its(:errors) { should be_empty }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
28
36
|
context 'with invalid data' do
|
29
|
-
let(:data_source) { [ [ ] ]}
|
37
|
+
let(:data_source) { [ [ ] ] }
|
30
38
|
it { expect { subject.validate_data! }.to raise_error('undefined name on [0, 0]') }
|
31
39
|
context 'with header' do
|
32
40
|
before { subject.definition.header_size = 1 }
|
@@ -37,8 +45,8 @@ describe Validator::Collection do
|
|
37
45
|
|
38
46
|
context 'wihtout exception' do
|
39
47
|
let(:data_source) { [ [ ] ]}
|
40
|
-
before { subject.
|
41
|
-
it { subject.parse.should eql ",\"undefined name on [0, 0]\"\n" }
|
48
|
+
before { subject.exception_mode = false }
|
49
|
+
it { subject.parse.errors.to_csv.should eql ",\"undefined name on [0, 0]\"\n" }
|
42
50
|
|
43
51
|
context 'errors should be filled' do
|
44
52
|
before { subject.parse }
|
@@ -47,13 +55,14 @@ describe Validator::Collection do
|
|
47
55
|
|
48
56
|
context 'original csv + errors should returned' do
|
49
57
|
let(:definition) do
|
50
|
-
Definition.new(
|
58
|
+
Csv2hash::Definition.new(
|
59
|
+
[{ position: 0, key: 'agree', values: ['yes', 'no'] }], Csv2hash::Definition::COLLECTION).tap do |d|
|
51
60
|
d.validate!; d.default!
|
52
61
|
end
|
53
62
|
end
|
54
63
|
let(:data_source) { [ [ 'what?' ], [ 'yes' ], [ 'no' ] ] }
|
55
|
-
it { subject.parse.should eql "what?,\"agree not supported, please use one of [\"\"yes\"\", \"\"no\"\"]\"\n" }
|
64
|
+
it { subject.parse.errors.to_csv.should eql "what?,\"agree not supported, please use one of [\"\"yes\"\", \"\"no\"\"]\"\n" }
|
56
65
|
end
|
57
66
|
end
|
58
67
|
|
59
|
-
end
|
68
|
+
end
|
@@ -1,18 +1,16 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe Validator::Mapping do
|
3
|
+
describe Csv2hash::Validator::Mapping do
|
4
4
|
|
5
5
|
let(:definition) do
|
6
|
-
Definition.new([ { position: [0,0], key: 'name' } ], Definition::MAPPING).tap do |definition|
|
6
|
+
Csv2hash::Definition.new([ { position: [0,0], key: 'name' } ], Csv2hash::Definition::MAPPING).tap do |definition|
|
7
7
|
definition.validate!
|
8
8
|
definition.default!
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
12
|
subject do
|
13
|
-
Csv2hash.new(definition, 'file_path'
|
14
|
-
csv2hash.data_source = data_source
|
15
|
-
end
|
13
|
+
Csv2hash.new(definition, 'file_path', true, data_source)
|
16
14
|
end
|
17
15
|
|
18
16
|
context 'with valid data' do
|
@@ -26,9 +24,9 @@ describe Validator::Mapping do
|
|
26
24
|
end
|
27
25
|
|
28
26
|
context 'wihtout exception' do
|
29
|
-
let(:data_source) { [ [ ] ]}
|
30
|
-
before { subject.
|
31
|
-
it { subject.parse.should eql ",\"undefined name on [0, 0]\"\n" }
|
27
|
+
let(:data_source) { [ [ ] ] }
|
28
|
+
before { subject.exception_mode = false }
|
29
|
+
it { subject.parse.errors.to_csv.should eql ",\"undefined name on [0, 0]\"\n" }
|
32
30
|
|
33
31
|
context 'errors should be filled' do
|
34
32
|
before { subject.parse }
|
@@ -37,7 +35,7 @@ describe Validator::Mapping do
|
|
37
35
|
|
38
36
|
context 'original csv + errors should be returned' do
|
39
37
|
let(:definition) do
|
40
|
-
Definition.new(rules, Definition::MAPPING).tap do |d|
|
38
|
+
Csv2hash::Definition.new(rules, Csv2hash::Definition::MAPPING).tap do |d|
|
41
39
|
d.validate!; d.default!
|
42
40
|
end
|
43
41
|
end
|
@@ -50,7 +48,8 @@ describe Validator::Mapping do
|
|
50
48
|
]
|
51
49
|
end
|
52
50
|
let(:data_source) { [ [ 'what?' ], [ 'yes', 'what?' ], [ 'yes', 'what?', 'no' ] ] }
|
53
|
-
it { subject.parse.should eql
|
51
|
+
it { subject.parse.errors.to_csv.should eql(
|
52
|
+
"what?,\"agree not supported, please use one of [\"\"yes\"\", \"\"no\"\"]\"\n") }
|
54
53
|
end
|
55
54
|
context 'range values' do
|
56
55
|
let(:rules) do
|
@@ -61,8 +60,37 @@ describe Validator::Mapping do
|
|
61
60
|
]
|
62
61
|
end
|
63
62
|
let(:data_source) { [ [ 12 ], [ 2, 12 ], [ 3, 12, 1 ] ] }
|
64
|
-
it { subject.parse.should eql
|
63
|
+
it { subject.parse.errors.to_csv.should eql("12,\"score not supported, please use one of 1..10\"\n") }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'with extra_validator' do
|
68
|
+
let(:definition) do
|
69
|
+
Csv2hash::Definition.new([
|
70
|
+
{ position: [0,0], key: 'name', extra_validator: DowncaseValidator.new,
|
71
|
+
message: 'your data should be writting in downcase only'
|
72
|
+
}
|
73
|
+
],
|
74
|
+
Csv2hash::Definition::MAPPING).tap do |definition|
|
75
|
+
definition.validate!; definition.default!
|
76
|
+
end
|
77
|
+
end
|
78
|
+
before { subject.parse }
|
79
|
+
context 'not valid data' do
|
80
|
+
let(:data_source) { [ [ 'Foo' ] ] }
|
81
|
+
its(:errors) { should eql [{x: 0, y: 0, message: 'your data should be writting in downcase only', key: 'name'}]}
|
82
|
+
end
|
83
|
+
context 'valid data' do
|
84
|
+
let(:data_source) { [ [ 'foo' ] ] }
|
85
|
+
its(:errors) { should be_empty }
|
65
86
|
end
|
66
87
|
end
|
67
88
|
end
|
68
|
-
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class DowncaseValidator < Csv2hash::ExtraValidator
|
92
|
+
def valid? rule, value
|
93
|
+
!!(value.match /^[a-z]+$/)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
@@ -1,22 +1,22 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe Validator do
|
3
|
+
describe Csv2hash::Validator do
|
4
4
|
|
5
5
|
let(:definition) do
|
6
|
-
Definition.new([ { position: [0,0], key: 'name' } ], Definition::MAPPING).tap do |definition|
|
6
|
+
Csv2hash::Definition.new([ { position: [0,0], key: 'name' } ], Csv2hash::Definition::MAPPING).tap do |definition|
|
7
7
|
definition.validate!
|
8
8
|
definition.default!
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
12
|
subject do
|
13
|
-
Csv2hash.new(definition, 'file_path').tap do |
|
14
|
-
|
13
|
+
Csv2hash.new(definition, 'file_path').tap do |parser|
|
14
|
+
parser.instance_variable_set :@data_source, data_source
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
18
|
describe '#message' do
|
19
|
-
subject { Csv2hash.new double('definition', type: Definition::COLLECTION), nil }
|
19
|
+
subject { Csv2hash.new double('definition', type: Csv2hash::Definition::COLLECTION), nil }
|
20
20
|
|
21
21
|
context 'string value' do
|
22
22
|
let(:rule) { { foo: 'bar', message: ':foo are value of foo key' } }
|
@@ -43,4 +43,4 @@ describe Validator do
|
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
|
-
end
|
46
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: csv2hash
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: '0.1'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joel AZEMAR
|
@@ -52,7 +52,7 @@ dependencies:
|
|
52
52
|
- - '>='
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
|
-
description: DSL
|
55
|
+
description: DSL to map a CSV to a Ruby Hash
|
56
56
|
email: joel.azemar@gmail.com
|
57
57
|
executables:
|
58
58
|
- load_rvm
|
@@ -70,12 +70,16 @@ files:
|
|
70
70
|
- LICENSE
|
71
71
|
- README.md
|
72
72
|
- Rakefile
|
73
|
+
- ReleaseNotes.md
|
73
74
|
- bin/load_rvm
|
74
75
|
- bin/publish
|
75
76
|
- csv2hash.gemspec
|
76
77
|
- lib/csv2hash.rb
|
77
78
|
- lib/csv2hash/csv_array.rb
|
79
|
+
- lib/csv2hash/data_wrapper.rb
|
78
80
|
- lib/csv2hash/definition.rb
|
81
|
+
- lib/csv2hash/extra_validator.rb
|
82
|
+
- lib/csv2hash/notifier.rb
|
79
83
|
- lib/csv2hash/parser.rb
|
80
84
|
- lib/csv2hash/parser/collection.rb
|
81
85
|
- lib/csv2hash/parser/mapping.rb
|
@@ -115,7 +119,7 @@ rubyforge_project:
|
|
115
119
|
rubygems_version: 2.1.11
|
116
120
|
signing_key:
|
117
121
|
specification_version: 4
|
118
|
-
summary: Mapping CSV to Ruby Hash
|
122
|
+
summary: Mapping a CSV to a Ruby Hash
|
119
123
|
test_files:
|
120
124
|
- spec/csv2hash/definition_spec.rb
|
121
125
|
- spec/csv2hash/parser/collection_spec.rb
|