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.
@@ -1,8 +1,24 @@
1
1
  require 'accessory/accessor'
2
2
 
3
- class Accessory::InstanceVariableAccessor < Accessory::Accessor
4
- def initialize(ivar_name, **kwargs)
5
- super(**kwargs)
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
- def default_fn_for_previous_step
19
- lambda{ Object.new }
35
+ # @!visibility private
36
+ def ensure_valid(traversal_result)
37
+ traversal_result || Object.new
20
38
  end
21
39
 
22
- def value_from(data)
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 = value_or_default(data)
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 = value_or_default(data)
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
- class Accessory::LastAccessor < Accessory::Accessor
4
- def default_fn_for_previous_step
5
- lambda{ Array.new }
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
- def value_from(data)
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 = value_or_default(data)
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 = value_or_default(data)
58
+ old_value = traverse_or_default(data)
26
59
 
27
60
  case yield(old_value)
28
61
  in [result, new_value]
29
- data[-1] = new_value
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
- class Accessory::SubscriptAccessor < Accessory::Accessor
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
- def default_fn_for_previous_step
23
- lambda{ Hash.new }
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
- def value_from(data)
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 = value_or_default(data)
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 = value_or_default(data)
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
@@ -1,68 +1,239 @@
1
1
  module Accessory; end
2
2
 
3
- require 'accessory/lens_path'
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
- def self.on(doc, path: nil)
7
- self.new(doc, path || Accessory::LensPath.empty).freeze
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
- def initialize(doc, lens_path)
15
- @doc = doc
16
- @path = lens_path
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
- attr_reader :path
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
- def inspect
22
- "#<Lens on=#{@doc.inspect} #{@path.inspect(format: :short)}>"
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
- @path = @path.then(accessor)
80
+ @parts = @parts.dup
81
+ append_accessor!(accessor)
29
82
  end
30
83
  d.freeze
31
84
  end
32
85
 
33
- def +(lens_path)
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
- @path = @path + lens_path
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
- def get_in
44
- @path.get_in(@doc)
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
- def get_and_update_in(&mutator_fn)
48
- @path.get_and_update_in(@doc, &mutator_fn)
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
- def update_in(&new_value_fn)
52
- @path.update_in(@doc, &new_value_fn)
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
- def put_in(new_value)
56
- @path.put_in(@doc, new_value)
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
- def pop_in
60
- @path.pop_in(@doc)
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
- class Accessory::LensPath
65
- def on(doc)
66
- Accessory::Lens.on(doc, path: self)
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