accessory 0.1.5 → 0.1.6

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: 9880e7e5ea88c0b373a1cc3d5d4861788f368905c761b2ec3a7236d7282ada39
4
+ data.tar.gz: 7386d98dc728a663904564e0a6825de0d7690e806950e5e95d6e8b39d4d20cd6
5
5
  SHA512:
6
- metadata.gz: 8dc468e225a4150d392727c30d2eb6104fedccc29bcbb22b18320d6b8b3b4afb311a74857540fcb9653d4ad1837f29d4bc1b20800d8d7179d7244c031da3177a
7
- data.tar.gz: ae313d407b95a1079f93ad71adb8296a5c8b3c177bc001a5b9866ebe7945dbb2ac46b152c199d2648ac03afc2bb1609aff6190e6beeea170944ac1fe19d16628
6
+ metadata.gz: dbee544b8dc699adb962559b98278d48edeed2a4d5b9c4d3c6c92ad3c4a38ffb4df5ced18274b465dfe68fd34dd15a50c06c2febae4e54049a4049d377cb10cd
7
+ data.tar.gz: 0d1be0f43650e87a1616b5c2ac968b7407c5d8e380abb7e30ad84823816d486341a2c1b791814c55695dc77f2bf0337df834816c2a703bacbb2a3f582f362907
@@ -1,14 +1,17 @@
1
+ ##
2
+ #
3
+
1
4
  module Accessory; end
2
5
 
3
6
  require 'accessory/version'
4
- require 'accessory/lens_path'
5
7
  require 'accessory/lens'
8
+ require 'accessory/bound_lens'
6
9
  require 'accessory/access'
7
10
 
8
11
  module Accessory
9
12
  refine ::Object do
10
13
  def lens(...)
11
- ::Accessory::Lens.on(self, ...)
14
+ ::Accessory::BoundLens.on(self, ...)
12
15
  end
13
16
  end
14
17
  end
@@ -10,6 +10,13 @@ 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
21
  # (see Accessory::SubscriptAccessor)
15
22
  def self.subscript(...)
@@ -67,6 +74,14 @@ module Accessory::Access
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
86
  # (see Accessory::SubscriptAccessor)
72
87
  def subscript(...)
@@ -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
@@ -22,16 +22,12 @@ module Accessory; end
22
22
  # * {Accessor#default_data_constructor}
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
 
@@ -92,17 +89,12 @@ class Accessory::Accessor
92
89
  # calling {default_data_constructor} on the successor-accessor in the accessor
93
90
  # chain.
94
91
  def traverse_or_default(data)
95
- return nil if data.nil?
92
+ traversal_result = traverse(data) || @default_value
96
93
 
97
- case traverse(data)
98
- in [:ok, traversal_result]
94
+ if @successor
95
+ @successor.ensure_valid(traversal_result)
96
+ else
99
97
  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
98
  end
107
99
  end
108
100
 
@@ -124,7 +116,7 @@ class Accessory::Accessor
124
116
  #
125
117
  # @param data [Enumerable] the data yielded by the predecessor accessor.
126
118
  # @param succ [Proc] a thunk to the successor accessor. When {get} is called
127
- # by a {LensPath}, this is passed implicitly.
119
+ # by a {Lens}, this is passed implicitly.
128
120
  # @return [Object] the data to pass back to the predecessor accessor as a
129
121
  # yield result.
130
122
  def get(data, &succ)
@@ -171,7 +163,7 @@ class Accessory::Accessor
171
163
  #
172
164
  # @param data [Enumerable] the data yielded by the predecessor accessor.
173
165
  # @param succ [Proc] a thunk to the successor accessor. When {get} is called
174
- # by a {LensPath}, this is passed implicitly.
166
+ # by a {Lens}, this is passed implicitly.
175
167
  # @return [Object] the modification-command to pass back to the predecessor
176
168
  # accessor as a yield result.
177
169
  def get_and_update(data, &succ)
@@ -187,35 +179,36 @@ class Accessory::Accessor
187
179
  # traversal-results.
188
180
  #
189
181
  # 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.
182
+ # it performs. {traverse_or_default} takes care of feeding in a default +data+
183
+ # in the case where the predecessor passed invalid data.
192
184
  #
193
185
  # @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
186
+ # @return [Object] the result of traversal
197
187
  def traverse(data)
198
188
  raise NotImplementedError, "Accessor subclass #{self.class} must implement #traverse to use #traverse_or_default"
199
189
  end
200
190
 
201
- # Constructs a default value; called by {traverse_or_default}.
191
+ # Ensures that the predecessor accessor's traversal result is one this
192
+ # accessor can operate on; called by {traverse_or_default}.
193
+ #
194
+ # This callback should validate that the +traversal_result+ is one that can
195
+ # be traversed by the traversal-method of this accessor. If it can, the
196
+ # +traversal_result+ should be returned unchanged. If it cannot, an object
197
+ # that _can_ be traversed should be returned instead.
202
198
  #
203
- # Returns a default constructor Proc for the {traverse_or_default} call in
204
- # the *predecessor* accessor to use.
205
199
  #
206
200
  # 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.
201
+ # {AllAccessor}), then this method should validate that the +traversal_result+
202
+ # is +Enumerable+; and, if it isn't, return something that is — e.g. an empty
203
+ # +Array+.
209
204
  #
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.
205
+ # This logic is used to replace invalid intermediate values (e.g. `nil`s and
206
+ # scalars) with containers during {Lens#put_in} et al.
214
207
  #
215
- # @return [Proc] a Proc that, when called, produces a default traversal-result
216
- def default_data_constructor
208
+ # @return [Object] a now-valid traversal result
209
+ def ensure_valid(traversal_result)
217
210
  lambda do
218
- raise NotImplementedError, "Accessor subclass #{self.class} must implement #default_data_constructor to allow chain-predecessor to use #traverse_or_default"
211
+ raise NotImplementedError, "Accessor subclass #{self.class} must implement #ensure_valid to allow chain-predecessor to use #traverse_or_default"
219
212
  end
220
213
  end
221
214
 
@@ -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+}
@@ -17,8 +17,12 @@ require 'accessory/accessor'
17
17
 
18
18
  class Accessory::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
@@ -13,7 +13,7 @@ require 'accessory/accessor'
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
  #
@@ -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,7 +85,7 @@ 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
91
  in [result, new_value]
@@ -5,12 +5,12 @@ require 'accessory/traversal_position/enumerable_before_offset'
5
5
  # Traverses the positions "between" the elements of an +Enumerable+, including
6
6
  # the positions at the "edges" (i.e. before the first, and after the last.)
7
7
  #
8
- # {BetweenEachAccessor} can be used with {LensPath#put_in} to insert new
8
+ # {BetweenEachAccessor} can be used with {Lens#put_in} to insert new
9
9
  # elements into an Enumerable between the existing ones.
10
10
  #
11
11
  # *Aliases*
12
12
  # * {Access.between_each}
13
- # * {Access::FluentHelpers#between_each} (included in {LensPath} and {Lens})
13
+ # * {Access::FluentHelpers#between_each} (included in {Lens} and {BoundLens})
14
14
  #
15
15
  # <b>Default constructor</b> used by predecessor accessor
16
16
  #
@@ -18,8 +18,12 @@ require 'accessory/traversal_position/enumerable_before_offset'
18
18
 
19
19
  class Accessory::BetweenEachAccessor < Accessory::Accessor
20
20
  # @!visibility private
21
- def default_data_constructor
22
- lambda{ Array.new }
21
+ def ensure_valid(traversal_result)
22
+ if traversal_result.kind_of?(Enumerable)
23
+ traversal_result
24
+ else
25
+ []
26
+ end
23
27
  end
24
28
 
25
29
  # @!visibility private
@@ -46,7 +50,7 @@ class Accessory::BetweenEachAccessor < Accessory::Accessor
46
50
  # @param data [Enumerable] the +Enumerable+ to iterate through
47
51
  # @return [Array] the generated {TraversalPosition::EnumerableBeforeOffset}s
48
52
  def get(data)
49
- positions = value_or_default(data || [])
53
+ positions = traverse_or_default(data || [])
50
54
 
51
55
  if block_given?
52
56
  positions.map{ |rec| yield(rec) }
@@ -72,7 +76,7 @@ class Accessory::BetweenEachAccessor < Accessory::Accessor
72
76
  results = []
73
77
  new_data = []
74
78
 
75
- positions = value_or_default(data || [])
79
+ positions = traverse_or_default(data || [])
76
80
 
77
81
  positions.each do |pos|
78
82
  case yield(pos)
@@ -13,14 +13,14 @@ require 'accessory/traversal_position/enumerable_before_offset'
13
13
  # The +offset+ in this accessor has equivalent semantics to the offset in
14
14
  # <tt>Array#insert(offset, obj)</tt>.
15
15
  #
16
- # {BetwixtAccessor} can be used with {LensPath#put_in} to insert new
16
+ # {BetwixtAccessor} can be used with {Lens#put_in} to insert new
17
17
  # elements into an Enumerable between the existing ones. If you want to extend
18
18
  # an +Enumerable+ as you would with <tt>#push</tt> or <tt>#unshift</tt>, this
19
19
  # accessor will have better behavior than using {SubscriptAccessor} would.
20
20
  #
21
21
  # *Aliases*
22
22
  # * {Access.betwixt}
23
- # * {Access::FluentHelpers#betwixt} (included in {LensPath} and {Lens})
23
+ # * {Access::FluentHelpers#betwixt} (included in {Lens} and {BoundLens})
24
24
  #
25
25
  # <b>Default constructor</b> used by predecessor accessor
26
26
  #
@@ -40,21 +40,30 @@ class Accessory::BetwixtAccessor < Accessory::Accessor
40
40
  end
41
41
 
42
42
  # @!visibility private
43
- def default_data_constructor
44
- lambda{ Array.new }
43
+ def ensure_valid(traversal_result)
44
+ if traversal_result.kind_of?(Enumerable)
45
+ traversal_result
46
+ else
47
+ []
48
+ end
45
49
  end
46
50
 
47
51
  # @!visibility private
48
52
  def traverse(data)
49
- data_len = data.length
53
+ nil
54
+ # return :error unless data.kind_of?(Enumerable)
55
+
56
+ # data_len = data.length
57
+
58
+ # ebo = Accessory::TraversalPosition::EnumerableBeforeOffset.new(
59
+ # @offset,
60
+ # (@offset > 0) ? data[@offset - 1] : nil,
61
+ # (@offset < (data_len - 1)) ? data[@offset + 1] : nil,
62
+ # is_first: @offset == 0,
63
+ # is_last: @offset == data_len
64
+ # )
50
65
 
51
- Accessory::TraversalPosition::EnumerableBeforeOffset.new(
52
- @offset,
53
- (@offset > 0) ? data[@offset - 1] : nil,
54
- (@offset < (data_len - 1)) ? data[@offset + 1] : nil,
55
- is_first: @offset == 0,
56
- is_last: @offset == data_len
57
- )
66
+ # [:ok, ebo]
58
67
  end
59
68
 
60
69
  # Feeds a {TraversalPosition::EnumerableBeforeOffset} representing the
@@ -64,7 +73,7 @@ class Accessory::BetwixtAccessor < Accessory::Accessor
64
73
  # @param data [Enumerable] the +Enumerable+ to traverse into
65
74
  # @return [Array] the generated {TraversalPosition::EnumerableBeforeOffset}
66
75
  def get(data)
67
- pos = value_or_default(data || [])
76
+ pos = traverse_or_default(data || [])
68
77
 
69
78
  if block_given?
70
79
  yield(pos)
@@ -87,7 +96,7 @@ class Accessory::BetwixtAccessor < Accessory::Accessor
87
96
  # 1. the generated {TraversalPosition::EnumerableBeforeOffset}
88
97
  # 2. the new {data}
89
98
  def get_and_update(data)
90
- pos = value_or_default(data || [])
99
+ pos = traverse_or_default(data || [])
91
100
 
92
101
  case yield(pos)
93
102
  in [result, new_value]
@@ -6,7 +6,7 @@ require 'accessory/accessor'
6
6
  #
7
7
  # *Aliases*
8
8
  # * {Access.filter}
9
- # * {Access::FluentHelpers#filter} (included in {LensPath} and {Lens})
9
+ # * {Access::FluentHelpers#filter} (included in {Lens} and {BoundLens})
10
10
  #
11
11
  # *Equivalents* in Elixir's {https://hexdocs.pm/elixir/Access.html +Access+} module
12
12
  # * {https://hexdocs.pm/elixir/Access.html#filter/1 +Access.filter/1+}
@@ -34,8 +34,12 @@ class Accessory::FilterAccessor < Accessory::Accessor
34
34
  end
35
35
 
36
36
  # @!visibility private
37
- def default_data_constructor
38
- lambda{ Array.new }
37
+ def ensure_valid(traversal_result)
38
+ if traversal_result.kind_of?(Enumerable)
39
+ traversal_result
40
+ else
41
+ []
42
+ end
39
43
  end
40
44
 
41
45
  # Feeds each element of +data+ matching the predicate down the accessor chain,
@@ -9,7 +9,7 @@ require 'accessory/accessor'
9
9
  #
10
10
  # *Aliases*
11
11
  # * {Access.first}
12
- # * {Access::FluentHelpers#first} (included in {LensPath} and {Lens})
12
+ # * {Access::FluentHelpers#first} (included in {Lens} and {BoundLens})
13
13
  #
14
14
  # <b>Default constructor</b> used by predecessor accessor
15
15
  #
@@ -17,8 +17,12 @@ require 'accessory/accessor'
17
17
 
18
18
  class Accessory::FirstAccessor < 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
@@ -33,7 +37,7 @@ class Accessory::FirstAccessor < Accessory::Accessor
33
37
  # @param data [Object] the object to traverse
34
38
  # @return [Object] the value derived from the rest of the accessor chain
35
39
  def get(data)
36
- value = value_or_default(data)
40
+ value = traverse_or_default(data)
37
41
 
38
42
  if block_given?
39
43
  yield(value)
@@ -51,7 +55,7 @@ class Accessory::FirstAccessor < Accessory::Accessor
51
55
  # @param data [Object] the object to traverse
52
56
  # @return [Array] a two-element array containing 1. the original value found; and 2. the result value from the accessor chain
53
57
  def get_and_update(data)
54
- old_value = value_or_default(data)
58
+ old_value = traverse_or_default(data)
55
59
 
56
60
  case yield(old_value)
57
61
  in [result, new_value]