accessory 0.1.2 → 0.1.7
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 +3 -3
- data/lib/accessory/access.rb +67 -21
- data/lib/accessory/accessor.rb +173 -13
- data/lib/accessory/accessors/all_accessor.rb +35 -3
- data/lib/accessory/accessors/attribute_accessor.rb +60 -8
- data/lib/accessory/accessors/between_each_accessor.rb +48 -8
- data/lib/accessory/accessors/betwixt_accessor.rb +75 -17
- data/lib/accessory/accessors/filter_accessor.rb +46 -5
- data/lib/accessory/accessors/first_accessor.rb +44 -7
- data/lib/accessory/accessors/instance_variable_accessor.rb +41 -8
- data/lib/accessory/accessors/last_accessor.rb +44 -7
- data/lib/accessory/accessors/subscript_accessor.rb +57 -6
- data/lib/accessory/bound_lens.rb +139 -0
- data/lib/accessory/lens.rb +197 -26
- 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 +5 -4
- data/lib/accessory/array_cursor_position.rb +0 -18
- data/lib/accessory/lens_path.rb +0 -150
@@ -1,8 +1,24 @@
|
|
1
1
|
require 'accessory/accessor'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
##
|
4
|
+
# Traverses into a named instance-variable of an arbitrary object.
|
5
|
+
#
|
6
|
+
# For example, given <tt>InstanceVariableAccessor.new(:foo)</tt>, the
|
7
|
+
# instance-variable <tt>@foo</tt> of the input data will be traversed.
|
8
|
+
#
|
9
|
+
# *Aliases*
|
10
|
+
# * {Access.ivar}
|
11
|
+
# * {Access::FluentHelpers#ivar} (included in {Lens} and {BoundLens})
|
12
|
+
#
|
13
|
+
# <b>Default constructor</b> used by predecessor accessor
|
14
|
+
#
|
15
|
+
# * +Object.new+
|
16
|
+
|
17
|
+
class Accessory::Accessors::InstanceVariableAccessor < Accessory::Accessor
|
18
|
+
# @param ivar_name [Symbol] the instance-variable name
|
19
|
+
# @param default [Object] the default to use if the predecessor accessor passes +nil+ data
|
20
|
+
def initialize(ivar_name, default: nil)
|
21
|
+
super(default)
|
6
22
|
|
7
23
|
ivar_name = ivar_name.to_s
|
8
24
|
ivar_name = "@#{ivar_name}" unless ivar_name.to_s.start_with?("@")
|
@@ -11,20 +27,27 @@ class Accessory::InstanceVariableAccessor < Accessory::Accessor
|
|
11
27
|
@ivar_name = ivar_name
|
12
28
|
end
|
13
29
|
|
30
|
+
# @!visibility private
|
14
31
|
def inspect_args
|
15
32
|
@ivar_name.to_s
|
16
33
|
end
|
17
34
|
|
18
|
-
|
19
|
-
|
35
|
+
# @!visibility private
|
36
|
+
def ensure_valid(traversal_result)
|
37
|
+
traversal_result || Object.new
|
20
38
|
end
|
21
39
|
|
22
|
-
|
40
|
+
# @!visibility private
|
41
|
+
def traverse(data)
|
23
42
|
data.instance_variable_get(@ivar_name)
|
24
43
|
end
|
25
44
|
|
45
|
+
# Finds <tt>data.instance_variable_get(:"@#{ivar_name}")</tt>, feeds it
|
46
|
+
# down the accessor chain, and returns the result.
|
47
|
+
# @param data [Object] the object to traverse
|
48
|
+
# @return [Object] the value derived from the rest of the accessor chain
|
26
49
|
def get(data)
|
27
|
-
value =
|
50
|
+
value = traverse_or_default(data)
|
28
51
|
|
29
52
|
if block_given?
|
30
53
|
yield(value)
|
@@ -33,8 +56,18 @@ class Accessory::InstanceVariableAccessor < Accessory::Accessor
|
|
33
56
|
end
|
34
57
|
end
|
35
58
|
|
59
|
+
# Finds <tt>data.instance_variable_get(:"@#{ivar_name}")</tt>, feeds it down
|
60
|
+
# the accessor chain, and uses
|
61
|
+
# <tt>data.instance_variable_set(:"@#{ivar_name}")</tt> to overwrite the
|
62
|
+
# stored value with the returned result.
|
63
|
+
#
|
64
|
+
# If +:pop+ is returned from the accessor chain, the stored value will be
|
65
|
+
# removed using <tt>data.remove_instance_variable(:"@#{ivar_name}")</tt>.
|
66
|
+
#
|
67
|
+
# @param data [Object] the object to traverse
|
68
|
+
# @return [Array] a two-element array containing 1. the original value found; and 2. the result value from the accessor chain
|
36
69
|
def get_and_update(data)
|
37
|
-
value =
|
70
|
+
value = traverse_or_default(data)
|
38
71
|
|
39
72
|
case yield(value)
|
40
73
|
in [result, new_value]
|
@@ -1,18 +1,43 @@
|
|
1
1
|
require 'accessory/accessor'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
##
|
4
|
+
# Traverses into the "last" element within an +Enumerable+, using
|
5
|
+
# <tt>#last</tt>.
|
6
|
+
#
|
7
|
+
# This accessor can be preferable to {SubscriptAccessor} for objects that
|
8
|
+
# are not subscriptable, e.g. {Range}.
|
9
|
+
#
|
10
|
+
# *Aliases*
|
11
|
+
# * {Access.last}
|
12
|
+
# * {Access::FluentHelpers#last} (included in {Lens} and {BoundLens})
|
13
|
+
#
|
14
|
+
# <b>Default constructor</b> used by predecessor accessor
|
15
|
+
#
|
16
|
+
# * +Array.new+
|
17
|
+
|
18
|
+
class Accessory::Accessors::LastAccessor < Accessory::Accessor
|
19
|
+
# @!visibility private
|
20
|
+
def ensure_valid(traversal_result)
|
21
|
+
if traversal_result.kind_of?(Enumerable)
|
22
|
+
traversal_result
|
23
|
+
else
|
24
|
+
[]
|
25
|
+
end
|
6
26
|
end
|
7
27
|
|
28
|
+
# @!visibility private
|
8
29
|
def inspect_args; nil; end
|
9
30
|
|
10
|
-
|
31
|
+
# @!visibility private
|
32
|
+
def traverse(data)
|
11
33
|
data.last
|
12
34
|
end
|
13
35
|
|
36
|
+
# Feeds <tt>data.last</tt> down the accessor chain, returning the result.
|
37
|
+
# @param data [Object] the object to traverse
|
38
|
+
# @return [Object] the value derived from the rest of the accessor chain
|
14
39
|
def get(data)
|
15
|
-
value =
|
40
|
+
value = traverse_or_default(data)
|
16
41
|
|
17
42
|
if block_given?
|
18
43
|
yield(value)
|
@@ -21,12 +46,24 @@ class Accessory::LastAccessor < Accessory::Accessor
|
|
21
46
|
end
|
22
47
|
end
|
23
48
|
|
49
|
+
# Finds <tt>data.last</tt>, feeds it down the accessor chain, and overwrites
|
50
|
+
# the stored value with the returned result.
|
51
|
+
#
|
52
|
+
# If +:pop+ is returned from the accessor chain, the stored value will be
|
53
|
+
# removed using <tt>data.delete_at(-1)</tt>.
|
54
|
+
#
|
55
|
+
# @param data [Object] the object to traverse
|
56
|
+
# @return [Array] a two-element array containing 1. the original value found; and 2. the result value from the accessor chain
|
24
57
|
def get_and_update(data)
|
25
|
-
old_value =
|
58
|
+
old_value = traverse_or_default(data)
|
26
59
|
|
27
60
|
case yield(old_value)
|
28
61
|
in [result, new_value]
|
29
|
-
data
|
62
|
+
if data.respond_to?(:"last=")
|
63
|
+
data.last = new_value
|
64
|
+
else
|
65
|
+
data[-1] = new_value
|
66
|
+
end
|
30
67
|
[result, data]
|
31
68
|
in :pop
|
32
69
|
data.delete_at(-1)
|
@@ -1,11 +1,44 @@
|
|
1
1
|
require 'accessory/accessor'
|
2
2
|
|
3
|
-
|
3
|
+
##
|
4
|
+
# Traverses into a specified +key+ for an arbitrary container-object which supports the +#[]+ and +#[]=+ methods.
|
5
|
+
#
|
6
|
+
# @param key [Object] the key to pass to the +#[]+ and +#[]=+ methods.
|
7
|
+
#
|
8
|
+
# *Aliases*
|
9
|
+
# * {Access.subscript}
|
10
|
+
# * {Access::FluentHelpers#subscript} (included in {Lens} and {BoundLens})
|
11
|
+
# * {Access::FluentHelpers#[]} (included in {Lens} and {BoundLens})
|
12
|
+
# * just passing a +key+ will also work, when +not(key.kind_of?(Accessor))+
|
13
|
+
# (this is a special case in {Lens#initialize})
|
14
|
+
#
|
15
|
+
# *Equivalents* in Elixir's {https://hexdocs.pm/elixir/Access.html +Access+} module
|
16
|
+
# * {https://hexdocs.pm/elixir/Access.html#at/1 +Access.at/1+}
|
17
|
+
# * {https://hexdocs.pm/elixir/Access.html#key/2 +Access.key/2+}
|
18
|
+
#
|
19
|
+
# <b>Default constructor</b> used by predecessor accessor
|
20
|
+
#
|
21
|
+
# * +Hash.new+
|
22
|
+
#
|
23
|
+
# == Usage Notes:
|
24
|
+
# Subscripting into an +Array+ will *work*, but may not have the results you expect:
|
25
|
+
#
|
26
|
+
# # extends the Array
|
27
|
+
# [].lens[3].put_in(1) # => [nil, nil, nil, 1]
|
28
|
+
#
|
29
|
+
# # default-constructs a Hash, not an Array
|
30
|
+
# [].lens[0][0].put_in(1) # => [{0=>1}]
|
31
|
+
#
|
32
|
+
# Other accessors ({FirstAccessor}, {BetwixtAccessor}, etc.) may fit your expectations more closely for +Array+ traversal.
|
33
|
+
|
34
|
+
class Accessory::Accessors::SubscriptAccessor < Accessory::Accessor
|
35
|
+
# @!visibility private
|
4
36
|
def initialize(key, **kwargs)
|
5
37
|
super(**kwargs)
|
6
38
|
@key = key
|
7
39
|
end
|
8
40
|
|
41
|
+
# @!visibility private
|
9
42
|
def inspect(format: :long)
|
10
43
|
case format
|
11
44
|
when :long
|
@@ -15,20 +48,31 @@ class Accessory::SubscriptAccessor < Accessory::Accessor
|
|
15
48
|
end
|
16
49
|
end
|
17
50
|
|
51
|
+
# @!visibility private
|
18
52
|
def inspect_args
|
19
53
|
@key.inspect
|
20
54
|
end
|
21
55
|
|
22
|
-
|
23
|
-
|
56
|
+
# @!visibility private
|
57
|
+
def ensure_valid(traversal_result)
|
58
|
+
if traversal_result.respond_to?(:[])
|
59
|
+
traversal_result
|
60
|
+
else
|
61
|
+
{}
|
62
|
+
end
|
24
63
|
end
|
25
64
|
|
26
|
-
|
65
|
+
# @!visibility private
|
66
|
+
def traverse(data)
|
27
67
|
data[@key]
|
28
68
|
end
|
29
69
|
|
70
|
+
# Finds <tt>data[@key]</tt>, feeds it down the accessor chain, and returns
|
71
|
+
# the result.
|
72
|
+
# @param data [Enumerable] the +Enumerable+ to index into
|
73
|
+
# @return [Object] the value derived from the rest of the accessor chain
|
30
74
|
def get(data)
|
31
|
-
value =
|
75
|
+
value = traverse_or_default(data)
|
32
76
|
|
33
77
|
if block_given?
|
34
78
|
yield(value)
|
@@ -37,8 +81,15 @@ class Accessory::SubscriptAccessor < Accessory::Accessor
|
|
37
81
|
end
|
38
82
|
end
|
39
83
|
|
84
|
+
# Finds <tt>data[@key]</tt>, feeds it down the accessor chain, and overwrites
|
85
|
+
# <tt>data[@key]</tt> with the returned result.
|
86
|
+
#
|
87
|
+
# If +:pop+ is returned from the accessor chain, the key is instead deleted
|
88
|
+
# from the {data} with <tt>data.delete(@key)</tt>.
|
89
|
+
# @param data [Enumerable] the +Enumerable+ to index into
|
90
|
+
# @return [Array] a two-element array containing 1. the original value found; and 2. the result value from the accessor chain
|
40
91
|
def get_and_update(data)
|
41
|
-
value =
|
92
|
+
value = traverse_or_default(data)
|
42
93
|
|
43
94
|
case yield(value)
|
44
95
|
in [result, new_value]
|
@@ -0,0 +1,139 @@
|
|
1
|
+
module Accessory; end
|
2
|
+
|
3
|
+
require 'accessory/lens'
|
4
|
+
|
5
|
+
##
|
6
|
+
# A BoundLens represents a {Lens} bound to a specified subject document.
|
7
|
+
# See {Lens} for the general theory.
|
8
|
+
#
|
9
|
+
# A BoundLens can be used to traverse its subject document, using {get_in},
|
10
|
+
# {put_in}, {pop_in}, etc.
|
11
|
+
#
|
12
|
+
# Ordinarily, you don't create and hold onto a BoundLens, but rather you will
|
13
|
+
# temporarily create Lenses in method-call chains when doing traversals.
|
14
|
+
#
|
15
|
+
# It may sometimes be useful to create a collection of Lenses and then "build
|
16
|
+
# them up" by extending their {Lens}es over various collection-passes, rather
|
17
|
+
# than building up {Lens}es and only binding them to subjects at the end.
|
18
|
+
#
|
19
|
+
# Lenses are created frozen. Methods that "extend" a BoundLens actually
|
20
|
+
# create and return new derived Lenses.
|
21
|
+
|
22
|
+
class Accessory::BoundLens
|
23
|
+
|
24
|
+
# Creates a BoundLens that will traverse +subject+.
|
25
|
+
#
|
26
|
+
# @overload on(subject, lens)
|
27
|
+
# Creates a BoundLens that will traverse +subject+ along +lens+.
|
28
|
+
#
|
29
|
+
# @param subject [Object] the data-structure this BoundLens will traverse
|
30
|
+
# @param lens [Lens] the {Lens} that will be used to traverse +subject+
|
31
|
+
#
|
32
|
+
# @overload on(subject, *accessors)
|
33
|
+
# Creates a BoundLens that will traverse +subject+ using a {Lens} built
|
34
|
+
# from +accessors+.
|
35
|
+
#
|
36
|
+
# @param subject [Object] the data-structure this BoundLens will traverse
|
37
|
+
# @param accessors [Array] the accessors for the new {Lens}
|
38
|
+
def self.on(subject, *accessors)
|
39
|
+
lens =
|
40
|
+
if accessors.length == 1 && accessors[0].kind_of?(Lens)
|
41
|
+
accessors[0]
|
42
|
+
else
|
43
|
+
Accessory::Lens[*accessors]
|
44
|
+
end
|
45
|
+
|
46
|
+
self.new(subject, lens).freeze
|
47
|
+
end
|
48
|
+
|
49
|
+
class << self
|
50
|
+
private :new
|
51
|
+
end
|
52
|
+
|
53
|
+
# @!visibility private
|
54
|
+
def initialize(subject, lens)
|
55
|
+
@subject = subject
|
56
|
+
@lens = lens
|
57
|
+
end
|
58
|
+
|
59
|
+
# @return [Lens] the +subject+ this BoundLens will traverse
|
60
|
+
attr_reader :subject
|
61
|
+
|
62
|
+
# @return [Lens] the {Lens} for this BoundLens
|
63
|
+
attr_reader :lens
|
64
|
+
|
65
|
+
# @!visibility private
|
66
|
+
def inspect
|
67
|
+
"#<BoundLens on=#{@subject.inspect} #{@lens.inspect(format: :short)}>"
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns a new BoundLens resulting from appending +accessor+ to the
|
71
|
+
# receiver's {Lens}.
|
72
|
+
#
|
73
|
+
# === See also:
|
74
|
+
# * {Lens#then}
|
75
|
+
#
|
76
|
+
# @param accessor [Object] the accessor to append
|
77
|
+
# @return [Lens] the new BoundLens, containing a new joined Lens
|
78
|
+
def then(accessor)
|
79
|
+
d = self.dup
|
80
|
+
d.instance_eval do
|
81
|
+
@lens = @lens.then(accessor)
|
82
|
+
end
|
83
|
+
d.freeze
|
84
|
+
end
|
85
|
+
|
86
|
+
# Returns a new BoundLens resulting from concatenating +other+ to the
|
87
|
+
# receiver's {Lens}.
|
88
|
+
#
|
89
|
+
# === See also:
|
90
|
+
# * {Lens#+}
|
91
|
+
#
|
92
|
+
# @param other [Object] an accessor, an +Array+ of accessors, or a {Lens}
|
93
|
+
# @return [Lens] the new BoundLens, containing a new joined Lens
|
94
|
+
def +(other)
|
95
|
+
d = self.dup
|
96
|
+
d.instance_eval do
|
97
|
+
@lens = @lens + other
|
98
|
+
end
|
99
|
+
d.freeze
|
100
|
+
end
|
101
|
+
|
102
|
+
alias_method :/, :+
|
103
|
+
|
104
|
+
# (see Lens#get_in)
|
105
|
+
def get_in
|
106
|
+
@lens.get_in(@subject)
|
107
|
+
end
|
108
|
+
|
109
|
+
# (see Lens#get_and_update_in)
|
110
|
+
def get_and_update_in(&mutator_fn)
|
111
|
+
@lens.get_and_update_in(@subject, &mutator_fn)
|
112
|
+
end
|
113
|
+
|
114
|
+
# (see Lens#update_in)
|
115
|
+
def update_in(&new_value_fn)
|
116
|
+
@lens.update_in(@subject, &new_value_fn)
|
117
|
+
end
|
118
|
+
|
119
|
+
# (see Lens#put_in)
|
120
|
+
def put_in(new_value)
|
121
|
+
@lens.put_in(@subject, new_value)
|
122
|
+
end
|
123
|
+
|
124
|
+
# (see Lens#pop_in)
|
125
|
+
def pop_in
|
126
|
+
@lens.pop_in(@subject)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
class Accessory::Lens
|
131
|
+
# Returns a new {BoundLens} wrapping this Lens, bound to the specified
|
132
|
+
# +subject+.
|
133
|
+
# @param subject [Object] the data-structure to traverse
|
134
|
+
# @return [BoundLens] a new BoundLens that will traverse +subject+ using
|
135
|
+
# this Lens
|
136
|
+
def on(subject)
|
137
|
+
Accessory::BoundLens.on(subject, self)
|
138
|
+
end
|
139
|
+
end
|
data/lib/accessory/lens.rb
CHANGED
@@ -1,68 +1,239 @@
|
|
1
1
|
module Accessory; end
|
2
2
|
|
3
|
-
require 'accessory/
|
3
|
+
require 'accessory/accessor'
|
4
|
+
require 'accessory/accessors/subscript_accessor'
|
5
|
+
|
6
|
+
##
|
7
|
+
# A Lens is a "free-floating" lens (i.e. not bound to a subject document.)
|
8
|
+
# It serves as a container for {Accessor} instances, and represents the
|
9
|
+
# traversal path one would take to get from a hypothetical subject document,
|
10
|
+
# to a data value nested somewhere within it.
|
11
|
+
#
|
12
|
+
# A Lens can be used directly to traverse documents, using {get_in},
|
13
|
+
# {put_in}, {pop_in}, etc. These methods take an explicit subject document to
|
14
|
+
# traverse, rather than requiring that the Lens be bound to a document
|
15
|
+
# first.
|
16
|
+
#
|
17
|
+
# As such, a Lens is reusable. A common use of a Lens is to access the
|
18
|
+
# same deeply-nested traversal position within a large collection of subject
|
19
|
+
# documents, e.g.:
|
20
|
+
#
|
21
|
+
# foo_bar_baz = Lens[:foo, :bar, :baz]
|
22
|
+
# docs.map{ |doc| foo_bar_baz.get_in(doc) }
|
23
|
+
#
|
24
|
+
# A Lens can also be bound to a specific subject document to create a
|
25
|
+
# {BoundLens}. See {BoundLens.on}.
|
26
|
+
#
|
27
|
+
# Lenses are created frozen. Methods that "extend" a Lens actually create and
|
28
|
+
# return new derived Lenses.
|
4
29
|
|
5
30
|
class Accessory::Lens
|
6
|
-
|
7
|
-
|
31
|
+
# Returns the empty (identity) Lens.
|
32
|
+
# @return [Lens] the empty (identity) Lens.
|
33
|
+
def self.empty
|
34
|
+
@empty_lens ||= (new([]).freeze)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns a {Lens} containing the specified +accessors+.
|
38
|
+
# @return [Lens] a Lens containing the specified +accessors+.
|
39
|
+
def self.[](*accessors)
|
40
|
+
new(accessors).freeze
|
8
41
|
end
|
9
42
|
|
10
43
|
class << self
|
11
44
|
private :new
|
12
45
|
end
|
13
46
|
|
14
|
-
|
15
|
-
|
16
|
-
@
|
47
|
+
# @!visibility private
|
48
|
+
def initialize(initial_parts)
|
49
|
+
@parts = []
|
50
|
+
|
51
|
+
for part in initial_parts
|
52
|
+
append_accessor!(part)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# @!visibility private
|
57
|
+
def to_a
|
58
|
+
@parts
|
17
59
|
end
|
18
60
|
|
19
|
-
|
61
|
+
# @!visibility private
|
62
|
+
def inspect(format: :long)
|
63
|
+
parts_desc = @parts.map{ |part| part.inspect(format: :short) }.join(', ')
|
64
|
+
parts_desc = "[#{parts_desc}]"
|
20
65
|
|
21
|
-
|
22
|
-
|
66
|
+
case format
|
67
|
+
when :long
|
68
|
+
"#Lens#{parts_desc}"
|
69
|
+
when :short
|
70
|
+
parts_desc
|
71
|
+
end
|
23
72
|
end
|
24
73
|
|
74
|
+
# Returns a new {Lens} resulting from appending +accessor+ to the receiver.
|
75
|
+
# @param accessor [Object] the accessor to append
|
76
|
+
# @return [Lens] the new joined Lens
|
25
77
|
def then(accessor)
|
26
78
|
d = self.dup
|
27
79
|
d.instance_eval do
|
28
|
-
@
|
80
|
+
@parts = @parts.dup
|
81
|
+
append_accessor!(accessor)
|
29
82
|
end
|
30
83
|
d.freeze
|
31
84
|
end
|
32
85
|
|
33
|
-
|
86
|
+
# Returns a new {Lens} resulting from concatenating +other+ to the end
|
87
|
+
# of the receiver.
|
88
|
+
# @param other [Object] an accessor, an +Array+ of accessors, or another Lens
|
89
|
+
# @return [Lens] the new joined Lens
|
90
|
+
def +(other)
|
91
|
+
parts =
|
92
|
+
case other
|
93
|
+
when Accessory::Lens
|
94
|
+
other.to_a
|
95
|
+
when Array
|
96
|
+
other
|
97
|
+
else
|
98
|
+
[other]
|
99
|
+
end
|
100
|
+
|
34
101
|
d = self.dup
|
35
102
|
d.instance_eval do
|
36
|
-
|
103
|
+
for part in parts
|
104
|
+
append_accessor!(part)
|
105
|
+
end
|
37
106
|
end
|
38
107
|
d.freeze
|
39
108
|
end
|
40
109
|
|
41
110
|
alias_method :/, :+
|
42
111
|
|
43
|
-
|
44
|
-
|
112
|
+
# Traverses +subject+ using the chain of accessors held in this Lens,
|
113
|
+
# returning the discovered value.
|
114
|
+
#
|
115
|
+
# *Equivalent* in Elixir: {https://hexdocs.pm/elixir/Kernel.html#get_in/2 +Kernel.get_in/2+}
|
116
|
+
#
|
117
|
+
# @return [Object] the value found after all traversals.
|
118
|
+
def get_in(subject)
|
119
|
+
if @parts.empty?
|
120
|
+
subject
|
121
|
+
else
|
122
|
+
get_in_step(subject, @parts)
|
123
|
+
end
|
45
124
|
end
|
46
125
|
|
47
|
-
|
48
|
-
|
126
|
+
# Traverses +subject+ using the chain of accessors held in this Lens,
|
127
|
+
# modifying the final value at the end of the traversal chain using
|
128
|
+
# the passed +mutator_fn+, and returning the original targeted value(s)
|
129
|
+
# pre-modification.
|
130
|
+
#
|
131
|
+
# +mutator_fn+ must return one of two data "shapes":
|
132
|
+
# * a two-element +Array+, representing:
|
133
|
+
# 1. the value to surface as the "get" value of the traversal
|
134
|
+
# 2. the new value to replace at the traversal-position
|
135
|
+
# * the Symbol +:pop+ — which will remove the value from its parent, and
|
136
|
+
# return it as-is.
|
137
|
+
#
|
138
|
+
# *Equivalent* in Elixir: {https://hexdocs.pm/elixir/Kernel.html#get_and_update_in/3 +Kernel.get_and_update_in/3+}
|
139
|
+
#
|
140
|
+
# @param subject [Object] the data-structure to traverse
|
141
|
+
# @param mutator_fn [Proc] a block taking the original value derived from
|
142
|
+
# traversing +subject+, and returning a modification operation.
|
143
|
+
# @return [Array] a two-element +Array+, consisting of
|
144
|
+
# 1. the _old_ value(s) found after all traversals, and
|
145
|
+
# 2. the updated +subject+
|
146
|
+
def get_and_update_in(subject, &mutator_fn)
|
147
|
+
if @parts.empty?
|
148
|
+
subject
|
149
|
+
else
|
150
|
+
get_and_update_in_step(subject, @parts, mutator_fn)
|
151
|
+
end
|
49
152
|
end
|
50
153
|
|
51
|
-
|
52
|
-
|
154
|
+
# Traverses +subject+ using the chain of accessors held in this Lens,
|
155
|
+
# replacing the final value at the end of the traversal chain with the
|
156
|
+
# result from the passed +new_value_fn+.
|
157
|
+
#
|
158
|
+
# *Equivalent* in Elixir: {https://hexdocs.pm/elixir/Kernel.html#update_in/3 +Kernel.update_in/3+}
|
159
|
+
#
|
160
|
+
# @param subject [Object] the data-structure to traverse
|
161
|
+
# @param new_value_fn [Proc] a block taking the original value derived from
|
162
|
+
# traversing +subject+, and returning a replacement value.
|
163
|
+
# @return [Array] a two-element +Array+, consisting of
|
164
|
+
# 1. the _old_ value(s) found after all traversals, and
|
165
|
+
# 2. the updated +subject+
|
166
|
+
def update_in(subject, &new_value_fn)
|
167
|
+
_, new_data = self.get_and_update_in(data){ |v| [nil, new_value_fn.call(v)] }
|
168
|
+
new_data
|
53
169
|
end
|
54
170
|
|
55
|
-
|
56
|
-
|
171
|
+
# Traverses +subject+ using the chain of accessors held in this Lens,
|
172
|
+
# replacing the final value at the end of the traversal chain with
|
173
|
+
# +new_value+.
|
174
|
+
#
|
175
|
+
# *Equivalent* in Elixir: {https://hexdocs.pm/elixir/Kernel.html#put_in/3 +Kernel.put_in/3+}
|
176
|
+
#
|
177
|
+
# @param subject [Object] the data-structure to traverse
|
178
|
+
# @param new_value [Object] a replacement value at the traversal position.
|
179
|
+
# @return [Object] the updated +subject+
|
180
|
+
def put_in(subject, new_value)
|
181
|
+
_, new_data = self.get_and_update_in(subject){ [nil, new_value] }
|
182
|
+
new_data
|
57
183
|
end
|
58
184
|
|
59
|
-
|
60
|
-
|
185
|
+
# Traverses +subject+ using the chain of accessors held in this Lens,
|
186
|
+
# removing the final value at the end of the traversal chain from its position
|
187
|
+
# within its parent container.
|
188
|
+
#
|
189
|
+
# *Equivalent* in Elixir: {https://hexdocs.pm/elixir/Kernel.html#pop_in/2 +Kernel.pop_in/2+}
|
190
|
+
#
|
191
|
+
# @param subject [Object] the data-structure to traverse
|
192
|
+
# @return [Object] the updated +subject+
|
193
|
+
def pop_in(subject)
|
194
|
+
self.get_and_update_in(subject){ :pop }
|
61
195
|
end
|
62
|
-
end
|
63
196
|
|
64
|
-
|
65
|
-
|
66
|
-
|
197
|
+
def append_accessor!(part)
|
198
|
+
accessor =
|
199
|
+
case part
|
200
|
+
when Accessory::Accessor
|
201
|
+
part
|
202
|
+
when Array
|
203
|
+
Accessory::Accessors::SubscriptAccessor.new(part[0], default: part[1])
|
204
|
+
else
|
205
|
+
Accessory::Accessors::SubscriptAccessor.new(part)
|
206
|
+
end
|
207
|
+
|
208
|
+
unless @parts.empty?
|
209
|
+
@parts.last.successor = accessor
|
210
|
+
end
|
211
|
+
|
212
|
+
@parts.push(accessor)
|
213
|
+
end
|
214
|
+
private :append_accessor!
|
215
|
+
|
216
|
+
def get_in_step(data, path)
|
217
|
+
step_accessor = path.first
|
218
|
+
rest_of_path = path[1..-1]
|
219
|
+
|
220
|
+
if rest_of_path.empty?
|
221
|
+
step_accessor.get(data)
|
222
|
+
else
|
223
|
+
step_accessor.get(data){ |v| get_in_step(v, rest_of_path) }
|
224
|
+
end
|
225
|
+
end
|
226
|
+
private :get_in_step
|
227
|
+
|
228
|
+
def get_and_update_in_step(data, path, mutator_fn)
|
229
|
+
step_accessor = path.first
|
230
|
+
rest_of_path = path[1..-1]
|
231
|
+
|
232
|
+
if rest_of_path.empty?
|
233
|
+
step_accessor.get_and_update(data, &mutator_fn)
|
234
|
+
else
|
235
|
+
step_accessor.get_and_update(data){ |v| get_and_update_in_step(v, rest_of_path, mutator_fn) }
|
236
|
+
end
|
67
237
|
end
|
238
|
+
private :get_and_update_in_step
|
68
239
|
end
|