qo 0.1.10 → 0.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/README.md +18 -12
- data/Rakefile +72 -0
- data/lib/qo.rb +17 -84
- data/lib/qo/exceptions.rb +39 -0
- data/lib/qo/helpers.rb +37 -0
- data/lib/qo/matchers/array_matcher.rb +63 -0
- data/lib/qo/matchers/base_matcher.rb +104 -0
- data/lib/qo/matchers/guard_block_matcher.rb +37 -0
- data/lib/qo/matchers/hash_matcher.rb +131 -0
- data/lib/qo/matchers/pattern_match.rb +93 -0
- data/lib/qo/public_api.rb +121 -0
- data/lib/qo/version.rb +1 -1
- data/performance_report.txt +24 -24
- metadata +10 -4
- data/lib/qo/guard_block_matcher.rb +0 -19
- data/lib/qo/matcher.rb +0 -286
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dcbbe9e009a9ccf04769b8ccb7eca4343ea972a5
|
4
|
+
data.tar.gz: 53dae10eca53508dacfb75ad940207f0f047d288
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 62bceca4a54804a1e18c86a26802e3903693a61d213fa27b959e0e6a362b54470aeb22f45101ebd8b73cb5dfa90a7c1214c7d78574d3a366a679734744005007
|
7
|
+
data.tar.gz: 40a0319fa994a9e82e1f55001deab434e95187819efc1ef5eaa6bdd9be015987c481b7b7097b00820974cdc43eeed815fc4b9f7d442f5388b1881549ad00eb62
|
data/README.md
CHANGED
@@ -10,11 +10,19 @@ Short for Query Object, my play at Ruby pattern matching and fluent querying
|
|
10
10
|
|
11
11
|
## How does it work?
|
12
12
|
|
13
|
-
|
13
|
+
Mostly by using Ruby language features like `to_proc` and `===`.
|
14
14
|
|
15
|
-
|
15
|
+
There's an article explaining most of the base mechanics behind Qo:
|
16
16
|
|
17
|
-
|
17
|
+
[For Want of Pattern Matching in Ruby - The Creation of Qo](https://medium.com/@baweaver/for-want-of-pattern-matching-in-ruby-the-creation-of-qo-c3b267109b25)
|
18
|
+
|
19
|
+
Most of it, though, utilizes Triple Equals. If you're not familiar with what all you can do with it in Ruby, I would encourage you to read this article as well:
|
20
|
+
|
21
|
+
[Triple Equals Black Magic](https://medium.com/rubyinside/triple-equals-black-magic-d934936a6379)
|
22
|
+
|
23
|
+
The original inspiration was from a chat I'd had with a few other Rubyists about pattern matching, which led to this experiment:
|
24
|
+
|
25
|
+
[Having fun with M and Q](https://gist.github.com/baweaver/611389c41c9005d025fb8e55448bf5f5)
|
18
26
|
|
19
27
|
Fast forward a few months and I kind of wanted to make it real, so here it is. Introducing Qo!
|
20
28
|
|
@@ -36,7 +44,7 @@ people.select(&Qo[age: 18..30])
|
|
36
44
|
|
37
45
|
# How about some "right-hand assignment" pattern matching
|
38
46
|
name_longer_than_three = -> person { person.name.size > 3 }
|
39
|
-
people_with_truncated_names = people.map(&Qo.
|
47
|
+
people_with_truncated_names = people.map(&Qo.match(
|
40
48
|
Qo.m(name_longer_than_three) { |person| Person.new(person.name[0..2], person.age) },
|
41
49
|
Qo.m(:*) # Identity function, catch-all
|
42
50
|
))
|
@@ -63,7 +71,7 @@ Qo[/Rob/, 22]
|
|
63
71
|
Qo.and(/Rob/, 22)
|
64
72
|
|
65
73
|
# This is shorthand for
|
66
|
-
Qo::
|
74
|
+
Qo::Matchers::BaseMatcher.new('and', /Rob/, 22)
|
67
75
|
|
68
76
|
# An `or` matcher uses the same shorthand as `and` but uses `any?` behind the scenes instead:
|
69
77
|
Qo.or(/Rob/, 22)
|
@@ -111,7 +119,7 @@ Qo[:*] === :literally_anything_here
|
|
111
119
|
The first way a Qo matcher can be defined is by using `*varargs`:
|
112
120
|
|
113
121
|
```ruby
|
114
|
-
|
122
|
+
Qo::Matchers::BaseMatcher(type, *varargs, **kwargs)
|
115
123
|
```
|
116
124
|
|
117
125
|
This gives us the `and` matcher shorthand for array matchers.
|
@@ -277,7 +285,7 @@ Checks to see if the key is even present on the other object, false if not.
|
|
277
285
|
|
278
286
|
##### 3.1.2 - Match value and target are hashes
|
279
287
|
|
280
|
-
If both the match value (`match_key:
|
288
|
+
If both the match value (`match_key: matcher`) and the match target are hashes, Qo will begin a recursive descent starting at the match key until it finds a matcher to try out:
|
281
289
|
|
282
290
|
```ruby
|
283
291
|
Qo[a: {b: {c: 5..15}}] === {a: {b: {c: 10}}}
|
@@ -444,8 +452,6 @@ people_hashes.select(&Qo[age: :nil?])
|
|
444
452
|
|
445
453
|
### 4 - Right Hand Pattern Matching
|
446
454
|
|
447
|
-
> ALPHA - This feature is alpha, currently testing. Considering whether or not to add `or` and `not` as `m_or` and `m_not`.
|
448
|
-
|
449
455
|
This is where I start going a bit off into the weeds. We're going to try and get RHA style pattern matching in Ruby.
|
450
456
|
|
451
457
|
```ruby
|
@@ -470,12 +476,12 @@ In this case it's trying to do a few things:
|
|
470
476
|
|
471
477
|
If no block function is provided, it assumes an identity function (`-> v { v }`) instead. If no match is found, `nil` will be returned.
|
472
478
|
|
473
|
-
|
479
|
+
If an initial target is not furnished, the matcher will become a curried proc awaiting a target. In more simple terms it just wants a target to run against, so let's give it a few with map:
|
474
480
|
|
475
481
|
```ruby
|
476
482
|
name_longer_than_three = -> person { person.name.size > 3 }
|
477
483
|
|
478
|
-
people_objects.map(&Qo.
|
484
|
+
people_objects.map(&Qo.match(
|
479
485
|
Qo.m(name_longer_than_three) { |person|
|
480
486
|
person.name = person.name[0..2]
|
481
487
|
person
|
@@ -596,7 +602,7 @@ The nice thing about Unix style commands is that they use headers, which means C
|
|
596
602
|
```ruby
|
597
603
|
rows = CSV.new(`df -h`, col_sep: " ", headers: true).read.map(&:to_h)
|
598
604
|
|
599
|
-
rows.map(&Qo.
|
605
|
+
rows.map(&Qo.match(
|
600
606
|
Qo.m(Avail: /Gi$/) { |row|
|
601
607
|
"#{row['Filesystem']} mounted on #{row['Mounted']} [#{row['Avail']} / #{row['Size']}]"
|
602
608
|
}
|
data/Rakefile
CHANGED
@@ -105,3 +105,75 @@ task :perf do
|
|
105
105
|
}
|
106
106
|
)
|
107
107
|
end
|
108
|
+
|
109
|
+
# Below this mark are mostly my experiments to see what features perform a bit better
|
110
|
+
# than others, and are mostly left to check different versions of Ruby against eachother.
|
111
|
+
#
|
112
|
+
# Feel free to use them in development, but the general consensus of them is that
|
113
|
+
# `send` type methods are barely slower. One _could_ write an IIFE to get around
|
114
|
+
# that and maintain the flexibility but it's a net loss of clarity.
|
115
|
+
#
|
116
|
+
# Proc wise, they're all within margin of error. We just need to be really careful
|
117
|
+
# of the 2.4+ bug of lambdas not destructuring automatically, which will wreak
|
118
|
+
# havoc on hash matchers.
|
119
|
+
|
120
|
+
task :perf_predicates do
|
121
|
+
array = (1..1000).to_a
|
122
|
+
|
123
|
+
run_benchmark('Predicates any?',
|
124
|
+
'block_any?': -> { array.any? { |v| v.even? } },
|
125
|
+
'proc_any?': -> { array.any?(&:even?) },
|
126
|
+
'send_proc_any?': -> { array.public_send(:any?, &:even?) }
|
127
|
+
)
|
128
|
+
|
129
|
+
run_benchmark('Predicates all?',
|
130
|
+
'block_all?': -> { array.all? { |v| v.even? } },
|
131
|
+
'proc_all?': -> { array.all?(&:even?) },
|
132
|
+
'send_proc_all?': -> { array.public_send(:all?, &:even?) }
|
133
|
+
)
|
134
|
+
|
135
|
+
run_benchmark('Predicates none?',
|
136
|
+
'block_none?': -> { array.none? { |v| v.even? } },
|
137
|
+
'proc_none?': -> { array.none?(&:even?) },
|
138
|
+
'send_proc_none?': -> { array.public_send(:none?, &:even?) },
|
139
|
+
)
|
140
|
+
|
141
|
+
even_stabby_lambda = -> n { n % 2 == 0 }
|
142
|
+
even_lambda = lambda { |n| n % 2 == 0 }
|
143
|
+
even_proc_new = Proc.new { |n| n % 2 == 0 }
|
144
|
+
even_proc_short = proc { |n| n % 2 == 0 }
|
145
|
+
even_to_proc = :even?.to_proc
|
146
|
+
|
147
|
+
run_benchmark('Types of Functions in Ruby',
|
148
|
+
even_stabby_lambda: -> { array.all?(&even_stabby_lambda) },
|
149
|
+
even_lambda: -> { array.all?(&even_lambda) },
|
150
|
+
even_proc_new: -> { array.all?(&even_proc_new) },
|
151
|
+
even_proc_short: -> { array.all?(&even_proc_short) },
|
152
|
+
even_to_proc: -> { array.all?(&even_to_proc) },
|
153
|
+
)
|
154
|
+
end
|
155
|
+
|
156
|
+
task :perf_random do
|
157
|
+
# run_benchmark('Empty on blank array',
|
158
|
+
# 'empty?': -> { [].empty? },
|
159
|
+
# 'size == 0': -> { [].size == 0 },
|
160
|
+
# 'size.zero?': -> { [].size.zero? },
|
161
|
+
# )
|
162
|
+
|
163
|
+
array = (1..1000).to_a
|
164
|
+
# run_benchmark('Empty on several elements array',
|
165
|
+
# 'empty?': -> { array.empty? },
|
166
|
+
# 'size == 0': -> { array.size == 0 },
|
167
|
+
# 'size.zero?': -> { array.size.zero? },
|
168
|
+
# )
|
169
|
+
|
170
|
+
hash = array.map { |v| [v, v] }.to_h
|
171
|
+
|
172
|
+
run_benchmark('Empty on blank hash vs array',
|
173
|
+
'hash empty?': -> { {}.empty? },
|
174
|
+
'array empty?': -> { [].empty? },
|
175
|
+
|
176
|
+
'full hash empty?': -> { hash.empty? },
|
177
|
+
'full array empty?': -> { array.empty? },
|
178
|
+
)
|
179
|
+
end
|
data/lib/qo.rb
CHANGED
@@ -1,92 +1,25 @@
|
|
1
1
|
require "qo/version"
|
2
|
-
require 'qo/matcher'
|
3
|
-
require 'qo/guard_block_matcher'
|
4
2
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
# Creates a Guard Block matcher.
|
11
|
-
#
|
12
|
-
# A guard block matcher is used to guard a function from running unless
|
13
|
-
# the left-hand matcher passes. Once called with a value, it will either
|
14
|
-
# return `[false, false]` or `[true, Any]`.
|
15
|
-
#
|
16
|
-
# This wrapping is done to preserve intended false or nil responses,
|
17
|
-
# and is unwrapped with match below.
|
18
|
-
#
|
19
|
-
# @param *array_matchers [Array] varargs matchers
|
20
|
-
# @param **keyword_matchers [Hash] kwargs matchers
|
21
|
-
# @param &fn [Proc] Guarded function
|
22
|
-
#
|
23
|
-
# @return [Proc[Any]]
|
24
|
-
# Any -> Proc[Any]
|
25
|
-
def matcher(*array_matchers, **keyword_matchers, &fn)
|
26
|
-
Qo::GuardBlockMatcher.new(*array_matchers, **keyword_matchers, &fn)
|
27
|
-
end
|
28
|
-
|
29
|
-
alias_method :m, :matcher
|
30
|
-
|
31
|
-
|
32
|
-
# Takes a set of Guard Block matchers, runs each in sequence, then
|
33
|
-
# unfolds the response from the first passing block.
|
34
|
-
#
|
35
|
-
# @param target [Any] Target object to run against
|
36
|
-
# @param *qo_matchers [Array[GuardBlockMatcher]] Collection of matchers to run
|
37
|
-
#
|
38
|
-
# @return [type] [description]
|
39
|
-
def match(target, *qo_matchers)
|
40
|
-
all_are_guards = qo_matchers.all? { |q| q.is_a?(Qo::GuardBlockMatcher) }
|
41
|
-
raise 'Must patch Qo GuardBlockMatchers!' unless all_are_guards
|
42
|
-
|
43
|
-
qo_matchers.reduce(nil) { |_, matcher|
|
44
|
-
did_match, match_result = matcher.call(target)
|
45
|
-
break match_result if did_match
|
46
|
-
}
|
47
|
-
end
|
3
|
+
# Matchers
|
4
|
+
require 'qo/matchers/base_matcher'
|
5
|
+
require 'qo/matchers/array_matcher'
|
6
|
+
require 'qo/matchers/hash_matcher'
|
7
|
+
require 'qo/matchers/guard_block_matcher'
|
48
8
|
|
49
|
-
|
50
|
-
|
51
|
-
# @param *qo_matchers [Array[GuardBlockMatcher]] Collection of matchers to run
|
52
|
-
#
|
53
|
-
# @return [Proc[Any]]
|
54
|
-
# Any -> Any
|
55
|
-
def match_fn(*qo_matchers)
|
56
|
-
-> target { match(target, *qo_matchers) }
|
57
|
-
end
|
9
|
+
# Meta Matchers
|
10
|
+
require 'qo/matchers/pattern_match'
|
58
11
|
|
59
|
-
|
60
|
-
|
61
|
-
end
|
12
|
+
# Helpers
|
13
|
+
require 'qo/helpers'
|
62
14
|
|
63
|
-
|
15
|
+
# Public API
|
16
|
+
require 'qo/exceptions'
|
17
|
+
require 'qo/public_api'
|
64
18
|
|
65
|
-
|
66
|
-
|
67
|
-
end
|
68
|
-
|
69
|
-
def not(*array_matchers, **keyword_matchers)
|
70
|
-
Qo::Matcher.new('not', *array_matchers, **keyword_matchers)
|
71
|
-
end
|
72
|
-
|
73
|
-
# Utility functions. Consider placing these elsewhere.
|
74
|
-
|
75
|
-
def dig(path_map, expected_value)
|
76
|
-
-> hash {
|
77
|
-
segments = path_map.split('.')
|
78
|
-
|
79
|
-
expected_value === hash.dig(*segments) ||
|
80
|
-
expected_value === hash.dig(*segments.map(&:to_sym))
|
81
|
-
}
|
82
|
-
end
|
83
|
-
|
84
|
-
def count_by(targets, &fn)
|
85
|
-
fn ||= -> v { v }
|
19
|
+
module Qo
|
20
|
+
WILDCARD_MATCH = :*
|
86
21
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
end
|
91
|
-
end
|
22
|
+
extend Qo::Exceptions
|
23
|
+
extend Qo::Helpers
|
24
|
+
extend Qo::PublicApi
|
92
25
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Qo
|
2
|
+
# Defines common exception classes for use throughout the library
|
3
|
+
#
|
4
|
+
# @author [baweaver]
|
5
|
+
#
|
6
|
+
module Exceptions
|
7
|
+
# If no matchers in either Array or Hash style are provided.
|
8
|
+
#
|
9
|
+
# @author [lemur]
|
10
|
+
#
|
11
|
+
class NoMatchersProvided < ArgumentError
|
12
|
+
def to_s
|
13
|
+
"No Qo matchers were provided!"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# If both Array and Hash style matchers are provided.
|
18
|
+
#
|
19
|
+
# @author [lemur]
|
20
|
+
#
|
21
|
+
class MultipleMatchersProvided < ArgumentError
|
22
|
+
def to_s
|
23
|
+
"Cannot provide both array and keyword matchers!"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# In the case of a Pattern Match, we need to ensure all arguments are
|
28
|
+
# GuardBlockMatchers.
|
29
|
+
#
|
30
|
+
# @author [lemur]
|
31
|
+
#
|
32
|
+
class NotAllGuardMatchersProvided < ArgumentError
|
33
|
+
def to_s
|
34
|
+
"All provided matchers must be of type Qo::Matchers::GuardBlockMatcher " +
|
35
|
+
"defined with `Qo.matcher` or `Qo.m` instead of regular matchers."
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/qo/helpers.rb
ADDED
@@ -0,0 +1,37 @@
|
|
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] Dot-delimited path
|
9
|
+
# @param expected_value [Any] Matcher
|
10
|
+
#
|
11
|
+
# @return [Proc]
|
12
|
+
# Hash -> Bool # Status of digging against the hash
|
13
|
+
def dig(path_map, expected_value)
|
14
|
+
Proc.new { |hash|
|
15
|
+
segments = path_map.split('.')
|
16
|
+
|
17
|
+
expected_value === hash.dig(*segments) ||
|
18
|
+
expected_value === hash.dig(*segments.map(&:to_sym))
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
# Counts by a function. This is entirely because I hackney this everywhere in
|
23
|
+
# pry anyways, so I want a function to do it for me already.
|
24
|
+
#
|
25
|
+
# @param targets [Array[Any]] Targets to count
|
26
|
+
# @param &fn [Proc] Function to define count key
|
27
|
+
#
|
28
|
+
# @return [Hash[Any, Integer]] Counts
|
29
|
+
def count_by(targets, &fn)
|
30
|
+
fn ||= -> v { v }
|
31
|
+
|
32
|
+
targets.each_with_object(Hash.new(0)) { |target, counts|
|
33
|
+
counts[fn[target]] += 1
|
34
|
+
}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,63 @@
|
|
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
|
+
# In the case of an Array matching against an Object, it will match each provided
|
9
|
+
# matcher against the object.
|
10
|
+
#
|
11
|
+
# All variants present in the BaseMatcher are present here, including 'and',
|
12
|
+
# 'not', and 'or'.
|
13
|
+
#
|
14
|
+
# @author [baweaver]
|
15
|
+
#
|
16
|
+
class ArrayMatcher < BaseMatcher
|
17
|
+
# Used to match against a matcher made from an Array, like:
|
18
|
+
#
|
19
|
+
# Qo['Foo', 'Bar']
|
20
|
+
#
|
21
|
+
# @param matchers [Array[respond_to?(===)]] indexed tuple to match the target object against
|
22
|
+
#
|
23
|
+
# @return [Proc[Any]]
|
24
|
+
# Array -> Bool # Tuple match against targets index
|
25
|
+
# Object -> Bool # Boolean public send
|
26
|
+
def to_proc
|
27
|
+
Proc.new { |target| self.call(target) }
|
28
|
+
end
|
29
|
+
|
30
|
+
# Invocation for the match sequence. Will determine the target and applicable
|
31
|
+
# matchers to run against it.
|
32
|
+
#
|
33
|
+
# @param target [Any]
|
34
|
+
#
|
35
|
+
# @return [Boolean] Match status
|
36
|
+
def call(target)
|
37
|
+
return true if @array_matchers == target
|
38
|
+
|
39
|
+
if target.is_a?(::Array)
|
40
|
+
match_with(@array_matchers.each_with_index) { |matcher, i|
|
41
|
+
match_value?(target[i], matcher)
|
42
|
+
}
|
43
|
+
else
|
44
|
+
match_with(@array_matchers) { |matcher|
|
45
|
+
match_value?(target, matcher)
|
46
|
+
}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Defines what it means for a value to match a matcher
|
51
|
+
#
|
52
|
+
# @param target [Any] Target to match against
|
53
|
+
# @param matcher [Any] Any matcher to run against, most frequently responds to ===
|
54
|
+
#
|
55
|
+
# @return [Boolean] Match status
|
56
|
+
private def match_value?(target, matcher)
|
57
|
+
wildcard_match?(matcher) ||
|
58
|
+
case_match?(target, matcher) ||
|
59
|
+
method_matches?(target, matcher)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module Qo
|
2
|
+
module Matchers
|
3
|
+
# Base instance of matcher which is meant to take in either Array style or
|
4
|
+
# Keyword style arguments to run a match against various datatypes.
|
5
|
+
#
|
6
|
+
# Will delegate responsibilities to either Array or Hash style matchers if
|
7
|
+
# invoked directly.
|
8
|
+
#
|
9
|
+
# @author [baweaver]
|
10
|
+
#
|
11
|
+
class BaseMatcher
|
12
|
+
def initialize(type, *array_matchers, **keyword_matchers)
|
13
|
+
@array_matchers = array_matchers
|
14
|
+
@keyword_matchers = keyword_matchers
|
15
|
+
@type = type
|
16
|
+
end
|
17
|
+
|
18
|
+
# Converts a Matcher to a proc for use in querying, such as:
|
19
|
+
#
|
20
|
+
# data.select(&Qo[...])
|
21
|
+
#
|
22
|
+
# @return [Proc]
|
23
|
+
def to_proc
|
24
|
+
@array_matchers.empty? ?
|
25
|
+
Qo::Matchers::HashMatcher.new(@type, **@keyword_matchers).to_proc :
|
26
|
+
Qo::Matchers::ArrayMatcher.new(@type, *@array_matchers).to_proc
|
27
|
+
end
|
28
|
+
|
29
|
+
# You can directly call a matcher as well, much like a Proc,
|
30
|
+
# using one of call, ===, or []
|
31
|
+
#
|
32
|
+
# @param target [Any] Object to match against
|
33
|
+
#
|
34
|
+
# @return [type] [description]
|
35
|
+
def call(target)
|
36
|
+
self.to_proc.call(target)
|
37
|
+
end
|
38
|
+
|
39
|
+
alias_method :===, :call
|
40
|
+
alias_method :[], :call
|
41
|
+
|
42
|
+
# Wrapper around public send to encapsulate the matching method (any, all, none)
|
43
|
+
#
|
44
|
+
# @param collection [Enumerable] Any collection that can be enumerated over
|
45
|
+
# @param fn [Proc] Function to match with
|
46
|
+
#
|
47
|
+
# @return [Enumerable] Resulting collection
|
48
|
+
private def match_with(collection, &fn)
|
49
|
+
return collection.any?(&fn) if @type == 'or'
|
50
|
+
return collection.none?(&fn) if @type == 'not'
|
51
|
+
|
52
|
+
collection.all?(&fn)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Wraps wildcard in case we want to do anything fun with it later
|
56
|
+
#
|
57
|
+
# @param value [Any] Value to test against the wild card
|
58
|
+
#
|
59
|
+
# @note The rescue is because some classes override `==` to do silly things,
|
60
|
+
# like IPAddr, and I kinda want to use that.
|
61
|
+
#
|
62
|
+
# @return [Boolean]
|
63
|
+
private def wildcard_match?(value)
|
64
|
+
value == WILDCARD_MATCH rescue false
|
65
|
+
end
|
66
|
+
|
67
|
+
# Wraps a case equality statement to make it a bit easier to read. The
|
68
|
+
# typical left bias of `===` can be confusing reading down a page, so
|
69
|
+
# more of a clarity thing than anything. Also makes for nicer stack traces.
|
70
|
+
#
|
71
|
+
# @param target [Any] Target to match against
|
72
|
+
# @param matcher [respond_to?(:===)]
|
73
|
+
# Anything that responds to ===, preferably in a unique and entertaining way.
|
74
|
+
#
|
75
|
+
# @return [Boolean]
|
76
|
+
private def case_match?(target, matcher)
|
77
|
+
matcher === target
|
78
|
+
end
|
79
|
+
|
80
|
+
# Guarded version of `public_send` meant to stamp out more
|
81
|
+
# obscure errors when running against non-matching types.
|
82
|
+
#
|
83
|
+
# @param target [Any] Object to send to
|
84
|
+
# @param matcher [respond_to?(:to_sym)] Anything that can be coerced into a method name
|
85
|
+
#
|
86
|
+
# @return [Any] Response of sending to the method, or false if failed
|
87
|
+
private def method_send(target, matcher)
|
88
|
+
matcher.respond_to?(:to_sym) &&
|
89
|
+
target.respond_to?(matcher.to_sym) &&
|
90
|
+
target.public_send(matcher)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Predicate variant of `method_send` with the same guard concerns
|
94
|
+
#
|
95
|
+
# @param target [Any] Object to send to
|
96
|
+
# @param matcher [respond_to?(:to_sym)] Anything that can be coerced into a method name
|
97
|
+
#
|
98
|
+
# @return [Boolean] Success status of predicate
|
99
|
+
private def method_matches?(target, matcher)
|
100
|
+
!!method_send(target, matcher)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|