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.
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