monolens 0.1.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +14 -4
  3. data/bin/monolens +11 -0
  4. data/lib/monolens/array/compact.rb +2 -2
  5. data/lib/monolens/array/join.rb +13 -0
  6. data/lib/monolens/array/map.rb +57 -0
  7. data/lib/monolens/array.rb +12 -0
  8. data/lib/monolens/coerce/date.rb +22 -6
  9. data/lib/monolens/coerce/date_time.rb +30 -6
  10. data/lib/monolens/coerce/integer.rb +15 -0
  11. data/lib/monolens/coerce/string.rb +13 -0
  12. data/lib/monolens/coerce.rb +12 -3
  13. data/lib/monolens/command.rb +87 -0
  14. data/lib/monolens/core/chain.rb +2 -2
  15. data/lib/monolens/core/dig.rb +52 -0
  16. data/lib/monolens/core/mapping.rb +15 -0
  17. data/lib/monolens/core.rb +10 -4
  18. data/lib/monolens/error.rb +9 -2
  19. data/lib/monolens/error_handler.rb +21 -0
  20. data/lib/monolens/file.rb +2 -7
  21. data/lib/monolens/lens/fetch_support.rb +19 -0
  22. data/lib/monolens/lens/location.rb +17 -0
  23. data/lib/monolens/lens/options.rb +41 -0
  24. data/lib/monolens/lens.rb +41 -18
  25. data/lib/monolens/object/extend.rb +53 -0
  26. data/lib/monolens/object/keys.rb +8 -10
  27. data/lib/monolens/object/rename.rb +3 -3
  28. data/lib/monolens/object/select.rb +58 -0
  29. data/lib/monolens/object/transform.rb +34 -12
  30. data/lib/monolens/object/values.rb +34 -10
  31. data/lib/monolens/object.rb +12 -0
  32. data/lib/monolens/skip/null.rb +1 -1
  33. data/lib/monolens/str/downcase.rb +2 -2
  34. data/lib/monolens/str/split.rb +14 -0
  35. data/lib/monolens/str/strip.rb +3 -1
  36. data/lib/monolens/str/upcase.rb +2 -2
  37. data/lib/monolens/str.rb +12 -6
  38. data/lib/monolens/version.rb +1 -1
  39. data/lib/monolens.rb +7 -1
  40. data/spec/fixtures/coerce.yml +3 -2
  41. data/spec/fixtures/transform.yml +5 -4
  42. data/spec/monolens/array/test_compact.rb +15 -0
  43. data/spec/monolens/array/test_join.rb +27 -0
  44. data/spec/monolens/array/test_map.rb +96 -0
  45. data/spec/monolens/coerce/test_date.rb +34 -4
  46. data/spec/monolens/coerce/test_datetime.rb +70 -7
  47. data/spec/monolens/coerce/test_integer.rb +46 -0
  48. data/spec/monolens/coerce/test_string.rb +15 -0
  49. data/spec/monolens/command/map-upcase.lens.yml +5 -0
  50. data/spec/monolens/command/names-with-null.json +5 -0
  51. data/spec/monolens/command/names.json +4 -0
  52. data/spec/monolens/command/robust-map-upcase.lens.yml +7 -0
  53. data/spec/monolens/core/test_dig.rb +78 -0
  54. data/spec/monolens/core/test_mapping.rb +76 -0
  55. data/spec/monolens/lens/test_options.rb +73 -0
  56. data/spec/monolens/object/test_extend.rb +94 -0
  57. data/spec/monolens/object/test_keys.rb +54 -22
  58. data/spec/monolens/object/test_rename.rb +1 -1
  59. data/spec/monolens/object/test_select.rb +202 -0
  60. data/spec/monolens/object/test_transform.rb +93 -6
  61. data/spec/monolens/object/test_values.rb +110 -12
  62. data/spec/monolens/skip/test_null.rb +2 -2
  63. data/spec/monolens/str/test_downcase.rb +13 -0
  64. data/spec/monolens/str/test_split.rb +39 -0
  65. data/spec/monolens/str/test_strip.rb +13 -0
  66. data/spec/monolens/str/test_upcase.rb +13 -0
  67. data/spec/monolens/test_command.rb +128 -0
  68. data/spec/monolens/test_error_traceability.rb +60 -0
  69. data/spec/monolens/test_lens.rb +1 -1
  70. data/spec/test_readme.rb +8 -6
  71. metadata +39 -5
  72. data/lib/monolens/core/map.rb +0 -18
  73. data/spec/monolens/core/test_map.rb +0 -11
@@ -1,31 +1,63 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Monolens, 'object.keys' do
4
- subject do
5
- Monolens.lens('object.keys' => ['str.upcase'])
4
+ context 'with string keys' do
5
+ subject do
6
+ Monolens.lens('object.keys' => ['str.upcase'])
7
+ end
8
+
9
+ it 'works as expected' do
10
+ input = {
11
+ 'firstname' => 'Bernard',
12
+ 'lastname' => 'Lambeau'
13
+ }
14
+ expected = {
15
+ 'FIRSTNAME' => 'Bernard',
16
+ 'LASTNAME' => 'Lambeau'
17
+ }
18
+ expect(subject.call(input)).to eql(expected)
19
+ end
6
20
  end
7
21
 
8
- it 'works as expected' do
9
- input = {
10
- 'firstname' => 'Bernard',
11
- 'lastname' => 'Lambeau'
12
- }
13
- expected = {
14
- 'FIRSTNAME' => 'Bernard',
15
- 'LASTNAME' => 'Lambeau'
16
- }
17
- expect(subject.call(input)).to eql(expected)
22
+ context 'with symbol keys' do
23
+ subject do
24
+ Monolens.lens('object.keys' => ['coerce.string', 'str.upcase'])
25
+ end
26
+
27
+ it 'works as expected with Symbol keys' do
28
+ input = {
29
+ firstname: 'Bernard',
30
+ lastname: 'Lambeau'
31
+ }
32
+ expected = {
33
+ FIRSTNAME: 'Bernard',
34
+ LASTNAME: 'Lambeau'
35
+ }
36
+ expect(subject.call(input)).to eql(expected)
37
+ end
18
38
  end
19
39
 
20
- it 'works as expected with Symbol keys' do
21
- input = {
22
- firstname: 'Bernard',
23
- lastname: 'Lambeau'
24
- }
25
- expected = {
26
- FIRSTNAME: 'Bernard',
27
- LASTNAME: 'Lambeau'
28
- }
29
- expect(subject.call(input)).to eql(expected)
40
+ describe 'error handling' do
41
+ let(:lens) do
42
+ Monolens.lens('object.keys' => ['str.upcase'])
43
+ end
44
+
45
+ subject do
46
+ lens.call(input)
47
+ nil
48
+ rescue Monolens::LensError => ex
49
+ ex
50
+ end
51
+
52
+ let(:input) do
53
+ {
54
+ 'firstname' => 'Bernard',
55
+ nil => 'Lambeau'
56
+ }
57
+ end
58
+
59
+ it 'correctly updates the location' do
60
+ expect(subject.location).to eql([nil])
61
+ end
30
62
  end
31
63
  end
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe Monolens, 'object.rename' do
4
4
  subject do
5
- Monolens.lens('object.rename' => { lastname: :name })
5
+ Monolens.lens('object.rename' => { defn: { lastname: :name } })
6
6
  end
7
7
 
8
8
  it 'works as expected' do
@@ -0,0 +1,202 @@
1
+ require 'spec_helper'
2
+
3
+ describe Monolens, 'object.select' do
4
+ context 'when using symbols in the definition' do
5
+ subject do
6
+ Monolens.lens('object.select' => {
7
+ defn: {
8
+ name: [:firstname, :lastname],
9
+ status: :priority
10
+ }
11
+ })
12
+ end
13
+
14
+ it 'works as expected' do
15
+ input = {
16
+ firstname: 'Bernard',
17
+ lastname: 'Lambeau',
18
+ priority: 12
19
+ }
20
+ expected = {
21
+ name: ['Bernard', 'Lambeau'],
22
+ status: 12
23
+ }
24
+ expect(subject.call(input)).to eql(expected)
25
+ end
26
+
27
+ it 'works as with Strings' do
28
+ input = {
29
+ 'firstname' => 'Bernard',
30
+ 'lastname' => 'Lambeau',
31
+ 'priority' => 12
32
+ }
33
+ expected = {
34
+ 'name' => ['Bernard', 'Lambeau'],
35
+ 'status' => 12
36
+ }
37
+ expect(subject.call(input)).to eql(expected)
38
+ end
39
+ end
40
+
41
+ context 'when using strings in the definition' do
42
+ subject do
43
+ Monolens.lens('object.select' => {
44
+ 'defn' => {
45
+ 'name' => ['firstname', 'lastname'],
46
+ 'status' => 'priority'
47
+ }
48
+ })
49
+ end
50
+
51
+ it 'works as expected with Symbols' do
52
+ input = {
53
+ firstname: 'Bernard',
54
+ lastname: 'Lambeau',
55
+ priority: 12
56
+ }
57
+ expected = {
58
+ name: ['Bernard', 'Lambeau'],
59
+ status: 12
60
+ }
61
+ expect(subject.call(input)).to eql(expected)
62
+ end
63
+
64
+ it 'works with Strings' do
65
+ input = {
66
+ 'firstname' => 'Bernard',
67
+ 'lastname' => 'Lambeau',
68
+ 'priority' => 12
69
+ }
70
+ expected = {
71
+ 'name' => ['Bernard', 'Lambeau'],
72
+ 'status' => 12
73
+ }
74
+ expect(subject.call(input)).to eql(expected)
75
+ end
76
+ end
77
+
78
+ context 'when using an array as selection' do
79
+ subject do
80
+ Monolens.lens('object.select' => {
81
+ defn: [
82
+ :firstname,
83
+ :priority
84
+ ]
85
+ })
86
+ end
87
+
88
+ it 'works as expected' do
89
+ input = {
90
+ firstname: 'Bernard',
91
+ lastname: 'Lambeau',
92
+ priority: 12
93
+ }
94
+ expected = {
95
+ firstname: 'Bernard',
96
+ priority: 12
97
+ }
98
+ expect(subject.call(input)).to eql(expected)
99
+ end
100
+ end
101
+
102
+ context 'when a key is missing and no option' do
103
+ subject do
104
+ Monolens.lens('object.select' => {
105
+ defn: {
106
+ name: [:firstname, :lastname],
107
+ status: :priority
108
+ }
109
+ })
110
+ end
111
+
112
+ it 'raises an error' do
113
+ input = {
114
+ firstname: 'Bernard'
115
+ }
116
+ expect{
117
+ subject.call(input)
118
+ }.to raise_error(Monolens::LensError, /lastname/)
119
+ end
120
+ end
121
+
122
+ context 'when using on_missing: skip' do
123
+ subject do
124
+ Monolens.lens('object.select' => {
125
+ on_missing: :skip,
126
+ defn: {
127
+ name: [:firstname, :lastname],
128
+ status: :priority
129
+ }
130
+ })
131
+ end
132
+
133
+ it 'works as expected' do
134
+ input = {
135
+ firstname: 'Bernard'
136
+ }
137
+ expected = {
138
+ name: ['Bernard']
139
+ }
140
+ expect(subject.call(input)).to eql(expected)
141
+ end
142
+ end
143
+
144
+ context 'when using on_missing: null' do
145
+ subject do
146
+ Monolens.lens('object.select' => {
147
+ on_missing: :null,
148
+ defn: {
149
+ name: [:firstname, :lastname],
150
+ status: :priority
151
+ }
152
+ })
153
+ end
154
+
155
+ it 'works as expected' do
156
+ input = {
157
+ firstname: 'Bernard'
158
+ }
159
+ expected = {
160
+ name: ['Bernard', nil],
161
+ status: nil
162
+ }
163
+ expect(subject.call(input)).to eql(expected)
164
+ end
165
+
166
+ it 'works as expected' do
167
+ input = {
168
+ priority: 12
169
+ }
170
+ expected = {
171
+ name: [nil, nil],
172
+ status: 12
173
+ }
174
+ expect(subject.call(input)).to eql(expected)
175
+ end
176
+ end
177
+
178
+ describe 'error traceability' do
179
+ let(:lens) do
180
+ Monolens.lens('object.select' => {
181
+ defn: {
182
+ status: :priority
183
+ }
184
+ })
185
+ end
186
+
187
+ subject do
188
+ lens.call(input)
189
+ nil
190
+ rescue Monolens::LensError => ex
191
+ ex
192
+ end
193
+
194
+ let(:input) do
195
+ {}
196
+ end
197
+
198
+ it 'correctly updates the location' do
199
+ expect(subject.location).to eql([:status])
200
+ end
201
+ end
202
+ end
@@ -1,15 +1,102 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Monolens, 'object.transform' do
4
- subject do
5
- Monolens.lens('object.transform' => { firstname: 'str.upcase' })
4
+ context 'with default options' do
5
+ subject do
6
+ Monolens.lens('object.transform' => {
7
+ defn: { firstname: 'str.upcase' }
8
+ })
9
+ end
10
+
11
+ it 'works as expected' do
12
+ expect(subject.call(firstname: 'Bernard')).to eql(firstname: 'BERNARD')
13
+ end
14
+
15
+ it 'works as expected on an object with String keys' do
16
+ expect(subject.call('firstname' => 'Bernard')).to eql('firstname' => 'BERNARD')
17
+ end
18
+
19
+ it 'raises an error if input object does not have a key' do
20
+ expect {
21
+ subject.call(lastname: 'Lambeau')
22
+ }.to raise_error(Monolens::LensError, /firstname/)
23
+ end
24
+ end
25
+
26
+ context 'with on_missing: skip' do
27
+ subject do
28
+ Monolens.lens('object.transform' => {
29
+ on_missing: :skip,
30
+ defn: { firstname: 'str.upcase' }
31
+ })
32
+ end
33
+
34
+ it 'works as expected' do
35
+ expect(subject.call(firstname: 'Bernard')).to eql(firstname: 'BERNARD')
36
+ end
37
+
38
+ it 'skpis if missing' do
39
+ expect(subject.call(lastname: 'Lambeau')).to eql(lastname: 'Lambeau')
40
+ end
6
41
  end
7
42
 
8
- it 'works as expected' do
9
- expect(subject.call(firstname: 'Bernard')).to eql(firstname: 'BERNARD')
43
+ context 'with on_missing: null' do
44
+ subject do
45
+ Monolens.lens('object.transform' => {
46
+ on_missing: :null,
47
+ defn: { firstname: 'str.upcase' }
48
+ })
49
+ end
50
+
51
+ it 'works as expected' do
52
+ expect(subject.call(firstname: 'Bernard')).to eql(firstname: 'BERNARD')
53
+ end
54
+
55
+ it 'skpis if missing' do
56
+ expect(subject.call(lastname: 'Lambeau')).to eql(firstname: nil, lastname: 'Lambeau')
57
+ end
10
58
  end
11
59
 
12
- it 'works as expected on an object with String keys' do
13
- expect(subject.call('firstname' => 'Bernard')).to eql('firstname' => 'BERNARD')
60
+ describe 'error traceability' do
61
+ let(:lens) do
62
+ Monolens.lens({
63
+ 'array.map' => {
64
+ :lenses => {
65
+ 'object.transform' => {
66
+ defn: { firstname: 'str.upcase' }
67
+ }
68
+ }
69
+ }
70
+ })
71
+ end
72
+
73
+ subject do
74
+ lens.call(input)
75
+ nil
76
+ rescue Monolens::LensError => ex
77
+ ex
78
+ end
79
+
80
+ context 'when missing key' do
81
+ let(:input) do
82
+ [{}]
83
+ end
84
+
85
+ it 'correctly updates the location' do
86
+ expect(subject.location).to eql([0])
87
+ end
88
+ end
89
+
90
+ context 'when an error down the line' do
91
+ let(:input) do
92
+ [{
93
+ firstname: nil
94
+ }]
95
+ end
96
+
97
+ it 'correctly updates the location' do
98
+ expect(subject.location).to eql([0, :firstname])
99
+ end
100
+ end
14
101
  end
15
102
  end
@@ -1,19 +1,117 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Monolens, 'object.values' do
4
- subject do
5
- Monolens.lens('object.values' => ['str.upcase'])
4
+ context 'with default options' do
5
+ subject do
6
+ Monolens.lens('object.values' => ['str.upcase'])
7
+ end
8
+
9
+ it 'works as expected' do
10
+ input = {
11
+ firstname: 'Bernard',
12
+ lastname: 'Lambeau'
13
+ }
14
+ expected = {
15
+ firstname: 'BERNARD',
16
+ lastname: 'LAMBEAU'
17
+ }
18
+ expect(subject.call(input)).to eql(expected)
19
+ end
20
+
21
+ it 'raises an error on any problem' do
22
+ input = {
23
+ firstname: nil,
24
+ lastname: 'Lambeau'
25
+ }
26
+ expect {
27
+ subject.call(input)
28
+ }.to raise_error(Monolens::LensError)
29
+ end
30
+ end
31
+
32
+ context 'with on_error: skip' do
33
+ subject do
34
+ Monolens.lens('object.values' => {
35
+ 'on_error' => 'skip',
36
+ 'lenses' => ['str.upcase']
37
+ })
38
+ end
39
+
40
+ it 'skips key/value when an error occurs' do
41
+ input = {
42
+ firstname: nil,
43
+ lastname: 'Lambeau'
44
+ }
45
+ expected = {
46
+ lastname: 'LAMBEAU'
47
+ }
48
+ expect(subject.call(input)).to eql(expected)
49
+ end
50
+ end
51
+
52
+ context 'with on_error: null' do
53
+ subject do
54
+ Monolens.lens('object.values' => {
55
+ 'on_error' => 'null',
56
+ 'lenses' => ['str.upcase']
57
+ })
58
+ end
59
+
60
+ it 'uses nil as value' do
61
+ input = {
62
+ firstname: 12,
63
+ lastname: 'Lambeau'
64
+ }
65
+ expected = {
66
+ firstname: nil,
67
+ lastname: 'LAMBEAU'
68
+ }
69
+ expect(subject.call(input)).to eql(expected)
70
+ end
71
+ end
72
+
73
+ context 'with on_error: keep' do
74
+ subject do
75
+ Monolens.lens('object.values' => {
76
+ 'on_error' => 'keep',
77
+ 'lenses' => ['str.upcase']
78
+ })
79
+ end
80
+
81
+ it 'uses nil as value' do
82
+ input = {
83
+ firstname: 12,
84
+ lastname: 'Lambeau'
85
+ }
86
+ expected = {
87
+ firstname: 12,
88
+ lastname: 'LAMBEAU'
89
+ }
90
+ expect(subject.call(input)).to eql(expected)
91
+ end
6
92
  end
7
93
 
8
- it 'works as expected' do
9
- input = {
10
- firstname: 'Bernard',
11
- lastname: 'Lambeau'
12
- }
13
- expected = {
14
- firstname: 'BERNARD',
15
- lastname: 'LAMBEAU'
16
- }
17
- expect(subject.call(input)).to eql(expected)
94
+ describe 'error traceability' do
95
+ let(:lens) do
96
+ Monolens.lens('object.values' => ['str.upcase'])
97
+ end
98
+
99
+ subject do
100
+ lens.call(input)
101
+ nil
102
+ rescue Monolens::LensError => ex
103
+ ex
104
+ end
105
+
106
+ let(:input) do
107
+ {
108
+ 'firstname' => 'Bernard',
109
+ 'lastname' => nil
110
+ }
111
+ end
112
+
113
+ it 'correctly updates the location' do
114
+ expect(subject.location).to eql(['lastname'])
115
+ end
18
116
  end
19
117
  end
@@ -17,7 +17,7 @@ describe Monolens, 'skip.null' do
17
17
 
18
18
  context 'when used in a Map' do
19
19
  subject do
20
- Monolens.lens('map' => ['skip.null', 'str.upcase'])
20
+ Monolens.lens('array.map' => ['skip.null', 'str.upcase'])
21
21
  end
22
22
 
23
23
  it 'maps nils' do
@@ -27,7 +27,7 @@ describe Monolens, 'skip.null' do
27
27
 
28
28
  context 'when used in a Map, but we want no nils' do
29
29
  subject do
30
- Monolens.lens(['array.compact', { 'map' => ['skip.null', 'str.upcase'] }])
30
+ Monolens.lens(['array.compact', { 'array.map' => ['skip.null', 'str.upcase'] }])
31
31
  end
32
32
 
33
33
  it 'works' do
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe Monolens, 'str.downcase' do
4
+ subject do
5
+ Monolens.lens('str.downcase')
6
+ end
7
+
8
+ it 'converts to lowercase' do
9
+ input = 'FOO'
10
+ expected = 'foo'
11
+ expect(subject.call(input)).to eql(expected)
12
+ end
13
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ describe Monolens, 'str.split' do
4
+ context 'without options' do
5
+ subject do
6
+ Monolens.lens('str.split')
7
+ end
8
+
9
+ it 'splits a string as an array using space separator' do
10
+ input = 'foo bar'
11
+ expected = ['foo', 'bar']
12
+ expect(subject.call(input)).to eql(expected)
13
+ end
14
+
15
+ it 'is greedy on spaces and splits on carriage returns and tabs' do
16
+ input = "foo bar\nbaz\tboz"
17
+ expected = ['foo', 'bar', 'baz', 'boz']
18
+ expect(subject.call(input)).to eql(expected)
19
+ end
20
+ end
21
+
22
+ context 'when specifying the separator' do
23
+ subject do
24
+ Monolens.lens('str.split' => { separator: ',' })
25
+ end
26
+
27
+ it 'uses it' do
28
+ input = 'foo,bar'
29
+ expected = ['foo', 'bar']
30
+ expect(subject.call(input)).to eql(expected)
31
+ end
32
+
33
+ it 'does not make whitespace magic' do
34
+ input = "foo, bar"
35
+ expected = ['foo', ' bar']
36
+ expect(subject.call(input)).to eql(expected)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe Monolens, 'str.strip' do
4
+ subject do
5
+ Monolens.lens('str.strip')
6
+ end
7
+
8
+ it 'strips leading and trailing spaces' do
9
+ input = ' foo '
10
+ expected = 'foo'
11
+ expect(subject.call(input)).to eql(expected)
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe Monolens, 'str.upcase' do
4
+ subject do
5
+ Monolens.lens('str.upcase')
6
+ end
7
+
8
+ it 'converts to uppercase' do
9
+ input = 'foo'
10
+ expected = 'FOO'
11
+ expect(subject.call(input)).to eql(expected)
12
+ end
13
+ end