accessory 0.1.5 → 0.1.10

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6a3ac947a1bb5743f72e27fca953624b93a0fdab7c94a483e4f2577f3833ccb0
4
- data.tar.gz: 4c8570697b002e4513d2ee117e0ec5290418186e2ea9ad2879a2ba14308b7c25
3
+ metadata.gz: d946b468b04343c14a9a11fc36428673c56d6851365a6b7119db3dec69111dc8
4
+ data.tar.gz: 5f2ea0b5fbd0edf89d1529428030370c2e7407f1a62ca4048bc9a078350db379
5
5
  SHA512:
6
- metadata.gz: 8dc468e225a4150d392727c30d2eb6104fedccc29bcbb22b18320d6b8b3b4afb311a74857540fcb9653d4ad1837f29d4bc1b20800d8d7179d7244c031da3177a
7
- data.tar.gz: ae313d407b95a1079f93ad71adb8296a5c8b3c177bc001a5b9866ebe7945dbb2ac46b152c199d2648ac03afc2bb1609aff6190e6beeea170944ac1fe19d16628
6
+ metadata.gz: a20295b442cb3e2be9c9c7fba21438aa48e759192b24169e475354f72516c1536db19a99fbdc5d41c5b95459183a280d34f5bb2289f149b469e16f8c41027911
7
+ data.tar.gz: 82d72455e0a4f5e0b078966d644da4d0d26a44d015c74347280da273fa3adfba0b194d736a183c816eb6109477cb0a45dbff2d294a2c8b983411823aacc87b1a
@@ -1,14 +1,14 @@
1
1
  module Accessory; end
2
2
 
3
3
  require 'accessory/version'
4
- require 'accessory/lens_path'
5
4
  require 'accessory/lens'
5
+ require 'accessory/bound_lens'
6
6
  require 'accessory/access'
7
7
 
8
8
  module Accessory
9
9
  refine ::Object do
10
10
  def lens(...)
11
- ::Accessory::Lens.on(self, ...)
11
+ ::Accessory::BoundLens.on(self, ...)
12
12
  end
13
13
  end
14
14
  end
@@ -10,87 +10,102 @@ require 'accessory/accessors/all_accessor'
10
10
  require 'accessory/accessors/first_accessor'
11
11
  require 'accessory/accessors/last_accessor'
12
12
 
13
+ ##
14
+ # A set of convenient module-function helpers to use with <tt>Lens[...]</tt>.
15
+ #
16
+ # These functions aren't very convenient unless you
17
+ #
18
+ # include Accessory
19
+
13
20
  module Accessory::Access
14
- # (see Accessory::SubscriptAccessor)
21
+ # (see Accessory::Accessors::SubscriptAccessor)
15
22
  def self.subscript(...)
16
- Accessory::SubscriptAccessor.new(...)
23
+ Accessory::Accessors::SubscriptAccessor.new(...)
17
24
  end
18
25
 
19
- # (see Accessory::AttributeAccessor)
26
+ # (see Accessory::Accessors::AttributeAccessor)
20
27
  def self.attr(...)
21
- Accessory::AttributeAccessor.new(...)
28
+ Accessory::Accessors::AttributeAccessor.new(...)
22
29
  end
23
30
 
24
- # (see Accessory::InstanceVariableAccessor)
31
+ # (see Accessory::Accessors::InstanceVariableAccessor)
25
32
  def self.ivar(...)
26
- Accessory::InstanceVariableAccessor.new(...)
33
+ Accessory::Accessors::InstanceVariableAccessor.new(...)
27
34
  end
28
35
 
29
- # (see Accessory::BetwixtAccessor)
36
+ # (see Accessory::Accessors::BetwixtAccessor)
30
37
  def self.betwixt(...)
31
- Accessory::BetwixtAccessor.new(...)
38
+ Accessory::Accessors::BetwixtAccessor.new(...)
32
39
  end
33
40
 
34
- # Alias for +Accessory::Access.betwixt(0)+. See {Access.betwixt}
41
+ # Alias for +Accessory::Accessors::Access.betwixt(0)+. See {Access.betwixt}
35
42
  def self.before_first
36
43
  self.betwixt(0)
37
44
  end
38
45
 
39
- # Alias for +Accessory::Access.betwixt(-1)+. See {Access.betwixt}
46
+ # Alias for +Accessory::Accessors::Access.betwixt(-1)+. See {Access.betwixt}
40
47
  def self.after_last
41
48
  self.betwixt(-1)
42
49
  end
43
50
 
44
- # (see Accessory::BetweenEachAccessor)
51
+ # (see Accessory::Accessors::BetweenEachAccessor)
45
52
  def self.between_each
46
- Accessory::BetweenEachAccessor.new
53
+ Accessory::Accessors::BetweenEachAccessor.new
47
54
  end
48
55
 
49
- # (see Accessory::AllAccessor)
56
+ # (see Accessory::Accessors::AllAccessor)
50
57
  def self.all
51
- Accessory::AllAccessor.new
58
+ Accessory::Accessors::AllAccessor.new
52
59
  end
53
60
 
54
- # (see Accessory::FirstAccessor)
61
+ # (see Accessory::Accessors::FirstAccessor)
55
62
  def self.first
56
- Accessory::FirstAccessor.new
63
+ Accessory::Accessors::FirstAccessor.new
57
64
  end
58
65
 
59
- # (see Accessory::LastAccessor)
66
+ # (see Accessory::Accessors::LastAccessor)
60
67
  def self.last
61
- Accessory::LastAccessor.new
68
+ Accessory::Accessors::LastAccessor.new
62
69
  end
63
70
 
64
- # (see Accessory::FilterAccessor)
71
+ # (see Accessory::Accessors::FilterAccessor)
65
72
  def self.filter(&pred)
66
- Accessory::FilterAccessor.new(pred)
73
+ Accessory::Accessors::FilterAccessor.new(pred)
67
74
  end
68
75
  end
69
76
 
77
+ ##
78
+ # A set of convenient "fluent API" builder methods
79
+ # that get mixed into {Lens} and {BoundLens}.
80
+ #
81
+ # These do the same thing as the {Access} helper of the same name, but
82
+ # wrap the resulting accessor in a call to <tt>#then</tt>, deriving a new
83
+ # {Lens} or {BoundLens} from the addition of the accessor.
84
+
70
85
  module Accessory::Access::FluentHelpers
71
- # (see Accessory::SubscriptAccessor)
86
+ # (see Accessory::Accessors::SubscriptAccessor)
72
87
  def subscript(...)
73
- self.then(Accessory::SubscriptAccessor.new(...))
88
+ self.then(Accessory::Accessors::SubscriptAccessor.new(...))
74
89
  end
75
90
 
76
91
  # Alias for {#subscript}
77
92
  def [](...)
78
- self.then(Accessory::SubscriptAccessor.new(...))
93
+ self.then(Accessory::Accessors::SubscriptAccessor.new(...))
79
94
  end
80
95
 
81
- # (see Accessory::AttributeAccessor)
96
+ # (see Accessory::Accessors::AttributeAccessor)
82
97
  def attr(...)
83
- self.then(Accessory::AttributeAccessor.new(...))
98
+ self.then(Accessory::Accessors::AttributeAccessor.new(...))
84
99
  end
85
100
 
86
- # (see Accessory::InstanceVariableAccessor)
101
+ # (see Accessory::Accessors::InstanceVariableAccessor)
87
102
  def ivar(...)
88
- self.then(Accessory::InstanceVariableAccessor.new(...))
103
+ self.then(Accessory::Accessors::InstanceVariableAccessor.new(...))
89
104
  end
90
105
 
91
- # (see Accessory::BetwixtAccessor)
106
+ # (see Accessory::Accessors::BetwixtAccessor)
92
107
  def betwixt(...)
93
- self.then(Accessory::BetwixtAccessor.new(...))
108
+ self.then(Accessory::Accessors::BetwixtAccessor.new(...))
94
109
  end
95
110
 
96
111
  # Alias for +#betwixt(0)+. See {#betwixt}
@@ -103,29 +118,29 @@ module Accessory::Access::FluentHelpers
103
118
  self.betwixt(-1)
104
119
  end
105
120
 
106
- # (see Accessory::BetweenEachAccessor)
121
+ # (see Accessory::Accessors::BetweenEachAccessor)
107
122
  def between_each
108
- self.then(Accessory::BetweenEachAccessor.new)
123
+ self.then(Accessory::Accessors::BetweenEachAccessor.new)
109
124
  end
110
125
 
111
- # (see Accessory::AllAccessor)
126
+ # (see Accessory::Accessors::AllAccessor)
112
127
  def all
113
- self.then(Accessory::AllAccessor.new)
128
+ self.then(Accessory::Accessors::AllAccessor.new)
114
129
  end
115
130
 
116
- # (see Accessory::FirstAccessor)
131
+ # (see Accessory::Accessors::FirstAccessor)
117
132
  def first
118
- self.then(Accessory::FirstAccessor.new)
133
+ self.then(Accessory::Accessors::FirstAccessor.new)
119
134
  end
120
135
 
121
- # (see Accessory::LastAccessor)
136
+ # (see Accessory::Accessors::LastAccessor)
122
137
  def last
123
- self.then(Accessory::LastAccessor.new)
138
+ self.then(Accessory::Accessors::LastAccessor.new)
124
139
  end
125
140
 
126
- # (see Accessory::FilterAccessor)
141
+ # (see Accessory::Accessors::FilterAccessor)
127
142
  def filter(&pred)
128
- self.then(Accessory::FilterAccessor.new(pred))
143
+ self.then(Accessory::Accessors::FilterAccessor.new(pred))
129
144
  end
130
145
  end
131
146
 
@@ -133,6 +148,6 @@ class Accessory::Lens
133
148
  include Accessory::Access::FluentHelpers
134
149
  end
135
150
 
136
- class Accessory::LensPath
151
+ class Accessory::BoundLens
137
152
  include Accessory::Access::FluentHelpers
138
153
  end
@@ -19,19 +19,15 @@ module Accessory; end
19
19
  # info):
20
20
  #
21
21
  # * {Accessor#traverse}
22
- # * {Accessor#default_data_constructor}
22
+ # * {Accessor#ensure_valid}
23
23
 
24
24
  class Accessory::Accessor
25
- # @!visibility private
26
- DEFAULT_NOT_SET_SENTINEL = :"98e47971-e708-42ca-bee7-0c62fe5e11c9"
27
-
28
- # @!visibility private
29
25
  TERMINAL_DEFAULT_FN = lambda{ nil }
26
+ private_constant :TERMINAL_DEFAULT_FN
30
27
 
31
28
  # @!visibility private
32
- def initialize(default = nil)
33
- @default_value = default || DEFAULT_NOT_SET_SENTINEL
34
- @succ_default_data_constructor = TERMINAL_DEFAULT_FN
29
+ def initialize(default_value = nil)
30
+ @default_value = default_value
35
31
  end
36
32
 
37
33
  # @!visibility private
@@ -58,7 +54,8 @@ class Accessory::Accessor
58
54
  end
59
55
 
60
56
  # @!visibility private
61
- HIDDEN_IVARS = [:@default_value, :@succ_default_data_constructor]
57
+ HIDDEN_IVARS = [:@default_value, :@successor]
58
+ private_constant :HIDDEN_IVARS
62
59
 
63
60
  # @!visibility private
64
61
  def inspect_args
@@ -69,7 +66,7 @@ class Accessory::Accessor
69
66
  end
70
67
 
71
68
  # @!visibility private
72
- attr_accessor :succ_default_data_constructor
69
+ attr_accessor :successor
73
70
 
74
71
  # @!group Helpers
75
72
 
@@ -81,28 +78,20 @@ class Accessory::Accessor
81
78
  # call <tt>traverse_or_default(data)</tt> within your implementation to safely
82
79
  # get a traversal-result to operate on.
83
80
  #
84
- # This method will return +nil+ if the input-data is +nil+, _without_ calling
85
- # your {traverse} callback. This means that accessors that use
86
- # {traverse_or_default} will _forward_ +nil+ traversal-results along the chain
87
- # without being confused by them.
88
- #
89
- # If your {traverse} callback returns <tt>:error</tt>, a default value will
90
- # be used. This is either the +default+ passed to {initialize} by your
91
- # implementation calling <tt>super(default)</tt>; or it's the result of
92
- # calling {default_data_constructor} on the successor-accessor in the accessor
93
- # chain.
81
+ # The value returned by your {traverse} callback will be validated by your
82
+ # calling the {ensure_valid} callback of the _successor accessor_ in the Lens
83
+ # path. {ensure_valid} will replace any value it considers invalid with a
84
+ # reasonable default. This means you don't need to worry about what will
85
+ # happen if your traversal doesn't find a value and so returns +nil+. The
86
+ # successor's {ensure_valid} will replace that +nil+ with a value that works
87
+ # for it.
94
88
  def traverse_or_default(data)
95
- return nil if data.nil?
89
+ traversal_result = traverse(data) || @default_value
96
90
 
97
- case traverse(data)
98
- in [:ok, traversal_result]
91
+ if @successor
92
+ @successor.ensure_valid(traversal_result)
93
+ else
99
94
  traversal_result
100
- in :error
101
- if DEFAULT_NOT_SET_SENTINEL.equal?(@default_value)
102
- @succ_default_data_constructor.call
103
- else
104
- @default_value
105
- end
106
95
  end
107
96
  end
108
97
 
@@ -124,7 +113,7 @@ class Accessory::Accessor
124
113
  #
125
114
  # @param data [Enumerable] the data yielded by the predecessor accessor.
126
115
  # @param succ [Proc] a thunk to the successor accessor. When {get} is called
127
- # by a {LensPath}, this is passed implicitly.
116
+ # by a {Lens}, this is passed implicitly.
128
117
  # @return [Object] the data to pass back to the predecessor accessor as a
129
118
  # yield result.
130
119
  def get(data, &succ)
@@ -145,7 +134,8 @@ class Accessory::Accessor
145
134
  # of the +get_result+s should replicate the data flow of {get}.
146
135
  # * the +new_value+ should be used to *replace* or *overwrite* the result of
147
136
  # this accessor's traversal within +data+. For example, in
148
- # {SubscriptAccessor}, <tt>data[key] = new_value</tt> is executed.
137
+ # {Accessors::SubscriptAccessor}, <tt>data[key] = new_value</tt> is
138
+ # executed.
149
139
  #
150
140
  # In the <tt>:pop</tt> command case:
151
141
  # * the result of the traversal (*before* the yield) should be returned. This
@@ -153,7 +143,7 @@ class Accessory::Accessor
153
143
  # traversal-results before feeding them into yield, in order to return them
154
144
  # here.
155
145
  # * the traversal-result should be *removed* from +data+. For example, in
156
- # {SubscriptAccessor}, <tt>data.delete(key)</tt> is executed.
146
+ # {Accessors::SubscriptAccessor}, <tt>data.delete(key)</tt> is executed.
157
147
  #
158
148
  # The successor in the accessor chain will receive the yielded
159
149
  # traversal-results as its own +data+.
@@ -171,7 +161,7 @@ class Accessory::Accessor
171
161
  #
172
162
  # @param data [Enumerable] the data yielded by the predecessor accessor.
173
163
  # @param succ [Proc] a thunk to the successor accessor. When {get} is called
174
- # by a {LensPath}, this is passed implicitly.
164
+ # by a {Lens}, this is passed implicitly.
175
165
  # @return [Object] the modification-command to pass back to the predecessor
176
166
  # accessor as a yield result.
177
167
  def get_and_update(data, &succ)
@@ -187,37 +177,40 @@ class Accessory::Accessor
187
177
  # traversal-results.
188
178
  #
189
179
  # This method can assume that +data+ is a valid receiver for the traversal
190
- # it performs. {traverse_or_default} takes care of feeding in a default +data+ in
191
- # the case where the predecessor passed invalid data.
180
+ # it performs. {traverse_or_default} takes care of feeding in a default +data+
181
+ # in the case where the predecessor passed invalid data.
192
182
  #
193
183
  # @param data [Object] the object to be traversed
194
- # @return [Object]
195
- # * <tt>[:ok, traversal_results]</tt> if traversal succeeds
196
- # * +:error+ if traversal fails
184
+ # @return [Object] the result of traversal
197
185
  def traverse(data)
198
186
  raise NotImplementedError, "Accessor subclass #{self.class} must implement #traverse to use #traverse_or_default"
199
187
  end
200
188
 
201
- # Constructs a default value; called by {traverse_or_default}.
189
+ # Ensures that the predecessor accessor's traversal result is one this
190
+ # accessor can operate on; called by {traverse_or_default}.
191
+ #
192
+ # This callback should validate that the +traversal_result+ is one that can
193
+ # be traversed by the traversal-method of this accessor. If it can, the
194
+ # +traversal_result+ should be returned unchanged. If it cannot, an object
195
+ # that _can_ be traversed should be returned instead.
202
196
  #
203
- # Returns a default constructor Proc for the {traverse_or_default} call in
204
- # the *predecessor* accessor to use.
205
197
  #
206
198
  # For example, if your accessor operates on +Enumerable+ values (like
207
- # {AllAccessor}), then a useful default for the predecessor accessor to
208
- # pass you as +data+ would be an Array.
199
+ # {Accessors::AllAccessor}), then this method should validate that the
200
+ # +traversal_result+ is +Enumerable+; and, if it isn't, return something that
201
+ # is — e.g. an empty +Array+.
209
202
  #
210
- # In that case, you can return `lambda{ Array.new }` here. This default
211
- # constructor will be passed along the {LensPath} to the predecessor, which
212
- # will then use it in {traverse_or_default} if it was not configured with
213
- # an explicit default.
203
+ # This logic is used to replace invalid intermediate values (e.g. `nil`s and
204
+ # scalars) with containers during {Lens#put_in} et al.
214
205
  #
215
- # @return [Proc] a Proc that, when called, produces a default traversal-result
216
- def default_data_constructor
206
+ # @return [Object] a now-valid traversal result
207
+ def ensure_valid(traversal_result)
217
208
  lambda do
218
- raise NotImplementedError, "Accessor subclass #{self.class} must implement #default_data_constructor to allow chain-predecessor to use #traverse_or_default"
209
+ raise NotImplementedError, "Accessor subclass #{self.class} must implement #ensure_valid to allow chain-predecessor to use #traverse_or_default"
219
210
  end
220
211
  end
221
212
 
222
213
  # @!endgroup
223
214
  end
215
+
216
+ module Accessory::Accessors; end
@@ -5,7 +5,7 @@ require 'accessory/accessor'
5
5
  #
6
6
  # *Aliases*
7
7
  # * {Access.all}
8
- # * {Access::FluentHelpers#all} (included in {LensPath} and {Lens})
8
+ # * {Access::FluentHelpers#all} (included in {Lens} and {BoundLens})
9
9
  #
10
10
  # *Equivalents* in Elixir's {https://hexdocs.pm/elixir/Access.html +Access+} module
11
11
  # * {https://hexdocs.pm/elixir/Access.html#all/0 +Access.all/0+}
@@ -15,10 +15,14 @@ require 'accessory/accessor'
15
15
  #
16
16
  # * +Array.new+
17
17
 
18
- class Accessory::AllAccessor < Accessory::Accessor
18
+ class Accessory::Accessors::AllAccessor < Accessory::Accessor
19
19
  # @!visibility private
20
- def default_data_constructor
21
- lambda{ Array.new }
20
+ def ensure_valid(traversal_result)
21
+ if traversal_result.kind_of?(Enumerable)
22
+ traversal_result
23
+ else
24
+ []
25
+ end
22
26
  end
23
27
 
24
28
  # @!visibility private
@@ -46,17 +50,28 @@ class Accessory::AllAccessor < Accessory::Accessor
46
50
  def get_and_update(data)
47
51
  results = []
48
52
  new_data = []
53
+ dirty = false
49
54
 
50
55
  (data || []).each do |pos|
51
56
  case yield(pos)
52
- in [result, new_value]
57
+ in [:clean, result, _]
58
+ results.push(result)
59
+ new_data.push(pos)
60
+ # ok
61
+ in [:dirty, result, new_value]
53
62
  results.push(result)
54
63
  new_data.push(new_value)
64
+ dirty = true
55
65
  in :pop
56
66
  results.push(pos)
67
+ dirty = true
57
68
  end
58
69
  end
59
70
 
60
- [results, new_data]
71
+ if dirty
72
+ [:dirty, results, new_data]
73
+ else
74
+ [:clean, results, data]
75
+ end
61
76
  end
62
77
  end
@@ -8,18 +8,18 @@ require 'accessory/accessor'
8
8
  # the getter/setter pair <tt>.foo</tt> and <tt>.foo=</tt>.
9
9
  #
10
10
  # The abstract "attribute" does not have to correspond to an actual
11
- # +attr_accessor+; the {AttributeAccessor} will work as long as the relevant
12
- # named getter/setter methods exist on the receiver.
11
+ # +attr_accessor+; the AttributeAccessor will work as long as the
12
+ # relevant named getter/setter methods exist on the receiver.
13
13
  #
14
14
  # *Aliases*
15
15
  # * {Access.attr}
16
- # * {Access::FluentHelpers#attr} (included in {LensPath} and {Lens})
16
+ # * {Access::FluentHelpers#attr} (included in {Lens} and {BoundLens})
17
17
  #
18
18
  # <b>Default constructor</b> used by predecessor accessor
19
19
  #
20
20
  # * +OpenStruct.new+
21
21
 
22
- class Accessory::AttributeAccessor < Accessory::Accessor
22
+ class Accessory::Accessors::AttributeAccessor < Accessory::Accessor
23
23
  # @param attr_name [Symbol] the attribute name (i.e. name of the getter method)
24
24
  # @param default [Object] the default to use if the predecessor accessor passes +nil+ data
25
25
  def initialize(attr_name, default: nil)
@@ -47,8 +47,10 @@ class Accessory::AttributeAccessor < Accessory::Accessor
47
47
  end
48
48
 
49
49
  # @!visibility private
50
- def default_data_constructor
51
- lambda do
50
+ def ensure_valid(traversal_result)
51
+ if traversal_result
52
+ traversal_result
53
+ else
52
54
  require 'ostruct'
53
55
  OpenStruct.new
54
56
  end
@@ -64,7 +66,7 @@ class Accessory::AttributeAccessor < Accessory::Accessor
64
66
  # @param data [Object] the object to traverse
65
67
  # @return [Object] the value derived from the rest of the accessor chain
66
68
  def get(data)
67
- value = value_or_default(data)
69
+ value = traverse_or_default(data)
68
70
 
69
71
  if block_given?
70
72
  yield(value)
@@ -83,15 +85,17 @@ class Accessory::AttributeAccessor < Accessory::Accessor
83
85
  # @param data [Object] the object to traverse
84
86
  # @return [Array] a two-element array containing 1. the original value found; and 2. the result value from the accessor chain
85
87
  def get_and_update(data)
86
- value = value_or_default(data)
88
+ value = traverse_or_default(data)
87
89
 
88
90
  case yield(value)
89
- in [result, new_value]
91
+ in [:clean, result, _]
92
+ [:clean, result, data]
93
+ in [:dirty, result, new_value]
90
94
  data.send(@setter_method_name, new_value)
91
- [result, data]
95
+ [:dirty, result, data]
92
96
  in :pop
93
97
  data.send(@setter_method_name, nil)
94
- [value, data]
98
+ [:dirty, value, data]
95
99
  end
96
100
  end
97
101
  end