accessory 0.1.4 → 0.1.5

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: 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