accessory 0.1.4 → 0.1.5

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: 1e6d18076731a0f1b3a28ce6ef77c75ef35dff6b7646f4880286b9bcf64ac393
4
- data.tar.gz: '07918b78b0d0b1ae8097f22f36039a04fdefe9e59a10cae62758da84785fac4f'
3
+ metadata.gz: 6a3ac947a1bb5743f72e27fca953624b93a0fdab7c94a483e4f2577f3833ccb0
4
+ data.tar.gz: 4c8570697b002e4513d2ee117e0ec5290418186e2ea9ad2879a2ba14308b7c25
5
5
  SHA512:
6
- metadata.gz: ccdb07d177f3b436c91303585b921494d64aac6baa30abc32debf6ebc7ee4a6ff032b3db48f51d3c7b5d6498daeb7a298bcfd75840682bdc842e8fd70581bcf0
7
- data.tar.gz: 2183b4b3513781e43b99490b4b86617ee8f08624c5c71a1355100fb84199bae2f27865d6b17496802c29c5571d39699e239b4dfc163983e789e618c39daf898d
6
+ metadata.gz: 8dc468e225a4150d392727c30d2eb6104fedccc29bcbb22b18320d6b8b3b4afb311a74857540fcb9653d4ad1837f29d4bc1b20800d8d7179d7244c031da3177a
7
+ data.tar.gz: ae313d407b95a1079f93ad71adb8296a5c8b3c177bc001a5b9866ebe7945dbb2ac46b152c199d2648ac03afc2bb1609aff6190e6beeea170944ac1fe19d16628
@@ -7,8 +7,8 @@ require 'accessory/access'
7
7
 
8
8
  module Accessory
9
9
  refine ::Object do
10
- def lens
11
- ::Accessory::Lens.on(self)
10
+ def lens(...)
11
+ ::Accessory::Lens.on(self, ...)
12
12
  end
13
13
  end
14
14
  end
@@ -1,9 +1,27 @@
1
1
  module Accessory; end
2
2
 
3
3
  ##
4
- # @!visibility private
5
- class Accessory::Accessor
4
+ # The parent class for accessors. Contains some shared behavior all accessors
5
+ # can rely on.
6
+ #
7
+ # It doesn't make sense to instantiate this class directly. Instantiate specific
8
+ # {Accessor} subclasses instead.
9
+ #
10
+ # == Implementing an Accessor
11
+ #
12
+ # To implement an {Accessor} subclass, you must define at minimum these two
13
+ # methods (see the method docs of these methods for details):
14
+ #
15
+ # * {Accessor#get}
16
+ # * {Accessor#get_and_update}
17
+ #
18
+ # You may also implement these two methods (again, see method docs for more
19
+ # info):
20
+ #
21
+ # * {Accessor#traverse}
22
+ # * {Accessor#default_data_constructor}
6
23
 
24
+ class Accessory::Accessor
7
25
  # @!visibility private
8
26
  DEFAULT_NOT_SET_SENTINEL = :"98e47971-e708-42ca-bee7-0c62fe5e11c9"
9
27
 
@@ -13,7 +31,7 @@ class Accessory::Accessor
13
31
  # @!visibility private
14
32
  def initialize(default = nil)
15
33
  @default_value = default || DEFAULT_NOT_SET_SENTINEL
16
- @make_default_fn = TERMINAL_DEFAULT_FN
34
+ @succ_default_data_constructor = TERMINAL_DEFAULT_FN
17
35
  end
18
36
 
19
37
  # @!visibility private
@@ -40,7 +58,9 @@ class Accessory::Accessor
40
58
  end
41
59
 
42
60
  # @!visibility private
43
- HIDDEN_IVARS = [:@default_value, :@make_default_fn]
61
+ HIDDEN_IVARS = [:@default_value, :@succ_default_data_constructor]
62
+
63
+ # @!visibility private
44
64
  def inspect_args
45
65
  (instance_variables - HIDDEN_IVARS).map do |ivar_k|
46
66
  ivar_v = instance_variable_get(ivar_k)
@@ -49,19 +69,155 @@ class Accessory::Accessor
49
69
  end
50
70
 
51
71
  # @!visibility private
52
- attr_accessor :make_default_fn
72
+ attr_accessor :succ_default_data_constructor
53
73
 
54
- # @!visibility private
55
- def value_or_default(data)
74
+ # @!group Helpers
75
+
76
+ # Safely traverses +data+, with useful defaults; simplifies implementations of
77
+ # {get} and {get_and_update}.
78
+ #
79
+ # Rather than writing redundant traversal logic into both methods, you can
80
+ # implement the callback method {traverse} to define your traversal, and then
81
+ # call <tt>traverse_or_default(data)</tt> within your implementation to safely
82
+ # get a traversal-result to operate on.
83
+ #
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.
94
+ def traverse_or_default(data)
56
95
  return nil if data.nil?
57
96
 
58
- maybe_value = value_from(data)
59
- return maybe_value unless maybe_value.nil?
97
+ case traverse(data)
98
+ in [:ok, traversal_result]
99
+ 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
+ end
107
+ end
108
+
109
+ # @!endgroup
60
110
 
61
- if DEFAULT_NOT_SET_SENTINEL.equal?(@default_value)
62
- @make_default_fn.call
63
- else
64
- @default_value
111
+ # Traverses +data+ in some way, and feeds the result of the traversal to the
112
+ # next step in the accessor chain by yielding the traversal-result to the
113
+ # passed-in block +succ+. The result from the yield is the result coming from
114
+ # the end of the accessor chain. Usually, it should be returned as-is.
115
+ #
116
+ # +succ+ can be yielded to multiple times, to run the rest of the accessor
117
+ # chain against multiple parts of +data+. In this case, the yield results
118
+ # should be gathered up into some container object to be returned together.
119
+ #
120
+ # The successor accessor will receive the yielded element as its +data+.
121
+ #
122
+ # After returning, the predecessor accessor will receive the result returned
123
+ # from {get} as the result of its own +yield+.
124
+ #
125
+ # @param data [Enumerable] the data yielded by the predecessor accessor.
126
+ # @param succ [Proc] a thunk to the successor accessor. When {get} is called
127
+ # by a {LensPath}, this is passed implicitly.
128
+ # @return [Object] the data to pass back to the predecessor accessor as a
129
+ # yield result.
130
+ def get(data, &succ)
131
+ raise NotImplementedError, "Accessor subclass #{self.class} must implement #get"
132
+ end
133
+
134
+ # Traverses +data+ in some way, and feeds the result of the traversal to the
135
+ # next step in the accessor chain by yielding the traversal-result to the
136
+ # passed-in block +succ+.
137
+ #
138
+ # The result of the yield will be a "modification command", one of these two:
139
+ # * an *update command* <tt>[get_result, new_value]</tt>
140
+ # * the symbol <tt>:pop</tt>
141
+ #
142
+ # In the *update command* case:
143
+ # * the +get_result+ should be returned as-is (or gathered together into
144
+ # a container object if this accessor yields multiple times.) The data flow
145
+ # of the +get_result+s should replicate the data flow of {get}.
146
+ # * the +new_value+ should be used to *replace* or *overwrite* the result of
147
+ # this accessor's traversal within +data+. For example, in
148
+ # {SubscriptAccessor}, <tt>data[key] = new_value</tt> is executed.
149
+ #
150
+ # In the <tt>:pop</tt> command case:
151
+ # * the result of the traversal (*before* the yield) should be returned. This
152
+ # implies that any {get_and_update} implementation must capture its
153
+ # traversal-results before feeding them into yield, in order to return them
154
+ # here.
155
+ # * the traversal-result should be *removed* from +data+. For example, in
156
+ # {SubscriptAccessor}, <tt>data.delete(key)</tt> is executed.
157
+ #
158
+ # The successor in the accessor chain will receive the yielded
159
+ # traversal-results as its own +data+.
160
+ #
161
+ # After returning, the predecessor accessor will receive the result returned
162
+ # from this method as the result of its own +yield+. This implies that
163
+ # this method should almost always be implemented to *return* an update
164
+ # command, looking like one of the following:
165
+ #
166
+ # # given [get_result, new_value]
167
+ # [get_result, data_after_update]
168
+ #
169
+ # # given :pop
170
+ # [traversal_result, data_after_removal]
171
+ #
172
+ # @param data [Enumerable] the data yielded by the predecessor accessor.
173
+ # @param succ [Proc] a thunk to the successor accessor. When {get} is called
174
+ # by a {LensPath}, this is passed implicitly.
175
+ # @return [Object] the modification-command to pass back to the predecessor
176
+ # accessor as a yield result.
177
+ def get_and_update(data, &succ)
178
+ raise NotImplementedError, "Accessor subclass #{self.class} must implement #get_and_update"
179
+ end
180
+
181
+ # @!group Callbacks
182
+
183
+ # Traverses +data+; called by {traverse_or_default}.
184
+ #
185
+ # This method should traverse +data+ however your accessor does that,
186
+ # producing either one traversal-result or a container-object of gathered
187
+ # traversal-results.
188
+ #
189
+ # 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.
192
+ #
193
+ # @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
197
+ def traverse(data)
198
+ raise NotImplementedError, "Accessor subclass #{self.class} must implement #traverse to use #traverse_or_default"
199
+ end
200
+
201
+ # Constructs a default value; called by {traverse_or_default}.
202
+ #
203
+ # Returns a default constructor Proc for the {traverse_or_default} call in
204
+ # the *predecessor* accessor to use.
205
+ #
206
+ # 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.
209
+ #
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.
214
+ #
215
+ # @return [Proc] a Proc that, when called, produces a default traversal-result
216
+ def default_data_constructor
217
+ lambda do
218
+ raise NotImplementedError, "Accessor subclass #{self.class} must implement #default_data_constructor to allow chain-predecessor to use #traverse_or_default"
65
219
  end
66
220
  end
221
+
222
+ # @!endgroup
67
223
  end
@@ -17,7 +17,7 @@ require 'accessory/accessor'
17
17
 
18
18
  class Accessory::AllAccessor < Accessory::Accessor
19
19
  # @!visibility private
20
- def default_fn_for_previous_step
20
+ def default_data_constructor
21
21
  lambda{ Array.new }
22
22
  end
23
23
 
@@ -47,7 +47,7 @@ class Accessory::AttributeAccessor < Accessory::Accessor
47
47
  end
48
48
 
49
49
  # @!visibility private
50
- def default_fn_for_previous_step
50
+ def default_data_constructor
51
51
  lambda do
52
52
  require 'ostruct'
53
53
  OpenStruct.new
@@ -55,7 +55,7 @@ class Accessory::AttributeAccessor < Accessory::Accessor
55
55
  end
56
56
 
57
57
  # @!visibility private
58
- def value_from(data)
58
+ def traverse(data)
59
59
  data.send(@getter_method_name)
60
60
  end
61
61
 
@@ -1,5 +1,5 @@
1
1
  require 'accessory/accessor'
2
- require 'accessory/array_cursor_position'
2
+ require 'accessory/traversal_position/enumerable_before_offset'
3
3
 
4
4
  ##
5
5
  # Traverses the positions "between" the elements of an +Enumerable+, including
@@ -18,7 +18,7 @@ require 'accessory/array_cursor_position'
18
18
 
19
19
  class Accessory::BetweenEachAccessor < Accessory::Accessor
20
20
  # @!visibility private
21
- def default_fn_for_previous_step
21
+ def default_data_constructor
22
22
  lambda{ Array.new }
23
23
  end
24
24
 
@@ -26,7 +26,7 @@ class Accessory::BetweenEachAccessor < Accessory::Accessor
26
26
  def inspect_args; nil; end
27
27
 
28
28
  # @!visibility private
29
- def value_from(data)
29
+ def traverse(data)
30
30
  data_len = data.length
31
31
 
32
32
  positions = [
@@ -36,15 +36,15 @@ class Accessory::BetweenEachAccessor < Accessory::Accessor
36
36
  ]
37
37
 
38
38
  positions.transpose.map do |(i, b, a)|
39
- Accessory::ArrayCursorPosition.new(i, b, a, is_first: i == 0, is_last: i == data_len)
39
+ Accessory::TraversalPosition::EnumerableBeforeOffset.new(i, b, a, is_first: i == 0, is_last: i == data_len)
40
40
  end
41
41
  end
42
42
 
43
- # Feeds {ArrayCursorPosition}s representing the positions between the elements
44
- # of +data+ down the accessor chain.
43
+ # Feeds {TraversalPosition::EnumerableBeforeOffset}s representing the
44
+ # positions between the elements of +data+ down the accessor chain.
45
45
  #
46
46
  # @param data [Enumerable] the +Enumerable+ to iterate through
47
- # @return [Array] the generated {ArrayCursorPosition}s
47
+ # @return [Array] the generated {TraversalPosition::EnumerableBeforeOffset}s
48
48
  def get(data)
49
49
  positions = value_or_default(data || [])
50
50
 
@@ -55,8 +55,9 @@ class Accessory::BetweenEachAccessor < Accessory::Accessor
55
55
  end
56
56
  end
57
57
 
58
- # Feeds {ArrayCursorPosition}s representing the positions between the elements
59
- # of +data+ down the accessor chain, manipulating +data+ using the results.
58
+ # Feeds {TraversalPosition::EnumerableBeforeOffset}s representing the
59
+ # positions between the elements of +data+ down the accessor chain,
60
+ # manipulating +data+ using the results.
60
61
  #
61
62
  # If a new element is returned up the accessor chain, the element is inserted
62
63
  # between the existing elements.
@@ -64,7 +65,9 @@ class Accessory::BetweenEachAccessor < Accessory::Accessor
64
65
  # If +:pop+ is returned up the accessor chain, no new element is added.
65
66
  #
66
67
  # @param data [Enumerable] the +Enumerable+ to iterate through
67
- # @return [Array] a two-element array containing 1. the {ArrayCursorPosition}s; and 2. the new {data}
68
+ # @return [Array] a two-element array containing
69
+ # 1. the {TraversalPosition::EnumerableBeforeOffset}s
70
+ # 2. the new {data}
68
71
  def get_and_update(data)
69
72
  results = []
70
73
  new_data = []
@@ -1,5 +1,5 @@
1
1
  require 'accessory/accessor'
2
- require 'accessory/array_cursor_position'
2
+ require 'accessory/traversal_position/enumerable_before_offset'
3
3
 
4
4
  ##
5
5
  # Traverses into a specified cursor-position "between" two elements of an
@@ -40,15 +40,15 @@ class Accessory::BetwixtAccessor < Accessory::Accessor
40
40
  end
41
41
 
42
42
  # @!visibility private
43
- def default_fn_for_previous_step
43
+ def default_data_constructor
44
44
  lambda{ Array.new }
45
45
  end
46
46
 
47
47
  # @!visibility private
48
- def value_from(data)
48
+ def traverse(data)
49
49
  data_len = data.length
50
50
 
51
- Accessory::ArrayCursorPosition.new(
51
+ Accessory::TraversalPosition::EnumerableBeforeOffset.new(
52
52
  @offset,
53
53
  (@offset > 0) ? data[@offset - 1] : nil,
54
54
  (@offset < (data_len - 1)) ? data[@offset + 1] : nil,
@@ -57,11 +57,12 @@ class Accessory::BetwixtAccessor < Accessory::Accessor
57
57
  )
58
58
  end
59
59
 
60
- # Feeds an {ArrayCursorPosition} representing the position between the
61
- # elements of +data+ at +@offset+ down the accessor chain.
60
+ # Feeds a {TraversalPosition::EnumerableBeforeOffset} representing the
61
+ # position between the elements of +data+ at +@offset+ down the accessor
62
+ # chain.
62
63
  #
63
64
  # @param data [Enumerable] the +Enumerable+ to traverse into
64
- # @return [Array] the generated {ArrayCursorPosition}
65
+ # @return [Array] the generated {TraversalPosition::EnumerableBeforeOffset}
65
66
  def get(data)
66
67
  pos = value_or_default(data || [])
67
68
 
@@ -72,9 +73,9 @@ class Accessory::BetwixtAccessor < Accessory::Accessor
72
73
  end
73
74
  end
74
75
 
75
- # Feeds an {ArrayCursorPosition} representing the position between the
76
- # elements of +data+ at +@offset+ down the accessor chain, manipulating
77
- # +data+ using the result.
76
+ # Feeds a {TraversalPosition::EnumerableBeforeOffset} representing the
77
+ # position between the elements of +data+ at +@offset+ down the accessor
78
+ # chain, manipulating +data+ using the result.
78
79
  #
79
80
  # If a new element is returned up the accessor chain, the element is inserted
80
81
  # at the specified position, using <tt>data.insert(@offset, e)</tt>.
@@ -82,7 +83,9 @@ class Accessory::BetwixtAccessor < Accessory::Accessor
82
83
  # If +:pop+ is returned up the accessor chain, no new element is added.
83
84
  #
84
85
  # @param data [Enumerable] the +Enumerable+ to traverse into
85
- # @return [Array] a two-element array containing 1. the generated {ArrayCursorPosition}; and 2. the new {data}
86
+ # @return [Array] a two-element array containing
87
+ # 1. the generated {TraversalPosition::EnumerableBeforeOffset}
88
+ # 2. the new {data}
86
89
  def get_and_update(data)
87
90
  pos = value_or_default(data || [])
88
91
 
@@ -34,7 +34,7 @@ class Accessory::FilterAccessor < Accessory::Accessor
34
34
  end
35
35
 
36
36
  # @!visibility private
37
- def default_fn_for_previous_step
37
+ def default_data_constructor
38
38
  lambda{ Array.new }
39
39
  end
40
40
 
@@ -17,7 +17,7 @@ require 'accessory/accessor'
17
17
 
18
18
  class Accessory::FirstAccessor < Accessory::Accessor
19
19
  # @!visibility private
20
- def default_fn_for_previous_step
20
+ def default_data_constructor
21
21
  lambda{ Array.new }
22
22
  end
23
23
 
@@ -25,7 +25,7 @@ class Accessory::FirstAccessor < Accessory::Accessor
25
25
  def inspect_args; nil; end
26
26
 
27
27
  # @!visibility private
28
- def value_from(data)
28
+ def traverse(data)
29
29
  data.first
30
30
  end
31
31
 
@@ -33,12 +33,12 @@ class Accessory::InstanceVariableAccessor < Accessory::Accessor
33
33
  end
34
34
 
35
35
  # @!visibility private
36
- def default_fn_for_previous_step
36
+ def default_data_constructor
37
37
  lambda{ Object.new }
38
38
  end
39
39
 
40
40
  # @!visibility private
41
- def value_from(data)
41
+ def traverse(data)
42
42
  data.instance_variable_get(@ivar_name)
43
43
  end
44
44
 
@@ -17,7 +17,7 @@ require 'accessory/accessor'
17
17
 
18
18
  class Accessory::LastAccessor < Accessory::Accessor
19
19
  # @!visibility private
20
- def default_fn_for_previous_step
20
+ def default_data_constructor
21
21
  lambda{ Array.new }
22
22
  end
23
23
 
@@ -25,7 +25,7 @@ class Accessory::LastAccessor < Accessory::Accessor
25
25
  def inspect_args; nil; end
26
26
 
27
27
  # @!visibility private
28
- def value_from(data)
28
+ def traverse(data)
29
29
  data.last
30
30
  end
31
31
 
@@ -53,12 +53,12 @@ class Accessory::SubscriptAccessor < Accessory::Accessor
53
53
  end
54
54
 
55
55
  # @!visibility private
56
- def default_fn_for_previous_step
56
+ def default_data_constructor
57
57
  lambda{ Hash.new }
58
58
  end
59
59
 
60
60
  # @!visibility private
61
- def value_from(data)
61
+ def traverse(data)
62
62
  data[@key]
63
63
  end
64
64
 
@@ -3,11 +3,31 @@ module Accessory; end
3
3
  require 'accessory/lens_path'
4
4
 
5
5
  class Accessory::Lens
6
- # Creates a Lens that will traverse +subject+ along +lens_path+.
7
- # @param subject [Object] the data-structure this Lens will traverse
8
- # @param lens_path [LensPath] the {LensPath} that will be used to traverse +subject+
9
- def self.on(subject, lens_path: Accessory::LensPath.empty)
10
- self.new(subject, path).freeze
6
+
7
+ # Creates a Lens that will traverse +subject+.
8
+ #
9
+ # @overload on(subject, lens_path)
10
+ # Creates a Lens that will traverse +subject+ along +lens_path+.
11
+ #
12
+ # @param subject [Object] the data-structure this Lens will traverse
13
+ # @param lens_path [LensPath] the {LensPath} that will be used to
14
+ # traverse +subject+
15
+ #
16
+ # @overload on(subject, *accessors)
17
+ # Creates a Lens that will traverse +subject+ using an {LensPath} built
18
+ # from +accessors+.
19
+ #
20
+ # @param subject [Object] the data-structure this Lens will traverse
21
+ # @param accessors [Array] the accessors for the new {LensPath}
22
+ def self.on(subject, *accessors)
23
+ lens_path =
24
+ if accessors.length == 1 && accessors[0].kind_of?(LensPath)
25
+ accessors[0]
26
+ else
27
+ LensPath[*accessors]
28
+ end
29
+
30
+ self.new(subject, lens_path).freeze
11
31
  end
12
32
 
13
33
  class << self
@@ -97,6 +117,6 @@ class Accessory::LensPath
97
117
  # @param subject [Object] the data-structure to traverse
98
118
  # @return [Lens] a new Lens that will traverse +subject+ using this LensPath
99
119
  def on(subject)
100
- Accessory::Lens.on(subject, lens_path: self)
120
+ Accessory::Lens.on(subject, self)
101
121
  end
102
122
  end
@@ -187,7 +187,7 @@ class Accessory::LensPath
187
187
  end
188
188
 
189
189
  unless @parts.empty?
190
- @parts.last.make_default_fn = accessor.default_fn_for_previous_step
190
+ @parts.last.succ_default_data_constructor = accessor.default_data_constructor
191
191
  end
192
192
 
193
193
  @parts.push(accessor)
@@ -0,0 +1,28 @@
1
+ module Accessory; end
2
+ module Accessory::TraversalPosition; end
3
+
4
+ ##
5
+ # Represents an element encountered during +#each+ traversal of an +Enumerable+.
6
+
7
+ class Accessory::TraversalPosition::EnumerableAtOffset
8
+ ##
9
+ # @!visibility private
10
+ def initialize(offset, elem_at, is_first: false, is_last: false)
11
+ @offset = offset
12
+ @elem_at = elem_at
13
+ @is_first = is_first
14
+ @is_last = is_last
15
+ end
16
+
17
+ # @return [Integer] the offset of +elem_at+ in the Enumerable
18
+ attr_reader :offset
19
+
20
+ # @return [Object] the element under the cursor, if applicable
21
+ attr_reader :elem_at
22
+
23
+ # @return [Boolean] true when {#elem_at} is the first element of the +Enumerable+
24
+ def first?; @is_first; end
25
+
26
+ # @return [Boolean] true when {#elem_at} is the last element of the +Enumerable+
27
+ def last?; @is_last; end
28
+ end
@@ -0,0 +1,55 @@
1
+ module Accessory; end
2
+ module Accessory::TraversalPosition; end
3
+
4
+ ##
5
+ # Represents the empty intervals between and surrounding the elements of an
6
+ # +Enumerable#each+ traversal.
7
+ #
8
+ # Examples to build intuition:
9
+ #
10
+ # * An +EnumerableBeforeOffset+ with an <tt>.offset</tt> of <tt>0</tt>
11
+ # represents the position directly before the first result from
12
+ # <tt>#each</tt>, i.e. "the beginning." Using {LensPath#put_in} at this position
13
+ # will _prepend_ to the +Enumerable+.
14
+ #
15
+ # * An +EnumerableBeforeOffset+ with an <tt>.offset</tt> equal to the
16
+ # <tt>#length</tt> of the +Enumerable+ (recognizable by
17
+ # <tt>EnumerableBeforeOffset#last?</tt> returning +true+) represents
18
+ # represents the position directly before the end of the enumeration,
19
+ # i.e. "the end" of the +Enumerable+. Using {LensPath#put_in} at this position
20
+ # will _append_ to the +Enumerable+.
21
+ #
22
+ # * In general, using {LensPath#put_in} with an +EnumerableBeforeOffset+ with an
23
+ # <tt>.offset</tt> of +n+ will insert an element _between_ elements
24
+ # <tt>n - 1</tt> and +n+ in the enumeration sequence.
25
+ #
26
+ # * Returning <tt>:pop</tt> from {LensPath#get_and_update_in} for an
27
+ # +EnumerableBeforeOffset+-terminated {LensPath} will have no effect, as
28
+ # you're removing an empty slice.
29
+
30
+ class Accessory::TraversalPosition::EnumerableBeforeOffset
31
+ ##
32
+ # @!visibility private
33
+ def initialize(offset, elem_before, elem_after, is_first: false, is_last: false)
34
+ @offset = offset
35
+ @elem_before = elem_before
36
+ @elem_after = elem_after
37
+ @is_first = is_first
38
+ @is_last = is_last
39
+ end
40
+
41
+ # @return [Integer] the offset of +elem_after+ in the Enumerable
42
+ attr_reader :offset
43
+
44
+ # @return [Object] the element before the cursor, if applicable
45
+ attr_reader :elem_before
46
+
47
+ # @return [Object] the element after the cursor, if applicable
48
+ attr_reader :elem_after
49
+
50
+ # @return [Boolean] true when {#elem_after} is the first element of the +Enumerable+
51
+ def first?; @is_first; end
52
+
53
+ # @return [Boolean] true when {#elem_before} is the last element of the +Enumerable+
54
+ def last?; @is_last; end
55
+ end
@@ -1,3 +1,3 @@
1
1
  module Accessory
2
- VERSION = "0.1.4"
2
+ VERSION = "0.1.5"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: accessory
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Levi Aul
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-01-11 00:00:00.000000000 Z
11
+ date: 2021-01-12 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -29,9 +29,10 @@ files:
29
29
  - lib/accessory/accessors/instance_variable_accessor.rb
30
30
  - lib/accessory/accessors/last_accessor.rb
31
31
  - lib/accessory/accessors/subscript_accessor.rb
32
- - lib/accessory/array_cursor_position.rb
33
32
  - lib/accessory/lens.rb
34
33
  - lib/accessory/lens_path.rb
34
+ - lib/accessory/traversal_position/enumerable_at_offset.rb
35
+ - lib/accessory/traversal_position/enumerable_before_offset.rb
35
36
  - lib/accessory/version.rb
36
37
  homepage: https://github.com/tsutsu/accessory
37
38
  licenses:
@@ -1,32 +0,0 @@
1
- module Accessory; end
2
-
3
- ##
4
- # Represents a cursor-position "between" two positions in an +Array+ or other
5
- # integer-indexed +Enumerable+.
6
-
7
- class Accessory::ArrayCursorPosition
8
- ##
9
- # @!visibility private
10
- def initialize(offset, elem_before, elem_after, is_first: false, is_last: false)
11
- @offset = offset
12
- @elem_before = elem_before
13
- @elem_after = elem_after
14
- @is_first = is_first
15
- @is_last = is_last
16
- end
17
-
18
- # @return [Integer] the offset of +elem_after+ in the Enumerable
19
- attr_reader :offset
20
-
21
- # @return [Object] the element before the cursor, if applicable
22
- attr_reader :elem_before
23
-
24
- # @return [Object] the element after the cursor, if applicable
25
- attr_reader :elem_after
26
-
27
- # @return [Object] true when {#elem_after} is the first element of the +Enumerable+
28
- def first?; @is_first; end
29
-
30
- # @return [Object] true when {#elem_before} is the last element of the +Enumerable+
31
- def last?; @is_last; end
32
- end