monolens 0.1.0 → 0.4.0

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.
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