accessory 0.1.4 → 0.1.9

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.
@@ -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 {Lens#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 {Lens#put_in} at this position
20
+ # will _append_ to the +Enumerable+.
21
+ #
22
+ # * In general, using {Lens#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 {Lens#get_and_update_in} for an
27
+ # +EnumerableBeforeOffset+-terminated {Lens} 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.9"
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.9
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-14 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
32
+ - lib/accessory/bound_lens.rb
33
33
  - lib/accessory/lens.rb
34
- - 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
@@ -1,219 +0,0 @@
1
- module Accessory; end
2
-
3
- require 'accessory/accessor'
4
- require 'accessory/accessors/subscript_accessor'
5
-
6
- class Accessory::LensPath
7
- # Returns the empty (identity) LensPath.
8
- # @return [LensPath] the empty (identity) LensPath.
9
- def self.empty
10
- @empty_lens_path ||= (new([]).freeze)
11
- end
12
-
13
- # Returns a {LensPath} containing the specified +accessors+.
14
- # @return [LensPath] a LensPath containing the specified +accessors+.
15
- def self.[](*accessors)
16
- new(accessors).freeze
17
- end
18
-
19
- class << self
20
- private :new
21
- end
22
-
23
- # @!visibility private
24
- def initialize(initial_parts)
25
- @parts = []
26
-
27
- for part in initial_parts
28
- append_accessor!(part)
29
- end
30
- end
31
-
32
- # @!visibility private
33
- def to_a
34
- @parts
35
- end
36
-
37
- # @!visibility private
38
- def inspect(format: :long)
39
- parts_desc = @parts.map{ |part| part.inspect(format: :short) }.join(', ')
40
- parts_desc = "[#{parts_desc}]"
41
-
42
- case format
43
- when :long
44
- "#LensPath#{parts_desc}"
45
- when :short
46
- parts_desc
47
- end
48
- end
49
-
50
- # Returns a new {LensPath} resulting from appending +accessor+ to the receiver.
51
- # @param accessor [Object] the accessor to append
52
- # @return [LensPath] the new joined LensPath
53
- def then(accessor)
54
- d = self.dup
55
- d.append_accessor!(accessor)
56
- d.freeze
57
- end
58
-
59
- # @!visibility private
60
- def dup
61
- d = super
62
- d.instance_eval do
63
- @parts = @parts.dup
64
- end
65
- d
66
- end
67
-
68
- # Returns a new {LensPath} resulting from concatenating +other+ to the end
69
- # of the receiver.
70
- # @param other [Object] an accessor, an +Array+ of accessors, or another LensPath
71
- # @return [LensPath] the new joined LensPath
72
- def +(other)
73
- parts =
74
- case other
75
- when Accessory::LensPath
76
- other.to_a
77
- when Array
78
- other
79
- else
80
- [other]
81
- end
82
-
83
- d = self.dup
84
- for part in parts
85
- d.append_accessor!(part)
86
- end
87
- d.freeze
88
- end
89
-
90
- alias_method :/, :+
91
-
92
- # Traverses +subject+ using the chain of accessors held in this LensPath,
93
- # returning the discovered value.
94
- #
95
- # *Equivalent* in Elixir: {https://hexdocs.pm/elixir/Kernel.html#get_in/2 +Kernel.get_in/2+}
96
- #
97
- # @return [Object] the value found after all traversals.
98
- def get_in(subject)
99
- if @parts.empty?
100
- subject
101
- else
102
- get_in_step(subject, @parts)
103
- end
104
- end
105
-
106
- # Traverses +subject+ using the chain of accessors held in this LensPath,
107
- # modifying the final value at the end of the traversal chain using
108
- # the passed +mutator_fn+, and returning the original targeted value(s)
109
- # pre-modification.
110
- #
111
- # +mutator_fn+ must return one of two data "shapes":
112
- # * a two-element +Array+, representing:
113
- # 1. the value to surface as the "get" value of the traversal
114
- # 2. the new value to replace at the traversal-position
115
- # * the Symbol +:pop+ — which will remove the value from its parent, and
116
- # return it as-is.
117
- #
118
- # *Equivalent* in Elixir: {https://hexdocs.pm/elixir/Kernel.html#get_and_update_in/3 +Kernel.get_and_update_in/3+}
119
- #
120
- # @param subject [Object] the data-structure to traverse
121
- # @param mutator_fn [Proc] a block taking the original value derived from
122
- # traversing +subject+, and returning a modification operation.
123
- # @return [Array] a two-element +Array+, consisting of
124
- # 1. the _old_ value(s) found after all traversals, and
125
- # 2. the updated +subject+
126
- def get_and_update_in(subject, &mutator_fn)
127
- if @parts.empty?
128
- subject
129
- else
130
- get_and_update_in_step(subject, @parts, mutator_fn)
131
- end
132
- end
133
-
134
- # Traverses +subject+ using the chain of accessors held in this LensPath,
135
- # replacing the final value at the end of the traversal chain with the
136
- # result from the passed +new_value_fn+.
137
- #
138
- # *Equivalent* in Elixir: {https://hexdocs.pm/elixir/Kernel.html#update_in/3 +Kernel.update_in/3+}
139
- #
140
- # @param subject [Object] the data-structure to traverse
141
- # @param new_value_fn [Proc] a block taking the original value derived from
142
- # traversing +subject+, and returning a replacement value.
143
- # @return [Array] a two-element +Array+, consisting of
144
- # 1. the _old_ value(s) found after all traversals, and
145
- # 2. the updated +subject+
146
- def update_in(subject, &new_value_fn)
147
- _, new_data = self.get_and_update_in(data){ |v| [nil, new_value_fn.call(v)] }
148
- new_data
149
- end
150
-
151
- # Traverses +subject+ using the chain of accessors held in this LensPath,
152
- # replacing the final value at the end of the traversal chain with
153
- # +new_value+.
154
- #
155
- # *Equivalent* in Elixir: {https://hexdocs.pm/elixir/Kernel.html#put_in/3 +Kernel.put_in/3+}
156
- #
157
- # @param subject [Object] the data-structure to traverse
158
- # @param new_value [Object] a replacement value at the traversal position.
159
- # @return [Object] the updated +subject+
160
- def put_in(subject, new_value)
161
- _, new_data = self.get_and_update_in(subject){ [nil, new_value] }
162
- new_data
163
- end
164
-
165
- # Traverses +subject+ using the chain of accessors held in this LensPath,
166
- # removing the final value at the end of the traversal chain from its position
167
- # within its parent container.
168
- #
169
- # *Equivalent* in Elixir: {https://hexdocs.pm/elixir/Kernel.html#pop_in/2 +Kernel.pop_in/2+}
170
- #
171
- # @param subject [Object] the data-structure to traverse
172
- # @return [Object] the updated +subject+
173
- def pop_in(subject)
174
- self.get_and_update_in(subject){ :pop }
175
- end
176
-
177
- protected
178
- def append_accessor!(part)
179
- accessor =
180
- case part
181
- when Accessory::Accessor
182
- part
183
- when Array
184
- Accessory::SubscriptAccessor.new(part[0], default: part[1])
185
- else
186
- Accessory::SubscriptAccessor.new(part)
187
- end
188
-
189
- unless @parts.empty?
190
- @parts.last.make_default_fn = accessor.default_fn_for_previous_step
191
- end
192
-
193
- @parts.push(accessor)
194
- end
195
-
196
- private
197
- def get_in_step(data, path)
198
- step_accessor = path.first
199
- rest_of_path = path[1..-1]
200
-
201
- if rest_of_path.empty?
202
- step_accessor.get(data)
203
- else
204
- step_accessor.get(data){ |v| get_in_step(v, rest_of_path) }
205
- end
206
- end
207
-
208
- private
209
- def get_and_update_in_step(data, path, mutator_fn)
210
- step_accessor = path.first
211
- rest_of_path = path[1..-1]
212
-
213
- if rest_of_path.empty?
214
- step_accessor.get_and_update(data, &mutator_fn)
215
- else
216
- step_accessor.get_and_update(data){ |v| get_and_update_in_step(v, rest_of_path, mutator_fn) }
217
- end
218
- end
219
- end