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
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.2.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: 2018-04-
|
11
|
+
date: 2018-04-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -117,8 +117,14 @@ files:
|
|
117
117
|
- docs/_config.yml
|
118
118
|
- img/qo_logo.png
|
119
119
|
- lib/qo.rb
|
120
|
-
- lib/qo/
|
121
|
-
- lib/qo/
|
120
|
+
- lib/qo/exceptions.rb
|
121
|
+
- lib/qo/helpers.rb
|
122
|
+
- lib/qo/matchers/array_matcher.rb
|
123
|
+
- lib/qo/matchers/base_matcher.rb
|
124
|
+
- lib/qo/matchers/guard_block_matcher.rb
|
125
|
+
- lib/qo/matchers/hash_matcher.rb
|
126
|
+
- lib/qo/matchers/pattern_match.rb
|
127
|
+
- lib/qo/public_api.rb
|
122
128
|
- lib/qo/version.rb
|
123
129
|
- performance_report.txt
|
124
130
|
- qo.gemspec
|
@@ -1,19 +0,0 @@
|
|
1
|
-
module Qo
|
2
|
-
class GuardBlockMatcher < Matcher
|
3
|
-
IDENTITY = -> v { v }
|
4
|
-
|
5
|
-
def initialize(*array_matchers, **keyword_matchers, &fn)
|
6
|
-
@fn = fn || IDENTITY
|
7
|
-
|
8
|
-
super('and', *array_matchers, **keyword_matchers)
|
9
|
-
end
|
10
|
-
|
11
|
-
def to_proc
|
12
|
-
Proc.new { |match_target|
|
13
|
-
next [false, false] unless super[match_target]
|
14
|
-
|
15
|
-
[true, @fn.call(match_target)]
|
16
|
-
}
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
data/lib/qo/matcher.rb
DELETED
@@ -1,286 +0,0 @@
|
|
1
|
-
module Qo
|
2
|
-
class Matcher
|
3
|
-
def initialize(type, *array_matchers, **keyword_matchers)
|
4
|
-
@match_method = get_match_method(type)
|
5
|
-
@array_matchers = array_matchers
|
6
|
-
@keyword_matchers = keyword_matchers
|
7
|
-
end
|
8
|
-
|
9
|
-
# Converts a Matcher to a proc for use in querying, such as:
|
10
|
-
#
|
11
|
-
# data.select(&Qo[...])
|
12
|
-
#
|
13
|
-
# @return [Proc]
|
14
|
-
def to_proc
|
15
|
-
if @array_matchers.empty?
|
16
|
-
match_against_hash(@keyword_matchers)
|
17
|
-
else
|
18
|
-
match_against_array(@array_matchers)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
# You can directly call a matcher as well, much like a Proc,
|
23
|
-
# using one of call, ===, or []
|
24
|
-
#
|
25
|
-
# @param match_target [Any] Object to match against
|
26
|
-
#
|
27
|
-
# @return [type] [description]
|
28
|
-
def call(match_target)
|
29
|
-
self.to_proc.call(match_target)
|
30
|
-
end
|
31
|
-
|
32
|
-
alias_method :===, :call
|
33
|
-
alias_method :[], :call
|
34
|
-
|
35
|
-
# Used to match against a matcher made from an Array, like:
|
36
|
-
#
|
37
|
-
# Qo['Foo', 'Bar']
|
38
|
-
#
|
39
|
-
# @param matchers [Array[respond_to?(===)]] indexed tuple to match the target object against
|
40
|
-
#
|
41
|
-
# @return [Proc[Any]]
|
42
|
-
# Array -> Bool # Tuple match against targets index
|
43
|
-
# Object -> Bool # Boolean public send
|
44
|
-
private def match_against_array(matchers)
|
45
|
-
Proc.new { |match_target|
|
46
|
-
next true if matchers == match_target
|
47
|
-
|
48
|
-
if match_target.is_a?(::Array)
|
49
|
-
match_collection = matchers.each_with_index
|
50
|
-
match_fn = array_against_array_matcher(match_target)
|
51
|
-
else
|
52
|
-
match_collection = matchers
|
53
|
-
match_fn = array_against_object_matcher(match_target)
|
54
|
-
end
|
55
|
-
|
56
|
-
match_with(match_collection, match_fn)
|
57
|
-
}
|
58
|
-
end
|
59
|
-
|
60
|
-
# Used to match against a matcher made from Keyword Arguments (a Hash)
|
61
|
-
#
|
62
|
-
# @param matchers [Hash[Any, respond_to?(===)]]
|
63
|
-
# Any key mapping to any value that responds to `===`. Notedly more
|
64
|
-
# satisfying when `===` does something fun.
|
65
|
-
#
|
66
|
-
# @return [Proc[Any]]
|
67
|
-
# Hash -> Bool # Value matching against similar keys, will attempt to coerce to_s because JSON
|
68
|
-
# Object -> Bool # Uses keys as methods with public send to `===` match against the value
|
69
|
-
private def match_against_hash(matchers)
|
70
|
-
Proc.new { |match_target|
|
71
|
-
next true if matchers == match_target
|
72
|
-
|
73
|
-
match_fn = match_target.is_a?(::Hash) ?
|
74
|
-
hash_against_hash_matcher(match_target) :
|
75
|
-
hash_against_object_matcher(match_target)
|
76
|
-
|
77
|
-
match_with(matchers, match_fn)
|
78
|
-
}
|
79
|
-
end
|
80
|
-
|
81
|
-
# Used to map the nicety names against the actual Ruby methods they represent
|
82
|
-
#
|
83
|
-
# @param name [String] Query type name
|
84
|
-
#
|
85
|
-
# @return [Symbol] The method name to use to query with
|
86
|
-
private def get_match_method(name)
|
87
|
-
case name
|
88
|
-
when 'and' then :all?
|
89
|
-
when 'or' then :any?
|
90
|
-
when 'not' then :none?
|
91
|
-
else :all?
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
# A function to match against an indexed array tuple
|
96
|
-
#
|
97
|
-
# @param match_target [Array] Target array
|
98
|
-
#
|
99
|
-
# @return [Proc]
|
100
|
-
# Any -> Int -> Bool # Match against wildcard or same position in target array
|
101
|
-
private def array_against_array_matcher(match_target)
|
102
|
-
Proc.new { |matcher, i|
|
103
|
-
wildcard_match?(matcher) ||
|
104
|
-
case_match?(match_target[i], matcher) ||
|
105
|
-
method_matches?(match_target[i], matcher)
|
106
|
-
}
|
107
|
-
end
|
108
|
-
|
109
|
-
# A function to match against an object using an array of predicates
|
110
|
-
#
|
111
|
-
# @param match_target [Any] Target object
|
112
|
-
#
|
113
|
-
# @return [Proc]
|
114
|
-
# String | Symbol -> Bool # Match against wildcard or boolean return of a predicate method
|
115
|
-
private def array_against_object_matcher(match_target)
|
116
|
-
Proc.new { |matcher|
|
117
|
-
wildcard_match?(matcher) ||
|
118
|
-
case_match?(match_target, matcher) ||
|
119
|
-
method_matches?(match_target, matcher)
|
120
|
-
}
|
121
|
-
end
|
122
|
-
|
123
|
-
# A function to match against a Hash using a Hash
|
124
|
-
#
|
125
|
-
# @param match_target [Hash[Any, Any]] Target Hash
|
126
|
-
#
|
127
|
-
# @return [Proc]
|
128
|
-
# Any -> Any -> Bool # Matches against wildcard or a key and value. Coerces key to_s if no matches for JSON.
|
129
|
-
private def hash_against_hash_matcher(match_target)
|
130
|
-
Proc.new { |(match_key, match_value)|
|
131
|
-
next true if hash_wildcard_match?(match_target, match_key, match_value)
|
132
|
-
|
133
|
-
next hash_recurse(match_target[match_key], match_value) if hash_should_recurse?(match_target, match_value)
|
134
|
-
|
135
|
-
hash_case_match?(match_target, match_key, match_value) ||
|
136
|
-
hash_method_case_match?(match_target, match_key, match_value)
|
137
|
-
}
|
138
|
-
end
|
139
|
-
|
140
|
-
# A function to match against an Object using a Hash
|
141
|
-
#
|
142
|
-
# @param match_target [Any] Target object
|
143
|
-
#
|
144
|
-
# @return [Proc]
|
145
|
-
# Any -> Any -> Bool # Matches against wildcard or match value versus the public send return of the target
|
146
|
-
private def hash_against_object_matcher(match_target)
|
147
|
-
Proc.new { |(match_key, match_value)|
|
148
|
-
object_wildcard_match?(match_target, match_key, match_value) ||
|
149
|
-
hash_method_case_match?(match_target, match_key, match_value)
|
150
|
-
}
|
151
|
-
end
|
152
|
-
|
153
|
-
# Wrapper around public send to encapsulate the matching method (any, all, none)
|
154
|
-
#
|
155
|
-
# @param collection [Enumerable] Any collection that can be enumerated over
|
156
|
-
# @param fn [Proc] Function to match with
|
157
|
-
#
|
158
|
-
# @return [Enumerable] Resulting collection
|
159
|
-
private def match_with(collection, fn)
|
160
|
-
collection.public_send(@match_method, &fn)
|
161
|
-
end
|
162
|
-
|
163
|
-
# Predicate variant of `method_send` with the same guard concerns
|
164
|
-
#
|
165
|
-
# @param target [Any] Object to send to
|
166
|
-
# @param matcher [respond_to?(:to_sym)] Anything that can be coerced into a method name
|
167
|
-
#
|
168
|
-
# @return [Boolean] Success status of predicate
|
169
|
-
private def method_matches?(target, matcher)
|
170
|
-
!!method_send(target, matcher)
|
171
|
-
end
|
172
|
-
|
173
|
-
# Guarded version of `public_send` meant to stamp out more
|
174
|
-
# obscure errors when running against non-matching types.
|
175
|
-
#
|
176
|
-
# @param target [Any] Object to send to
|
177
|
-
# @param matcher [respond_to?(:to_sym)] Anything that can be coerced into a method name
|
178
|
-
#
|
179
|
-
# @return [Any] Response of sending to the method, or false if failed
|
180
|
-
private def method_send(target, matcher)
|
181
|
-
matcher.respond_to?(:to_sym) &&
|
182
|
-
target.respond_to?(matcher.to_sym) &&
|
183
|
-
target.public_send(matcher)
|
184
|
-
end
|
185
|
-
|
186
|
-
# Wraps wildcard in case we want to do anything fun with it later
|
187
|
-
#
|
188
|
-
# @param value [Any] Value to test against the wild card
|
189
|
-
#
|
190
|
-
# @note The rescue is because some classes override `==` to do silly things,
|
191
|
-
# like IPAddr, and I kinda want to use that.
|
192
|
-
#
|
193
|
-
# @return [Boolean]
|
194
|
-
private def wildcard_match?(value)
|
195
|
-
value == WILDCARD_MATCH rescue false
|
196
|
-
end
|
197
|
-
|
198
|
-
# Wraps strict checks on keys with a wildcard match
|
199
|
-
#
|
200
|
-
# @param match_target [Hash]
|
201
|
-
# @param match_key [Symbol]
|
202
|
-
# @param match_value [Any]
|
203
|
-
#
|
204
|
-
# @return [Boolean]
|
205
|
-
private def hash_wildcard_match?(match_target, match_key, match_value)
|
206
|
-
return false unless match_target.key?(match_key)
|
207
|
-
|
208
|
-
wildcard_match?(match_value)
|
209
|
-
end
|
210
|
-
|
211
|
-
# Wraps strict checks for methods existing on objects with a wildcard match
|
212
|
-
#
|
213
|
-
# @param match_target [Hash]
|
214
|
-
# @param match_key [Symbol]
|
215
|
-
# @param match_value [Any]
|
216
|
-
#
|
217
|
-
# @return [Boolean]
|
218
|
-
private def object_wildcard_match?(match_target, match_key, match_value)
|
219
|
-
return false unless match_target.respond_to?(match_key)
|
220
|
-
|
221
|
-
wildcard_match?(match_value)
|
222
|
-
end
|
223
|
-
|
224
|
-
# Wraps a case equality statement to make it a bit easier to read. The
|
225
|
-
# typical left bias of `===` can be confusing reading down a page, so
|
226
|
-
# more of a clarity thing than anything. Also makes for nicer stack traces.
|
227
|
-
#
|
228
|
-
# @param match_target [Any] Target to match against
|
229
|
-
# @param match_value [respond_to?(:===)]
|
230
|
-
# Anything that responds to ===, preferably in a unique and entertaining way.
|
231
|
-
#
|
232
|
-
# @return [Boolean]
|
233
|
-
private def case_match?(match_target, match_value)
|
234
|
-
match_value === match_target
|
235
|
-
end
|
236
|
-
|
237
|
-
# Double wraps case match in order to ensure that we try against both Symbol
|
238
|
-
# and String variants of the keys, as this is a very common mixup in Ruby.
|
239
|
-
#
|
240
|
-
# @param match_target [Hash] Target of the match
|
241
|
-
# @param match_key [Symbol] Key to match against
|
242
|
-
# @param match_value [respond_to?(:===)] Matcher
|
243
|
-
#
|
244
|
-
# @return [Boolean]
|
245
|
-
private def hash_case_match?(match_target, match_key, match_value)
|
246
|
-
return true if case_match?(match_target[match_key], match_value)
|
247
|
-
|
248
|
-
match_key.respond_to?(:to_s) &&
|
249
|
-
match_target.key?(match_key.to_s) &&
|
250
|
-
case_match?(match_target[match_key.to_s], match_value)
|
251
|
-
end
|
252
|
-
|
253
|
-
# Attempts to run a case match against a method call derived from a hash
|
254
|
-
# key, and checks the result.
|
255
|
-
#
|
256
|
-
# @param match_target [Hash] Target of the match
|
257
|
-
# @param match_key [Symbol] Method to call
|
258
|
-
# @param match_value [respond_to?(:===)] Matcher
|
259
|
-
#
|
260
|
-
# @return [Boolean]
|
261
|
-
private def hash_method_case_match?(match_target, match_key, match_value)
|
262
|
-
case_match?(method_send(match_target, match_key), match_value)
|
263
|
-
end
|
264
|
-
|
265
|
-
# Defines preconditions for Hash recursion in matching. Currently it's
|
266
|
-
# only Hash and Hash, but may expand later to Arrays and other Enums.
|
267
|
-
#
|
268
|
-
# @param match_target [Any]
|
269
|
-
# @param match_value [Any]
|
270
|
-
#
|
271
|
-
# @return [Boolean]
|
272
|
-
private def hash_should_recurse?(match_target, match_value)
|
273
|
-
match_target.is_a?(Hash) && match_value.is_a?(Hash)
|
274
|
-
end
|
275
|
-
|
276
|
-
# Recurses on nested hashes.
|
277
|
-
#
|
278
|
-
# @param match_target [Hash]
|
279
|
-
# @param match_value [Hash]
|
280
|
-
#
|
281
|
-
# @return [Boolean]
|
282
|
-
private def hash_recurse(match_target, match_value)
|
283
|
-
match_against_hash(match_value).call(match_target)
|
284
|
-
end
|
285
|
-
end
|
286
|
-
end
|