dio 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2bdc81fcaa60149f840667355881ac6a53875ef032ca860a1bd8c695ecb5557e
4
- data.tar.gz: 7916ffd72b1a56a65425e1e43a91c48d788d44e3866c265826425f43c84f53e1
3
+ metadata.gz: b059e97a8c1f5e6d1d68b445e51c553c14657ec837de6b61bbe1ce6bd8ccc106
4
+ data.tar.gz: 327197a40cc4cc1a3e0f814c60bb0f78bab24bff8b8012d69430a93454712ad2
5
5
  SHA512:
6
- metadata.gz: abc3478a6776c6a37ce181437fe168ca54eae42f5919f48b17e8a331641fd1038fe12a465ad70a52ad4e5f69cef239c1ffa11f72ef05875fe908e0d2b0b532ba
7
- data.tar.gz: a8c94fcda04a3d2e9cd88f89107cf63f860f56c0d16b80a3c2fbf0a8788455d93fff5a35425378669e5c948287d9201da375c75f536ad32a43181cfa86e9ad06
6
+ metadata.gz: c9fd3543e1c9fca221b21db3629467b1f481c41e0a8191057e1d72bcfe1d190e859e902453e7292b45bb11a48ba56328582ff80d2dc10926aeca3d9a8b0dac11
7
+ data.tar.gz: '0194555bdc1bc59719e7c09167c26a7159ceff987dbb200754623694e3d49b2e1b87cf0ded4bfb4ac4af2eee05a1df9e2cb6f160bb95e889b6be3705caf55cdf'
data/.gitignore CHANGED
@@ -7,5 +7,8 @@
7
7
  /spec/reports/
8
8
  /tmp/
9
9
 
10
+ # Gem builds
11
+ *.gem
12
+
10
13
  # rspec failure tracking
11
14
  .rspec_status
@@ -0,0 +1,20 @@
1
+ # Changelog
2
+
3
+ ## Unreleased
4
+
5
+ * None
6
+
7
+ ## 0.0.3 - 01/17/2021
8
+
9
+ * Forwarders introduced
10
+ * Base concept of Forwarder and two new Forwarders introduced
11
+ * Attribute Forwarder to only work on `attr_*` methods, narrowing scope
12
+ * String Hash Forwarder to work on String keyed Hashes
13
+
14
+ ## 0.0.2 - 01/17/2021
15
+
16
+ * [Patch 1](https://github.com/baweaver/dio/issues/1) - Lock Ruby version to 3.x
17
+
18
+ ## 0.0.1 - 01/17/2021
19
+
20
+ * Initial release
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- dio (0.0.1)
4
+ dio (0.0.3)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -15,8 +15,166 @@ match against it.
15
15
 
16
16
  ## Usage
17
17
 
18
- Further usage instructions will be documented at a later date, see
19
- `spec/dio_spec.rb` for ideas in the mean time.
18
+ There are three core types of Forwarders, the center of how Dio works:
19
+
20
+ 1. **Dynamic** - Uses `public_send` for `Hash` matches, and `Array` method coercion for `Array` matches
21
+ 2. **Attribute** - Uses `attr_*` methods as source of match data
22
+ 3. **String Hash** - Treats `String` Hashes like `Symbol` ones for the purpose of matching.
23
+
24
+ Let's take a look at each of them.
25
+
26
+ ### Dynamic Forwarder
27
+
28
+ Used with `Dio.dynamic` or `Dio[]`, the default forwarder. This uses `public_send` to extract attributes for pattern matching.
29
+
30
+ #### With Hash Matching
31
+
32
+ With an `Integer` this might look like this:
33
+
34
+ ```ruby
35
+ Dio[1] in { succ: { succ: { succ: 4 } } }
36
+ # => true
37
+ ```
38
+
39
+ This has the same result as calling `1.succ.succ.succ` with each nested `Hash` in the pattern match working on the next value. That means it can also be used to do this:
40
+
41
+ ```ruby
42
+ Dio[1] in {
43
+ succ: { chr: "\x02", to_s: '2' },
44
+ to_s: '1'
45
+ }
46
+ ```
47
+
48
+ #### With Array Matching
49
+
50
+ If the object under the wrapper provides a method that can be used to coerce the value into an `Array` it can be used for an `Array` match.
51
+
52
+ Those methods are: `to_a`, `to_ary`, and `map`.
53
+
54
+ Given a `Node` class with a value and a set of children:
55
+
56
+ ```ruby
57
+ Node = Struct.new(:value, :children)
58
+ ```
59
+
60
+ We can match against it as if it were capable of natively pattern matching:
61
+
62
+ ```ruby
63
+ tree = Node[1,
64
+ Node[2, Node[3, Node[4]]],
65
+ Node[5],
66
+ Node[6, Node[7], Node[8]]
67
+ ]
68
+
69
+ case Dio.dynamic(tree)
70
+ in [1, [*, [5, _], *]]
71
+ true
72
+ else
73
+ false
74
+ end
75
+ ```
76
+
77
+ ### Attribute Forwarder
78
+
79
+ Attribute Forwarders are more conservative than Dynamic ones as they only work on public attributes, or those that are defined with `attr_*`. In the case of this class:
80
+
81
+ ```ruby
82
+ class Person
83
+ attr_reader :name, :age, :children
84
+
85
+ def initialize(name:, age:, children: [])
86
+ @name = name
87
+ @age = age
88
+ @children = children
89
+ end
90
+ end
91
+ ```
92
+
93
+ ...the attributes available would be `name`, `age`, and `children`. This also means that you can dive into `children` as well.
94
+
95
+ #### With Hash Matching
96
+
97
+ Let's say we had Alice here:
98
+
99
+ ```ruby
100
+ Person.new(
101
+ name: 'Alice',
102
+ age: 40,
103
+ children: [
104
+ Person.new(name: 'Jim', age: 10),
105
+ Person.new(name: 'Jill', age: 10)
106
+ ]
107
+ )
108
+ ```
109
+
110
+ With Hash style matching we can do this:
111
+
112
+ ```ruby
113
+ case Dio.attribute(alice)
114
+ in { name: /^A/, age: 30..50 }
115
+ true
116
+ else
117
+ false
118
+ end
119
+ ```
120
+
121
+ ...which, as pattern matches use `===` lets us use a lot of other fun things. We can even go deeper into searching through the `children` attribute:
122
+
123
+ ```ruby
124
+ case Dio.attribute(alice)
125
+ in { children: [*, { name: /^J/ }, *] }
126
+ true
127
+ else
128
+ false
129
+ end
130
+ ```
131
+
132
+ #### With Array Matching
133
+
134
+ This one is a bit more spurious, as it applies the attributes in the name it sees them. For something like our `Node` above with two attributes it will work the same as `dynamic`.
135
+
136
+ ### String Hash Forwarder
137
+
138
+ Pattern Matching cannot apply to `String` keys, which can be annoying when working with data and not wanting to deep transform it into `Symbol` keys. The String Hash Forwarder tries to address this.
139
+
140
+ #### With Hash Matching
141
+
142
+ Let's say we had the following `Hash`:
143
+
144
+ ```ruby
145
+ hash = {
146
+ 'a' => 1,
147
+ 'b' => 2,
148
+ 'c' => {
149
+ 'd' => 3,
150
+ 'e' => {
151
+ 'f' => 4
152
+ }
153
+ }
154
+ }
155
+ ```
156
+
157
+ We can match against it by using the `Symbol` equivalents of our `String` keys:
158
+
159
+ ```ruby
160
+ case Dio.string_hash(hash)
161
+ in { a: 1, b: 2 }
162
+ true
163
+ else
164
+ false
165
+ end
166
+ ```
167
+
168
+ ...and because of the nature of Dio you can continue to dive deeper:
169
+
170
+ ```ruby
171
+ case Dio.string_hash(hash)
172
+ in { a: 1, b: 2, c: { d: 1..10, e: { f: 3.. } } }
173
+ true
174
+ else
175
+ false
176
+ end
177
+ ```
20
178
 
21
179
  ## Installation
22
180
 
data/lib/dio.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'dio/version'
2
2
  require 'dio/public_api'
3
3
  require 'dio/errors'
4
- require 'dio/dive_forwarder'
4
+ require 'dio/forwarders'
5
5
 
6
6
  module Dio
7
7
  extend PublicApi
@@ -11,5 +11,17 @@ module Dio
11
11
 
12
12
  def initialize(msg=MSG) = super
13
13
  end
14
+
15
+ # Error raised when no deconstruction method is available on an object being
16
+ # treated like an Array deconstruction.
17
+ #
18
+ # @author [baweaver]
19
+ # @since 0.0.1
20
+ class UnknownAttributesProvided < ArgumentError
21
+ # Error message
22
+ MSG = 'Unknown attribute arguments provided to method'
23
+
24
+ def initialize(attributes) = super("#{MSG}: #{attributes.join(', ')}")
25
+ end
14
26
  end
15
27
  end
@@ -0,0 +1,8 @@
1
+ require 'dio/forwarders/base_forwarder'
2
+ require 'dio/forwarders/attribute_forwarder'
3
+ require 'dio/forwarders/string_hash_forwarder'
4
+
5
+ module Dio
6
+ module Forwarders
7
+ end
8
+ end
@@ -0,0 +1,76 @@
1
+ require 'delegate'
2
+
3
+ module Dio
4
+ module Forwarders
5
+ # A more controlled forwarder that targets only `attr_` type methods like
6
+ # `attr_reader` and `attr_accessor`. These attributes are found by diffing
7
+ # instance variables and method names to find generated accessor methods.
8
+ #
9
+ # It should be noted that this could be faster if there was an assumption
10
+ # of purity at the time of the match. I may make another variant for that.
11
+ #
12
+ # @author [baweaver]
13
+ # @since 0.0.3
14
+ #
15
+ class AttributeForwarder < BaseForwarder
16
+ # Wrapper for creating a new Forwarder
17
+ NEW_DIVE = -> v { new(v) }
18
+
19
+ def initialize(base_object)
20
+ ivars = Set.new base_object
21
+ .instance_variables
22
+ .map { _1.to_s.delete('@').to_sym }
23
+
24
+ all_methods = Set.new base_object.methods
25
+
26
+ @attributes = ivars.intersection(all_methods)
27
+
28
+ super
29
+ end
30
+
31
+ # Deconstructs from a list of attribute names, wrapping each value
32
+ # in a new dive in case the pattern match goes further than one level.
33
+ #
34
+ # @return [Array]
35
+ def deconstruct
36
+ return @base_object.map(&NEW_DIVE) if @base_object.is_a?(Array)
37
+
38
+ @attributes.map { NEW_DIVE[@base_object.public_send(_1)] }
39
+ end
40
+
41
+ # Deconstructs attributes from an object, wrapping each value in a new
42
+ # dive in case the pattern match goes further than one level.
43
+ #
44
+ # @param keys [Array]
45
+ # Keys to be extracted, diffed against possible attributes
46
+ #
47
+ # @raises [Dio::Errors::UnknownAttributesProvided]
48
+ # Guard against unknown attributes without an associated accessor
49
+ # method
50
+ #
51
+ # @return [Hash]
52
+ def deconstruct_keys(keys)
53
+ key_set = Set.new(keys)
54
+ unknown_keys = key_set - @attributes
55
+
56
+ if unknown_keys.any?
57
+ raise Dio::Errors::UnknownAttributesProvided.new(unknown_keys)
58
+ end
59
+
60
+ known_keys = @attributes.intersection(key_set)
61
+
62
+ known_keys.to_h { [_1, NEW_DIVE[@base_object.public_send(_1)]] }
63
+ end
64
+
65
+ # Unwrapped context, aliased afterwards to use Ruby's delegator interface
66
+ #
67
+ # @return [Any]
68
+ # Originally wrapped object
69
+ def value
70
+ @base_object
71
+ end
72
+
73
+ alias_method :__getobj__, :value
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,131 @@
1
+ require 'delegate'
2
+
3
+ module Dio
4
+ module Forwarders
5
+ # Allows for Pattern Matching against arbitrary objects by wrapping them
6
+ # in an interface that understands methods of deconstructing objects.
7
+ #
8
+ # **Approximating Deconstruction**
9
+ #
10
+ # As Ruby does not, by default, define `deconstruct` and `deconstruct_keys` on
11
+ # objects this class attempts to approximate them.
12
+ #
13
+ # This class does, however, do something unique in treating `deconstruct_keys`
14
+ # as a series of calls to sent to the class and its "nested" values.
15
+ #
16
+ # **Demonstrating Nested Values**
17
+ #
18
+ # Consider an integer:
19
+ #
20
+ # ```ruby
21
+ # Dio[1] in { succ: { succ: { succ: 4 } } }
22
+ # # => true
23
+ # ```
24
+ #
25
+ # It has no concept of deconstruction, except in that its `succ` method returns
26
+ # a "nested" value we can match against, allowing us to "dive into" the
27
+ # object, diving us our namesake Dio, or "Dive Into Object"
28
+ #
29
+ # **Delegation**
30
+ #
31
+ # As with most monadic-like design patterns that add additional behavior by
32
+ # wrapping objects we need to extract the value at the end to do anything
33
+ # particularly useful.
34
+ #
35
+ # By adding delegation to this class we have a cheat around this in that
36
+ # any method called on the nested DiveForwarder instances will call through
37
+ # to the associated base object instead.
38
+ #
39
+ # I am not 100% sold on this approach, and will consider it more in the
40
+ # future.
41
+ #
42
+ # @author [baweaver]
43
+ #
44
+ class BaseForwarder < ::Delegator
45
+ # Wrapper for creating a new Forwarder
46
+ NEW_DIVE = -> v { new(v) }
47
+
48
+ # Creates a new delegator that understands the pattern matching interface
49
+ #
50
+ # @param base_object [Any]
51
+ # Any object that does not necessarily understand pattern matching
52
+ #
53
+ # @return [DiveForwarder]
54
+ def initialize(base_object)
55
+ @base_object = base_object
56
+ end
57
+
58
+ # Approximation of an Array deconstruction:
59
+ #
60
+ # ```ruby
61
+ # [1, 2, 3] in [*, 2, *]
62
+ # ```
63
+ #
64
+ # Attempts to find a reasonable interface by which to extract values
65
+ # to be matched. If an object that knows how to match already is sent
66
+ # through wrap its child values for deeper matching.
67
+ #
68
+ # Current interface will work with `to_a`, `to_ary`, `map`, and values
69
+ # that can already `deconstruct`. If others are desired please submit a PR
70
+ # to add them
71
+ #
72
+ # @raises [Dio::Errors::NoDeconstructionMethod]
73
+ # If no method of deconstruction exists, an exception is raised to
74
+ # communicate the proper interface and note the abscense of a current one.
75
+ #
76
+ # @return [Array[DiveForwarder]]
77
+ # Values lifted into a Dio context for further matching
78
+ def deconstruct
79
+ return @base_object.deconstruct.map!(&NEW_DIVE) if @base_object.respond_to?(:deconstruct)
80
+
81
+ return @base_object.to_a.map!(&NEW_DIVE) if @base_object.respond_to?(:to_a)
82
+ return @base_object.to_ary.map!(&NEW_DIVE) if @base_object.respond_to?(:to_ary)
83
+ return @base_object.map(&NEW_DIVE) if @base_object.respond_to?(:map)
84
+
85
+ raise Dio::Errors::NoDeconstructionMethod
86
+ end
87
+
88
+ # Approximates `deconstruct_keys` for Hashes, except in adding `Qo`-like
89
+ # behavior that allows to treat objects as "nested values" through their
90
+ # method calls.
91
+ #
92
+ # **Deconstructing an Object**
93
+ #
94
+ # In `Qo` one could match against an object by calling to its methods using
95
+ # `public_send`. This allowed one to "dive into" an object through a series
96
+ # of method calls, approximating a Hash pattern match.
97
+ #
98
+ # **Native Behavior**
99
+ #
100
+ # If the object already responds to `deconstruct_keys` this method will
101
+ # behave similarly to `deconstruct` and wrap its values as new
102
+ # `DiveForwarder` contexts.
103
+ #
104
+ # @param keys [Array]
105
+ # Keys to be extracted from the object
106
+ #
107
+ # @return [Hash]
108
+ # Deconstructed keys pointing to associated values extracted from a Hash
109
+ # or an Object. Note that these values are matched against using `===`.
110
+ def deconstruct_keys(keys)
111
+ if @base_object.respond_to?(:deconstruct_keys)
112
+ @base_object
113
+ .deconstruct_keys(keys)
114
+ .transform_values!(&NEW_DIVE)
115
+ else
116
+ keys.to_h { |k| @base_object.public_send(k).then { |v| [k, NEW_DIVE[v]] } }
117
+ end
118
+ end
119
+
120
+ # Unwrapped context, aliased afterwards to use Ruby's delegator interface
121
+ #
122
+ # @return [Any]
123
+ # Originally wrapped object
124
+ def value
125
+ @base_object
126
+ end
127
+
128
+ alias_method :__getobj__, :value
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,63 @@
1
+ require 'delegate'
2
+
3
+ module Dio
4
+ module Forwarders
5
+ # Pattern Matching relies on Symbol keys for Hash matching, but a significant
6
+ # number of Ruby Hashes are keyed with Strings. This forwarder addresses
7
+ # that issue by transforming String keys into Symbols for the sake of matching
8
+ # against them:
9
+ #
10
+ # ```ruby
11
+ # Dio.string_hash({ 'a' => 1 }) in { a: 1 }
12
+ # # => true
13
+ # ```
14
+ #
15
+ # @author [baweaver]
16
+ # @since 0.0.3
17
+ #
18
+ class StringHashForwarder < BaseForwarder
19
+ # Wrapper for creating a new Forwarder
20
+ NEW_DIVE = -> v { new(v) }
21
+
22
+ # If an Array is provided from nested values we may want to match against
23
+ # it as well. Wrap the values in new forwarders to accomodate this.
24
+ #
25
+ # @raises [Dio::Errors::NoDeconstructionMethod]
26
+ # Debating on this one. If the base is not an Array it complicates how
27
+ # to Array match against a Hash. Raise an error for now, consider
28
+ # revisiting later.
29
+ #
30
+ # @return [Array]
31
+ def deconstruct
32
+ return @base_object.map(&NEW_DIVE) if @base_object.is_a?(Array)
33
+
34
+ # Debating on this one
35
+ raise Dio::Errors::NoDeconstructionMethod
36
+ end
37
+
38
+ # Extracts keys from a Hash by treating the provided keys like Strings.
39
+ # Return the base object with keys transformed to Symbols to accomodate
40
+ # pattern matching features.
41
+ #
42
+ # @param keys [Array]
43
+ # Keys to extract
44
+ #
45
+ # @return [Hash]
46
+ def deconstruct_keys(keys)
47
+ @base_object
48
+ .slice(*keys.map(&:to_s))
49
+ .to_h { |k, v| [k.to_sym, NEW_DIVE[v]] }
50
+ end
51
+
52
+ # Unwrapped context, aliased afterwards to use Ruby's delegator interface
53
+ #
54
+ # @return [Any]
55
+ # Originally wrapped object
56
+ def value
57
+ @base_object
58
+ end
59
+
60
+ alias_method :__getobj__, :value
61
+ end
62
+ end
63
+ end
@@ -1,4 +1,8 @@
1
1
  module Dio
2
+ # Public API for Dio
3
+ #
4
+ # @author [baweaver]
5
+ # @since 0.0.1
2
6
  module PublicApi
3
7
  # Treats `[]` like an alternative constructor and forwards to `DiveForwarder`
4
8
  #
@@ -7,6 +11,31 @@ module Dio
7
11
  #
8
12
  # @return [Dio::DiveForwarder]
9
13
  # Dio pattern matching interface
10
- def [](...) = Dio::DiveForwarder.new(...)
14
+ def [](...) = Dio::Forwarders::BaseForwarder.new(...)
15
+
16
+ # Dynamic Forwarder, uses `public_send` for Hash forwarding
17
+ #
18
+ # @param ... [Any]
19
+ # Arguments to match against
20
+ #
21
+ # @return [Dio::Forwarders::BaseForwarder]
22
+ def dynamic(...) = Dio::Forwarders::BaseForwarder.new(...)
23
+
24
+ # Attribute Forwarder, extracts `attr_*` methods to match against
25
+ #
26
+ # @param ... [Any]
27
+ # Arguments to match against
28
+ #
29
+ # @return [Dio::Forwarders::AttributeForwarder]
30
+ def attribute(...) = Dio::Forwarders::AttributeForwarder.new(...)
31
+
32
+ # String Hash Forwarder, treats a String Hash like a Symbol Hash for
33
+ # matching against.
34
+ #
35
+ # @param ... [Any]
36
+ # Arguments to match against
37
+ #
38
+ # @return [Dio::Forwarders::StringHashForwarder]
39
+ def string_hash(...) = Dio::Forwarders::StringHashForwarder.new(...)
11
40
  end
12
41
  end
@@ -1,3 +1,3 @@
1
1
  module Dio
2
- VERSION = '0.0.2'.freeze
2
+ VERSION = '0.0.3'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dio
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brandon Weaver
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-01-17 00:00:00.000000000 Z
11
+ date: 2021-01-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -75,6 +75,7 @@ extra_rdoc_files: []
75
75
  files:
76
76
  - ".gitignore"
77
77
  - ".rspec"
78
+ - CHANGELOG.md
78
79
  - CODE_OF_CONDUCT.md
79
80
  - Gemfile
80
81
  - Gemfile.lock
@@ -86,8 +87,11 @@ files:
86
87
  - bin/setup
87
88
  - dio.gemspec
88
89
  - lib/dio.rb
89
- - lib/dio/dive_forwarder.rb
90
90
  - lib/dio/errors.rb
91
+ - lib/dio/forwarders.rb
92
+ - lib/dio/forwarders/attribute_forwarder.rb
93
+ - lib/dio/forwarders/base_forwarder.rb
94
+ - lib/dio/forwarders/string_hash_forwarder.rb
91
95
  - lib/dio/public_api.rb
92
96
  - lib/dio/version.rb
93
97
  homepage: https://www.github.com/baweaver/dio
@@ -1,129 +0,0 @@
1
- require 'delegate'
2
-
3
- module Dio
4
- # Allows for Pattern Matching against arbitrary objects by wrapping them
5
- # in an interface that understands methods of deconstructing objects.
6
- #
7
- # **Approximating Deconstruction**
8
- #
9
- # As Ruby does not, by default, define `deconstruct` and `deconstruct_keys` on
10
- # objects this class attempts to approximate them.
11
- #
12
- # This class does, however, do something unique in treating `deconstruct_keys`
13
- # as a series of calls to sent to the class and its "nested" values.
14
- #
15
- # **Demonstrating Nested Values**
16
- #
17
- # Consider an integer:
18
- #
19
- # ```ruby
20
- # Dio[1] in { succ: { succ: { succ: 4 } } }
21
- # # => true
22
- # ```
23
- #
24
- # It has no concept of deconstruction, except in that its `succ` method returns
25
- # a "nested" value we can match against, allowing us to "dive into" the
26
- # object, diving us our namesake Dio, or "Dive Into Object"
27
- #
28
- # **Delegation**
29
- #
30
- # As with most monadic-like design patterns that add additional behavior by
31
- # wrapping objects we need to extract the value at the end to do anything
32
- # particularly useful.
33
- #
34
- # By adding delegation to this class we have a cheat around this in that
35
- # any method called on the nested DiveForwarder instances will call through
36
- # to the associated base object instead.
37
- #
38
- # I am not 100% sold on this approach, and will consider it more in the
39
- # future.
40
- #
41
- # @author [baweaver]
42
- #
43
- class DiveForwarder < ::Delegator
44
- # Wrapper for creating a new DiveForwarder
45
- NEW_DIVE = -> v { DiveForwarder.new(v) }
46
-
47
- # Creates a new delegator that understands the pattern matching interface
48
- #
49
- # @param base_object [Any]
50
- # Any object that does not necessarily understand pattern matching
51
- #
52
- # @return [DiveForwarder]
53
- def initialize(base_object)
54
- @base_object = base_object
55
- end
56
-
57
- # Approximation of an Array deconstruction:
58
- #
59
- # ```ruby
60
- # [1, 2, 3] in [*, 2, *]
61
- # ```
62
- #
63
- # Attempts to find a reasonable interface by which to extract values
64
- # to be matched. If an object that knows how to match already is sent
65
- # through wrap its child values for deeper matching.
66
- #
67
- # Current interface will work with `to_a`, `to_ary`, `map`, and values
68
- # that can already `deconstruct`. If others are desired please submit a PR
69
- # to add them
70
- #
71
- # @raises [Dio::Errors::NoDeconstructionMethod]
72
- # If no method of deconstruction exists, an exception is raised to
73
- # communicate the proper interface and note the abscense of a current one.
74
- #
75
- # @return [Array[DiveForwarder]]
76
- # Values lifted into a Dio context for further matching
77
- def deconstruct
78
- return @base_object.deconstruct.map!(&NEW_DIVE) if @base_object.respond_to?(:deconstruct)
79
-
80
- return @base_object.to_a.map!(&NEW_DIVE) if @base_object.respond_to?(:to_a)
81
- return @base_object.to_ary.map!(&NEW_DIVE) if @base_object.respond_to?(:to_ary)
82
- return @base_object.map(&NEW_DIVE) if @base_object.respond_to?(:map)
83
-
84
- raise Dio::Errors::NoDeconstructionMethod
85
- end
86
-
87
- # Approximates `deconstruct_keys` for Hashes, except in adding `Qo`-like
88
- # behavior that allows to treat objects as "nested values" through their
89
- # method calls.
90
- #
91
- # **Deconstructing an Object**
92
- #
93
- # In `Qo` one could match against an object by calling to its methods using
94
- # `public_send`. This allowed one to "dive into" an object through a series
95
- # of method calls, approximating a Hash pattern match.
96
- #
97
- # **Native Behavior**
98
- #
99
- # If the object already responds to `deconstruct_keys` this method will
100
- # behave similarly to `deconstruct` and wrap its values as new
101
- # `DiveForwarder` contexts.
102
- #
103
- # @param keys [Array]
104
- # Keys to be extracted from the object
105
- #
106
- # @return [Hash]
107
- # Deconstructed keys pointing to associated values extracted from a Hash
108
- # or an Object. Note that these values are matched against using `===`.
109
- def deconstruct_keys(keys)
110
- if @base_object.respond_to?(:deconstruct_keys)
111
- @base_object
112
- .deconstruct_keys(*keys)
113
- .transform_values!(&NEW_DIVE)
114
- else
115
- keys.to_h { |k| @base_object.public_send(k).then { |v| [k, NEW_DIVE[v]] } }
116
- end
117
- end
118
-
119
- # Unwrapped context, aliased afterwards to use Ruby's delegator interface
120
- #
121
- # @return [Any]
122
- # Originally wrapped object
123
- def value
124
- @base_object
125
- end
126
-
127
- alias_method :__getobj__, :value
128
- end
129
- end