qo 0.1.10 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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.1.10
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-14 00:00:00.000000000 Z
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/guard_block_matcher.rb
121
- - lib/qo/matcher.rb
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
@@ -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