fear 1.1.0 → 1.2.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/.github/workflows/rubocop.yml +39 -0
- data/.github/workflows/spec.yml +42 -0
- data/.rubocop.yml +1 -1
- data/.simplecov +17 -0
- data/CHANGELOG.md +12 -3
- data/Gemfile +0 -2
- data/Gemfile.lock +80 -39
- data/README.md +158 -9
- data/Rakefile +27 -0
- data/examples/pattern_extracting.rb +1 -1
- data/examples/pattern_extracting_ruby2.7.rb +15 -0
- data/examples/pattern_matching_binary_tree_set.rb +3 -0
- data/examples/pattern_matching_number_in_words.rb +2 -0
- data/fear.gemspec +7 -8
- data/lib/dry/types/fear.rb +8 -0
- data/lib/dry/types/fear/option.rb +125 -0
- data/lib/fear.rb +9 -0
- data/lib/fear/awaitable.rb +2 -2
- data/lib/fear/either.rb +5 -0
- data/lib/fear/either_pattern_match.rb +3 -0
- data/lib/fear/extractor.rb +2 -0
- data/lib/fear/extractor/any_matcher.rb +1 -1
- data/lib/fear/extractor/array_splat_matcher.rb +1 -1
- data/lib/fear/extractor/empty_list_matcher.rb +1 -1
- data/lib/fear/extractor/matcher.rb +0 -3
- data/lib/fear/extractor/pattern.rb +1 -0
- data/lib/fear/extractor/value_matcher.rb +1 -1
- data/lib/fear/failure.rb +7 -0
- data/lib/fear/future.rb +12 -6
- data/lib/fear/future_api.rb +2 -2
- data/lib/fear/left.rb +1 -0
- data/lib/fear/none.rb +18 -0
- data/lib/fear/option.rb +22 -0
- data/lib/fear/option_pattern_match.rb +1 -0
- data/lib/fear/partial_function.rb +6 -0
- data/lib/fear/partial_function/empty.rb +2 -0
- data/lib/fear/pattern_matching_api.rb +1 -0
- data/lib/fear/right.rb +2 -0
- data/lib/fear/some.rb +28 -0
- data/lib/fear/struct.rb +13 -0
- data/lib/fear/success.rb +6 -0
- data/lib/fear/try_pattern_match.rb +3 -0
- data/lib/fear/utils.rb +13 -0
- data/lib/fear/version.rb +1 -1
- data/spec/dry/types/fear/option/constrained_spec.rb +22 -0
- data/spec/dry/types/fear/option/core_spec.rb +77 -0
- data/spec/dry/types/fear/option/default_spec.rb +21 -0
- data/spec/dry/types/fear/option/hash_spec.rb +58 -0
- data/spec/dry/types/fear/option/option_spec.rb +97 -0
- data/spec/fear/awaitable_spec.rb +17 -0
- data/spec/fear/either_pattern_matching_spec.rb +28 -0
- data/spec/fear/future_spec.rb +11 -2
- data/spec/fear/none_spec.rb +1 -1
- data/spec/fear/option_pattern_matching_spec.rb +34 -0
- data/spec/fear/option_spec.rb +128 -0
- data/spec/fear/partial_function_spec.rb +50 -0
- data/spec/fear/pattern_matching_api_spec.rb +31 -0
- data/spec/fear/try_pattern_matching_spec.rb +34 -0
- data/spec/spec_helper.rb +6 -2
- data/spec/struct_pattern_matching_spec.rb +36 -0
- data/spec/struct_spec.rb +2 -2
- data/spec/support/dry_types.rb +6 -0
- metadata +110 -12
- data/.travis.yml +0 -17
data/lib/fear/future_api.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Fear
|
4
|
-
# rubocop: disable
|
4
|
+
# rubocop: disable Layout/LineLength
|
5
5
|
module FutureApi
|
6
6
|
# Asynchronously evaluates the block
|
7
7
|
# @param options [Hash] options will be passed directly to underlying +Concurrent::Promise+
|
@@ -17,5 +17,5 @@ module Fear
|
|
17
17
|
Future.new(options, &block)
|
18
18
|
end
|
19
19
|
end
|
20
|
-
# rubocop: enable
|
20
|
+
# rubocop: enable Layout/LineLength
|
21
21
|
end
|
data/lib/fear/left.rb
CHANGED
data/lib/fear/none.rb
CHANGED
@@ -14,6 +14,7 @@ module Fear
|
|
14
14
|
Fear.none
|
15
15
|
end
|
16
16
|
end
|
17
|
+
public_constant :EXTRACTOR
|
17
18
|
|
18
19
|
# @raise [NoSuchElementError]
|
19
20
|
def get
|
@@ -59,12 +60,29 @@ module Fear
|
|
59
60
|
def ===(other)
|
60
61
|
self == other
|
61
62
|
end
|
63
|
+
|
64
|
+
# @param other [Fear::Option]
|
65
|
+
# @return [Fear::Option]
|
66
|
+
def zip(other)
|
67
|
+
if other.is_a?(Option)
|
68
|
+
Fear.none
|
69
|
+
else
|
70
|
+
raise TypeError, "can't zip with #{other.class}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# @return [RightBiased::Left]
|
75
|
+
def filter_map
|
76
|
+
self
|
77
|
+
end
|
62
78
|
end
|
63
79
|
|
64
80
|
private_constant(:NoneClass)
|
65
81
|
|
66
82
|
# The only instance of NoneClass
|
83
|
+
# @api private
|
67
84
|
None = NoneClass.new.freeze
|
85
|
+
public_constant :None
|
68
86
|
|
69
87
|
class << NoneClass
|
70
88
|
def new
|
data/lib/fear/option.rb
CHANGED
@@ -82,6 +82,17 @@ module Fear
|
|
82
82
|
# Fear.some(42).map { |v| v/2 } #=> Fear.some(21)
|
83
83
|
# Fear.none.map { |v| v/2 } #=> None
|
84
84
|
#
|
85
|
+
# @!method filter_map(&block)
|
86
|
+
# Returns a new +Some+ of truthy results (everything except +false+ or +nil+) of
|
87
|
+
# running the block or +None+ otherwise.
|
88
|
+
# @yieldparam [any] value
|
89
|
+
# @yieldreturn [any]
|
90
|
+
# @example
|
91
|
+
# Fear.some(42).filter_map { |v| v/2 if v.even? } #=> Fear.some(21)
|
92
|
+
# Fear.some(42).filter_map { |v| v/2 if v.odd? } #=> Fear.none
|
93
|
+
# Fear.some(42).filter_map { |v| false } #=> Fear.none
|
94
|
+
# Fear.none.filter_map { |v| v/2 } #=> Fear.none
|
95
|
+
#
|
85
96
|
# @!method flat_map(&block)
|
86
97
|
# Returns the given block applied to the value from this +Some+
|
87
98
|
# or returns this if this is a +None+
|
@@ -153,6 +164,17 @@ module Fear
|
|
153
164
|
# m.else { 'error '}
|
154
165
|
# end
|
155
166
|
#
|
167
|
+
# @!method zip(other)
|
168
|
+
# @param other [Fear::Option]
|
169
|
+
# @return [Fear::Option] a +Fear::Some+ formed from this option and another option by
|
170
|
+
# combining the corresponding elements in an array.
|
171
|
+
#
|
172
|
+
# @example
|
173
|
+
# Fear.some("foo").zip(Fear.some("bar")) #=> Fear.some(["foo", "bar"])
|
174
|
+
# Fear.some("foo").zip(Fear.some("bar")) { |x, y| x + y } #=> Fear.some("foobar")
|
175
|
+
# Fear.some("foo").zip(Fear.none) #=> Fear.none
|
176
|
+
# Fear.none.zip(Fear.some("bar")) #=> Fear.none
|
177
|
+
#
|
156
178
|
# @see https://github.com/scala/scala/blob/2.11.x/src/library/scala/Option.scala
|
157
179
|
#
|
158
180
|
module Option
|
@@ -99,6 +99,12 @@ module Fear
|
|
99
99
|
# @param other [PartialFunction]
|
100
100
|
# @return [PartialFunction] a partial function which has as domain the union of the domains
|
101
101
|
# of this partial function and +other+.
|
102
|
+
# @example
|
103
|
+
# handle_even = Fear.case(:even?.to_proc) { |x| "#{x} is even" }
|
104
|
+
# handle_odd = Fear.case(:odd?.to_proc) { |x| "#{x} is odd" }
|
105
|
+
# handle_even_or_odd = handle_even.or_else(odd)
|
106
|
+
# handle_even_or_odd.(42) #=> 42 is even
|
107
|
+
# handle_even_or_odd.(42) #=> 21 is odd
|
102
108
|
def or_else(other)
|
103
109
|
OrElse.new(self, other)
|
104
110
|
end
|
@@ -130,6 +130,7 @@ module Fear
|
|
130
130
|
# pf = Fear.xcase('['ok', Some(body)]', ->(body:) { !body.empty? }) { }
|
131
131
|
#
|
132
132
|
def xcase(pattern, *guards, &function)
|
133
|
+
warn "NOTE: Fear.xcase is deprecated and will be removed in a future version. Use `case .. in ..` instead."
|
133
134
|
Fear[pattern].and_then(self.case(*guards, &function))
|
134
135
|
end
|
135
136
|
end
|
data/lib/fear/right.rb
CHANGED
data/lib/fear/some.rb
CHANGED
@@ -14,6 +14,8 @@ module Fear
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
+
public_constant :EXTRACTOR
|
18
|
+
|
17
19
|
attr_reader :value
|
18
20
|
protected :value
|
19
21
|
|
@@ -67,5 +69,31 @@ module Fear
|
|
67
69
|
|
68
70
|
# @return [String]
|
69
71
|
alias to_s inspect
|
72
|
+
|
73
|
+
# @param other [Fear::Option]
|
74
|
+
# @return [Fear::Option]
|
75
|
+
def zip(other)
|
76
|
+
if other.is_a?(Option)
|
77
|
+
other.map do |x|
|
78
|
+
if block_given?
|
79
|
+
yield(value, x)
|
80
|
+
else
|
81
|
+
[value, x]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
else
|
85
|
+
raise TypeError, "can't zip with #{other.class}"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# @return [RightBiased::Left, RightBiased::Right]
|
90
|
+
def filter_map(&filter)
|
91
|
+
map(&filter).select(&:itself)
|
92
|
+
end
|
93
|
+
|
94
|
+
# @return [Array<any>]
|
95
|
+
def deconstruct
|
96
|
+
[get]
|
97
|
+
end
|
70
98
|
end
|
71
99
|
end
|
data/lib/fear/struct.rb
CHANGED
@@ -192,6 +192,7 @@ module Fear
|
|
192
192
|
end
|
193
193
|
|
194
194
|
INSPECT_TEMPLATE = "<#Fear::Struct %{class_name} %{attributes}>"
|
195
|
+
private_constant :INSPECT_TEMPLATE
|
195
196
|
|
196
197
|
# @return [String]
|
197
198
|
#
|
@@ -208,6 +209,7 @@ module Fear
|
|
208
209
|
alias to_s inspect
|
209
210
|
|
210
211
|
MISSING_KEYWORDS_ERROR = "missing keywords: %{keywords}"
|
212
|
+
private_constant :MISSING_KEYWORDS_ERROR
|
211
213
|
|
212
214
|
private def _check_missing_attributes!(provided_attributes)
|
213
215
|
missing_attributes = members - provided_attributes.keys
|
@@ -218,6 +220,7 @@ module Fear
|
|
218
220
|
end
|
219
221
|
|
220
222
|
UNKNOWN_KEYWORDS_ERROR = "unknown keywords: %{keywords}"
|
223
|
+
private_constant :UNKNOWN_KEYWORDS_ERROR
|
221
224
|
|
222
225
|
private def _check_unknown_attributes!(provided_attributes)
|
223
226
|
unknown_attributes = provided_attributes.keys - members
|
@@ -231,5 +234,15 @@ module Fear
|
|
231
234
|
private def _set_attribute(name, value)
|
232
235
|
instance_variable_set(:"@#{name}", value)
|
233
236
|
end
|
237
|
+
|
238
|
+
# @param keys [Hash, nil]
|
239
|
+
# @return [Hash]
|
240
|
+
def deconstruct_keys(keys)
|
241
|
+
if keys
|
242
|
+
to_h.slice(*(self.class.attributes & keys))
|
243
|
+
else
|
244
|
+
to_h
|
245
|
+
end
|
246
|
+
end
|
234
247
|
end
|
235
248
|
end
|
data/lib/fear/success.rb
CHANGED
@@ -13,6 +13,7 @@ module Fear
|
|
13
13
|
Fear.none
|
14
14
|
end
|
15
15
|
end
|
16
|
+
public_constant :EXTRACTOR
|
16
17
|
|
17
18
|
attr_reader :value
|
18
19
|
protected :value
|
@@ -106,5 +107,10 @@ module Fear
|
|
106
107
|
|
107
108
|
# @return [String]
|
108
109
|
alias to_s inspect
|
110
|
+
|
111
|
+
# @return [<any>]
|
112
|
+
def deconstruct
|
113
|
+
[value]
|
114
|
+
end
|
109
115
|
end
|
110
116
|
end
|
@@ -7,7 +7,10 @@ module Fear
|
|
7
7
|
# @api private
|
8
8
|
class TryPatternMatch < Fear::PatternMatch
|
9
9
|
SUCCESS_EXTRACTOR = :get.to_proc
|
10
|
+
private_constant :SUCCESS_EXTRACTOR
|
11
|
+
|
10
12
|
FAILURE_EXTRACTOR = :exception.to_proc
|
13
|
+
private_constant :FAILURE_EXTRACTOR
|
11
14
|
|
12
15
|
# Match against +Fear::Success+
|
13
16
|
#
|
data/lib/fear/utils.rb
CHANGED
@@ -3,7 +3,20 @@
|
|
3
3
|
module Fear
|
4
4
|
# @private
|
5
5
|
module Utils
|
6
|
+
EMPTY_STRING = ""
|
7
|
+
public_constant :EMPTY_STRING
|
8
|
+
|
9
|
+
IDENTITY = :itself.to_proc
|
10
|
+
public_constant :IDENTITY
|
11
|
+
|
6
12
|
UNDEFINED = Object.new.freeze
|
13
|
+
public_constant :UNDEFINED
|
14
|
+
|
15
|
+
EMPTY_HASH = {}.freeze
|
16
|
+
public_constant :EMPTY_HASH
|
17
|
+
|
18
|
+
EMPTY_ARRAY = [].freeze
|
19
|
+
public_constant :EMPTY_ARRAY
|
7
20
|
|
8
21
|
class << self
|
9
22
|
def assert_arg_or_block!(method_name, *args)
|
data/lib/fear/version.rb
CHANGED
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "support/dry_types"
|
4
|
+
|
5
|
+
RSpec.describe Dry::Types::Constrained, :option do
|
6
|
+
context "with a option type" do
|
7
|
+
subject(:type) do
|
8
|
+
Dry::Types["nominal.string"].constrained(size: 4).option
|
9
|
+
end
|
10
|
+
|
11
|
+
it_behaves_like "Dry::Types::Nominal without primitive"
|
12
|
+
|
13
|
+
it "passes when constraints are not violated" do
|
14
|
+
expect(type[nil]).to be_none
|
15
|
+
expect(type["hell"]).to be_some_of("hell")
|
16
|
+
end
|
17
|
+
|
18
|
+
it "raises when a given constraint is violated" do
|
19
|
+
expect { type["hel"] }.to raise_error(Dry::Types::ConstraintError, /hel/)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "support/dry_types"
|
4
|
+
|
5
|
+
RSpec.describe Dry::Types::Nominal, :option do
|
6
|
+
describe "with opt-in option types" do
|
7
|
+
context "with strict string" do
|
8
|
+
let(:string) { Dry::Types["option.strict.string"] }
|
9
|
+
|
10
|
+
it_behaves_like "Dry::Types::Nominal without primitive" do
|
11
|
+
let(:type) { string }
|
12
|
+
end
|
13
|
+
|
14
|
+
it "accepts nil" do
|
15
|
+
expect(string[nil]).to be_none
|
16
|
+
end
|
17
|
+
|
18
|
+
it "accepts a string" do
|
19
|
+
expect(string["something"]).to be_some_of("something")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context "with coercible string" do
|
24
|
+
let(:string) { Dry::Types["option.coercible.string"] }
|
25
|
+
|
26
|
+
it_behaves_like "Dry::Types::Nominal without primitive" do
|
27
|
+
let(:type) { string }
|
28
|
+
end
|
29
|
+
|
30
|
+
it "accepts nil" do
|
31
|
+
expect(string[nil]).to be_none
|
32
|
+
end
|
33
|
+
|
34
|
+
it "accepts a string" do
|
35
|
+
expect(string[:something]).to be_some_of("something")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "defining coercible Option String" do
|
41
|
+
let(:option_string) { Dry::Types["coercible.string"].option }
|
42
|
+
|
43
|
+
it_behaves_like "Dry::Types::Nominal without primitive" do
|
44
|
+
let(:type) { option_string }
|
45
|
+
end
|
46
|
+
|
47
|
+
it "accepts nil" do
|
48
|
+
expect(option_string[nil]).to be_none
|
49
|
+
end
|
50
|
+
|
51
|
+
it "accepts an object coercible to a string" do
|
52
|
+
expect(option_string[123]).to be_some_of("123")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "defining Option String" do
|
57
|
+
let(:option_string) { Dry::Types["strict.string"].option }
|
58
|
+
|
59
|
+
it_behaves_like "Dry::Types::Nominal without primitive" do
|
60
|
+
let(:type) { option_string }
|
61
|
+
end
|
62
|
+
|
63
|
+
it "accepts nil and returns None instance" do
|
64
|
+
value = option_string[nil]
|
65
|
+
|
66
|
+
expect(value).to be_none
|
67
|
+
expect(value.map(&:downcase).map(&:upcase)).to be_none
|
68
|
+
end
|
69
|
+
|
70
|
+
it "accepts a string and returns Some instance" do
|
71
|
+
value = option_string["SomeThing"]
|
72
|
+
|
73
|
+
expect(value).to be_some_of("SomeThing")
|
74
|
+
expect(value.map(&:downcase).map(&:upcase)).to be_some_of("SOMETHING")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "support/dry_types"
|
4
|
+
|
5
|
+
RSpec.describe Dry::Types::Nominal, "#default", :option do
|
6
|
+
context "with a maybe" do
|
7
|
+
subject(:type) { Dry::Types["strict.integer"].option }
|
8
|
+
|
9
|
+
it_behaves_like "Dry::Types::Nominal without primitive" do
|
10
|
+
let(:type) { Dry::Types["strict.integer"].option.default(0) }
|
11
|
+
end
|
12
|
+
|
13
|
+
it "does not allow nil" do
|
14
|
+
expect { type.default(nil) }.to raise_error(ArgumentError, /nil/)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "accepts a non-nil value" do
|
18
|
+
expect(type.default(0)[0]).to be_some_of(0)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "support/dry_types"
|
4
|
+
|
5
|
+
RSpec.describe Dry::Types::Hash, :option do
|
6
|
+
let(:email) { Dry::Types["option.strict.string"] }
|
7
|
+
|
8
|
+
context "Symbolized constructor" do
|
9
|
+
subject(:hash) do
|
10
|
+
Dry::Types["nominal.hash"].schema(
|
11
|
+
name: "string",
|
12
|
+
email: email,
|
13
|
+
).with_key_transform(&:to_sym)
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#[]" do
|
17
|
+
it "sets None as a default value for option" do
|
18
|
+
result = hash["name" => "Jane"]
|
19
|
+
|
20
|
+
expect(result[:email]).to be_none
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context "Schema constructor" do
|
26
|
+
subject(:hash) do
|
27
|
+
Dry::Types["nominal.hash"].schema(
|
28
|
+
name: "string",
|
29
|
+
email: email,
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#[]" do
|
34
|
+
it "sets None as a default value for option types" do
|
35
|
+
result = hash[name: "Jane"]
|
36
|
+
|
37
|
+
expect(result[:email]).to be_none
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context "Strict with defaults" do
|
43
|
+
subject(:hash) do
|
44
|
+
Dry::Types["nominal.hash"].schema(
|
45
|
+
name: "string",
|
46
|
+
email: email,
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "#[]" do
|
51
|
+
it "sets None as a default value for option types" do
|
52
|
+
result = hash[name: "Jane"]
|
53
|
+
|
54
|
+
expect(result[:email]).to be_none
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|