monolens 0.6.3 → 0.6.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 937b78e577006ce716022cb0ae10fe2dfd21f174be62e67c2f9eefe161f07151
4
- data.tar.gz: 90493bf5855603ea1181a5c86a9ba313937954cadb659d95dbca6386b5ba085b
3
+ metadata.gz: 808d9126b39444b34dde2d81926a680af37ad5351994e6b640a035b3555807fb
4
+ data.tar.gz: f2eab3c0f2911e83096e7a72520d9812575830c38192ad98b0f609b68f6ab814
5
5
  SHA512:
6
- metadata.gz: 362211783818b1906bfa90fb42979084e9f1d2bdf7e893313ad5b1d0ef7f2184e1441661221a8df78cfcd530231f7011e678091f26900e041d06414ec92ff116
7
- data.tar.gz: ad30e2d1b2fddd5f72ec7c939f275ab098586400d70f4f8d6b4e0e4453c30bd63a32c10b6ebc68d815d8e6bde3aaddd041d50b4bb4b8342f84eb23419d31dda7
6
+ metadata.gz: 50586006606bd32d8f1cf2d35c885c906883d61da10a7746f63ca7cf1e66e6fd81624025def49287400edee1554e86c69e7f27573bc8ad9311872be8a545270a
7
+ data.tar.gz: acdb391077a3c35b1888fb2cb6f8bbddf8be6d5ba68001f6297a0686079fc5b1aa13077eca3079fa8d98c4d56d804da43ca0ccb5c912b579117f815d312a673f
@@ -0,0 +1,63 @@
1
+ module Monolens
2
+ module Core
3
+ class Digs
4
+ include Lens
5
+
6
+ signature(Type::Diggable, Type::Any, {
7
+ defn: [
8
+ Type::Array.of(Type::Array.of(Type::Any.of(Type::Integer, Type::String))),
9
+ true
10
+ ],
11
+ on_missing: [Type::Strategy.missing(%w{fail null}), false]
12
+ })
13
+
14
+ def call(arg, world = {})
15
+ to_dig = option(:defn, [])
16
+ to_dig.map {|digging|
17
+ digging.inject(arg) do |memo, part|
18
+ dig_on(part, memo, world)
19
+ end
20
+ }
21
+ end
22
+
23
+ private
24
+
25
+ def path
26
+ option(:defn, []).join('.')
27
+ end
28
+
29
+ def dig_on(attr, arg, world)
30
+ if arg.is_a?(::Array)
31
+ index = attr.to_i
32
+ on_missing(world) if index >= arg.size
33
+ arg[index]
34
+ elsif arg.is_a?(::Hash)
35
+ actual, value = fetch_on(attr, arg)
36
+ on_missing(world) unless actual
37
+ value
38
+ elsif arg
39
+ if attr.is_a?(::Integer)
40
+ is_array!(arg, world)
41
+ else
42
+ is_hash!(arg, world)
43
+ end
44
+ else
45
+ on_missing(world)
46
+ end
47
+ end
48
+
49
+ def on_missing(world)
50
+ strategy = option(:on_missing, :fail)
51
+ case strategy.to_sym
52
+ when :fail
53
+ fail!("Unable to find #{path}", world)
54
+ when :null
55
+ nil
56
+ else
57
+ raise Monolens::Error, "Unexpected missing strategy `#{strategy}`"
58
+ end
59
+ end
60
+ private :on_missing
61
+ end
62
+ end
63
+ end
@@ -12,6 +12,11 @@ module Monolens
12
12
  end
13
13
  module_function :dig
14
14
 
15
+ def digs(options, registry)
16
+ Digs.new(options, registry)
17
+ end
18
+ module_function :digs
19
+
15
20
  def literal(options, registry)
16
21
  Literal.new(options, registry)
17
22
  end
@@ -27,5 +32,6 @@ module Monolens
27
32
  end
28
33
  require_relative 'core/chain'
29
34
  require_relative 'core/dig'
35
+ require_relative 'core/digs'
30
36
  require_relative 'core/mapping'
31
37
  require_relative 'core/literal'
@@ -4,7 +4,8 @@ module Monolens
4
4
  include Lens
5
5
 
6
6
  signature(Type::Object, Type::Object, {
7
- strategy: [Type::Strategy.selection(%w{all first}), false],
7
+ strategy: [Type::Strategy.selection(%w{all first concat}), false],
8
+ separator: [Type::String, false],
8
9
  defn: [Type::Any.of(
9
10
  Type::Array.of(Type::Name),
10
11
  Type::Map.of(Type::Name, Type::Any.of(Type::Array.of(Type::Name), Type::Name))
@@ -41,13 +42,20 @@ module Monolens
41
42
  end
42
43
 
43
44
  def do_array_select(arg, selector, world)
44
- case option(:strategy, :all).to_sym
45
+ case strategy = option(:strategy, :all).to_sym
45
46
  when :all
46
47
  selector.each_with_object([]) do |old_attr, values|
47
48
  catch (:skip) do
48
49
  values << do_single_select(arg, old_attr, world)
49
50
  end
50
51
  end
52
+ when :concat
53
+ values = selector.each_with_object([]) do |old_attr, values|
54
+ catch (:skip) do
55
+ values << do_single_select(arg, old_attr, world)
56
+ end
57
+ end
58
+ values.join(option(:separator, ' '))
51
59
  when :first
52
60
  selector.each do |old_attr|
53
61
  actual, fetched = fetch_on(old_attr, arg)
@@ -0,0 +1,15 @@
1
+ module Monolens
2
+ module Str
3
+ class NullIfEmpty
4
+ include Lens
5
+
6
+ signature(Type::String, Type::String)
7
+
8
+ def call(arg, world = {})
9
+ is_string!(arg, world)
10
+
11
+ arg.to_s.strip.empty? ? nil : arg
12
+ end
13
+ end
14
+ end
15
+ end
@@ -7,6 +7,11 @@ module Monolens
7
7
  end
8
8
  module_function :downcase
9
9
 
10
+ def nullIfEmpty(options, registry)
11
+ NullIfEmpty.new(options, registry)
12
+ end
13
+ module_function :nullIfEmpty
14
+
10
15
  def strip(options, registry)
11
16
  Strip.new(options, registry)
12
17
  end
@@ -29,3 +34,4 @@ require_relative 'str/downcase'
29
34
  require_relative 'str/strip'
30
35
  require_relative 'str/split'
31
36
  require_relative 'str/upcase'
37
+ require_relative 'str/null_if_empty'
@@ -2,7 +2,7 @@ module Monolens
2
2
  module Version
3
3
  MAJOR = 0
4
4
  MINOR = 6
5
- TINY = 3
5
+ TINY = 4
6
6
  end
7
7
  VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::TINY}"
8
8
  end
@@ -0,0 +1,87 @@
1
+ require 'spec_helper'
2
+
3
+ describe Monolens, "core.digs" do
4
+ let(:lens) do
5
+ Monolens.lens('core.digs' => {
6
+ defn: [
7
+ ['foo'],
8
+ ['hobbies', 1, 'name'],
9
+ ]
10
+ })
11
+ end
12
+
13
+ it 'works' do
14
+ input = {
15
+ hobbies: [
16
+ { name: 'programming' },
17
+ { name: 'music' }
18
+ ],
19
+ foo: 'Hello'
20
+ }
21
+ expected = ['Hello', 'music']
22
+ expect(lens.call(input)).to eql(expected)
23
+ end
24
+
25
+ describe 'error handling' do
26
+ let(:lens) do
27
+ Monolens.lens({
28
+ 'array.map' => {
29
+ lenses: {
30
+ 'core.digs' => {
31
+ on_missing: on_missing,
32
+ defn: [
33
+ ['foo'],
34
+ ['hobbies', 1, 'name'],
35
+ ]
36
+ }.compact
37
+ }
38
+ }
39
+ })
40
+ end
41
+
42
+ subject do
43
+ begin
44
+ lens.call(input)
45
+ rescue Monolens::LensError => ex
46
+ ex
47
+ end
48
+ end
49
+
50
+ context 'default behavior' do
51
+ let(:on_missing) do
52
+ nil
53
+ end
54
+
55
+ let(:input) do
56
+ [{
57
+ hobbies: [
58
+ { name: 'programming' }
59
+ ]
60
+ }]
61
+ end
62
+
63
+ it 'fails as expected' do
64
+ expect(subject).to be_a(Monolens::LensError)
65
+ expect(subject.location).to eql([0])
66
+ end
67
+ end
68
+
69
+ context 'on_missing: null' do
70
+ let(:on_missing) do
71
+ :null
72
+ end
73
+
74
+ let(:input) do
75
+ [{
76
+ hobbies: [
77
+ { name: 'programming' }
78
+ ]
79
+ }]
80
+ end
81
+
82
+ it 'works' do
83
+ expect(subject).to eql([[nil, nil]])
84
+ end
85
+ end
86
+ end
87
+ end
@@ -160,6 +160,80 @@ describe Monolens, 'object.select' do
160
160
  end
161
161
  end
162
162
 
163
+ context 'when using strategy: concat' do
164
+ subject do
165
+ Monolens.lens('object.select' => {
166
+ defn: {
167
+ name: [:firstname, :lastname],
168
+ status: :priority
169
+ },
170
+ strategy: 'concat',
171
+ separator: "\n",
172
+ on_missing: on_missing
173
+ }.compact)
174
+ end
175
+
176
+ context 'with :skip as on_missing' do
177
+ let(:on_missing) do
178
+ :skip
179
+ end
180
+
181
+ it 'works as expected with first only is present' do
182
+ input = {
183
+ firstname: 'Bernard',
184
+ priority: 12
185
+ }
186
+ expected = {
187
+ name: 'Bernard',
188
+ status: 12
189
+ }
190
+ expect(subject.call(input)).to eql(expected)
191
+ end
192
+
193
+ it 'works as expected when second only is present' do
194
+ input = {
195
+ lastname: 'Lambeau',
196
+ priority: 12
197
+ }
198
+ expected = {
199
+ name: 'Lambeau',
200
+ status: 12
201
+ }
202
+ expect(subject.call(input)).to eql(expected)
203
+ end
204
+
205
+ it 'works as expected when both are present' do
206
+ input = {
207
+ firstname: 'Bernard',
208
+ lastname: 'Lambeau',
209
+ priority: 12
210
+ }
211
+ expected = {
212
+ name: "Bernard\nLambeau",
213
+ status: 12
214
+ }
215
+ expect(subject.call(input)).to eql(expected)
216
+ end
217
+ end
218
+
219
+ context 'with on_missing: null' do
220
+ let(:on_missing) do
221
+ :null
222
+ end
223
+
224
+ it 'works as expected when missing' do
225
+ input = {
226
+ priority: 12
227
+ }
228
+ expected = {
229
+ name: "\n",
230
+ status: 12
231
+ }
232
+ expect(subject.call(input)).to eql(expected)
233
+ end
234
+ end
235
+ end
236
+
163
237
  context 'when using an array as selection' do
164
238
  subject do
165
239
  Monolens.lens('object.select' => {
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: monolens
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.3
4
+ version: 0.6.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bernard Lambeau
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-07-09 00:00:00.000000000 Z
11
+ date: 2024-07-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -138,6 +138,7 @@ files:
138
138
  - lib/monolens/stdlib/core.rb
139
139
  - lib/monolens/stdlib/core/chain.rb
140
140
  - lib/monolens/stdlib/core/dig.rb
141
+ - lib/monolens/stdlib/core/digs.rb
141
142
  - lib/monolens/stdlib/core/literal.rb
142
143
  - lib/monolens/stdlib/core/mapping.rb
143
144
  - lib/monolens/stdlib/object.rb
@@ -153,6 +154,7 @@ files:
153
154
  - lib/monolens/stdlib/skip/null.rb
154
155
  - lib/monolens/stdlib/str.rb
155
156
  - lib/monolens/stdlib/str/downcase.rb
157
+ - lib/monolens/stdlib/str/null_if_empty.rb
156
158
  - lib/monolens/stdlib/str/split.rb
157
159
  - lib/monolens/stdlib/str/strip.rb
158
160
  - lib/monolens/stdlib/str/upcase.rb
@@ -200,6 +202,7 @@ files:
200
202
  - spec/monolens/stdlib/coerce/test_integer.rb
201
203
  - spec/monolens/stdlib/coerce/test_string.rb
202
204
  - spec/monolens/stdlib/core/test_dig.rb
205
+ - spec/monolens/stdlib/core/test_digs.rb
203
206
  - spec/monolens/stdlib/core/test_literal.rb
204
207
  - spec/monolens/stdlib/core/test_mapping.rb
205
208
  - spec/monolens/stdlib/object/test_allbut.rb