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 +4 -4
- data/CHANGELOG.md +100 -0
- data/lib/dry/monads/extensions/rspec.rb +193 -0
- data/lib/dry/monads/extensions/super_diff.rb +361 -0
- data/lib/dry/monads/extensions.rb +11 -0
- data/lib/dry/monads/version.rb +1 -1
- data/lib/dry/monads.rb +1 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 43619ae6aa239741e9730b8f824dafb848f999daea83491a78a0c3d8b4fec9f9
|
4
|
+
data.tar.gz: 3d2fcf419ad5b71ef88058db953b44f81d4f661796e87c88e410549b4f4340ab
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/lib/dry/monads/version.rb
CHANGED
data/lib/dry/monads.rb
CHANGED
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.
|
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-
|
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
|