qo 0.5.0 → 0.99.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.travis.yml +3 -3
- data/README.md +106 -9
- data/Rakefile +3 -3
- data/lib/qo.rb +17 -15
- data/lib/qo/branches/branch.rb +191 -0
- data/lib/qo/branches/branches.rb +37 -0
- data/lib/qo/branches/else_branch.rb +20 -0
- data/lib/qo/branches/error_branch.rb +26 -0
- data/lib/qo/branches/failure_branch.rb +26 -0
- data/lib/qo/branches/monadic_else_branch.rb +26 -0
- data/lib/qo/branches/monadic_when_branch.rb +26 -0
- data/lib/qo/branches/success_branch.rb +26 -0
- data/lib/qo/branches/when_branch.rb +21 -0
- data/lib/qo/destructurers/destructurer.rb +85 -0
- data/lib/qo/destructurers/destructurers.rb +15 -0
- data/lib/qo/exceptions.rb +3 -32
- data/lib/qo/matchers/matcher.rb +298 -0
- data/lib/qo/pattern_matchers/branching.rb +52 -0
- data/lib/qo/pattern_matchers/pattern_match.rb +170 -0
- data/lib/qo/pattern_matchers/pattern_matchers.rb +13 -0
- data/lib/qo/pattern_matchers/result_pattern_match.rb +45 -0
- data/lib/qo/public_api.rb +130 -8
- data/lib/qo/version.rb +1 -1
- data/qo.gemspec +11 -0
- metadata +28 -11
- data/lib/qo/helpers.rb +0 -44
- data/lib/qo/matchers/array_matcher.rb +0 -99
- data/lib/qo/matchers/base_matcher.rb +0 -110
- data/lib/qo/matchers/guard_block_matcher.rb +0 -91
- data/lib/qo/matchers/hash_matcher.rb +0 -144
- data/lib/qo/matchers/pattern_match.rb +0 -126
@@ -0,0 +1,20 @@
|
|
1
|
+
module Qo
|
2
|
+
module Branches
|
3
|
+
# A default branch to for when other conditions fail.
|
4
|
+
#
|
5
|
+
# ```ruby
|
6
|
+
# Qo.case(1) { |m|
|
7
|
+
# m.else { |v| v + 2 }
|
8
|
+
# }
|
9
|
+
# # => 3
|
10
|
+
# ```
|
11
|
+
#
|
12
|
+
# @author baweaver
|
13
|
+
# @since 1.0.0
|
14
|
+
class ElseBranch < Branch
|
15
|
+
def initialize(destructure: false)
|
16
|
+
super(name: 'else', destructure: destructure, default: true)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Qo
|
2
|
+
module Branches
|
3
|
+
# A tuple branch that will be triggered when the first value is
|
4
|
+
# `:err`.
|
5
|
+
#
|
6
|
+
# ```ruby
|
7
|
+
# ResultPatternMatch.new { |m|
|
8
|
+
# m.error { |v| "This is the error: #{v}" }
|
9
|
+
# }.call([:err, 'OH NO!'])
|
10
|
+
# # => "This is the error: OH NO!"
|
11
|
+
# ```
|
12
|
+
#
|
13
|
+
# @author baweaver
|
14
|
+
# @since 1.0.0
|
15
|
+
class ErrorBranch < Branch
|
16
|
+
def initialize(destructure: false)
|
17
|
+
super(
|
18
|
+
name: 'error',
|
19
|
+
destructure: destructure,
|
20
|
+
precondition: -> v { v.first == :err },
|
21
|
+
extractor: :last,
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Qo
|
2
|
+
module Branches
|
3
|
+
# A tuple branch that will be triggered when the first value is
|
4
|
+
# `:err`.
|
5
|
+
#
|
6
|
+
# ```ruby
|
7
|
+
# ResultPatternMatch.new { |m|
|
8
|
+
# m.failure { |v| "This is the error: #{v}" }
|
9
|
+
# }.call([:err, 'OH NO!'])
|
10
|
+
# # => "This is the error: OH NO!"
|
11
|
+
# ```
|
12
|
+
#
|
13
|
+
# @author baweaver
|
14
|
+
# @since 1.0.0
|
15
|
+
class FailureBranch < Branch
|
16
|
+
def initialize(destructure: false)
|
17
|
+
super(
|
18
|
+
name: 'failure',
|
19
|
+
destructure: destructure,
|
20
|
+
precondition: -> v { v.first == :err },
|
21
|
+
extractor: :last,
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Qo
|
2
|
+
module Branches
|
3
|
+
# Based on the `else` branch, except deals with monadic values by attempting
|
4
|
+
# to extract the `value` before yielding to the given function on a match:
|
5
|
+
#
|
6
|
+
# ```ruby
|
7
|
+
# Matcher.new.call(Some[1]) { |m|
|
8
|
+
# m.else { |v| v + 2 }
|
9
|
+
# }
|
10
|
+
# # => 3
|
11
|
+
# ```
|
12
|
+
#
|
13
|
+
# @author baweaver
|
14
|
+
# @since 1.0.0
|
15
|
+
class MonadicElseBranch < Branch
|
16
|
+
def initialize(destructure: false, extractor: :value)
|
17
|
+
super(
|
18
|
+
name: 'else',
|
19
|
+
destructure: destructure,
|
20
|
+
extractor: extractor,
|
21
|
+
default: true,
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Qo
|
2
|
+
module Branches
|
3
|
+
# Based on the `where` branch, except deals with monadic values by attempting
|
4
|
+
# to extract the `value` before yielding to the given function on a match:
|
5
|
+
#
|
6
|
+
# ```ruby
|
7
|
+
# Matcher.new.call(Some[1]) { |m|
|
8
|
+
# m.where(Some) { |v| v + 2 }
|
9
|
+
# }
|
10
|
+
# # => 3
|
11
|
+
# ```
|
12
|
+
#
|
13
|
+
# @author baweaver
|
14
|
+
# @since 1.0.0
|
15
|
+
class MonadicWhenBranch < Branch
|
16
|
+
def initialize(destructure: false, extractor: :value)
|
17
|
+
super(
|
18
|
+
name: 'where',
|
19
|
+
destructure: destructure,
|
20
|
+
extractor: extractor,
|
21
|
+
default: false,
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Qo
|
2
|
+
module Branches
|
3
|
+
# A tuple branch that will be triggered when the first value is
|
4
|
+
# `:ok`.
|
5
|
+
#
|
6
|
+
# ```ruby
|
7
|
+
# ResultPatternMatch.new { |m|
|
8
|
+
# m.success { |v| v + 2 }
|
9
|
+
# }.call([:ok, 1])
|
10
|
+
# # => 3
|
11
|
+
# ```
|
12
|
+
#
|
13
|
+
# @author baweaver
|
14
|
+
# @since 1.0.0
|
15
|
+
class SuccessBranch < Branch
|
16
|
+
def initialize(destructure: false)
|
17
|
+
super(
|
18
|
+
name: 'success',
|
19
|
+
destructure: destructure,
|
20
|
+
precondition: -> v { v.first == :ok },
|
21
|
+
extractor: :last,
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Qo
|
2
|
+
module Branches
|
3
|
+
class WhenBranch < Branch
|
4
|
+
# The traditional pattern matching branch, based off of `when` from
|
5
|
+
# Ruby's `case` statement:
|
6
|
+
#
|
7
|
+
# ```ruby
|
8
|
+
# Qo.case(1) { |m|
|
9
|
+
# m.when(Integer) { |v| v + 2 }
|
10
|
+
# }
|
11
|
+
# # => 3
|
12
|
+
# ```
|
13
|
+
#
|
14
|
+
# @author baweaver
|
15
|
+
# @since 1.0.0
|
16
|
+
def initialize(destructure: false)
|
17
|
+
super(name: 'when', destructure: destructure, default: false)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Qo
|
2
|
+
module Destructurers
|
3
|
+
# Classic destructuring. This gives great value to the names of a function's
|
4
|
+
# arguments, transforming the way blocks are normally yielded to. Take for
|
5
|
+
# example this function:
|
6
|
+
#
|
7
|
+
# ```ruby
|
8
|
+
# Proc.new { |name, age| ... }
|
9
|
+
# ```
|
10
|
+
#
|
11
|
+
# The names of the arguments are `name` and `age`. Destructuring involves
|
12
|
+
# using these names to extract the values of an object before the function
|
13
|
+
# is called:
|
14
|
+
#
|
15
|
+
# 1. Get the names of the arguments
|
16
|
+
# 2. Map over those names to extract values from an object by sending them
|
17
|
+
# as method calls
|
18
|
+
# 3. Call the function with the newly extracted values
|
19
|
+
#
|
20
|
+
# It's highly suggested to read through the "Destructuring in Ruby" article
|
21
|
+
# here:
|
22
|
+
#
|
23
|
+
# @see https://medium.com/rubyinside/destructuring-in-ruby-9e9bd2be0360
|
24
|
+
#
|
25
|
+
# @author baweaver
|
26
|
+
# @since 1.0.0
|
27
|
+
class Destructurer
|
28
|
+
# Creates a destructurer
|
29
|
+
#
|
30
|
+
# @param destructure: [Boolean]
|
31
|
+
# Whether or not to destructure an object before calling a function
|
32
|
+
#
|
33
|
+
# @param &function [Proc]
|
34
|
+
# Associated function to be called
|
35
|
+
#
|
36
|
+
# @return [Qo::Destructurers::Destructurer]
|
37
|
+
def initialize(destructure:, &function)
|
38
|
+
@destructure = destructure
|
39
|
+
@function = function || IDENTITY
|
40
|
+
@argument_names = argument_names
|
41
|
+
end
|
42
|
+
|
43
|
+
# Calls the destructurer to extract values from a target and call the
|
44
|
+
# function with those extracted values.
|
45
|
+
#
|
46
|
+
# @param target [Any]
|
47
|
+
# Target to extract values from
|
48
|
+
#
|
49
|
+
# @return [Any]
|
50
|
+
# Return of the given function
|
51
|
+
def call(target)
|
52
|
+
destructured_arguments = destructure? ? destructure_values(target) : target
|
53
|
+
|
54
|
+
@function.call(destructured_arguments)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Whether or not this method will destructure a passed object
|
58
|
+
#
|
59
|
+
# @return [Boolean]
|
60
|
+
def destructure?
|
61
|
+
@destructure
|
62
|
+
end
|
63
|
+
|
64
|
+
# Destructures values from a target object
|
65
|
+
#
|
66
|
+
# @param target [Any]
|
67
|
+
# Object to extract values from
|
68
|
+
#
|
69
|
+
# @return [Array[Any]]
|
70
|
+
# Extracted values
|
71
|
+
def destructure_values(target)
|
72
|
+
target.is_a?(::Hash) ?
|
73
|
+
argument_names.map { |n| target[n] } :
|
74
|
+
argument_names.map { |n| target.respond_to?(n) && target.public_send(n) }
|
75
|
+
end
|
76
|
+
|
77
|
+
# Names of the function's arguments
|
78
|
+
#
|
79
|
+
# @return [Array[Symbol]]
|
80
|
+
def argument_names
|
81
|
+
@argument_names ||= @function.parameters.map(&:last)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Qo
|
2
|
+
# Classes that allow for the destructuring of values from an object, meant
|
3
|
+
# to emulate Javascript object destructuring. While this is a very powerful
|
4
|
+
# expressive feature, it can also slow down execution by a small amount, so
|
5
|
+
# use destructuring wisely.
|
6
|
+
#
|
7
|
+
# @see https://medium.com/rubyinside/destructuring-in-ruby-9e9bd2be0360
|
8
|
+
#
|
9
|
+
# @author baweaver
|
10
|
+
# @since 1.0.0
|
11
|
+
module Destructurers
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
require 'qo/destructurers/destructurer'
|
data/lib/qo/exceptions.rb
CHANGED
@@ -1,41 +1,12 @@
|
|
1
1
|
module Qo
|
2
2
|
# Defines common exception classes for use throughout the library
|
3
3
|
#
|
4
|
+
# Currently there aren't any exceptions being used, but keeping this
|
5
|
+
# around for later use.
|
6
|
+
#
|
4
7
|
# @author baweaver
|
5
8
|
# @since 0.2.0
|
6
9
|
#
|
7
10
|
module Exceptions
|
8
|
-
# If no matchers in either Array or Hash style are provided.
|
9
|
-
#
|
10
|
-
# @author baweaver
|
11
|
-
# @since 0.2.0
|
12
|
-
#
|
13
|
-
class NoMatchersProvided < ArgumentError
|
14
|
-
def to_s
|
15
|
-
"No Qo matchers were provided!"
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
# If both Array and Hash style matchers are provided.
|
20
|
-
#
|
21
|
-
# @author baweaver
|
22
|
-
# @since 0.2.0
|
23
|
-
#
|
24
|
-
class MultipleMatchersProvided < ArgumentError
|
25
|
-
def to_s
|
26
|
-
"Cannot provide both array and keyword matchers!"
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
# In the case of a Pattern Match, we should only have one "else" clause
|
31
|
-
#
|
32
|
-
# @author baweaver
|
33
|
-
# @since 0.3.0
|
34
|
-
#
|
35
|
-
class MultipleElseClauses < ArgumentError
|
36
|
-
def to_s
|
37
|
-
"Cannot have more than one `else` clause in a Pattern Match."
|
38
|
-
end
|
39
|
-
end
|
40
11
|
end
|
41
12
|
end
|
@@ -0,0 +1,298 @@
|
|
1
|
+
module Qo
|
2
|
+
module Matchers
|
3
|
+
# Matcher used to determine whether a value matches a certain set of
|
4
|
+
# conditions
|
5
|
+
#
|
6
|
+
# @author baweaver
|
7
|
+
# @since 1.0.0
|
8
|
+
#
|
9
|
+
class Matcher
|
10
|
+
# Creates a new matcher
|
11
|
+
#
|
12
|
+
# @param type [String]
|
13
|
+
# Type of the matcher: any, all, or none. Used to determine how a
|
14
|
+
# match is determined
|
15
|
+
#
|
16
|
+
# @param array_matchers = [] [Array[Any]]
|
17
|
+
# Conditions given as an array
|
18
|
+
#
|
19
|
+
# @param keyword_matchers = {} [Hash[Any, Any]]
|
20
|
+
# Conditions given as keywords
|
21
|
+
#
|
22
|
+
# @return [Qo::Matchers::Matcher]
|
23
|
+
def initialize(type, array_matchers = [], keyword_matchers = {})
|
24
|
+
@type = type
|
25
|
+
@array_matchers = array_matchers
|
26
|
+
@keyword_matchers = keyword_matchers
|
27
|
+
end
|
28
|
+
|
29
|
+
# Proc-ified version of `call`
|
30
|
+
#
|
31
|
+
# @return [Proc[Any] => Boolean]
|
32
|
+
def to_proc
|
33
|
+
-> target { self.call(target) }
|
34
|
+
end
|
35
|
+
|
36
|
+
# Calls the matcher on a given target value
|
37
|
+
#
|
38
|
+
# @param target [Any]
|
39
|
+
# Target to match against
|
40
|
+
#
|
41
|
+
# @return [Boolean]
|
42
|
+
# Whether or not the target matched
|
43
|
+
def call(target)
|
44
|
+
combined_check(array_call(target), keyword_call(target))
|
45
|
+
end
|
46
|
+
|
47
|
+
alias_method :===, :call
|
48
|
+
alias_method :[], :call
|
49
|
+
alias_method :match?, :call
|
50
|
+
|
51
|
+
# Used to match against a matcher made from Keyword Arguments (a Hash)
|
52
|
+
#
|
53
|
+
# @param matchers [Hash[Any, #===]]
|
54
|
+
# Any key mapping to any value that responds to `===`. Notedly more
|
55
|
+
# satisfying when `===` does something fun.
|
56
|
+
#
|
57
|
+
# @return [Boolean]
|
58
|
+
# Result of the match
|
59
|
+
private def keyword_call(target)
|
60
|
+
return true if @keyword_matchers == target
|
61
|
+
|
62
|
+
match_fn = target.is_a?(::Hash) ?
|
63
|
+
Proc.new { |match_key, matcher| match_hash_value?(target, match_key, matcher) } :
|
64
|
+
Proc.new { |match_key, matcher| match_object_value?(target, match_key, matcher) }
|
65
|
+
|
66
|
+
match_with(@keyword_matchers, &match_fn)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Runs the matcher directly.
|
70
|
+
#
|
71
|
+
# If the target is an Array, it will be matched via index
|
72
|
+
#
|
73
|
+
# If the target is an Object, it will be matched via public send
|
74
|
+
#
|
75
|
+
# @param target [Any]
|
76
|
+
# Target to match against
|
77
|
+
#
|
78
|
+
# @return [Boolean]
|
79
|
+
# Result of the match
|
80
|
+
private def array_call(target)
|
81
|
+
return true if @array_matchers == target
|
82
|
+
|
83
|
+
if target.is_a?(::Array)
|
84
|
+
return false unless target.size == @array_matchers.size
|
85
|
+
|
86
|
+
match_with(@array_matchers.each_with_index) { |matcher, i|
|
87
|
+
match_value?(target[i], matcher)
|
88
|
+
}
|
89
|
+
else
|
90
|
+
match_with(@array_matchers) { |matcher|
|
91
|
+
match_value?(target, matcher)
|
92
|
+
}
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Wraps a case equality statement to make it a bit easier to read. The
|
97
|
+
# typical left bias of `===` can be confusing reading down a page, so
|
98
|
+
# more of a clarity thing than anything. Also makes for nicer stack traces.
|
99
|
+
#
|
100
|
+
# @param target [Any]
|
101
|
+
# Target to match against
|
102
|
+
#
|
103
|
+
# @param matcher [#===]
|
104
|
+
# Anything that responds to ===, preferably in a unique and entertaining way.
|
105
|
+
#
|
106
|
+
# @return [Boolean]
|
107
|
+
private def case_match?(target, matcher)
|
108
|
+
matcher === target
|
109
|
+
end
|
110
|
+
|
111
|
+
# Guarded version of `public_send` meant to stamp out more
|
112
|
+
# obscure errors when running against non-matching types.
|
113
|
+
#
|
114
|
+
# @param target [Any]
|
115
|
+
# Object to send to
|
116
|
+
#
|
117
|
+
# @param matcher [#to_sym]
|
118
|
+
# Anything that can be coerced into a method name
|
119
|
+
#
|
120
|
+
# @return [Any]
|
121
|
+
# Response of sending to the method, or false if failed
|
122
|
+
private def method_send(target, matcher)
|
123
|
+
matcher.respond_to?(:to_sym) &&
|
124
|
+
target.respond_to?(matcher.to_sym) &&
|
125
|
+
target.public_send(matcher)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Predicate variant of `method_send` with the same guard concerns
|
129
|
+
#
|
130
|
+
# @param target [Any]
|
131
|
+
# Object to send to
|
132
|
+
#
|
133
|
+
# @param matcher [#to_sym]
|
134
|
+
# Anything that can be coerced into a method name
|
135
|
+
#
|
136
|
+
# @return [Boolean]
|
137
|
+
# Success status of predicate
|
138
|
+
private def method_matches?(target, matcher)
|
139
|
+
!!method_send(target, matcher)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Defines what it means for a value to match a matcher
|
143
|
+
#
|
144
|
+
# @param target [Any]
|
145
|
+
# Target to match against
|
146
|
+
#
|
147
|
+
# @param matcher [Any]
|
148
|
+
# Any matcher to run against, most frequently responds to ===
|
149
|
+
#
|
150
|
+
# @return [Boolean]
|
151
|
+
# Match status
|
152
|
+
private def match_value?(target, matcher)
|
153
|
+
case_match?(target, matcher) ||
|
154
|
+
method_matches?(target, matcher)
|
155
|
+
end
|
156
|
+
|
157
|
+
# Checks if a hash value matches a given matcher
|
158
|
+
#
|
159
|
+
# @param target [Any]
|
160
|
+
# Target of the match
|
161
|
+
#
|
162
|
+
# @param match_key [Symbol]
|
163
|
+
# Key of the hash to reference
|
164
|
+
#
|
165
|
+
# @param matcher [#===]
|
166
|
+
# Any matcher responding to ===
|
167
|
+
#
|
168
|
+
# @return [Boolean]
|
169
|
+
# Match status
|
170
|
+
private def match_hash_value?(target, match_key, matcher)
|
171
|
+
return false unless target.key?(match_key)
|
172
|
+
|
173
|
+
return hash_recurse(target[match_key], matcher) if target.is_a?(Hash) && matcher.is_a?(Hash)
|
174
|
+
|
175
|
+
hash_case_match?(target, match_key, matcher) ||
|
176
|
+
hash_method_predicate_match?(target, match_key, matcher)
|
177
|
+
end
|
178
|
+
|
179
|
+
# Checks if an object property matches a given matcher
|
180
|
+
#
|
181
|
+
# @param target [Any]
|
182
|
+
# Target of the match
|
183
|
+
#
|
184
|
+
# @param match_property [Symbol]
|
185
|
+
# Property of the object to reference
|
186
|
+
#
|
187
|
+
# @param matcher [#===]
|
188
|
+
# Any matcher responding to ===
|
189
|
+
#
|
190
|
+
# @return [Boolean] Match status
|
191
|
+
private def match_object_value?(target, match_property, matcher)
|
192
|
+
return false unless target.respond_to?(match_property)
|
193
|
+
|
194
|
+
hash_method_case_match?(target, match_property, matcher)
|
195
|
+
end
|
196
|
+
|
197
|
+
# Double wraps case match in order to ensure that we try against both Symbol
|
198
|
+
# and String variants of the keys, as this is a very common mixup in Ruby.
|
199
|
+
#
|
200
|
+
# @param target [Hash]
|
201
|
+
# Target of the match
|
202
|
+
#
|
203
|
+
# @param match_key [Symbol]
|
204
|
+
# Key to match against
|
205
|
+
#
|
206
|
+
# @param matcher [#===]
|
207
|
+
# Matcher
|
208
|
+
#
|
209
|
+
# @return [Boolean]
|
210
|
+
private def hash_case_match?(target, match_key, matcher)
|
211
|
+
return true if case_match?(target[match_key], matcher)
|
212
|
+
return false unless target.keys.first.is_a?(String)
|
213
|
+
|
214
|
+
match_key.respond_to?(:to_s) &&
|
215
|
+
target.key?(match_key.to_s) &&
|
216
|
+
case_match?(target[match_key.to_s], matcher)
|
217
|
+
end
|
218
|
+
|
219
|
+
# Attempts to run a matcher as a predicate method against the target
|
220
|
+
#
|
221
|
+
# @param target [Hash]
|
222
|
+
# Target of the match
|
223
|
+
#
|
224
|
+
# @param match_key [Symbol]
|
225
|
+
# Method to call
|
226
|
+
#
|
227
|
+
# @param match_predicate [Symbol]
|
228
|
+
# Matcher
|
229
|
+
#
|
230
|
+
# @return [Boolean]
|
231
|
+
private def hash_method_predicate_match?(target, match_key, match_predicate)
|
232
|
+
method_matches?(target[match_key], match_predicate)
|
233
|
+
end
|
234
|
+
|
235
|
+
# Attempts to run a case match against a method call derived from a hash
|
236
|
+
# key, and checks the result.
|
237
|
+
#
|
238
|
+
# @param target [Hash]
|
239
|
+
# Target of the match
|
240
|
+
#
|
241
|
+
# @param match_property [Symbol]
|
242
|
+
# Method to call
|
243
|
+
#
|
244
|
+
# @param matcher [#===]
|
245
|
+
# Matcher
|
246
|
+
#
|
247
|
+
# @return [Boolean]
|
248
|
+
private def hash_method_case_match?(target, match_property, matcher)
|
249
|
+
case_match?(method_send(target, match_property), matcher)
|
250
|
+
end
|
251
|
+
|
252
|
+
# Recurses on nested hashes.
|
253
|
+
#
|
254
|
+
# @param target [Hash]
|
255
|
+
# Target to recurse into
|
256
|
+
#
|
257
|
+
# @param matcher [Hash]
|
258
|
+
# Matcher to use to recurse with
|
259
|
+
#
|
260
|
+
# @return [Boolean]
|
261
|
+
private def hash_recurse(target, matcher)
|
262
|
+
Qo::Matchers::Matcher.new(@type, [], matcher).call(target)
|
263
|
+
end
|
264
|
+
|
265
|
+
# Runs the relevant match method against the given collection with the
|
266
|
+
# given matcher function.
|
267
|
+
#
|
268
|
+
# @param collection [Enumerable] Any collection that can be enumerated over
|
269
|
+
# @param fn [Proc] Function to match with
|
270
|
+
#
|
271
|
+
# @return [Boolean] Result of the match
|
272
|
+
private def match_with(collection, &fn)
|
273
|
+
case @type
|
274
|
+
when 'and' then collection.all?(&fn)
|
275
|
+
when 'or' then collection.any?(&fn)
|
276
|
+
when 'not' then collection.none?(&fn)
|
277
|
+
else false
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
# When combining array and keyword type matchers, depending on how
|
282
|
+
# we're matching we may need to combine them slightly differently.
|
283
|
+
#
|
284
|
+
# @param *checks [Array]
|
285
|
+
# The checks we're combining
|
286
|
+
#
|
287
|
+
# @return [Boolean]
|
288
|
+
# Whether or not there's a match
|
289
|
+
private def combined_check(*checks)
|
290
|
+
case @type
|
291
|
+
when 'and', 'not' then checks.all?
|
292
|
+
when 'or' then checks.any?
|
293
|
+
else false
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|