csv2hash 0.6.8 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -2
- data/Gemfile.lock +1 -1
- data/README.md +32 -2
- data/UPGRADE.md +4 -0
- data/lib/csv2hash/discover.rb +32 -12
- data/lib/csv2hash/validator.rb +30 -5
- data/lib/csv2hash/version.rb +1 -1
- data/spec/csv2hash/parser/collection_spec.rb +41 -0
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d0017cb5da55df2b12a7a2a859cb6735dcfe2e17
|
4
|
+
data.tar.gz: a720666766679ea852bd1337f6d0795073d30f3f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 686218a873857c290b1352f83313031cc29bfb6fb23871eb83c2d3f2b520a59d654c0bc30be9141ea43f83816bc36828b7a44fca86a7e071541dab7186ff6cab
|
7
|
+
data.tar.gz: 7d77cc8c42c5facd840c4b8aa34bb9c140822f0947ab54bb229c1866e98993eb9e0b48cc8b6242451538ef8bd27e239c06cf0447b1b77195c3306b786038aff5
|
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,15 @@
|
|
1
|
+
### VERSION 0.7.0
|
2
|
+
|
3
|
+
* feature
|
4
|
+
* Auto discover is also available for collection.
|
5
|
+
|
6
|
+
* [fullchanges](https://github.com/FinalCAD/csv2hash/pull/20)
|
7
|
+
|
1
8
|
### VERSION 0.6.8
|
2
9
|
|
3
10
|
* bug fix
|
4
11
|
* fix typo on generator
|
5
|
-
|
12
|
+
|
6
13
|
### VERSION 0.6.7
|
7
14
|
|
8
15
|
* enhancements
|
@@ -10,7 +17,7 @@
|
|
10
17
|
|
11
18
|
* refactoring
|
12
19
|
* following rails way for naming file
|
13
|
-
|
20
|
+
|
14
21
|
* [fullchanges](https://github.com/FinalCAD/csv2hash/pull/17)
|
15
22
|
|
16
23
|
### VERSION 0.6.6
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -26,8 +26,9 @@ It is a DSL to validate and map a CSV to a Ruby Hash.
|
|
26
26
|
* [define where your data are expected](#define-where-your-data-are-expected)
|
27
27
|
* [Samples](#samples)
|
28
28
|
* [[MAPPING] Validation of cells with defined precision](#mapping-validation-of-cells-with-defined-precision)
|
29
|
-
* [Auto discover position feature](#auto-discover-position-feature)
|
29
|
+
* [Auto discover position feature in Mapping](#auto-discover-position-feature-in-mapping)
|
30
30
|
* [[COLLECTION] Validation of a collection (Regular CSV)](#collection-validation-of-a-collection-regular-csv)
|
31
|
+
* [Auto discover position feature in Collection](#auto-discover-position-feature-in-collection)
|
31
32
|
* [Structure validation rules](#structure-validation-rules)
|
32
33
|
* [CSV Headers](#csv-headers)
|
33
34
|
* [Parser and configuration](#parser-and-configuration)
|
@@ -210,7 +211,7 @@ class MyParser
|
|
210
211
|
end
|
211
212
|
```
|
212
213
|
|
213
|
-
### Auto discover position feature
|
214
|
+
### Auto discover position feature in Mapping
|
214
215
|
|
215
216
|
This is a special feature for finding the Y index of row where you data start. For instance you have this following data :
|
216
217
|
|
@@ -242,6 +243,7 @@ became
|
|
242
243
|
cell position: [[0, /Employment/],1], key: 'employment'
|
243
244
|
```
|
244
245
|
|
246
|
+
|
245
247
|
### [COLLECTION] Validation of a collection (Regular CSV)
|
246
248
|
|
247
249
|
Consider the following CSV:
|
@@ -288,6 +290,34 @@ class MyParser
|
|
288
290
|
end
|
289
291
|
```
|
290
292
|
|
293
|
+
### Auto discover position feature in Collection
|
294
|
+
|
295
|
+
This is a special feature for finding a specific column index on header. For example you have the following data:
|
296
|
+
|
297
|
+
|
298
|
+
```
|
299
|
+
| Name | Age |
|
300
|
+
|---------------|---------------|
|
301
|
+
| John Doe | 23 |
|
302
|
+
| Jane Doe | 28 |
|
303
|
+
| | |
|
304
|
+
| | |
|
305
|
+
```
|
306
|
+
|
307
|
+
You want to extract `Name` and 'Age' for all rows but you want the order of the columns to be able to change.
|
308
|
+
You change the position to the regex of column index you are looking for. So this how the position
|
309
|
+
|
310
|
+
```
|
311
|
+
cell position: 0, key: 'name'
|
312
|
+
```
|
313
|
+
|
314
|
+
can be change to
|
315
|
+
|
316
|
+
```
|
317
|
+
cell position: /Name/ key: 'name'
|
318
|
+
```
|
319
|
+
|
320
|
+
|
291
321
|
### Structure validation rules
|
292
322
|
|
293
323
|
You may want to validate some structure, like min or max number of columns, definition accepts structure_rules as a key for the third parameter.
|
data/UPGRADE.md
CHANGED
data/lib/csv2hash/discover.rb
CHANGED
@@ -1,22 +1,42 @@
|
|
1
1
|
module Csv2hash
|
2
2
|
module Discover
|
3
3
|
|
4
|
-
def find_dynamic_position cell
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
def find_dynamic_position cell, header = nil
|
5
|
+
header.present? ? find_dynamic_position_x(cell, header) : find_dynamic_position_y(cell)
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
def find_dynamic_position_y cell
|
10
|
+
y, x = cell.rules.fetch :position
|
11
|
+
column, matcher = y
|
12
|
+
dynamic_y_axe = data_source.index { |entries| entries[column] =~ matcher }
|
8
13
|
|
9
|
-
|
10
|
-
|
11
|
-
|
14
|
+
if dynamic_y_axe.nil?
|
15
|
+
if cell.rules.fetch(:allow_blank)
|
16
|
+
return nil
|
17
|
+
else
|
18
|
+
raise "Y doesn't found for #{cell.rules[:position]} on :#{cell.rules.fetch(:key)}"
|
19
|
+
end
|
12
20
|
else
|
13
|
-
|
21
|
+
cell.rules[:position] = [dynamic_y_axe, x]
|
22
|
+
cell
|
14
23
|
end
|
15
|
-
else
|
16
|
-
cell.rules[:position] = [dynamic_y_axe, x]
|
17
|
-
cell
|
18
24
|
end
|
19
|
-
end
|
20
25
|
|
26
|
+
def find_dynamic_position_x cell, header
|
27
|
+
x = cell.rules.fetch :position
|
28
|
+
dynamic_x_axe = header.index { |column| column =~ x }
|
29
|
+
|
30
|
+
if dynamic_x_axe.nil?
|
31
|
+
if cell.rules.fetch(:allow_blank)
|
32
|
+
return nil
|
33
|
+
else
|
34
|
+
raise "Column doesn't found in #{definition.name}"
|
35
|
+
end
|
36
|
+
else
|
37
|
+
cell.rules[:position] = dynamic_x_axe
|
38
|
+
cell
|
39
|
+
end
|
40
|
+
end
|
21
41
|
end
|
22
42
|
end
|
data/lib/csv2hash/validator.rb
CHANGED
@@ -5,8 +5,7 @@ module Csv2hash
|
|
5
5
|
include Discover
|
6
6
|
|
7
7
|
def validate_rules y=nil
|
8
|
-
|
9
|
-
|
8
|
+
definition.type == Definition::MAPPING ? find_or_remove_dynamic_fields_on_mapping! : find_or_remove_dynamic_fields_on_collection!(y)
|
10
9
|
definition.cells.each do |cell|
|
11
10
|
_y, x = position cell.rules.fetch(:position)
|
12
11
|
begin
|
@@ -75,14 +74,14 @@ module Csv2hash
|
|
75
74
|
end
|
76
75
|
end
|
77
76
|
|
78
|
-
def
|
77
|
+
def find_or_remove_dynamic_fields_on_mapping!
|
79
78
|
cells = definition.cells.dup
|
80
79
|
# cells without optional and not found dynamic field
|
81
80
|
definition.cells = [].tap do |_cells|
|
82
81
|
while(!cells.empty?) do
|
83
82
|
cell = cells.pop
|
84
83
|
_y, x = cell.rules.fetch(:position)
|
85
|
-
if
|
84
|
+
if dynamic_field_for_mapping?(_y)
|
86
85
|
begin
|
87
86
|
_cell = find_dynamic_position cell
|
88
87
|
_cells << _cell
|
@@ -98,9 +97,35 @@ module Csv2hash
|
|
98
97
|
nil
|
99
98
|
end
|
100
99
|
|
101
|
-
def
|
100
|
+
def find_or_remove_dynamic_fields_on_collection! y
|
101
|
+
cells = definition.cells.dup
|
102
|
+
# cells without optional and not found dynamic field
|
103
|
+
definition.cells = [].tap do |_cells|
|
104
|
+
while(!cells.empty?) do
|
105
|
+
cell = cells.pop
|
106
|
+
x = cell.rules.fetch(:position)
|
107
|
+
if dynamic_field_for_collection?(x)
|
108
|
+
begin
|
109
|
+
_cell = find_dynamic_position cell, data_source.first
|
110
|
+
_cells << _cell
|
111
|
+
rescue => e
|
112
|
+
self.errors << { y: y, x: x, message: e.message, key: cell.rules.fetch(:key) }
|
113
|
+
raise if break_on_failure
|
114
|
+
end
|
115
|
+
else
|
116
|
+
_cells << cell
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end.compact
|
120
|
+
nil
|
121
|
+
end
|
122
|
+
|
123
|
+
def dynamic_field_for_mapping? field
|
102
124
|
field.is_a?(Array)
|
103
125
|
end
|
104
126
|
|
127
|
+
def dynamic_field_for_collection? field
|
128
|
+
field.is_a?(Regexp)
|
129
|
+
end
|
105
130
|
end
|
106
131
|
end
|
data/lib/csv2hash/version.rb
CHANGED
@@ -34,6 +34,47 @@ module Csv2hash
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
+
context 'discover' do
|
38
|
+
|
39
|
+
context 'when header keyword present' do
|
40
|
+
let(:data_source) { [ ['Name','Age'], [ 'John Doe',34 ], [ 'Jane Doe', 25] ] }
|
41
|
+
before do
|
42
|
+
subject.definition.header_size = 1
|
43
|
+
definition.cells << Cell.new({ position: /Age/, key: 'age' })
|
44
|
+
definition.cells << Cell.new({ position: /Name/, key: 'name' })
|
45
|
+
end
|
46
|
+
it {
|
47
|
+
expect(subject.tap { |c| c.parse! }.data).to eql(
|
48
|
+
{ data: [ { 'name' => 'John Doe', 'age' => 34 }, { 'name' => 'Jane Doe', 'age' => 25} ] }
|
49
|
+
)
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'when no header keyword found and no break on failure' do
|
54
|
+
let(:data_source) { [ [ 'John Doe',34 ], [ 'Jane Doe', 25] ] }
|
55
|
+
before do
|
56
|
+
subject.break_on_failure = false
|
57
|
+
definition.cells << Cell.new({ position: /Name/, key: 'name' })
|
58
|
+
subject.parse
|
59
|
+
end
|
60
|
+
it{ expect(subject.data).to eql(nil)}
|
61
|
+
it{ expect(subject.errors.first[:message]).to eql("Column doesn't found in foo")}
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'when no header keyword found and break on failure' do
|
65
|
+
let(:data_source) { [ [ 'John Doe',34 ], [ 'Jane Doe', 25] ] }
|
66
|
+
before do
|
67
|
+
subject.break_on_failure = true
|
68
|
+
definition.cells << Cell.new({ position: /Name/, key: 'name' })
|
69
|
+
end
|
70
|
+
it 'should throw exception' do
|
71
|
+
expect {
|
72
|
+
subject.parse
|
73
|
+
}.to raise_error("Column doesn't found in foo")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
37
78
|
context 'with nested' do
|
38
79
|
let(:data_source) { [ [ 'John Doe', 22 ], [ 'Jane Doe', 19 ] ] }
|
39
80
|
before do
|