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 +4 -4
- data/lib/accessory.rb +2 -2
- data/lib/accessory/accessor.rb +169 -13
- data/lib/accessory/accessors/all_accessor.rb +1 -1
- data/lib/accessory/accessors/attribute_accessor.rb +2 -2
- data/lib/accessory/accessors/between_each_accessor.rb +13 -10
- data/lib/accessory/accessors/betwixt_accessor.rb +14 -11
- data/lib/accessory/accessors/filter_accessor.rb +1 -1
- data/lib/accessory/accessors/first_accessor.rb +2 -2
- data/lib/accessory/accessors/instance_variable_accessor.rb +2 -2
- data/lib/accessory/accessors/last_accessor.rb +2 -2
- data/lib/accessory/accessors/subscript_accessor.rb +2 -2
- data/lib/accessory/lens.rb +26 -6
- data/lib/accessory/lens_path.rb +1 -1
- data/lib/accessory/traversal_position/enumerable_at_offset.rb +28 -0
- data/lib/accessory/traversal_position/enumerable_before_offset.rb +55 -0
- data/lib/accessory/version.rb +1 -1
- metadata +4 -3
- data/lib/accessory/array_cursor_position.rb +0 -32
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6a3ac947a1bb5743f72e27fca953624b93a0fdab7c94a483e4f2577f3833ccb0
|
4
|
+
data.tar.gz: 4c8570697b002e4513d2ee117e0ec5290418186e2ea9ad2879a2ba14308b7c25
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8dc468e225a4150d392727c30d2eb6104fedccc29bcbb22b18320d6b8b3b4afb311a74857540fcb9653d4ad1837f29d4bc1b20800d8d7179d7244c031da3177a
|
7
|
+
data.tar.gz: ae313d407b95a1079f93ad71adb8296a5c8b3c177bc001a5b9866ebe7945dbb2ac46b152c199d2648ac03afc2bb1609aff6190e6beeea170944ac1fe19d16628
|
data/lib/accessory.rb
CHANGED
data/lib/accessory/accessor.rb
CHANGED
@@ -1,9 +1,27 @@
|
|
1
1
|
module Accessory; end
|
2
2
|
|
3
3
|
##
|
4
|
-
#
|
5
|
-
|
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
|
-
@
|
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, :@
|
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 :
|
72
|
+
attr_accessor :succ_default_data_constructor
|
53
73
|
|
54
|
-
# @!
|
55
|
-
|
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
|
-
|
59
|
-
|
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
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
@@ -47,7 +47,7 @@ class Accessory::AttributeAccessor < Accessory::Accessor
|
|
47
47
|
end
|
48
48
|
|
49
49
|
# @!visibility private
|
50
|
-
def
|
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
|
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/
|
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
|
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
|
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::
|
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 {
|
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 {
|
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 {
|
59
|
-
# of +data+ down the accessor chain,
|
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
|
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/
|
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
|
43
|
+
def default_data_constructor
|
44
44
|
lambda{ Array.new }
|
45
45
|
end
|
46
46
|
|
47
47
|
# @!visibility private
|
48
|
-
def
|
48
|
+
def traverse(data)
|
49
49
|
data_len = data.length
|
50
50
|
|
51
|
-
Accessory::
|
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
|
61
|
-
# elements of +data+ at +@offset+ down the accessor
|
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 {
|
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
|
76
|
-
# elements of +data+ at +@offset+ down the accessor
|
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
|
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
|
|
@@ -17,7 +17,7 @@ require 'accessory/accessor'
|
|
17
17
|
|
18
18
|
class Accessory::FirstAccessor < Accessory::Accessor
|
19
19
|
# @!visibility private
|
20
|
-
def
|
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
|
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
|
36
|
+
def default_data_constructor
|
37
37
|
lambda{ Object.new }
|
38
38
|
end
|
39
39
|
|
40
40
|
# @!visibility private
|
41
|
-
def
|
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
|
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
|
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
|
56
|
+
def default_data_constructor
|
57
57
|
lambda{ Hash.new }
|
58
58
|
end
|
59
59
|
|
60
60
|
# @!visibility private
|
61
|
-
def
|
61
|
+
def traverse(data)
|
62
62
|
data[@key]
|
63
63
|
end
|
64
64
|
|
data/lib/accessory/lens.rb
CHANGED
@@ -3,11 +3,31 @@ module Accessory; end
|
|
3
3
|
require 'accessory/lens_path'
|
4
4
|
|
5
5
|
class Accessory::Lens
|
6
|
-
|
7
|
-
#
|
8
|
-
#
|
9
|
-
|
10
|
-
|
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,
|
120
|
+
Accessory::Lens.on(subject, self)
|
101
121
|
end
|
102
122
|
end
|
data/lib/accessory/lens_path.rb
CHANGED
@@ -187,7 +187,7 @@ class Accessory::LensPath
|
|
187
187
|
end
|
188
188
|
|
189
189
|
unless @parts.empty?
|
190
|
-
@parts.last.
|
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
|
data/lib/accessory/version.rb
CHANGED
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
|
+
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
|
+
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
|