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