fear 1.0.0 → 2.0.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.
Files changed (143) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +27 -0
  3. data/.github/workflows/rubocop.yml +39 -0
  4. data/.github/workflows/spec.yml +42 -0
  5. data/.rubocop.yml +4 -60
  6. data/.simplecov +17 -0
  7. data/CHANGELOG.md +29 -1
  8. data/Gemfile +5 -5
  9. data/Gemfile.lock +86 -50
  10. data/README.md +240 -209
  11. data/Rakefile +72 -65
  12. data/examples/pattern_extracting.rb +10 -8
  13. data/examples/pattern_matching_binary_tree_set.rb +7 -2
  14. data/examples/pattern_matching_number_in_words.rb +48 -42
  15. data/fear.gemspec +33 -34
  16. data/lib/dry/types/fear/option.rb +125 -0
  17. data/lib/dry/types/fear.rb +8 -0
  18. data/lib/fear/await.rb +33 -0
  19. data/lib/fear/awaitable.rb +28 -0
  20. data/lib/fear/either.rb +15 -4
  21. data/lib/fear/either_api.rb +4 -0
  22. data/lib/fear/either_pattern_match.rb +9 -5
  23. data/lib/fear/empty_partial_function.rb +3 -1
  24. data/lib/fear/failure.rb +7 -7
  25. data/lib/fear/failure_pattern_match.rb +4 -0
  26. data/lib/fear/for.rb +4 -2
  27. data/lib/fear/for_api.rb +5 -1
  28. data/lib/fear/future.rb +157 -82
  29. data/lib/fear/future_api.rb +17 -4
  30. data/lib/fear/left.rb +3 -9
  31. data/lib/fear/left_pattern_match.rb +2 -0
  32. data/lib/fear/none.rb +28 -10
  33. data/lib/fear/none_pattern_match.rb +2 -0
  34. data/lib/fear/option.rb +30 -2
  35. data/lib/fear/option_api.rb +4 -0
  36. data/lib/fear/option_pattern_match.rb +8 -3
  37. data/lib/fear/partial_function/and_then.rb +4 -2
  38. data/lib/fear/partial_function/any.rb +2 -0
  39. data/lib/fear/partial_function/combined.rb +3 -1
  40. data/lib/fear/partial_function/empty.rb +6 -0
  41. data/lib/fear/partial_function/guard/and.rb +2 -0
  42. data/lib/fear/partial_function/guard/and3.rb +2 -0
  43. data/lib/fear/partial_function/guard/or.rb +2 -0
  44. data/lib/fear/partial_function/guard.rb +8 -6
  45. data/lib/fear/partial_function/lifted.rb +2 -0
  46. data/lib/fear/partial_function/or_else.rb +5 -1
  47. data/lib/fear/partial_function.rb +18 -9
  48. data/lib/fear/partial_function_class.rb +3 -1
  49. data/lib/fear/pattern_match.rb +3 -11
  50. data/lib/fear/pattern_matching_api.rb +6 -28
  51. data/lib/fear/promise.rb +7 -5
  52. data/lib/fear/right.rb +3 -9
  53. data/lib/fear/right_biased.rb +5 -3
  54. data/lib/fear/right_pattern_match.rb +4 -0
  55. data/lib/fear/some.rb +35 -8
  56. data/lib/fear/some_pattern_match.rb +2 -0
  57. data/lib/fear/struct.rb +237 -0
  58. data/lib/fear/success.rb +7 -8
  59. data/lib/fear/success_pattern_match.rb +4 -0
  60. data/lib/fear/try.rb +8 -2
  61. data/lib/fear/try_api.rb +4 -0
  62. data/lib/fear/try_pattern_match.rb +9 -5
  63. data/lib/fear/unit.rb +6 -2
  64. data/lib/fear/utils.rb +14 -2
  65. data/lib/fear/version.rb +4 -1
  66. data/lib/fear.rb +26 -44
  67. data/spec/dry/types/fear/option/constrained_spec.rb +22 -0
  68. data/spec/dry/types/fear/option/core_spec.rb +77 -0
  69. data/spec/dry/types/fear/option/default_spec.rb +21 -0
  70. data/spec/dry/types/fear/option/hash_spec.rb +58 -0
  71. data/spec/dry/types/fear/option/option_spec.rb +97 -0
  72. data/spec/fear/awaitable_spec.rb +19 -0
  73. data/spec/fear/done_spec.rb +7 -5
  74. data/spec/fear/either/mixin_spec.rb +4 -2
  75. data/spec/fear/either_pattern_match_spec.rb +10 -8
  76. data/spec/fear/either_pattern_matching_spec.rb +28 -0
  77. data/spec/fear/either_spec.rb +26 -0
  78. data/spec/fear/failure_spec.rb +57 -70
  79. data/spec/fear/for/mixin_spec.rb +15 -0
  80. data/spec/fear/for_spec.rb +19 -17
  81. data/spec/fear/future_spec.rb +477 -237
  82. data/spec/fear/guard_spec.rb +136 -24
  83. data/spec/fear/left_spec.rb +57 -70
  84. data/spec/fear/none_spec.rb +39 -43
  85. data/spec/fear/option/mixin_spec.rb +9 -7
  86. data/spec/fear/option_pattern_match_spec.rb +10 -8
  87. data/spec/fear/option_pattern_matching_spec.rb +34 -0
  88. data/spec/fear/option_spec.rb +142 -0
  89. data/spec/fear/partial_function/any_spec.rb +25 -0
  90. data/spec/fear/partial_function/empty_spec.rb +12 -10
  91. data/spec/fear/partial_function_and_then_spec.rb +39 -37
  92. data/spec/fear/partial_function_composition_spec.rb +46 -44
  93. data/spec/fear/partial_function_or_else_spec.rb +92 -90
  94. data/spec/fear/partial_function_spec.rb +91 -61
  95. data/spec/fear/pattern_match_spec.rb +19 -51
  96. data/spec/fear/pattern_matching_api_spec.rb +31 -0
  97. data/spec/fear/promise_spec.rb +23 -23
  98. data/spec/fear/right_biased/left.rb +28 -26
  99. data/spec/fear/right_biased/right.rb +51 -49
  100. data/spec/fear/right_spec.rb +48 -68
  101. data/spec/fear/some_spec.rb +30 -40
  102. data/spec/fear/success_spec.rb +40 -60
  103. data/spec/fear/try/mixin_spec.rb +19 -3
  104. data/spec/fear/try_api_spec.rb +23 -0
  105. data/spec/fear/try_pattern_match_spec.rb +10 -8
  106. data/spec/fear/try_pattern_matching_spec.rb +34 -0
  107. data/spec/fear/utils_spec.rb +16 -14
  108. data/spec/spec_helper.rb +13 -7
  109. data/spec/struct_pattern_matching_spec.rb +36 -0
  110. data/spec/struct_spec.rb +194 -0
  111. data/spec/support/dry_types.rb +6 -0
  112. metadata +128 -87
  113. data/.travis.yml +0 -13
  114. data/lib/fear/extractor/anonymous_array_splat_matcher.rb +0 -8
  115. data/lib/fear/extractor/any_matcher.rb +0 -15
  116. data/lib/fear/extractor/array_head_matcher.rb +0 -34
  117. data/lib/fear/extractor/array_matcher.rb +0 -38
  118. data/lib/fear/extractor/array_splat_matcher.rb +0 -14
  119. data/lib/fear/extractor/empty_list_matcher.rb +0 -18
  120. data/lib/fear/extractor/extractor_matcher.rb +0 -42
  121. data/lib/fear/extractor/grammar.rb +0 -201
  122. data/lib/fear/extractor/grammar.treetop +0 -129
  123. data/lib/fear/extractor/identifier_matcher.rb +0 -16
  124. data/lib/fear/extractor/matcher/and.rb +0 -36
  125. data/lib/fear/extractor/matcher.rb +0 -54
  126. data/lib/fear/extractor/named_array_splat_matcher.rb +0 -15
  127. data/lib/fear/extractor/pattern.rb +0 -55
  128. data/lib/fear/extractor/typed_identifier_matcher.rb +0 -24
  129. data/lib/fear/extractor/value_matcher.rb +0 -17
  130. data/lib/fear/extractor.rb +0 -108
  131. data/lib/fear/extractor_api.rb +0 -33
  132. data/spec/fear/extractor/array_matcher_spec.rb +0 -228
  133. data/spec/fear/extractor/extractor_matcher_spec.rb +0 -151
  134. data/spec/fear/extractor/grammar_array_spec.rb +0 -23
  135. data/spec/fear/extractor/identified_matcher_spec.rb +0 -47
  136. data/spec/fear/extractor/identifier_matcher_spec.rb +0 -66
  137. data/spec/fear/extractor/pattern_spec.rb +0 -32
  138. data/spec/fear/extractor/typed_identifier_matcher_spec.rb +0 -62
  139. data/spec/fear/extractor/value_matcher_number_spec.rb +0 -77
  140. data/spec/fear/extractor/value_matcher_string_spec.rb +0 -86
  141. data/spec/fear/extractor/value_matcher_symbol_spec.rb +0 -69
  142. data/spec/fear/extractor_api_spec.rb +0 -113
  143. data/spec/fear/extractor_spec.rb +0 -59
@@ -1,201 +0,0 @@
1
- module Fear
2
- module Extractor
3
- # This module contains AST nodes for GrammarParser
4
- # generated by +treetop+. The sole purpose of them all is to
5
- # generate matchers
6
- module Grammar
7
- class Node < Treetop::Runtime::SyntaxNode
8
- end
9
-
10
- class EmptyArray < Node
11
- def to_matcher
12
- EmptyListMatcher.new(node: self)
13
- end
14
- end
15
-
16
- class ArrayLiteral < Node
17
- def to_matcher
18
- elements[1].to_matcher
19
- end
20
- end
21
-
22
- class NonEmptyArray < Node
23
- def to_matcher
24
- head, tail = elements
25
- ArrayMatcher.new(head: head.to_matcher, tail: tail.to_matcher, node: self)
26
- end
27
- end
28
-
29
- class ArrayTail < Node
30
- def to_matcher
31
- elements[1].to_matcher
32
- end
33
- end
34
-
35
- class ArrayHead < Node
36
- def to_matcher
37
- ArrayHeadMatcher.new(matcher: elements[1].to_matcher, node: elements[1])
38
- end
39
- end
40
-
41
- class ArraySplat < Node
42
- def to_matcher
43
- elements[1].to_matcher
44
- end
45
- end
46
-
47
- class AnonymousArraySplat < Node
48
- def to_matcher
49
- AnonymousArraySplatMatcher.new(node: self)
50
- end
51
- end
52
-
53
- class FloatLiteral < Node
54
- def to_matcher
55
- ValueMatcher.new(value: value, node: self)
56
- end
57
-
58
- def value
59
- text_value.to_f
60
- end
61
- end
62
-
63
- class IntegerLiteral < Node
64
- def to_matcher
65
- ValueMatcher.new(value: value, node: self)
66
- end
67
-
68
- def value
69
- text_value.to_i
70
- end
71
- end
72
-
73
- class StringLiteral < Node
74
- def to_matcher
75
- ValueMatcher.new(value: value, node: self)
76
- end
77
-
78
- def value
79
- elements[1].text_value
80
- end
81
- end
82
-
83
- require 'yaml'
84
-
85
- class DoubleQuotedStringLiteral < StringLiteral
86
- def to_matcher
87
- ValueMatcher.new(value: value, node: self)
88
- end
89
-
90
- def value
91
- YAML.safe_load(%(---\n"#{super}"\n))
92
- end
93
- end
94
-
95
- class SymbolLiteral < Node
96
- def to_matcher
97
- ValueMatcher.new(value: value, node: self)
98
- end
99
-
100
- def value
101
- elements[1].value.to_sym
102
- end
103
- end
104
-
105
- class TrueLiteral < Node
106
- def to_matcher
107
- ValueMatcher.new(value: true, node: self)
108
- end
109
- end
110
-
111
- class FalseLiteral < Node
112
- def to_matcher
113
- ValueMatcher.new(value: false, node: self)
114
- end
115
- end
116
-
117
- class NilLiteral < Node
118
- def to_matcher
119
- ValueMatcher.new(value: nil, node: self)
120
- end
121
- end
122
-
123
- class AnyIdentifier < Node
124
- def to_matcher
125
- AnyMatcher.new(node: self)
126
- end
127
- end
128
-
129
- class Identifier < Node
130
- def to_matcher
131
- IdentifierMatcher.new(name: value, node: self)
132
- end
133
-
134
- def value
135
- text_value.to_sym
136
- end
137
- end
138
-
139
- class NamedArraySplat < Node
140
- def to_matcher
141
- NamedArraySplatMatcher.new(name: name, node: self)
142
- end
143
-
144
- def name
145
- text_value[1..-1].to_sym
146
- end
147
- end
148
-
149
- class TypeLiteral < Node
150
- def to_matcher
151
- ValueMatcher.new(value: value, node: self)
152
- end
153
-
154
- def value
155
- Object.const_get(text_value)
156
- end
157
- end
158
-
159
- class TypedIdentifier < Node
160
- def to_matcher
161
- identifier, type = elements.values_at(0, 2)
162
- type.to_matcher.and(identifier.to_matcher)
163
- end
164
- end
165
-
166
- class IdentifiedMatcher < Node
167
- def to_matcher
168
- identifier, matcher = elements.values_at(0, -1)
169
- identifier.to_matcher.and(matcher.to_matcher)
170
- end
171
- end
172
-
173
- class ExtractorLiteral < Node
174
- def to_matcher
175
- ExtractorMatcher.new(
176
- name: extractor_name,
177
- arguments_matcher: extractor_arguments,
178
- node: self,
179
- )
180
- end
181
-
182
- private def extractor_name
183
- name = elements[0].text_value
184
- if Object.const_defined?(name)
185
- Object.const_get(name)
186
- else
187
- name
188
- end
189
- end
190
-
191
- private def extractor_arguments
192
- if elements[2].empty?
193
- EmptyListMatcher.new(node: self)
194
- else
195
- elements[2].to_matcher
196
- end
197
- end
198
- end
199
- end
200
- end
201
- end
@@ -1,129 +0,0 @@
1
- # PEG grammar for pattern matching and extracting
2
-
3
- module Fear::Extractor
4
- grammar Grammar
5
- rule matcher
6
- identified_matcher / anonymous_matcher
7
- end
8
-
9
- rule identified_matcher
10
- identifier space '@' space anonymous_matcher <IdentifiedMatcher>
11
- end
12
-
13
- rule anonymous_matcher
14
- array / literal / identifier / extractor / type
15
- end
16
-
17
- rule array
18
- '[' (non_empty_array / array_splat / empty_array) ']' <ArrayLiteral>
19
- end
20
-
21
- rule empty_array
22
- '' <EmptyArray>
23
- end
24
-
25
- rule non_empty_array
26
- array_head (array_tail / empty_array) <NonEmptyArray>
27
- end
28
-
29
- rule array_tail
30
- ',' (non_empty_array / array_splat) <ArrayTail>
31
- end
32
-
33
- rule array_splat
34
- space? (named_array_splat / anonymous_array_splat) space? <ArraySplat>
35
- end
36
-
37
- rule array_splat_identifier
38
- space? anonymous_array_splat space? <ArraySplat>
39
- end
40
-
41
- rule anonymous_array_splat
42
- '*' '_'? <AnonymousArraySplat>
43
- end
44
-
45
- rule named_array_splat
46
- '*' [a-z] [a-z0-9_]* <NamedArraySplat>
47
- end
48
-
49
- rule array_head
50
- space? anonymous_matcher space? <ArrayHead>
51
- end
52
-
53
- rule space
54
- [\s]+
55
- end
56
-
57
- rule any
58
- '_' <AnyIdentifier>
59
- end
60
-
61
- rule variable_identifier
62
- [a-z_] [a-z0-9_]* <Identifier>
63
- end
64
-
65
- rule identifier
66
- typed_identifier / any / variable_identifier
67
- end
68
-
69
- rule typed_identifier
70
- (any / variable_identifier) ' : ' type <TypedIdentifier>
71
- end
72
-
73
- rule literal
74
- nil / true / false / number / string / symbol
75
- end
76
-
77
- rule number
78
- float / integer
79
- end
80
-
81
- rule integer
82
- ('+' / '-')? [0-9]+ <IntegerLiteral>
83
- end
84
-
85
- rule float
86
- ('+' / '-')? [0-9]+ '.' [0-9]+ <FloatLiteral>
87
- end
88
-
89
- rule symbol
90
- ':' (string / any / variable_identifier) <SymbolLiteral>
91
- end
92
-
93
- rule string
94
- double_quoted_string / single_quoted_string
95
- end
96
-
97
- rule double_quoted_string
98
- '"' ([^"\\] / '\\' . )* '"' <DoubleQuotedStringLiteral>
99
- end
100
-
101
- rule single_quoted_string
102
- "'" ([^'\\] / '\\' . )* "'" <StringLiteral>
103
- end
104
-
105
- rule true
106
- 'true' <TrueLiteral>
107
- end
108
-
109
- rule false
110
- 'false' <FalseLiteral>
111
- end
112
-
113
- rule nil
114
- 'nil' <NilLiteral>
115
- end
116
-
117
- rule extractor
118
- type '(' non_empty_array? ')' <ExtractorLiteral>
119
- end
120
-
121
- rule type
122
- constant ('::' constant)* <TypeLiteral>
123
- end
124
-
125
- rule constant
126
- [A-Z] [a-zA-Z0-9_]*
127
- end
128
- end
129
- end
@@ -1,16 +0,0 @@
1
- module Fear
2
- module Extractor
3
- class IdentifierMatcher < Matcher
4
- # @!attribute name
5
- # @return [Types::Strict::Symbol]
6
-
7
- def defined_at?(_)
8
- true
9
- end
10
-
11
- def bindings(other)
12
- { name => other }
13
- end
14
- end
15
- end
16
- end
@@ -1,36 +0,0 @@
1
- require 'ostruct'
2
-
3
- module Fear
4
- module Extractor
5
- class Matcher < OpenStruct
6
- # Combine two matchers, so both should pass
7
- class And < Matcher
8
- def initialize(matcher1, matcher2)
9
- @matcher1 = matcher1
10
- @matcher2 = matcher2
11
- end
12
- attr_reader :matcher1, :matcher2
13
-
14
- def defined_at?(arg)
15
- matcher1.defined_at?(arg) && matcher2.defined_at?(arg)
16
- end
17
-
18
- def bindings(arg)
19
- matcher1.bindings(arg).merge(matcher2.bindings(arg))
20
- end
21
-
22
- def failure_reason(arg)
23
- if matcher1.defined_at?(arg)
24
- if matcher2.defined_at?(arg)
25
- Fear.none
26
- else
27
- matcher2.failure_reason(arg)
28
- end
29
- else
30
- matcher1.failure_reason(arg)
31
- end
32
- end
33
- end
34
- end
35
- end
36
- end
@@ -1,54 +0,0 @@
1
- require 'ostruct'
2
-
3
- module Fear
4
- module Extractor
5
- # @abstract abstract matcher to inherit from.
6
- class Matcher < OpenStruct
7
- autoload :And, 'fear/extractor/matcher/and'
8
-
9
- EMPTY_HASH = {}.freeze
10
- EMPTY_ARRAY = [].freeze
11
-
12
- # @param node [Fear::Extractor::Grammar::Node]
13
- def initialize(node:, **attributes)
14
- @input = node.input
15
- @input_position = node.interval.first
16
- super(attributes)
17
- end
18
- attr_reader :input_position, :input
19
- private :input
20
- private :input_position
21
-
22
- def call(arg)
23
- call_or_else(arg, &PartialFunction::EMPTY)
24
- end
25
-
26
- def and(other)
27
- And.new(self, other)
28
- end
29
-
30
- # @param arg [any]
31
- # @yield [arg] if function not defined
32
- def call_or_else(arg)
33
- if defined_at?(arg)
34
- bindings(arg)
35
- else
36
- yield arg
37
- end
38
- end
39
-
40
- # Shows why matcher has failed. Use it for debugging.
41
- # @example
42
- # Fear['[1, 2, _]'].failure_reason([1, 3, 4])
43
- # # it will show that the second element hasn't match
44
- #
45
- def failure_reason(other)
46
- if defined_at?(other)
47
- Fear.none
48
- else
49
- Fear.some("Expected `#{other.inspect}` to match:\n#{input}\n#{'~' * input_position}^")
50
- end
51
- end
52
- end
53
- end
54
- end
@@ -1,15 +0,0 @@
1
- module Fear
2
- module Extractor
3
- # Match against array splat, and capture rest of an array
4
- # E.g. +[1, 2, *tail]+
5
- #
6
- class NamedArraySplatMatcher < ArraySplatMatcher
7
- # @!attribute name
8
- # @return [Types::Strict::Symbol]
9
-
10
- def bindings(other)
11
- { name => other }
12
- end
13
- end
14
- end
15
- end
@@ -1,55 +0,0 @@
1
- require 'lru_redux'
2
-
3
- module Fear
4
- module Extractor
5
- # Parse pattern. Used within +Fear[]+
6
- class Pattern
7
- DEFAULT_PATTERN_CACHE_SIZE = 10_000
8
- @pattern_cache = LruRedux::Cache.new(ENV.fetch('FEAR_PATTERNS_CACHE_SIZE', DEFAULT_PATTERN_CACHE_SIZE))
9
-
10
- class << self
11
- attr_reader :pattern_cache
12
- end
13
-
14
- def initialize(pattern)
15
- @matcher = compile_pattern(pattern)
16
- end
17
- attr_reader :matcher
18
- private :matcher
19
-
20
- private def compile_pattern(pattern)
21
- self.class.pattern_cache.getset(pattern) do
22
- compile_pattern_without_cache(pattern)
23
- end
24
- end
25
-
26
- private def compile_pattern_without_cache(pattern)
27
- parser = Extractor::GrammarParser.new
28
- if (result = parser.parse(pattern))
29
- result.to_matcher
30
- else
31
- raise PatternSyntaxError, syntax_error_message(parser, pattern)
32
- end
33
- end
34
-
35
- def ===(other)
36
- matcher.defined_at?(other)
37
- end
38
-
39
- def and_then(other)
40
- Fear::PartialFunction::Combined.new(matcher, other)
41
- end
42
-
43
- def failure_reason(other)
44
- matcher.failure_reason(other).get_or_else { 'It matches' }
45
- end
46
-
47
- private def syntax_error_message(parser, pattern)
48
- parser.failure_reason =~ /^(Expected .+) after/m
49
- "#{Regexp.last_match(1).gsub("\n", '$NEWLINE')}:\n" +
50
- pattern.split("\n")[parser.failure_line - 1] + "\n" \
51
- "#{'~' * (parser.failure_column - 1)}^\n"
52
- end
53
- end
54
- end
55
- end
@@ -1,24 +0,0 @@
1
- module Fear
2
- module Extractor
3
- # Match and capture identifier with specific type. E.g. +foo : Integer+
4
- #
5
- class TypedIdentifierMatcher < Matcher
6
- # @!attribute identifier
7
- # @return [IdentifierMatcher]
8
- # @!attribute type
9
- # @return [ValueMatcher]
10
-
11
- def defined_at?(other)
12
- type.defined_at?(other)
13
- end
14
-
15
- def bindings(other)
16
- { identifier.name => other }
17
- end
18
-
19
- def failure_reason(other)
20
- type.failure_reason(other)
21
- end
22
- end
23
- end
24
- end
@@ -1,17 +0,0 @@
1
- module Fear
2
- module Extractor
3
- # Match against values -- true, false, 1, "foo" etc.
4
- class ValueMatcher < Matcher
5
- # @!attribute value
6
- # @return [Any]
7
-
8
- def defined_at?(arg)
9
- value === arg
10
- end
11
-
12
- def bindings(_)
13
- EMPTY_HASH
14
- end
15
- end
16
- end
17
- end
@@ -1,108 +0,0 @@
1
- require 'treetop'
2
- require 'fear/extractor/grammar'
3
- Treetop.load File.expand_path('extractor/grammar.treetop', __dir__)
4
-
5
- module Fear
6
- # @api private
7
- module Extractor
8
- autoload :Pattern, 'fear/extractor/pattern'
9
- autoload :Matcher, 'fear/extractor/matcher'
10
-
11
- autoload :AnonymousArraySplatMatcher, 'fear/extractor/anonymous_array_splat_matcher'
12
- autoload :AnyMatcher, 'fear/extractor/any_matcher'
13
- autoload :ArrayHeadMatcher, 'fear/extractor/array_head_matcher'
14
- autoload :ArrayMatcher, 'fear/extractor/array_matcher'
15
- autoload :ArraySplatMatcher, 'fear/extractor/array_splat_matcher'
16
- autoload :EmptyListMatcher, 'fear/extractor/empty_list_matcher'
17
- autoload :ExtractorMatcher, 'fear/extractor/extractor_matcher'
18
- autoload :IdentifierMatcher, 'fear/extractor/identifier_matcher'
19
- autoload :NamedArraySplatMatcher, 'fear/extractor/named_array_splat_matcher'
20
- autoload :TypedIdentifierMatcher, 'fear/extractor/typed_identifier_matcher'
21
- autoload :ValueMatcher, 'fear/extractor/value_matcher'
22
-
23
- ExtractorNotFound = Class.new(Error)
24
-
25
- @mutex = Mutex.new
26
- @registry = PartialFunction::EMPTY
27
-
28
- EXTRACTOR_NOT_FOUND = proc do |klass|
29
- raise ExtractorNotFound, 'could not find extractor for ' + klass.inspect
30
- end
31
-
32
- class << self
33
- # @param klass [Class, String]
34
- # @api private
35
- def find_extractor(klass)
36
- @registry.call_or_else(klass, &EXTRACTOR_NOT_FOUND)
37
- end
38
-
39
- # Register extractor for given class
40
- # @!method register_extractor(*names, extractor)
41
- # @param names [<Class, String>, Class, String] name of a class. You can also pass alias for the name
42
- # @param extractor [Proc<any => Fear::Option>] proc taking any argument and returned Option
43
- # of extracted value('s)
44
- #
45
- # @example
46
- # register_extractor(Fear::Some, Fear.case(Fear::Some) { |some| some.get }.lift)
47
- #
48
- # register_extractor(User, Fear.case(User) { |user|} [user.id, user.email] , )
49
- #
50
- # @example registering an alias. Alias should be CamelCased string
51
- # register_extractor(Fear::Some, 'Some', Fear.case(Fear::Some) { |some| some.get }.lift)
52
- #
53
- # # no you can extract Fear::Some's using Some alias
54
- # m.case(Fear['Some(value : Integer)']) { |value:| value * 2 }
55
- #
56
- def register_extractor(*args)
57
- *keys, extractor = *args
58
-
59
- @mutex.synchronize do
60
- keys.uniq.each do |key|
61
- @registry = BUILD_EXTRACTOR.call(key, extractor).or_else(@registry)
62
- end
63
- end
64
- self
65
- end
66
-
67
- BUILD_EXTRACTOR = proc do |key, extractor|
68
- Fear.matcher do |m|
69
- case key
70
- when String
71
- m.case(Module, ->(lookup) { lookup.to_s == key }) { extractor }
72
- m.case(String, key) { extractor }
73
- when Module
74
- m.case(Module, ->(lookup) { lookup <= key }) { extractor }
75
- m.case(String, key.to_s) { extractor }
76
- else
77
- m.case(key) { extractor } # may it be useful to register other types of keys? lambda?
78
- end
79
- end
80
- end
81
- end
82
-
83
- # Multiple arguments extractor example
84
- register_extractor('Date', proc do |other|
85
- if other.class.name == 'Date'
86
- Fear.some([other.year, other.month, other.day])
87
- else
88
- Fear.none
89
- end
90
- end)
91
- register_extractor(Struct, Fear.case(Struct, &:to_a).lift)
92
- # No argument boolean extractor example
93
- register_extractor('IsEven', proc do |int|
94
- if int.is_a?(Integer) && int.even?
95
- Fear.some([])
96
- else
97
- Fear.none
98
- end
99
- end)
100
- # Single argument extractor example
101
- register_extractor('Fear::Some', 'Some', Some::EXTRACTOR)
102
- register_extractor('Fear::None', 'None', NoneClass::EXTRACTOR)
103
- register_extractor('Fear::Right', 'Right', Right::EXTRACTOR)
104
- register_extractor('Fear::Left', 'Left', Left::EXTRACTOR)
105
- register_extractor('Fear::Success', 'Success', Success::EXTRACTOR)
106
- register_extractor('Fear::Failure', 'Failure', Failure::EXTRACTOR)
107
- end
108
- end
@@ -1,33 +0,0 @@
1
- module Fear
2
- module ExtractorApi
3
- # Allows to pattern match and extract matcher variables
4
- #
5
- # @param pattern [String]
6
- # @return [Extractor::Pattern]
7
- # @note it is not intended to be used by itself, rather then with partial functions
8
- def [](pattern)
9
- Extractor::Pattern.new(pattern)
10
- end
11
-
12
- # Register extractor for given class
13
- # @!method register_extractor(*names, extractor)
14
- # @param names [<Class, String>, Class, String] name of a class. You can also pass alias for the name
15
- # @param extractor [Proc<any => Fear::Option>] proc taking any argument and returned Option
16
- # of extracted value('s)
17
- #
18
- # @example
19
- # register_extractor(Fear::Some, Fear.case(Fear::Some) { |some| some.get }.lift)
20
- #
21
- # register_extractor(User, Fear.case(User) { |user|} [user.id, user.email] , )
22
- #
23
- # @example registering an alias. Alias should be CamelCased string
24
- # register_extractor(Fear::Some, 'Some', Fear.case(Fear::Some) { |some| some.get }.lift)
25
- #
26
- # # no you can extract Fear::Some's using Some alias
27
- # m.case(Fear['Some(value : Integer)']) { |value:| value * 2 }
28
- #
29
- def register_extractor(*args)
30
- Extractor.register_extractor(*args)
31
- end
32
- end
33
- end