qo 0.5.0 → 0.99.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 +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
|