dry-monads 1.7.1 → 1.8.2

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: 0aa936c14c5b5099ef82374d3fdf1d09ac321089b24ff5928646915c5474c156
4
+ data.tar.gz: 3b0e347df2726f44c3a85d12dfb33e174e034c82d2de2ebd140550ac116e20d2
5
5
  SHA512:
6
- metadata.gz: f9164e029f4482abaaa3bae2111607417ba3c3867ffc44f10a6626697ffa8793d291cc96518563d2eec8950f0594814167cf04f242f6354f581873256862b672
7
- data.tar.gz: 8c915830df01f6461efe56db8c38ec248cb9778270a6b5f5f1b0c2464e0cb6794806d031a62c9e47ed770f0f964b38ba461599afa40d2ac2f35ecc71904961cd
6
+ metadata.gz: 5c881bc75da61d00e66d06ae79b1ab898cd62c1fd77bba22fe5a67ee89b024f2164d66e277c84f65e83dfa8c54901538b09e1d33a4db7393ccbe26adea7a0d01
7
+ data.tar.gz: b01cb23a977f6bb61f4b5505816562dc28431a53d9e0e2f9e01fa37d2545cd6ebc29e7c22cb76b868f77a82beecfe3f90a04b6de4502eb38af0ca1987429cef1
data/CHANGELOG.md CHANGED
@@ -1,5 +1,125 @@
1
1
  <!--- DO NOT EDIT THIS FILE - IT'S AUTOMATICALLY GENERATED VIA DEVTOOLS --->
2
2
 
3
+ ## 1.8.2 2025-03-15
4
+
5
+
6
+ ### Fixed
7
+
8
+ - Fix be_* matchers for non-monadic values (@flash-gordon, issue #186)
9
+
10
+
11
+ [Compare v1.8.1...v1.8.2](https://github.com/dry-rb/dry-monads/compare/v1.8.1...v1.8.2)
12
+
13
+ ## 1.8.1 2025-03-12
14
+
15
+
16
+ ### Fixed
17
+
18
+ - Exclude extensions from the Zeitwerk loader (@flash-gordon, issue #185)
19
+
20
+
21
+ [Compare v1.8.0...v1.8.1](https://github.com/dry-rb/dry-monads/compare/v1.8.0...v1.8.1)
22
+
23
+ ## 1.8.0 2025-03-12
24
+
25
+
26
+ ### Added
27
+
28
+ - New extension for RSpec (@flash-gordon in #183):
29
+ One of the pain points of testing monads is referencing class constants from specs.
30
+ This extension catches missing class constants, analyzes the call site and
31
+ returns a matching constant.
32
+
33
+ Before, this code would raise a `NameError` because `Failure` is a constant
34
+ that is missing in `Object`:
35
+
36
+ ```ruby
37
+ example "missing constant" do
38
+ expect(call_operation).to eql(Failure[:some_error, "some message"])
39
+ end
40
+ ```
41
+
42
+ Now, after enabling the extension, it will return the correct constant:
43
+
44
+ ```ruby
45
+ Dry::Monads.load_extensions(:rspec)
46
+
47
+ example "missing constant" do
48
+ Failure[:some_error, "some message"] # => Failure[:some_error, "some message"]
49
+ end
50
+ ```
51
+
52
+ Out of the box, the extension will check if `Success`, `Failure`, `Some`, and
53
+ `None` are referenced from a file ending with `_spec.rb`.
54
+
55
+ More involved analysis is possible if you add `debug_inspector` to your Gemfile:
56
+
57
+ ```ruby
58
+ group :test do
59
+ gem "debug_inspector"
60
+ end
61
+ ```
62
+
63
+ This will allow referencing constants from other modules, such as rspec helpers.
64
+
65
+ The extension also adds new matchers for `Success`, `Failure`, `Some`, and
66
+ `None` values.
67
+
68
+ ```ruby
69
+ expect(Success(1)).to be_success
70
+ expect(Success(1)).to be_success(1)
71
+ expect(Success(1)).to be_success { |x| x > 0 }
72
+ expect(Success(1)).to be_a_success { |x| x > 0 }
73
+
74
+ expect(Failure(1)).to be_failure(1)
75
+
76
+ expect(Some(1)).to be_some
77
+ expect(Some(1)).to be_success
78
+
79
+ expect(None()).to be_none
80
+ expect(None()).to be_failure
81
+ ```
82
+ - New extension for super_diff (@flash-gordon in #184):
83
+
84
+ Adds support for improved diff output in specs when using the super_diff gem.
85
+ This makes it easier to understand the differences between monad values in test failures.
86
+
87
+ To use this extension:
88
+ 1. Add super_diff to your Gemfile's test group:
89
+ ```ruby
90
+ group :test do
91
+ gem "super_diff"
92
+ end
93
+ ```
94
+ 2. Load the extension:
95
+ ```ruby
96
+ require "dry/monads"
97
+ Dry::Monads.load_extensions(:super_diff)
98
+ ```
99
+
100
+ This will change the diff output for monad values to be more readable.
101
+
102
+ Before:
103
+
104
+ ```
105
+ -Success({a: 2, c: 2})
106
+ +Success({a: 1, b: 2})
107
+ ```
108
+
109
+ After:
110
+
111
+ ```
112
+ Success(
113
+ - a: 2,
114
+ + a: 1,
115
+ - c: 2
116
+ + b: 2
117
+ )
118
+ ```
119
+
120
+
121
+ [Compare v1.7.1...v1.8.0](https://github.com/dry-rb/dry-monads/compare/v1.7.1...v1.8.0)
122
+
3
123
  ## 1.7.1 2025-01-21
4
124
 
5
125
 
@@ -0,0 +1,211 @@
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
+ PREDICATES = {
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: [::Dry::Monads::Maybe::Some],
38
+ extract_value: :value!.to_proc
39
+ }
40
+ }.freeze
41
+
42
+ CONSTRUCTOR_CLASSES = PREDICATES.values.flat_map { |definition|
43
+ definition[:expected_classes]
44
+ }.freeze
45
+
46
+ PREDICATES.each do |name, args|
47
+ args => { expected_classes:, extract_value: }
48
+ expected_constructors = expected_classes.map(&:name).map do |c|
49
+ c.split("::").last
50
+ end
51
+
52
+ matcher :"be_#{name}" do |expected = Undefined|
53
+ predicate = "#{name}?"
54
+
55
+ match do |actual|
56
+ if expected_classes.any? { |klass| actual.is_a?(klass) }
57
+ exact_match = actual.is_a?(expected_classes[0])
58
+
59
+ if exact_match && block_arg
60
+ block_arg.call(extract_value.call(actual))
61
+ elsif Undefined.equal?(expected)
62
+ true
63
+ elsif exact_match
64
+ extract_value.call(actual) == expected
65
+ else
66
+ false
67
+ end
68
+ elsif actual.respond_to?(predicate)
69
+ actual.__send__(predicate)
70
+ else
71
+ false
72
+ end
73
+ end
74
+
75
+ failure_message do |actual|
76
+ if expected_classes.none? { |klass| actual.is_a?(klass) }
77
+ if actual.respond_to?(predicate)
78
+ "expected #{actual.inspect}.#{predicate} to return truthy value, " \
79
+ "but it returned false or nil"
80
+ elsif expected_classes.size > 1
81
+ "expected #{actual.inspect} to be one of the following values: " \
82
+ "#{expected_constructors.join(", ")} or respond to #{predicate}, " \
83
+ "but it's #{actual.class}"
84
+ else
85
+ "expected #{actual.inspect} to be a #{expected_constructors[0]} value " \
86
+ "or respond to #{predicate}, but it's #{actual.class}"
87
+ end
88
+ elsif actual.is_a?(expected_classes[0]) && block_arg
89
+ "expected #{actual.inspect} to have a value satisfying the given block"
90
+ else
91
+ "expected #{actual.inspect} to have value #{expected.inspect}, " \
92
+ "but it was #{extract_value.call(actual).inspect}"
93
+ end
94
+ end
95
+
96
+ failure_message_when_negated do |actual|
97
+ if expected_classes.none? { |klass| actual.is_a?(klass) }
98
+ if actual.respond_to?(predicate)
99
+ "expected #{actual.inspect}.#{predicate} to return falsey value, " \
100
+ "but it returned truthy value"
101
+ else
102
+ "expected #{actual.inspect} to respond to #{predicate}, " \
103
+ "but it doesn't"
104
+ end
105
+ elsif expected_classes.size > 1
106
+ "expected #{actual.inspect} to not be one of the following values: " \
107
+ "#{expected_constructors.join(", ")}, but it is"
108
+ else
109
+ "expected #{actual.inspect} to not be a #{expected_constructors[0]} value, " \
110
+ "but it is"
111
+ end
112
+ end
113
+ end
114
+
115
+ alias_matcher :"be_a_#{name}", :"be_#{name}"
116
+ end
117
+
118
+ matcher :be_none do
119
+ match { |actual| actual.is_a?(::Dry::Monads::Maybe::None) }
120
+
121
+ failure_message do |actual|
122
+ "expected #{actual.inspect} to be none"
123
+ end
124
+
125
+ failure_message_when_negated do |actual|
126
+ "expected #{actual.inspect} to not be none"
127
+ end
128
+ end
129
+ end
130
+
131
+ Constructors = Monads[:result, :maybe]
132
+
133
+ CONSTANTS = %i[Success Failure Some None List].to_set
134
+
135
+ NESTED_CONSTANTS = CONSTANTS.to_set { |c| "::#{c}" }
136
+
137
+ class << self
138
+ def resolve_constant_name(name)
139
+ if CONSTANTS.include?(name)
140
+ name
141
+ elsif NESTED_CONSTANTS.any? { |c| name.to_s.end_with?(c) }
142
+ name[/::(\w+)$/, 1].to_sym
143
+ else
144
+ nil
145
+ end
146
+ end
147
+
148
+ def name_to_const(name)
149
+ case name
150
+ in :Success
151
+ ::Dry::Monads::Result::Success
152
+ in :Failure
153
+ ::Dry::Monads::Result::Failure
154
+ in :Some
155
+ ::Dry::Monads::Maybe::Some
156
+ in :None
157
+ ::Dry::Monads::Maybe::None
158
+ in :List
159
+ ::Dry::Monads::List
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
166
+
167
+ catch_missing_const = Module.new do
168
+ if debug_inspector_available
169
+ def const_missing(name)
170
+ const_name = Dry::Monads::RSpec.resolve_constant_name(name)
171
+
172
+ if const_name
173
+ DebugInspector.open do |dc|
174
+ if dc.frame_binding(2).receiver.is_a?(RSpec::Core::ExampleGroup)
175
+ Dry::Monads::RSpec.name_to_const(const_name)
176
+ else
177
+ super
178
+ end
179
+ end
180
+ else
181
+ super
182
+ end
183
+ end
184
+ else
185
+ def const_missing(name)
186
+ const_name = Dry::Monads::RSpec.resolve_constant_name(name)
187
+
188
+ if const_name && caller_locations(1, 1).first.path.end_with?("_spec.rb")
189
+ Dry::Monads::RSpec.name_to_const(const_name)
190
+ else
191
+ super
192
+ end
193
+ end
194
+ end
195
+
196
+ define_method(:include) do |*modules|
197
+ super(*modules).tap do
198
+ modules.each do |m|
199
+ m.extend(catch_missing_const) unless m.frozen?
200
+ end
201
+ end
202
+ end
203
+ end
204
+
205
+ Object.extend(catch_missing_const)
206
+
207
+ RSpec.configure do |config|
208
+ config.include Dry::Monads::RSpec::Matchers
209
+ config.include Dry::Monads::RSpec::Constructors
210
+ config.extend(catch_missing_const)
211
+ 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.2"
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
@@ -22,7 +23,9 @@ module Dry
22
23
  loader.ignore(
23
24
  "#{root}/dry-monads.rb",
24
25
  "#{root}/dry/monads/{all,constants,errors,registry,version}.rb",
25
- "#{root}/json/**/*.rb"
26
+ "#{root}/json/**/*.rb",
27
+ "#{root}/dry/monads/extensions.rb",
28
+ "#{root}/dry/monads/extensions/**/*.rb"
26
29
  )
27
30
  end
28
31
  end
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.2
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-15 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