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
data/lib/qo/version.rb
CHANGED
data/qo.gemspec
CHANGED
@@ -13,6 +13,17 @@ Gem::Specification.new do |spec|
|
|
13
13
|
spec.homepage = "https://www.github.com/baweaver/qo"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
|
+
spec.post_install_message = <<~MESSAGE
|
17
|
+
Qo 0.99.0 is the last version of Qo under the official name "Qo". After this
|
18
|
+
version, Qo will be adopted into dry-rb (https://dry-rb.org/) as the new
|
19
|
+
dry-matcher.
|
20
|
+
|
21
|
+
As I'm fond of the name, "Qo" will remain an alias for "Dry::Matcher"
|
22
|
+
so you can use it as you have before. Qo v0.99.0 and Dry Matcher v1.0.0
|
23
|
+
will be directly compatible with eachother, and semantic versioning will
|
24
|
+
take over from there.
|
25
|
+
MESSAGE
|
26
|
+
|
16
27
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
28
|
f.match(%r{^(test|spec|features)/})
|
18
29
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: qo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.99.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brandon Weaver
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-02-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -206,13 +206,23 @@ files:
|
|
206
206
|
- img/qo_logo.png
|
207
207
|
- img/whoa_lemur.png
|
208
208
|
- lib/qo.rb
|
209
|
+
- lib/qo/branches/branch.rb
|
210
|
+
- lib/qo/branches/branches.rb
|
211
|
+
- lib/qo/branches/else_branch.rb
|
212
|
+
- lib/qo/branches/error_branch.rb
|
213
|
+
- lib/qo/branches/failure_branch.rb
|
214
|
+
- lib/qo/branches/monadic_else_branch.rb
|
215
|
+
- lib/qo/branches/monadic_when_branch.rb
|
216
|
+
- lib/qo/branches/success_branch.rb
|
217
|
+
- lib/qo/branches/when_branch.rb
|
218
|
+
- lib/qo/destructurers/destructurer.rb
|
219
|
+
- lib/qo/destructurers/destructurers.rb
|
209
220
|
- lib/qo/exceptions.rb
|
210
|
-
- lib/qo/
|
211
|
-
- lib/qo/
|
212
|
-
- lib/qo/
|
213
|
-
- lib/qo/
|
214
|
-
- lib/qo/
|
215
|
-
- lib/qo/matchers/pattern_match.rb
|
221
|
+
- lib/qo/matchers/matcher.rb
|
222
|
+
- lib/qo/pattern_matchers/branching.rb
|
223
|
+
- lib/qo/pattern_matchers/pattern_match.rb
|
224
|
+
- lib/qo/pattern_matchers/pattern_matchers.rb
|
225
|
+
- lib/qo/pattern_matchers/result_pattern_match.rb
|
216
226
|
- lib/qo/public_api.rb
|
217
227
|
- lib/qo/version.rb
|
218
228
|
- performance_report.txt
|
@@ -221,7 +231,15 @@ homepage: https://www.github.com/baweaver/qo
|
|
221
231
|
licenses:
|
222
232
|
- MIT
|
223
233
|
metadata: {}
|
224
|
-
post_install_message:
|
234
|
+
post_install_message: |
|
235
|
+
Qo 0.99.0 is the last version of Qo under the official name "Qo". After this
|
236
|
+
version, Qo will be adopted into dry-rb (https://dry-rb.org/) as the new
|
237
|
+
dry-matcher.
|
238
|
+
|
239
|
+
As I'm fond of the name, "Qo" will remain an alias for "Dry::Matcher"
|
240
|
+
so you can use it as you have before. Qo v0.99.0 and Dry Matcher v1.0.0
|
241
|
+
will be directly compatible with eachother, and semantic versioning will
|
242
|
+
take over from there.
|
225
243
|
rdoc_options: []
|
226
244
|
require_paths:
|
227
245
|
- lib
|
@@ -236,8 +254,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
236
254
|
- !ruby/object:Gem::Version
|
237
255
|
version: '0'
|
238
256
|
requirements: []
|
239
|
-
|
240
|
-
rubygems_version: 2.6.14.1
|
257
|
+
rubygems_version: 3.0.1
|
241
258
|
signing_key:
|
242
259
|
specification_version: 4
|
243
260
|
summary: Qo is a querying library for Ruby pattern matching
|
data/lib/qo/helpers.rb
DELETED
@@ -1,44 +0,0 @@
|
|
1
|
-
module Qo
|
2
|
-
module Helpers
|
3
|
-
# A curried variant of Hash#dig meant to be passed as a matcher util.
|
4
|
-
#
|
5
|
-
# @note This method will attempt to coerce path segments to Symbols
|
6
|
-
# if unsuccessful in first dig.
|
7
|
-
#
|
8
|
-
# @param path_map [String]
|
9
|
-
# Dot-delimited path
|
10
|
-
#
|
11
|
-
# @param expected_value [Any]
|
12
|
-
# Matcher
|
13
|
-
#
|
14
|
-
# @return [Proc]
|
15
|
-
# Hash -> Bool # Status of digging against the hash
|
16
|
-
def dig(path_map, expected_value)
|
17
|
-
Proc.new { |hash|
|
18
|
-
segments = path_map.split('.')
|
19
|
-
|
20
|
-
expected_value === hash.dig(*segments) ||
|
21
|
-
expected_value === hash.dig(*segments.map(&:to_sym))
|
22
|
-
}
|
23
|
-
end
|
24
|
-
|
25
|
-
# Counts by a function. This is entirely because I hackney this everywhere in
|
26
|
-
# pry anyways, so I want a function to do it for me already.
|
27
|
-
#
|
28
|
-
# @param targets [Array[Any]]
|
29
|
-
# Targets to count
|
30
|
-
#
|
31
|
-
# @param &fn [Proc]
|
32
|
-
# Function to define count key
|
33
|
-
#
|
34
|
-
# @return [Hash[Any, Integer]]
|
35
|
-
# Counts
|
36
|
-
def count_by(targets, &fn)
|
37
|
-
fn ||= -> v { v }
|
38
|
-
|
39
|
-
targets.each_with_object(Hash.new(0)) { |target, counts|
|
40
|
-
counts[fn[target]] += 1
|
41
|
-
}
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
@@ -1,99 +0,0 @@
|
|
1
|
-
module Qo
|
2
|
-
module Matchers
|
3
|
-
# An Array Matcher is a matcher that uses only `*varargs` to define a sequence
|
4
|
-
# of matches to perform against either an object or another Array.
|
5
|
-
#
|
6
|
-
# In the case of an Array matching against an Array it will compare via index.
|
7
|
-
#
|
8
|
-
# ```ruby
|
9
|
-
# # Shorthand
|
10
|
-
# Qo[1..10, 1..10].call([1, 2])
|
11
|
-
# # => true
|
12
|
-
#
|
13
|
-
# Qo::Matchers::ArrayMatcher.new([1..10, 1..10]).call([1, 2])
|
14
|
-
# # => true
|
15
|
-
# ```
|
16
|
-
#
|
17
|
-
# It should be noted that arrays of dissimilar size will result in an instant
|
18
|
-
# false return value. If you wish to do a single value match, simply use the
|
19
|
-
# provided `Any` type as such:
|
20
|
-
#
|
21
|
-
# ```ruby
|
22
|
-
# array.select(&Any)
|
23
|
-
# ```
|
24
|
-
#
|
25
|
-
# In the case of an Array matching against an Object, it will match each provided
|
26
|
-
# matcher against the object.
|
27
|
-
#
|
28
|
-
# ```ruby
|
29
|
-
# # Shorthand
|
30
|
-
# Qo[Integer, 1..10].call(1)
|
31
|
-
# # => true
|
32
|
-
#
|
33
|
-
# Qo::Matchers::ArrayMatcher.new([1..10, 1..10]).call(1)
|
34
|
-
# # => true
|
35
|
-
# ```
|
36
|
-
#
|
37
|
-
# All variants present in the BaseMatcher are present here, including 'and',
|
38
|
-
# 'not', and 'or'.
|
39
|
-
#
|
40
|
-
# @author baweaver
|
41
|
-
# @since 0.2.0
|
42
|
-
#
|
43
|
-
class ArrayMatcher < BaseMatcher
|
44
|
-
def initialize(type, array_matchers)
|
45
|
-
@array_matchers = array_matchers
|
46
|
-
@type = type
|
47
|
-
end
|
48
|
-
|
49
|
-
# Wrapper around call to allow for invocation in an Enumerable function,
|
50
|
-
# such as:
|
51
|
-
#
|
52
|
-
# ```ruby
|
53
|
-
# people.select(&Qo[/Foo/, 20..40])
|
54
|
-
# ```
|
55
|
-
#
|
56
|
-
# @return [Proc[Any]]
|
57
|
-
# Proc awaiting a target to match against
|
58
|
-
def to_proc
|
59
|
-
Proc.new { |target| self.call(target) }
|
60
|
-
end
|
61
|
-
|
62
|
-
# Runs the matcher directly.
|
63
|
-
#
|
64
|
-
# If the target is an Array, it will be matched via index
|
65
|
-
#
|
66
|
-
# If the target is an Object, it will be matched via public send
|
67
|
-
#
|
68
|
-
# @param target [Any] Target to match against
|
69
|
-
#
|
70
|
-
# @return [Boolean] Result of the match
|
71
|
-
def call(target)
|
72
|
-
return true if @array_matchers == target
|
73
|
-
|
74
|
-
if target.is_a?(::Array)
|
75
|
-
return false unless target.size == @array_matchers.size
|
76
|
-
|
77
|
-
match_with(@array_matchers.each_with_index) { |matcher, i|
|
78
|
-
match_value?(target[i], matcher)
|
79
|
-
}
|
80
|
-
else
|
81
|
-
match_with(@array_matchers) { |matcher|
|
82
|
-
match_value?(target, matcher)
|
83
|
-
}
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
# Defines what it means for a value to match a matcher
|
88
|
-
#
|
89
|
-
# @param target [Any] Target to match against
|
90
|
-
# @param matcher [Any] Any matcher to run against, most frequently responds to ===
|
91
|
-
#
|
92
|
-
# @return [Boolean] Match status
|
93
|
-
private def match_value?(target, matcher)
|
94
|
-
case_match?(target, matcher) ||
|
95
|
-
method_matches?(target, matcher)
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
@@ -1,110 +0,0 @@
|
|
1
|
-
module Qo
|
2
|
-
# A Qo Matcher is a class that acts like a Proc. It takes in a set of match
|
3
|
-
# values or key value pairs and a target value to evaluate against, and returns
|
4
|
-
# the status of that match.
|
5
|
-
#
|
6
|
-
# It is possible to override this behavior via `to_proc` overloading and
|
7
|
-
# utilization of `super` as noted in `GuardBlockMatcher`.
|
8
|
-
#
|
9
|
-
# @see Qo::Matchers::GuardBlockMatcher
|
10
|
-
#
|
11
|
-
# @author baweaver
|
12
|
-
# @since 0.2.0
|
13
|
-
#
|
14
|
-
module Matchers
|
15
|
-
# Base instance of matcher which is meant to take in either Array style or
|
16
|
-
# Keyword style arguments to run a match against various datatypes.
|
17
|
-
#
|
18
|
-
# Will delegate responsibilities to either Array or Hash style matchers if
|
19
|
-
# invoked directly.
|
20
|
-
#
|
21
|
-
# @author baweaver
|
22
|
-
# @since 0.2.0
|
23
|
-
#
|
24
|
-
class BaseMatcher
|
25
|
-
def initialize(type, array_matchers, keyword_matchers)
|
26
|
-
@type = type
|
27
|
-
|
28
|
-
@full_matcher = array_matchers.empty? ?
|
29
|
-
Qo::Matchers::HashMatcher.new(type, keyword_matchers) :
|
30
|
-
Qo::Matchers::ArrayMatcher.new(type, array_matchers)
|
31
|
-
end
|
32
|
-
|
33
|
-
# Converts a Matcher to a proc for use in querying, such as:
|
34
|
-
#
|
35
|
-
# data.select(&Qo[...])
|
36
|
-
#
|
37
|
-
# @return [Proc[Any]]
|
38
|
-
def to_proc
|
39
|
-
@full_matcher.to_proc
|
40
|
-
end
|
41
|
-
|
42
|
-
# You can directly call a matcher as well, much like a Proc,
|
43
|
-
# using one of call, ===, or []
|
44
|
-
#
|
45
|
-
# @param target [Any] Object to match against
|
46
|
-
#
|
47
|
-
# @return [Boolean] Result of the match
|
48
|
-
def call(target)
|
49
|
-
@full_matcher.call(target)
|
50
|
-
end
|
51
|
-
|
52
|
-
alias_method :===, :call
|
53
|
-
alias_method :[], :call
|
54
|
-
alias_method :match?, :call
|
55
|
-
|
56
|
-
# Runs the relevant match method against the given collection with the
|
57
|
-
# given matcher function.
|
58
|
-
#
|
59
|
-
# @param collection [Enumerable] Any collection that can be enumerated over
|
60
|
-
# @param fn [Proc] Function to match with
|
61
|
-
#
|
62
|
-
# @return [Boolean] Result of the match
|
63
|
-
private def match_with(collection, &fn)
|
64
|
-
case @type
|
65
|
-
when 'and' then collection.all?(&fn)
|
66
|
-
when 'or' then collection.any?(&fn)
|
67
|
-
when 'not' then collection.none?(&fn)
|
68
|
-
else false
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
# Wraps a case equality statement to make it a bit easier to read. The
|
73
|
-
# typical left bias of `===` can be confusing reading down a page, so
|
74
|
-
# more of a clarity thing than anything. Also makes for nicer stack traces.
|
75
|
-
#
|
76
|
-
# @param target [Any]
|
77
|
-
# Target to match against
|
78
|
-
# @param matcher [#===]
|
79
|
-
# Anything that responds to ===, preferably in a unique and entertaining way.
|
80
|
-
#
|
81
|
-
# @return [Boolean]
|
82
|
-
private def case_match?(target, matcher)
|
83
|
-
matcher === target
|
84
|
-
end
|
85
|
-
|
86
|
-
# Guarded version of `public_send` meant to stamp out more
|
87
|
-
# obscure errors when running against non-matching types.
|
88
|
-
#
|
89
|
-
# @param target [Any] Object to send to
|
90
|
-
# @param matcher [#to_sym] Anything that can be coerced into a method name
|
91
|
-
#
|
92
|
-
# @return [Any] Response of sending to the method, or false if failed
|
93
|
-
private def method_send(target, matcher)
|
94
|
-
matcher.respond_to?(:to_sym) &&
|
95
|
-
target.respond_to?(matcher.to_sym) &&
|
96
|
-
target.public_send(matcher)
|
97
|
-
end
|
98
|
-
|
99
|
-
# Predicate variant of `method_send` with the same guard concerns
|
100
|
-
#
|
101
|
-
# @param target [Any] Object to send to
|
102
|
-
# @param matcher [#to_sym] Anything that can be coerced into a method name
|
103
|
-
#
|
104
|
-
# @return [Boolean] Success status of predicate
|
105
|
-
private def method_matches?(target, matcher)
|
106
|
-
!!method_send(target, matcher)
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|
110
|
-
end
|
@@ -1,91 +0,0 @@
|
|
1
|
-
module Qo
|
2
|
-
module Matchers
|
3
|
-
# A GuardBlockMatcher is like a regular matcher, except in that if it
|
4
|
-
# "matches" it will provide its match target to its associated block.
|
5
|
-
#
|
6
|
-
# It returns tuples of (status, result) in order to prevent masking of
|
7
|
-
# legitimate falsy or nil values returned.
|
8
|
-
#
|
9
|
-
# Guard Block Matchers are best used with a PatternMatch, and chances are
|
10
|
-
# you're going to want to read that documentation first.
|
11
|
-
#
|
12
|
-
# @example
|
13
|
-
# Qo::Matchers::GuardBlockMatcher
|
14
|
-
#
|
15
|
-
# guard_matcher = Qo::Matchers::GuardBlockMatcher.new(Integer) { |v| v * 2 }
|
16
|
-
# guard_matcher.call(1) # => [true, 2]
|
17
|
-
# guard_matcher.call(:x) # => [false, false]
|
18
|
-
#
|
19
|
-
# @see Qo::Matchers::PatternMatch
|
20
|
-
# Pattern Match using GuardBlockMatchers
|
21
|
-
#
|
22
|
-
# @author baweaver
|
23
|
-
# @since 0.1.5
|
24
|
-
#
|
25
|
-
class GuardBlockMatcher < BaseMatcher
|
26
|
-
# Definition of a non-match
|
27
|
-
NON_MATCH = [false, false]
|
28
|
-
|
29
|
-
def initialize(array_matchers, keyword_matchers, &fn)
|
30
|
-
@fn = fn || Qo::IDENTITY
|
31
|
-
|
32
|
-
super('and', array_matchers, keyword_matchers)
|
33
|
-
end
|
34
|
-
|
35
|
-
# Direct test of whether or not a target matches the GuardBlock's
|
36
|
-
# condition
|
37
|
-
#
|
38
|
-
# @param target [Any]
|
39
|
-
# Target value to match against
|
40
|
-
#
|
41
|
-
# @return [Boolean]
|
42
|
-
# Whether or not the target matched
|
43
|
-
def match?(target)
|
44
|
-
super(target)
|
45
|
-
end
|
46
|
-
|
47
|
-
# Forces a resolution of a match. Note that this method should
|
48
|
-
# not be used outside of pattern matches, as only a pattern
|
49
|
-
# match will have the necessary additional context to call
|
50
|
-
# it correctly.
|
51
|
-
#
|
52
|
-
# @param target [Any]
|
53
|
-
# Target value to match against
|
54
|
-
#
|
55
|
-
# @return [Any]
|
56
|
-
# Result of the function being called on the target
|
57
|
-
def match(target)
|
58
|
-
@fn.call(target)
|
59
|
-
end
|
60
|
-
|
61
|
-
# Overrides the base matcher's #to_proc to wrap the value in a status
|
62
|
-
# and potentially call through to the associated block if a base
|
63
|
-
# matcher would have passed
|
64
|
-
#
|
65
|
-
# @see Qo::Matchers::GuardBlockMatcher#call
|
66
|
-
#
|
67
|
-
# @return [Proc[Any]]
|
68
|
-
# Function awaiting target value
|
69
|
-
def to_proc
|
70
|
-
Proc.new { |target| self.call(target) }
|
71
|
-
end
|
72
|
-
|
73
|
-
|
74
|
-
# Overrides the call method to wrap values in a return tuple to represent
|
75
|
-
# a positive match to guard against valid falsy returns
|
76
|
-
#
|
77
|
-
# @param target [Any]
|
78
|
-
# Target value to match against
|
79
|
-
#
|
80
|
-
# @return [Array[false, false]]
|
81
|
-
# The guard block did not match
|
82
|
-
#
|
83
|
-
# @return [Array[true, Any]]
|
84
|
-
# The guard block matched, and the provided function called through
|
85
|
-
# providing a return value.
|
86
|
-
def call(target)
|
87
|
-
super(target) ? [true, @fn.call(target)] : NON_MATCH
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|
@@ -1,144 +0,0 @@
|
|
1
|
-
module Qo
|
2
|
-
module Matchers
|
3
|
-
# A Hash Matcher is a matcher that uses only keyword args to define a sequence
|
4
|
-
# of matches to perform against either an Object or another Hash.
|
5
|
-
#
|
6
|
-
# In the case of a Hash matching against a Hash, it will compare the intersection
|
7
|
-
# of keys and match the values against eachother.
|
8
|
-
#
|
9
|
-
# ```ruby
|
10
|
-
# Qo[name: /Foo/, age: 30..50].call({name: 'Foo', age: 42})
|
11
|
-
# # => true
|
12
|
-
# ```
|
13
|
-
#
|
14
|
-
# In the case of a Hash matching against an Object, it will treat the keys as
|
15
|
-
# method property invocations to be matched against the provided values.
|
16
|
-
#
|
17
|
-
# # ```ruby
|
18
|
-
# Qo[name: /Foo/, age: 30..50].call(Person.new('Foo', 42))
|
19
|
-
# # => true
|
20
|
-
# ```
|
21
|
-
#
|
22
|
-
# All variants present in the BaseMatcher are present here, including 'and',
|
23
|
-
# 'not', and 'or'.
|
24
|
-
#
|
25
|
-
# @author baweaver
|
26
|
-
# @since 0.2.0
|
27
|
-
#
|
28
|
-
class HashMatcher < BaseMatcher
|
29
|
-
def initialize(type, keyword_matchers)
|
30
|
-
@keyword_matchers = keyword_matchers
|
31
|
-
@type = type
|
32
|
-
end
|
33
|
-
|
34
|
-
# Wrapper around call to allow for invocation in an Enumerable function,
|
35
|
-
# such as:
|
36
|
-
#
|
37
|
-
# ```ruby
|
38
|
-
# people.select(&Qo[name: /Foo/, age: 20..40])
|
39
|
-
# ```
|
40
|
-
#
|
41
|
-
# @return [Proc[Any]]
|
42
|
-
# Proc awaiting a target to match against
|
43
|
-
def to_proc
|
44
|
-
Proc.new { |target| self.call(target) }
|
45
|
-
end
|
46
|
-
|
47
|
-
# Used to match against a matcher made from Keyword Arguments (a Hash)
|
48
|
-
#
|
49
|
-
# @param matchers [Hash[Any, #===]]
|
50
|
-
# Any key mapping to any value that responds to `===`. Notedly more
|
51
|
-
# satisfying when `===` does something fun.
|
52
|
-
#
|
53
|
-
# @return [Boolean] Result of the match
|
54
|
-
def call(target)
|
55
|
-
return true if @keyword_matchers == target
|
56
|
-
|
57
|
-
match_fn = target.is_a?(::Hash) ?
|
58
|
-
Proc.new { |match_key, matcher| match_hash_value?(target, match_key, matcher) } :
|
59
|
-
Proc.new { |match_key, matcher| match_object_value?(target, match_key, matcher) }
|
60
|
-
|
61
|
-
match_with(@keyword_matchers, &match_fn)
|
62
|
-
end
|
63
|
-
|
64
|
-
# Checks if a hash value matches a given matcher
|
65
|
-
#
|
66
|
-
# @param target [Any] Target of the match
|
67
|
-
# @param match_key [Symbol] Key of the hash to reference
|
68
|
-
# @param matcher [#===] Any matcher responding to ===
|
69
|
-
#
|
70
|
-
# @return [Boolean] Match status
|
71
|
-
private def match_hash_value?(target, match_key, matcher)
|
72
|
-
return false unless target.key?(match_key)
|
73
|
-
|
74
|
-
return hash_recurse(target[match_key], matcher) if target.is_a?(Hash) && matcher.is_a?(Hash)
|
75
|
-
|
76
|
-
hash_case_match?(target, match_key, matcher) ||
|
77
|
-
hash_method_predicate_match?(target, match_key, matcher)
|
78
|
-
end
|
79
|
-
|
80
|
-
# Checks if an object property matches a given matcher
|
81
|
-
#
|
82
|
-
# @param target [Any] Target of the match
|
83
|
-
# @param match_property [Symbol] Property of the object to reference
|
84
|
-
# @param matcher [#===] Any matcher responding to ===
|
85
|
-
#
|
86
|
-
# @return [Boolean] Match status
|
87
|
-
private def match_object_value?(target, match_property, matcher)
|
88
|
-
return false unless target.respond_to?(match_property)
|
89
|
-
|
90
|
-
hash_method_case_match?(target, match_property, matcher)
|
91
|
-
end
|
92
|
-
|
93
|
-
# Double wraps case match in order to ensure that we try against both Symbol
|
94
|
-
# and String variants of the keys, as this is a very common mixup in Ruby.
|
95
|
-
#
|
96
|
-
# @param target [Hash] Target of the match
|
97
|
-
# @param match_key [Symbol] Key to match against
|
98
|
-
# @param matcher [#===] Matcher
|
99
|
-
#
|
100
|
-
# @return [Boolean]
|
101
|
-
private def hash_case_match?(target, match_key, matcher)
|
102
|
-
return true if case_match?(target[match_key], matcher)
|
103
|
-
return false unless target.keys.first.is_a?(String)
|
104
|
-
|
105
|
-
match_key.respond_to?(:to_s) &&
|
106
|
-
target.key?(match_key.to_s) &&
|
107
|
-
case_match?(target[match_key.to_s], matcher)
|
108
|
-
end
|
109
|
-
|
110
|
-
# Attempts to run a matcher as a predicate method against the target
|
111
|
-
#
|
112
|
-
# @param target [Hash] Target of the match
|
113
|
-
# @param match_key [Symbol] Method to call
|
114
|
-
# @param match_predicate [Symbol] Matcher
|
115
|
-
#
|
116
|
-
# @return [Boolean]
|
117
|
-
private def hash_method_predicate_match?(target, match_key, match_predicate)
|
118
|
-
method_matches?(target[match_key], match_predicate)
|
119
|
-
end
|
120
|
-
|
121
|
-
# Attempts to run a case match against a method call derived from a hash
|
122
|
-
# key, and checks the result.
|
123
|
-
#
|
124
|
-
# @param target [Hash] Target of the match
|
125
|
-
# @param match_property [Symbol] Method to call
|
126
|
-
# @param matcher [#===] Matcher
|
127
|
-
#
|
128
|
-
# @return [Boolean]
|
129
|
-
private def hash_method_case_match?(target, match_property, matcher)
|
130
|
-
case_match?(method_send(target, match_property), matcher)
|
131
|
-
end
|
132
|
-
|
133
|
-
# Recurses on nested hashes.
|
134
|
-
#
|
135
|
-
# @param target [Hash]
|
136
|
-
# @param matcher [Hash]
|
137
|
-
#
|
138
|
-
# @return [Boolean]
|
139
|
-
private def hash_recurse(target, matcher)
|
140
|
-
Qo::Matchers::HashMatcher.new(@type, **matcher).call(target)
|
141
|
-
end
|
142
|
-
end
|
143
|
-
end
|
144
|
-
end
|