qo 0.5.0 → 0.99.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.
@@ -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