csv2hash 0.0.2 → 0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Coverage Status](https://coveralls.io/repos/joel/csv2hash/badge.png)](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
|