qo 0.5.0 → 0.99.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,3 @@
1
1
  module Qo
2
- VERSION = '0.5.0'
2
+ VERSION = '0.99.0'
3
3
  end
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.5.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: 2018-08-09 00:00:00.000000000 Z
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/helpers.rb
211
- - lib/qo/matchers/array_matcher.rb
212
- - lib/qo/matchers/base_matcher.rb
213
- - lib/qo/matchers/guard_block_matcher.rb
214
- - lib/qo/matchers/hash_matcher.rb
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
- rubyforge_project:
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
@@ -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