accessory 0.1.5 → 0.1.6
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 +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]
|