dry-monads 1.7.1 → 1.8.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 71e72f068689b13c930f32796d5d235638fbc8f685b4274070b6e418d1bb1ec3
4
- data.tar.gz: 81523f64fc36c7ddfe4f9b90490f7477ad94d43abb52d9b594b9b0bb8b1f4c1f
3
+ metadata.gz: 43619ae6aa239741e9730b8f824dafb848f999daea83491a78a0c3d8b4fec9f9
4
+ data.tar.gz: 3d2fcf419ad5b71ef88058db953b44f81d4f661796e87c88e410549b4f4340ab
5
5
  SHA512:
6
- metadata.gz: f9164e029f4482abaaa3bae2111607417ba3c3867ffc44f10a6626697ffa8793d291cc96518563d2eec8950f0594814167cf04f242f6354f581873256862b672
7
- data.tar.gz: 8c915830df01f6461efe56db8c38ec248cb9778270a6b5f5f1b0c2464e0cb6794806d031a62c9e47ed770f0f964b38ba461599afa40d2ac2f35ecc71904961cd
6
+ metadata.gz: 8991316ee0e95c6ca866b5fa7005e060af38501891080f0184c060dcf5ef81603b30816f55e1dda77e5a086e1f6ba667e313ade3be81a62db0a9a022e2aab334
7
+ data.tar.gz: 111db3521d27e89567df5f63862bfc7d6b07edf55ffa928583d09cba2a1a1d94278cc309cd3c254f9d1ce754c97cf3f760aea835dbd18d997bc2cac342cabf3c
data/CHANGELOG.md CHANGED
@@ -1,5 +1,105 @@
1
1
  <!--- DO NOT EDIT THIS FILE - IT'S AUTOMATICALLY GENERATED VIA DEVTOOLS --->
2
2
 
3
+ ## 1.8.0 unreleased
4
+
5
+
6
+ ### Added
7
+
8
+ - New extension for RSpec (@flash-gordon in #183):
9
+ One of the pain points of testing monads is referencing class constants from specs.
10
+ This extension catches missing class constants, analyzes the call site and
11
+ returns a matching constant.
12
+
13
+ Before, this code would raise a `NameError` because `Failure` is a constant
14
+ that is missing in `Object`:
15
+
16
+ ```ruby
17
+ example "missing constant" do
18
+ expect(call_operation).to eql(Failure[:some_error, "some message"])
19
+ end
20
+ ```
21
+
22
+ Now, after enabling the extension, it will return the correct constant:
23
+
24
+ ```ruby
25
+ Dry::Monads.load_extensions(:rspec)
26
+
27
+ example "missing constant" do
28
+ Failure[:some_error, "some message"] # => Failure[:some_error, "some message"]
29
+ end
30
+ ```
31
+
32
+ Out of the box, the extension will check if `Success`, `Failure`, `Some`, and
33
+ `None` are referenced from a file ending with `_spec.rb`.
34
+
35
+ More involved analysis is possible if you add `debug_inspector` to your Gemfile:
36
+
37
+ ```ruby
38
+ group :test do
39
+ gem "debug_inspector"
40
+ end
41
+ ```
42
+
43
+ This will allow referencing constants from other modules, such as rspec helpers.
44
+
45
+ The extension also adds new matchers for `Success`, `Failure`, `Some`, and
46
+ `None` values.
47
+
48
+ ```ruby
49
+ expect(Success(1)).to be_success
50
+ expect(Success(1)).to be_success(1)
51
+ expect(Success(1)).to be_success { |x| x > 0 }
52
+ expect(Success(1)).to be_a_success { |x| x > 0 }
53
+
54
+ expect(Failure(1)).to be_failure(1)
55
+
56
+ expect(Some(1)).to be_some
57
+ expect(Some(1)).to be_success
58
+
59
+ expect(None()).to be_none
60
+ expect(None()).to be_failure
61
+ ```
62
+ - New extension for super_diff (@flash-gordon in #184):
63
+
64
+ Adds support for improved diff output in specs when using the super_diff gem.
65
+ This makes it easier to understand the differences between monad values in test failures.
66
+
67
+ To use this extension:
68
+ 1. Add super_diff to your Gemfile's test group:
69
+ ```ruby
70
+ group :test do
71
+ gem "super_diff"
72
+ end
73
+ ```
74
+ 2. Load the extension:
75
+ ```ruby
76
+ require "dry/monads"
77
+ Dry::Monads.load_extensions(:super_diff)
78
+ ```
79
+
80
+ This will change the diff output for monad values to be more readable.
81
+
82
+ Before:
83
+
84
+ ```
85
+ -Success({a: 2, c: 2})
86
+ +Success({a: 1, b: 2})
87
+ ```
88
+
89
+ After:
90
+
91
+ ```
92
+ Success(
93
+ - a: 2,
94
+ + a: 1,
95
+ - c: 2
96
+ + b: 2
97
+ )
98
+ ```
99
+
100
+
101
+ [Compare v1.7.1...v1.8.0](https://github.com/dry-rb/dry-monads/compare/v1.7.1...v1.8.0)
102
+
3
103
  ## 1.7.1 2025-01-21
4
104
 
5
105
 
@@ -0,0 +1,193 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rspec/matchers"
4
+
5
+ debug_inspector_available =
6
+ begin
7
+ require "debug_inspector"
8
+ defined?(DebugInspector)
9
+ rescue LoadError
10
+ false
11
+ end
12
+
13
+ module Dry
14
+ module Monads
15
+ module RSpec
16
+ module Matchers
17
+ extend ::RSpec::Matchers::DSL
18
+
19
+ {
20
+ failure: {
21
+ expected_classes: [
22
+ ::Dry::Monads::Result::Failure,
23
+ ::Dry::Monads::Maybe::None,
24
+ ::Dry::Monads::Try::Error
25
+ ],
26
+ extract_value: :failure.to_proc
27
+ },
28
+ success: {
29
+ expected_classes: [
30
+ ::Dry::Monads::Result::Success,
31
+ ::Dry::Monads::Maybe::Some,
32
+ ::Dry::Monads::Try::Value
33
+ ],
34
+ extract_value: :value!.to_proc
35
+ },
36
+ some: {
37
+ expected_classes: [
38
+ ::Dry::Monads::Maybe::Some
39
+ ],
40
+ extract_value: :value!.to_proc
41
+ }
42
+ }.each do |name, args|
43
+ args => { expected_classes:, extract_value: }
44
+ expected_constructors = expected_classes.map(&:name).map do |c|
45
+ c.split("::").last
46
+ end
47
+
48
+ matcher :"be_#{name}" do |expected = Undefined|
49
+ match do |actual|
50
+ if expected_classes.any? { |klass| actual.is_a?(klass) }
51
+ exact_match = actual.is_a?(expected_classes[0])
52
+
53
+ if exact_match && block_arg
54
+ block_arg.call(extract_value.call(actual))
55
+ elsif Undefined.equal?(expected)
56
+ true
57
+ elsif exact_match
58
+ extract_value.call(actual) == expected
59
+ else
60
+ false
61
+ end
62
+ else
63
+ false
64
+ end
65
+ end
66
+
67
+ failure_message do |actual|
68
+ if expected_classes.none? { |klass| actual.is_a?(klass) }
69
+ if expected_classes.size > 1
70
+ "expected #{actual.inspect} to be one of the following values: " \
71
+ "#{expected_constructors.join(", ")}, but it's #{actual.class}"
72
+ else
73
+ "expected #{actual.inspect} to be a #{expected_constructors[0]} value, " \
74
+ "but it's #{actual.class}"
75
+ end
76
+ elsif actual.is_a?(expected_classes[0]) && block_arg
77
+ "expected #{actual.inspect} to have a value satisfying the given block"
78
+ else
79
+ "expected #{actual.inspect} to have value #{expected.inspect}, " \
80
+ "but it was #{extract_value.call(actual).inspect}"
81
+ end
82
+ end
83
+
84
+ failure_message_when_negated do |actual|
85
+ if expected_classes.size > 1
86
+ "expected #{actual.inspect} to not be one of the following values: " \
87
+ "#{expected_constructors.join(", ")}, but it is"
88
+ else
89
+ "expected #{actual.inspect} to not be a #{expected_constructors[0]} value, " \
90
+ "but it is"
91
+ end
92
+ end
93
+ end
94
+
95
+ alias_matcher :"be_a_#{name}", :"be_#{name}"
96
+ end
97
+
98
+ matcher :be_none do
99
+ match do |actual|
100
+ actual.is_a?(::Dry::Monads::Maybe::None)
101
+ end
102
+
103
+ failure_message do |actual|
104
+ "expected #{actual.inspect} to be none"
105
+ end
106
+
107
+ failure_message_when_negated do |actual|
108
+ "expected #{actual.inspect} to not be none"
109
+ end
110
+ end
111
+ end
112
+
113
+ Constructors = Monads[:result, :maybe]
114
+
115
+ CONSTANTS = %i[Success Failure Some None List].to_set
116
+
117
+ NESTED_CONSTANTS = CONSTANTS.to_set { |c| "::#{c}" }
118
+
119
+ class << self
120
+ def resolve_constant_name(name)
121
+ if CONSTANTS.include?(name)
122
+ name
123
+ elsif NESTED_CONSTANTS.any? { |c| name.to_s.end_with?(c) }
124
+ name[/::(\w+)$/, 1].to_sym
125
+ else
126
+ nil
127
+ end
128
+ end
129
+
130
+ def name_to_const(name)
131
+ case name
132
+ in :Success
133
+ ::Dry::Monads::Result::Success
134
+ in :Failure
135
+ ::Dry::Monads::Result::Failure
136
+ in :Some
137
+ ::Dry::Monads::Maybe::Some
138
+ in :None
139
+ ::Dry::Monads::Maybe::None
140
+ in :List
141
+ ::Dry::Monads::List
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
148
+
149
+ catch_missing_const = Module.new do
150
+ if debug_inspector_available
151
+ def const_missing(name)
152
+ const_name = Dry::Monads::RSpec.resolve_constant_name(name)
153
+
154
+ if const_name
155
+ DebugInspector.open do |dc|
156
+ if dc.frame_binding(2).receiver.is_a?(RSpec::Core::ExampleGroup)
157
+ Dry::Monads::RSpec.name_to_const(const_name)
158
+ else
159
+ super
160
+ end
161
+ end
162
+ else
163
+ super
164
+ end
165
+ end
166
+ else
167
+ def const_missing(name)
168
+ const_name = Dry::Monads::RSpec.resolve_constant_name(name)
169
+
170
+ if const_name && caller_locations(1, 1).first.path.end_with?("_spec.rb")
171
+ Dry::Monads::RSpec.name_to_const(const_name)
172
+ else
173
+ super
174
+ end
175
+ end
176
+ end
177
+
178
+ define_method(:include) do |*modules|
179
+ super(*modules).tap do
180
+ modules.each do |m|
181
+ m.extend(catch_missing_const) unless m.frozen?
182
+ end
183
+ end
184
+ end
185
+ end
186
+
187
+ Object.extend(catch_missing_const)
188
+
189
+ RSpec.configure do |config|
190
+ config.include Dry::Monads::RSpec::Matchers
191
+ config.include Dry::Monads::RSpec::Constructors
192
+ config.extend(catch_missing_const)
193
+ end
@@ -0,0 +1,361 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "delegate"
4
+ require "super_diff"
5
+ require "super_diff/rspec"
6
+
7
+ if Gem::Version.new("0.15.0") > SuperDiff::VERSION
8
+ raise "SuperDiff version must be >= 0.15.0"
9
+ end
10
+
11
+ module Dry
12
+ module Monads
13
+ module SuperDiff
14
+ VALUES = [
15
+ Result::Success,
16
+ Result::Failure,
17
+ Maybe::Some,
18
+ Maybe::None,
19
+ Try::Value
20
+ ].freeze
21
+
22
+ EXTRACT_VALUE_MAP = {
23
+ Result::Success => lambda(&:value!),
24
+ Result::Failure => lambda(&:failure),
25
+ Maybe::Some => lambda(&:value!),
26
+ Maybe::None => lambda { |_| Unit },
27
+ Try::Value => lambda(&:value!)
28
+ }.freeze
29
+
30
+ EXTRACT_VALUE = lambda do |v|
31
+ EXTRACT_VALUE_MAP[v.class].(v)
32
+ end
33
+
34
+ IS_ARRAY = lambda do |v|
35
+ EXTRACT_VALUE.(v).is_a?(::Array)
36
+ end
37
+
38
+ IS_HASH = lambda do |v|
39
+ EXTRACT_VALUE.(v).is_a?(::Hash)
40
+ end
41
+
42
+ TOKEN_MAP = {
43
+ Result::Success => "Success",
44
+ Result::Failure => "Failure",
45
+ Maybe::Some => "Some",
46
+ Maybe::None => "None",
47
+ Try::Value => "Value"
48
+ }.freeze
49
+
50
+ class Tuple < ::SimpleDelegator
51
+ def is_a?(klass) = klass <= Tuple
52
+ end
53
+
54
+ class Dict < ::SimpleDelegator
55
+ def is_a?(klass) = klass <= Dict
56
+ end
57
+
58
+ module OTFlatteners
59
+ module MonasAsCollectionConstructor
60
+ def call = tiered_lines
61
+
62
+ protected
63
+
64
+ def build_tiered_lines = inner_lines
65
+
66
+ # prevent super_diff from adding a newline after the open token
67
+ # for arrays
68
+ def build_lines_for_non_change_operation(*)
69
+ @indentation_level -= 1
70
+ super
71
+ ensure
72
+ @indentation_level += 1
73
+ end
74
+
75
+ def open_token = ""
76
+
77
+ def close_token = ""
78
+ end
79
+
80
+ class RegularConstructor < ::SuperDiff::Basic::OperationTreeFlatteners::CustomObject
81
+ private
82
+
83
+ def initialize(...)
84
+ super
85
+
86
+ @klass = operation_tree.underlying_object.class
87
+ end
88
+
89
+ def open_token = "#{TOKEN_MAP[@klass]}("
90
+
91
+ def close_token = ")"
92
+
93
+ def item_prefix_for(_) = ""
94
+ end
95
+
96
+ class TupleConstructor < RegularConstructor
97
+ private
98
+
99
+ def open_token = "#{TOKEN_MAP[@klass]}["
100
+
101
+ def close_token = "]"
102
+ end
103
+
104
+ class Tuple < ::SuperDiff::Basic::OperationTreeFlatteners::Array
105
+ include MonasAsCollectionConstructor
106
+ end
107
+
108
+ class Dict < ::SuperDiff::Basic::OperationTreeFlatteners::Hash
109
+ include MonasAsCollectionConstructor
110
+ end
111
+ end
112
+
113
+ module OT
114
+ class RegularConstructor < ::SuperDiff::Basic::OperationTrees::CustomObject
115
+ def self.applies_to?(value) = VALUES.include?(value.class)
116
+
117
+ def operation_tree_flattener_class = OTFlatteners::RegularConstructor
118
+ end
119
+
120
+ class TupleConstructor < RegularConstructor
121
+ def self.applies_to?(value) = super && IS_ARRAY.call(value)
122
+
123
+ def operation_tree_flattener_class = OTFlatteners::TupleConstructor
124
+ end
125
+
126
+ class Tuple < ::SuperDiff::Basic::OperationTrees::Array
127
+ def self.applies_to?(value) = value.is_a?(::Dry::Monads::SuperDiff::Tuple)
128
+
129
+ def operation_tree_flattener_class = OTFlatteners::Tuple
130
+ end
131
+
132
+ class Dict < ::SuperDiff::Basic::OperationTrees::Hash
133
+ def self.applies_to?(value) = value.is_a?(::Dry::Monads::SuperDiff::Dict)
134
+
135
+ def operation_tree_flattener_class = OTFlatteners::Dict
136
+ end
137
+ end
138
+
139
+ module OTBuilders
140
+ class CompareDefault < ::SuperDiff::Basic::OperationTreeBuilders::CustomObject
141
+ def self.applies_to?(expected, actual)
142
+ VALUES.include?(expected.class) &&
143
+ actual.instance_of?(expected.class)
144
+ end
145
+
146
+ protected
147
+
148
+ def build_operation_tree
149
+ OT::RegularConstructor.new([], underlying_object: actual)
150
+ end
151
+
152
+ def attribute_names = [:value]
153
+
154
+ private
155
+
156
+ def establish_expected_and_actual_attributes
157
+ @expected_attributes = get_value(expected)
158
+ @actual_attributes = get_value(actual)
159
+ end
160
+
161
+ def get_value(object)
162
+ v = EXTRACT_VALUE.(object)
163
+
164
+ if Unit.equal?(v)
165
+ EMPTY_HASH
166
+ else
167
+ {value: v}
168
+ end
169
+ end
170
+ end
171
+
172
+ class Tuple < ::SuperDiff::Basic::OperationTreeBuilders::Array
173
+ def self.applies_to?(expected, actual)
174
+ expected.is_a?(::Dry::Monads::SuperDiff::Tuple) &&
175
+ actual.instance_of?(expected.class)
176
+ end
177
+
178
+ private
179
+
180
+ def operation_tree
181
+ @operation_tree ||= OT::Tuple.new([])
182
+ end
183
+ end
184
+
185
+ class Dict < ::SuperDiff::Basic::OperationTreeBuilders::Hash
186
+ def self.applies_to?(expected, actual)
187
+ expected.is_a?(::Dry::Monads::SuperDiff::Dict) &&
188
+ actual.instance_of?(expected.class)
189
+ end
190
+
191
+ private
192
+
193
+ def build_operation_tree = OT::Dict.new([])
194
+ end
195
+
196
+ class CompareTuples < CompareDefault
197
+ def self.applies_to?(expected, actual)
198
+ super && IS_ARRAY.call(expected) && IS_ARRAY.call(actual)
199
+ end
200
+
201
+ private
202
+
203
+ def get_value(object)
204
+ v = EXTRACT_VALUE.(object)
205
+
206
+ {value: ::Dry::Monads::SuperDiff::Tuple.new(v)}
207
+ end
208
+
209
+ def build_operation_tree
210
+ OT::TupleConstructor.new([], underlying_object: actual)
211
+ end
212
+ end
213
+
214
+ class CompareDicts < CompareDefault
215
+ def self.applies_to?(expected, actual)
216
+ super && IS_HASH.call(expected) && IS_HASH.call(actual)
217
+ end
218
+
219
+ private
220
+
221
+ def get_value(object)
222
+ v = EXTRACT_VALUE.(object)
223
+
224
+ {value: ::Dry::Monads::SuperDiff::Dict.new(v)}
225
+ end
226
+ end
227
+ end
228
+
229
+ module Differs
230
+ class CompareDefault < ::SuperDiff::Basic::Differs::CustomObject
231
+ def self.applies_to?(expected, actual)
232
+ VALUES.include?(expected.class) &&
233
+ expected.instance_of?(actual.class)
234
+ end
235
+
236
+ def operation_tree_builder_class = OTBuilders::CompareDefault
237
+ end
238
+
239
+ class CompareTuples < CompareDefault
240
+ def self.applies_to?(expected, actual)
241
+ super && IS_ARRAY.call(expected) && IS_ARRAY.call(actual)
242
+ end
243
+
244
+ def operation_tree_builder_class = OTBuilders::CompareTuples
245
+ end
246
+
247
+ class CompareDicts < CompareDefault
248
+ def self.applies_to?(expected, actual)
249
+ super && IS_HASH.call(expected) && IS_HASH.call(actual)
250
+ end
251
+
252
+ def operation_tree_builder_class = OTBuilders::CompareDicts
253
+ end
254
+ end
255
+
256
+ module ITBuilders
257
+ class RegularConstructor < ::SuperDiff::Basic::InspectionTreeBuilders::CustomObject
258
+ def self.applies_to?(object)
259
+ VALUES.include?(object.class)
260
+ end
261
+
262
+ def call
263
+ build_tree do |t2|
264
+ t2.add_text("#{TOKEN_MAP[object.class]}(")
265
+
266
+ v = EXTRACT_VALUE.(object)
267
+
268
+ unless Unit.equal?(v)
269
+ t2.nested do |t3|
270
+ t3.add_inspection_of v
271
+ end
272
+ end
273
+
274
+ t2.add_text(")")
275
+ end
276
+ end
277
+
278
+ private
279
+
280
+ def build_tree(&block)
281
+ ::SuperDiff::Core::InspectionTree.new do |t1|
282
+ t1.as_lines_when_rendering_to_lines(
283
+ collection_bookend: :open, &block
284
+ )
285
+ end
286
+ end
287
+ end
288
+
289
+ class TupleConstructor < RegularConstructor
290
+ def self.applies_to?(object) = super && IS_ARRAY.call(object)
291
+
292
+ def call
293
+ build_tree do |t2|
294
+ t2.add_text(TOKEN_MAP[object.class])
295
+
296
+ t2.nested do |t3|
297
+ t3.add_inspection_of EXTRACT_VALUE.(object)
298
+ end
299
+ end
300
+ end
301
+ end
302
+
303
+ class DictConstructor < RegularConstructor
304
+ def self.applies_to?(object) = super && IS_HASH.call(object)
305
+
306
+ def call
307
+ build_tree do |t2|
308
+ t2.add_text("#{TOKEN_MAP[object.class]}(")
309
+
310
+ t2.nested do |t3|
311
+ t3.add_inspection_of ::Dry::Monads::SuperDiff::Dict.new(
312
+ EXTRACT_VALUE.(object)
313
+ )
314
+ end
315
+
316
+ t2.add_text(")")
317
+ end
318
+ end
319
+ end
320
+
321
+ class Dict < ::SuperDiff::Basic::InspectionTreeBuilders::Hash
322
+ def self.applies_to?(object) = object.is_a?(::Dry::Monads::SuperDiff::Dict)
323
+
324
+ def call
325
+ ::SuperDiff::Core::InspectionTree.new do |t1|
326
+ t1.only_when empty do |t2|
327
+ t2.as_lines_when_rendering_to_lines do |t3|
328
+ t3.add_text "{}"
329
+ end
330
+ end
331
+
332
+ t1.only_when nonempty do |t2|
333
+ t2.nested do |t3|
334
+ t3.insert_hash_inspection_of(object)
335
+ end
336
+ end
337
+ end
338
+ end
339
+ end
340
+ end
341
+ end
342
+ end
343
+ end
344
+
345
+ SuperDiff.configuration.tap do |config|
346
+ config.prepend_extra_differ_classes(
347
+ Dry::Monads::SuperDiff::Differs::CompareTuples,
348
+ Dry::Monads::SuperDiff::Differs::CompareDicts,
349
+ Dry::Monads::SuperDiff::Differs::CompareDefault
350
+ )
351
+ config.prepend_extra_inspection_tree_builder_classes(
352
+ Dry::Monads::SuperDiff::ITBuilders::TupleConstructor,
353
+ Dry::Monads::SuperDiff::ITBuilders::DictConstructor,
354
+ Dry::Monads::SuperDiff::ITBuilders::RegularConstructor,
355
+ Dry::Monads::SuperDiff::ITBuilders::Dict
356
+ )
357
+ config.prepend_extra_operation_tree_builder_classes(
358
+ Dry::Monads::SuperDiff::OTBuilders::Tuple,
359
+ Dry::Monads::SuperDiff::OTBuilders::Dict
360
+ )
361
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ Dry::Monads.extend(Dry::Core::Extensions)
4
+
5
+ Dry::Monads.register_extension(:rspec) do
6
+ require "dry/monads/extensions/rspec"
7
+ end
8
+
9
+ Dry::Monads.register_extension(:super_diff) do
10
+ require "dry/monads/extensions/super_diff"
11
+ end
@@ -3,6 +3,6 @@
3
3
  module Dry
4
4
  module Monads
5
5
  # Gem version
6
- VERSION = "1.7.1"
6
+ VERSION = "1.8.0"
7
7
  end
8
8
  end
data/lib/dry/monads.rb CHANGED
@@ -6,6 +6,7 @@ require "dry/core"
6
6
  require "dry/monads/constants"
7
7
  require "dry/monads/errors"
8
8
  require "dry/monads/registry"
9
+ require "dry/monads/extensions"
9
10
 
10
11
  module Dry
11
12
  # Common, idiomatic monads for Ruby
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dry-monads
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.1
4
+ version: 1.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nikita Shilnikov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-01-21 00:00:00.000000000 Z
11
+ date: 2025-03-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -73,6 +73,9 @@ files:
73
73
  - lib/dry/monads/do/all.rb
74
74
  - lib/dry/monads/do/mixin.rb
75
75
  - lib/dry/monads/errors.rb
76
+ - lib/dry/monads/extensions.rb
77
+ - lib/dry/monads/extensions/rspec.rb
78
+ - lib/dry/monads/extensions/super_diff.rb
76
79
  - lib/dry/monads/lazy.rb
77
80
  - lib/dry/monads/list.rb
78
81
  - lib/dry/monads/maybe.rb