accessory 0.1.5 → 0.1.6
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 +5 -2
- data/lib/accessory/access.rb +16 -1
- data/lib/accessory/accessor.rb +30 -37
- data/lib/accessory/accessors/all_accessor.rb +7 -3
- data/lib/accessory/accessors/attribute_accessor.rb +7 -5
- data/lib/accessory/accessors/between_each_accessor.rb +10 -6
- data/lib/accessory/accessors/betwixt_accessor.rb +23 -14
- data/lib/accessory/accessors/filter_accessor.rb +7 -3
- data/lib/accessory/accessors/first_accessor.rb +9 -5
- data/lib/accessory/accessors/instance_variable_accessor.rb +6 -6
- data/lib/accessory/accessors/last_accessor.rb +9 -5
- data/lib/accessory/accessors/subscript_accessor.rb +12 -7
- data/lib/accessory/bound_lens.rb +139 -0
- data/lib/accessory/lens.rb +192 -75
- data/lib/accessory/traversal_position/enumerable_before_offset.rb +5 -5
- data/lib/accessory/version.rb +1 -1
- metadata +3 -3
- data/lib/accessory/lens_path.rb +0 -219
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9880e7e5ea88c0b373a1cc3d5d4861788f368905c761b2ec3a7236d7282ada39
|
4
|
+
data.tar.gz: 7386d98dc728a663904564e0a6825de0d7690e806950e5e95d6e8b39d4d20cd6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dbee544b8dc699adb962559b98278d48edeed2a4d5b9c4d3c6c92ad3c4a38ffb4df5ced18274b465dfe68fd34dd15a50c06c2febae4e54049a4049d377cb10cd
|
7
|
+
data.tar.gz: 0d1be0f43650e87a1616b5c2ac968b7407c5d8e380abb7e30ad84823816d486341a2c1b791814c55695dc77f2bf0337df834816c2a703bacbb2a3f582f362907
|
data/lib/accessory.rb
CHANGED
@@ -1,14 +1,17 @@
|
|
1
|
+
##
|
2
|
+
#
|
3
|
+
|
1
4
|
module Accessory; end
|
2
5
|
|
3
6
|
require 'accessory/version'
|
4
|
-
require 'accessory/lens_path'
|
5
7
|
require 'accessory/lens'
|
8
|
+
require 'accessory/bound_lens'
|
6
9
|
require 'accessory/access'
|
7
10
|
|
8
11
|
module Accessory
|
9
12
|
refine ::Object do
|
10
13
|
def lens(...)
|
11
|
-
::Accessory::
|
14
|
+
::Accessory::BoundLens.on(self, ...)
|
12
15
|
end
|
13
16
|
end
|
14
17
|
end
|
data/lib/accessory/access.rb
CHANGED
@@ -10,6 +10,13 @@ require 'accessory/accessors/all_accessor'
|
|
10
10
|
require 'accessory/accessors/first_accessor'
|
11
11
|
require 'accessory/accessors/last_accessor'
|
12
12
|
|
13
|
+
##
|
14
|
+
# A set of convenient module-function helpers to use with <tt>Lens[...]</tt>.
|
15
|
+
#
|
16
|
+
# These functions aren't very convenient unless you
|
17
|
+
#
|
18
|
+
# include Accessory
|
19
|
+
|
13
20
|
module Accessory::Access
|
14
21
|
# (see Accessory::SubscriptAccessor)
|
15
22
|
def self.subscript(...)
|
@@ -67,6 +74,14 @@ module Accessory::Access
|
|
67
74
|
end
|
68
75
|
end
|
69
76
|
|
77
|
+
##
|
78
|
+
# A set of convenient "fluent API" builder methods
|
79
|
+
# that get mixed into {Lens} and {BoundLens}.
|
80
|
+
#
|
81
|
+
# These do the same thing as the {Access} helper of the same name, but
|
82
|
+
# wrap the resulting accessor in a call to <tt>#then</tt>, deriving a new
|
83
|
+
# {Lens} or {BoundLens} from the addition of the accessor.
|
84
|
+
|
70
85
|
module Accessory::Access::FluentHelpers
|
71
86
|
# (see Accessory::SubscriptAccessor)
|
72
87
|
def subscript(...)
|
@@ -133,6 +148,6 @@ class Accessory::Lens
|
|
133
148
|
include Accessory::Access::FluentHelpers
|
134
149
|
end
|
135
150
|
|
136
|
-
class Accessory::
|
151
|
+
class Accessory::BoundLens
|
137
152
|
include Accessory::Access::FluentHelpers
|
138
153
|
end
|
data/lib/accessory/accessor.rb
CHANGED
@@ -22,16 +22,12 @@ module Accessory; end
|
|
22
22
|
# * {Accessor#default_data_constructor}
|
23
23
|
|
24
24
|
class Accessory::Accessor
|
25
|
-
# @!visibility private
|
26
|
-
DEFAULT_NOT_SET_SENTINEL = :"98e47971-e708-42ca-bee7-0c62fe5e11c9"
|
27
|
-
|
28
|
-
# @!visibility private
|
29
25
|
TERMINAL_DEFAULT_FN = lambda{ nil }
|
26
|
+
private_constant :TERMINAL_DEFAULT_FN
|
30
27
|
|
31
28
|
# @!visibility private
|
32
|
-
def initialize(
|
33
|
-
@default_value =
|
34
|
-
@succ_default_data_constructor = TERMINAL_DEFAULT_FN
|
29
|
+
def initialize(default_value = nil)
|
30
|
+
@default_value = default_value
|
35
31
|
end
|
36
32
|
|
37
33
|
# @!visibility private
|
@@ -58,7 +54,8 @@ class Accessory::Accessor
|
|
58
54
|
end
|
59
55
|
|
60
56
|
# @!visibility private
|
61
|
-
HIDDEN_IVARS = [:@default_value, :@
|
57
|
+
HIDDEN_IVARS = [:@default_value, :@successor]
|
58
|
+
private_constant :HIDDEN_IVARS
|
62
59
|
|
63
60
|
# @!visibility private
|
64
61
|
def inspect_args
|
@@ -69,7 +66,7 @@ class Accessory::Accessor
|
|
69
66
|
end
|
70
67
|
|
71
68
|
# @!visibility private
|
72
|
-
attr_accessor :
|
69
|
+
attr_accessor :successor
|
73
70
|
|
74
71
|
# @!group Helpers
|
75
72
|
|
@@ -92,17 +89,12 @@ class Accessory::Accessor
|
|
92
89
|
# calling {default_data_constructor} on the successor-accessor in the accessor
|
93
90
|
# chain.
|
94
91
|
def traverse_or_default(data)
|
95
|
-
|
92
|
+
traversal_result = traverse(data) || @default_value
|
96
93
|
|
97
|
-
|
98
|
-
|
94
|
+
if @successor
|
95
|
+
@successor.ensure_valid(traversal_result)
|
96
|
+
else
|
99
97
|
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
98
|
end
|
107
99
|
end
|
108
100
|
|
@@ -124,7 +116,7 @@ class Accessory::Accessor
|
|
124
116
|
#
|
125
117
|
# @param data [Enumerable] the data yielded by the predecessor accessor.
|
126
118
|
# @param succ [Proc] a thunk to the successor accessor. When {get} is called
|
127
|
-
# by a {
|
119
|
+
# by a {Lens}, this is passed implicitly.
|
128
120
|
# @return [Object] the data to pass back to the predecessor accessor as a
|
129
121
|
# yield result.
|
130
122
|
def get(data, &succ)
|
@@ -171,7 +163,7 @@ class Accessory::Accessor
|
|
171
163
|
#
|
172
164
|
# @param data [Enumerable] the data yielded by the predecessor accessor.
|
173
165
|
# @param succ [Proc] a thunk to the successor accessor. When {get} is called
|
174
|
-
# by a {
|
166
|
+
# by a {Lens}, this is passed implicitly.
|
175
167
|
# @return [Object] the modification-command to pass back to the predecessor
|
176
168
|
# accessor as a yield result.
|
177
169
|
def get_and_update(data, &succ)
|
@@ -187,35 +179,36 @@ class Accessory::Accessor
|
|
187
179
|
# traversal-results.
|
188
180
|
#
|
189
181
|
# 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+
|
191
|
-
# the case where the predecessor passed invalid data.
|
182
|
+
# it performs. {traverse_or_default} takes care of feeding in a default +data+
|
183
|
+
# in the case where the predecessor passed invalid data.
|
192
184
|
#
|
193
185
|
# @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
|
186
|
+
# @return [Object] the result of traversal
|
197
187
|
def traverse(data)
|
198
188
|
raise NotImplementedError, "Accessor subclass #{self.class} must implement #traverse to use #traverse_or_default"
|
199
189
|
end
|
200
190
|
|
201
|
-
#
|
191
|
+
# Ensures that the predecessor accessor's traversal result is one this
|
192
|
+
# accessor can operate on; called by {traverse_or_default}.
|
193
|
+
#
|
194
|
+
# This callback should validate that the +traversal_result+ is one that can
|
195
|
+
# be traversed by the traversal-method of this accessor. If it can, the
|
196
|
+
# +traversal_result+ should be returned unchanged. If it cannot, an object
|
197
|
+
# that _can_ be traversed should be returned instead.
|
202
198
|
#
|
203
|
-
# Returns a default constructor Proc for the {traverse_or_default} call in
|
204
|
-
# the *predecessor* accessor to use.
|
205
199
|
#
|
206
200
|
# For example, if your accessor operates on +Enumerable+ values (like
|
207
|
-
# {AllAccessor}), then
|
208
|
-
#
|
201
|
+
# {AllAccessor}), then this method should validate that the +traversal_result+
|
202
|
+
# is +Enumerable+; and, if it isn't, return something that is — e.g. an empty
|
203
|
+
# +Array+.
|
209
204
|
#
|
210
|
-
#
|
211
|
-
#
|
212
|
-
# will then use it in {traverse_or_default} if it was not configured with
|
213
|
-
# an explicit default.
|
205
|
+
# This logic is used to replace invalid intermediate values (e.g. `nil`s and
|
206
|
+
# scalars) with containers during {Lens#put_in} et al.
|
214
207
|
#
|
215
|
-
# @return [
|
216
|
-
def
|
208
|
+
# @return [Object] a now-valid traversal result
|
209
|
+
def ensure_valid(traversal_result)
|
217
210
|
lambda do
|
218
|
-
raise NotImplementedError, "Accessor subclass #{self.class} must implement #
|
211
|
+
raise NotImplementedError, "Accessor subclass #{self.class} must implement #ensure_valid to allow chain-predecessor to use #traverse_or_default"
|
219
212
|
end
|
220
213
|
end
|
221
214
|
|
@@ -5,7 +5,7 @@ require 'accessory/accessor'
|
|
5
5
|
#
|
6
6
|
# *Aliases*
|
7
7
|
# * {Access.all}
|
8
|
-
# * {Access::FluentHelpers#all} (included in {
|
8
|
+
# * {Access::FluentHelpers#all} (included in {Lens} and {BoundLens})
|
9
9
|
#
|
10
10
|
# *Equivalents* in Elixir's {https://hexdocs.pm/elixir/Access.html +Access+} module
|
11
11
|
# * {https://hexdocs.pm/elixir/Access.html#all/0 +Access.all/0+}
|
@@ -17,8 +17,12 @@ require 'accessory/accessor'
|
|
17
17
|
|
18
18
|
class Accessory::AllAccessor < Accessory::Accessor
|
19
19
|
# @!visibility private
|
20
|
-
def
|
21
|
-
|
20
|
+
def ensure_valid(traversal_result)
|
21
|
+
if traversal_result.kind_of?(Enumerable)
|
22
|
+
traversal_result
|
23
|
+
else
|
24
|
+
[]
|
25
|
+
end
|
22
26
|
end
|
23
27
|
|
24
28
|
# @!visibility private
|
@@ -13,7 +13,7 @@ require 'accessory/accessor'
|
|
13
13
|
#
|
14
14
|
# *Aliases*
|
15
15
|
# * {Access.attr}
|
16
|
-
# * {Access::FluentHelpers#attr} (included in {
|
16
|
+
# * {Access::FluentHelpers#attr} (included in {Lens} and {BoundLens})
|
17
17
|
#
|
18
18
|
# <b>Default constructor</b> used by predecessor accessor
|
19
19
|
#
|
@@ -47,8 +47,10 @@ class Accessory::AttributeAccessor < Accessory::Accessor
|
|
47
47
|
end
|
48
48
|
|
49
49
|
# @!visibility private
|
50
|
-
def
|
51
|
-
|
50
|
+
def ensure_valid(traversal_result)
|
51
|
+
if traversal_result
|
52
|
+
traversal_result
|
53
|
+
else
|
52
54
|
require 'ostruct'
|
53
55
|
OpenStruct.new
|
54
56
|
end
|
@@ -64,7 +66,7 @@ class Accessory::AttributeAccessor < Accessory::Accessor
|
|
64
66
|
# @param data [Object] the object to traverse
|
65
67
|
# @return [Object] the value derived from the rest of the accessor chain
|
66
68
|
def get(data)
|
67
|
-
value =
|
69
|
+
value = traverse_or_default(data)
|
68
70
|
|
69
71
|
if block_given?
|
70
72
|
yield(value)
|
@@ -83,7 +85,7 @@ class Accessory::AttributeAccessor < Accessory::Accessor
|
|
83
85
|
# @param data [Object] the object to traverse
|
84
86
|
# @return [Array] a two-element array containing 1. the original value found; and 2. the result value from the accessor chain
|
85
87
|
def get_and_update(data)
|
86
|
-
value =
|
88
|
+
value = traverse_or_default(data)
|
87
89
|
|
88
90
|
case yield(value)
|
89
91
|
in [result, new_value]
|
@@ -5,12 +5,12 @@ require 'accessory/traversal_position/enumerable_before_offset'
|
|
5
5
|
# Traverses the positions "between" the elements of an +Enumerable+, including
|
6
6
|
# the positions at the "edges" (i.e. before the first, and after the last.)
|
7
7
|
#
|
8
|
-
# {BetweenEachAccessor} can be used with {
|
8
|
+
# {BetweenEachAccessor} can be used with {Lens#put_in} to insert new
|
9
9
|
# elements into an Enumerable between the existing ones.
|
10
10
|
#
|
11
11
|
# *Aliases*
|
12
12
|
# * {Access.between_each}
|
13
|
-
# * {Access::FluentHelpers#between_each} (included in {
|
13
|
+
# * {Access::FluentHelpers#between_each} (included in {Lens} and {BoundLens})
|
14
14
|
#
|
15
15
|
# <b>Default constructor</b> used by predecessor accessor
|
16
16
|
#
|
@@ -18,8 +18,12 @@ require 'accessory/traversal_position/enumerable_before_offset'
|
|
18
18
|
|
19
19
|
class Accessory::BetweenEachAccessor < Accessory::Accessor
|
20
20
|
# @!visibility private
|
21
|
-
def
|
22
|
-
|
21
|
+
def ensure_valid(traversal_result)
|
22
|
+
if traversal_result.kind_of?(Enumerable)
|
23
|
+
traversal_result
|
24
|
+
else
|
25
|
+
[]
|
26
|
+
end
|
23
27
|
end
|
24
28
|
|
25
29
|
# @!visibility private
|
@@ -46,7 +50,7 @@ class Accessory::BetweenEachAccessor < Accessory::Accessor
|
|
46
50
|
# @param data [Enumerable] the +Enumerable+ to iterate through
|
47
51
|
# @return [Array] the generated {TraversalPosition::EnumerableBeforeOffset}s
|
48
52
|
def get(data)
|
49
|
-
positions =
|
53
|
+
positions = traverse_or_default(data || [])
|
50
54
|
|
51
55
|
if block_given?
|
52
56
|
positions.map{ |rec| yield(rec) }
|
@@ -72,7 +76,7 @@ class Accessory::BetweenEachAccessor < Accessory::Accessor
|
|
72
76
|
results = []
|
73
77
|
new_data = []
|
74
78
|
|
75
|
-
positions =
|
79
|
+
positions = traverse_or_default(data || [])
|
76
80
|
|
77
81
|
positions.each do |pos|
|
78
82
|
case yield(pos)
|
@@ -13,14 +13,14 @@ require 'accessory/traversal_position/enumerable_before_offset'
|
|
13
13
|
# The +offset+ in this accessor has equivalent semantics to the offset in
|
14
14
|
# <tt>Array#insert(offset, obj)</tt>.
|
15
15
|
#
|
16
|
-
# {BetwixtAccessor} can be used with {
|
16
|
+
# {BetwixtAccessor} can be used with {Lens#put_in} to insert new
|
17
17
|
# elements into an Enumerable between the existing ones. If you want to extend
|
18
18
|
# an +Enumerable+ as you would with <tt>#push</tt> or <tt>#unshift</tt>, this
|
19
19
|
# accessor will have better behavior than using {SubscriptAccessor} would.
|
20
20
|
#
|
21
21
|
# *Aliases*
|
22
22
|
# * {Access.betwixt}
|
23
|
-
# * {Access::FluentHelpers#betwixt} (included in {
|
23
|
+
# * {Access::FluentHelpers#betwixt} (included in {Lens} and {BoundLens})
|
24
24
|
#
|
25
25
|
# <b>Default constructor</b> used by predecessor accessor
|
26
26
|
#
|
@@ -40,21 +40,30 @@ class Accessory::BetwixtAccessor < Accessory::Accessor
|
|
40
40
|
end
|
41
41
|
|
42
42
|
# @!visibility private
|
43
|
-
def
|
44
|
-
|
43
|
+
def ensure_valid(traversal_result)
|
44
|
+
if traversal_result.kind_of?(Enumerable)
|
45
|
+
traversal_result
|
46
|
+
else
|
47
|
+
[]
|
48
|
+
end
|
45
49
|
end
|
46
50
|
|
47
51
|
# @!visibility private
|
48
52
|
def traverse(data)
|
49
|
-
|
53
|
+
nil
|
54
|
+
# return :error unless data.kind_of?(Enumerable)
|
55
|
+
|
56
|
+
# data_len = data.length
|
57
|
+
|
58
|
+
# ebo = Accessory::TraversalPosition::EnumerableBeforeOffset.new(
|
59
|
+
# @offset,
|
60
|
+
# (@offset > 0) ? data[@offset - 1] : nil,
|
61
|
+
# (@offset < (data_len - 1)) ? data[@offset + 1] : nil,
|
62
|
+
# is_first: @offset == 0,
|
63
|
+
# is_last: @offset == data_len
|
64
|
+
# )
|
50
65
|
|
51
|
-
|
52
|
-
@offset,
|
53
|
-
(@offset > 0) ? data[@offset - 1] : nil,
|
54
|
-
(@offset < (data_len - 1)) ? data[@offset + 1] : nil,
|
55
|
-
is_first: @offset == 0,
|
56
|
-
is_last: @offset == data_len
|
57
|
-
)
|
66
|
+
# [:ok, ebo]
|
58
67
|
end
|
59
68
|
|
60
69
|
# Feeds a {TraversalPosition::EnumerableBeforeOffset} representing the
|
@@ -64,7 +73,7 @@ class Accessory::BetwixtAccessor < Accessory::Accessor
|
|
64
73
|
# @param data [Enumerable] the +Enumerable+ to traverse into
|
65
74
|
# @return [Array] the generated {TraversalPosition::EnumerableBeforeOffset}
|
66
75
|
def get(data)
|
67
|
-
pos =
|
76
|
+
pos = traverse_or_default(data || [])
|
68
77
|
|
69
78
|
if block_given?
|
70
79
|
yield(pos)
|
@@ -87,7 +96,7 @@ class Accessory::BetwixtAccessor < Accessory::Accessor
|
|
87
96
|
# 1. the generated {TraversalPosition::EnumerableBeforeOffset}
|
88
97
|
# 2. the new {data}
|
89
98
|
def get_and_update(data)
|
90
|
-
pos =
|
99
|
+
pos = traverse_or_default(data || [])
|
91
100
|
|
92
101
|
case yield(pos)
|
93
102
|
in [result, new_value]
|
@@ -6,7 +6,7 @@ require 'accessory/accessor'
|
|
6
6
|
#
|
7
7
|
# *Aliases*
|
8
8
|
# * {Access.filter}
|
9
|
-
# * {Access::FluentHelpers#filter} (included in {
|
9
|
+
# * {Access::FluentHelpers#filter} (included in {Lens} and {BoundLens})
|
10
10
|
#
|
11
11
|
# *Equivalents* in Elixir's {https://hexdocs.pm/elixir/Access.html +Access+} module
|
12
12
|
# * {https://hexdocs.pm/elixir/Access.html#filter/1 +Access.filter/1+}
|
@@ -34,8 +34,12 @@ class Accessory::FilterAccessor < Accessory::Accessor
|
|
34
34
|
end
|
35
35
|
|
36
36
|
# @!visibility private
|
37
|
-
def
|
38
|
-
|
37
|
+
def ensure_valid(traversal_result)
|
38
|
+
if traversal_result.kind_of?(Enumerable)
|
39
|
+
traversal_result
|
40
|
+
else
|
41
|
+
[]
|
42
|
+
end
|
39
43
|
end
|
40
44
|
|
41
45
|
# Feeds each element of +data+ matching the predicate down the accessor chain,
|
@@ -9,7 +9,7 @@ require 'accessory/accessor'
|
|
9
9
|
#
|
10
10
|
# *Aliases*
|
11
11
|
# * {Access.first}
|
12
|
-
# * {Access::FluentHelpers#first} (included in {
|
12
|
+
# * {Access::FluentHelpers#first} (included in {Lens} and {BoundLens})
|
13
13
|
#
|
14
14
|
# <b>Default constructor</b> used by predecessor accessor
|
15
15
|
#
|
@@ -17,8 +17,12 @@ require 'accessory/accessor'
|
|
17
17
|
|
18
18
|
class Accessory::FirstAccessor < Accessory::Accessor
|
19
19
|
# @!visibility private
|
20
|
-
def
|
21
|
-
|
20
|
+
def ensure_valid(traversal_result)
|
21
|
+
if traversal_result.kind_of?(Enumerable)
|
22
|
+
traversal_result
|
23
|
+
else
|
24
|
+
[]
|
25
|
+
end
|
22
26
|
end
|
23
27
|
|
24
28
|
# @!visibility private
|
@@ -33,7 +37,7 @@ class Accessory::FirstAccessor < Accessory::Accessor
|
|
33
37
|
# @param data [Object] the object to traverse
|
34
38
|
# @return [Object] the value derived from the rest of the accessor chain
|
35
39
|
def get(data)
|
36
|
-
value =
|
40
|
+
value = traverse_or_default(data)
|
37
41
|
|
38
42
|
if block_given?
|
39
43
|
yield(value)
|
@@ -51,7 +55,7 @@ class Accessory::FirstAccessor < Accessory::Accessor
|
|
51
55
|
# @param data [Object] the object to traverse
|
52
56
|
# @return [Array] a two-element array containing 1. the original value found; and 2. the result value from the accessor chain
|
53
57
|
def get_and_update(data)
|
54
|
-
old_value =
|
58
|
+
old_value = traverse_or_default(data)
|
55
59
|
|
56
60
|
case yield(old_value)
|
57
61
|
in [result, new_value]
|