accessory 0.1.4 → 0.1.9

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.
@@ -8,14 +8,14 @@ require 'accessory/accessor'
8
8
  #
9
9
  # *Aliases*
10
10
  # * {Access.ivar}
11
- # * {Access::FluentHelpers#ivar} (included in {LensPath} and {Lens})
11
+ # * {Access::FluentHelpers#ivar} (included in {Lens} and {BoundLens})
12
12
  #
13
13
  # <b>Default constructor</b> used by predecessor accessor
14
14
  #
15
15
  # * +Object.new+
16
16
 
17
- class Accessory::InstanceVariableAccessor < Accessory::Accessor
18
- # @param attr_name [Symbol] the instance-variable name
17
+ class Accessory::Accessors::InstanceVariableAccessor < Accessory::Accessor
18
+ # @param ivar_name [Symbol] the instance-variable name
19
19
  # @param default [Object] the default to use if the predecessor accessor passes +nil+ data
20
20
  def initialize(ivar_name, default: nil)
21
21
  super(default)
@@ -33,12 +33,12 @@ class Accessory::InstanceVariableAccessor < Accessory::Accessor
33
33
  end
34
34
 
35
35
  # @!visibility private
36
- def default_fn_for_previous_step
37
- lambda{ Object.new }
36
+ def ensure_valid(traversal_result)
37
+ traversal_result || Object.new
38
38
  end
39
39
 
40
40
  # @!visibility private
41
- def value_from(data)
41
+ def traverse(data)
42
42
  data.instance_variable_get(@ivar_name)
43
43
  end
44
44
 
@@ -47,7 +47,7 @@ class Accessory::InstanceVariableAccessor < Accessory::Accessor
47
47
  # @param data [Object] the object to traverse
48
48
  # @return [Object] the value derived from the rest of the accessor chain
49
49
  def get(data)
50
- value = value_or_default(data)
50
+ value = traverse_or_default(data)
51
51
 
52
52
  if block_given?
53
53
  yield(value)
@@ -67,15 +67,17 @@ class Accessory::InstanceVariableAccessor < Accessory::Accessor
67
67
  # @param data [Object] the object to traverse
68
68
  # @return [Array] a two-element array containing 1. the original value found; and 2. the result value from the accessor chain
69
69
  def get_and_update(data)
70
- value = value_or_default(data)
70
+ value = traverse_or_default(data)
71
71
 
72
72
  case yield(value)
73
- in [result, new_value]
73
+ in [:clean, result, _]
74
+ [:clean, result, data]
75
+ in [:dirty, result, new_value]
74
76
  data.instance_variable_set(@ivar_name, new_value)
75
- [result, data]
77
+ [:dirty, result, data]
76
78
  in :pop
77
79
  data.remove_instance_variable(@ivar_name)
78
- [value, data]
80
+ [:dirty, value, data]
79
81
  end
80
82
  end
81
83
  end
@@ -4,28 +4,32 @@ require 'accessory/accessor'
4
4
  # Traverses into the "last" element within an +Enumerable+, using
5
5
  # <tt>#last</tt>.
6
6
  #
7
- # This accessor can be preferable to {SubscriptAccessor} for objects that
8
- # are not subscriptable, e.g. {Range}.
7
+ # This accessor can be preferable to {Accessors::SubscriptAccessor} for objects
8
+ # that are not subscriptable, e.g. +Range+.
9
9
  #
10
10
  # *Aliases*
11
11
  # * {Access.last}
12
- # * {Access::FluentHelpers#last} (included in {LensPath} and {Lens})
12
+ # * {Access::FluentHelpers#last} (included in {Lens} and {BoundLens})
13
13
  #
14
14
  # <b>Default constructor</b> used by predecessor accessor
15
15
  #
16
16
  # * +Array.new+
17
17
 
18
- class Accessory::LastAccessor < Accessory::Accessor
18
+ class Accessory::Accessors::LastAccessor < Accessory::Accessor
19
19
  # @!visibility private
20
- def default_fn_for_previous_step
21
- lambda{ Array.new }
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
25
29
  def inspect_args; nil; end
26
30
 
27
31
  # @!visibility private
28
- def value_from(data)
32
+ def traverse(data)
29
33
  data.last
30
34
  end
31
35
 
@@ -33,7 +37,7 @@ class Accessory::LastAccessor < 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 = value_or_default(data)
40
+ value = traverse_or_default(data)
37
41
 
38
42
  if block_given?
39
43
  yield(value)
@@ -51,19 +55,21 @@ class Accessory::LastAccessor < 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 = value_or_default(data)
58
+ old_value = traverse_or_default(data)
55
59
 
56
60
  case yield(old_value)
57
- in [result, new_value]
61
+ in [:clean, result, _]
62
+ [:clean, result, data]
63
+ in [:dirty, result, new_value]
58
64
  if data.respond_to?(:"last=")
59
65
  data.last = new_value
60
66
  else
61
67
  data[-1] = new_value
62
68
  end
63
- [result, data]
69
+ [:dirty, result, data]
64
70
  in :pop
65
71
  data.delete_at(-1)
66
- [old_value, data]
72
+ [:dirty, old_value, data]
67
73
  end
68
74
  end
69
75
  end
@@ -7,9 +7,10 @@ require 'accessory/accessor'
7
7
  #
8
8
  # *Aliases*
9
9
  # * {Access.subscript}
10
- # * {Access::FluentHelpers#subscript} (included in {LensPath} and {Lens})
11
- # * {Access::FluentHelpers#[]} (included in {LensPath} and {Lens})
12
- # * just passing a +key+ will also work, when +not(key.kind_of?(Accessor))+ (this is a special case in {LensPath#initialize})
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})
13
14
  #
14
15
  # *Equivalents* in Elixir's {https://hexdocs.pm/elixir/Access.html +Access+} module
15
16
  # * {https://hexdocs.pm/elixir/Access.html#at/1 +Access.at/1+}
@@ -28,9 +29,9 @@ require 'accessory/accessor'
28
29
  # # default-constructs a Hash, not an Array
29
30
  # [].lens[0][0].put_in(1) # => [{0=>1}]
30
31
  #
31
- # Other accessors ({FirstAccessor}, {BetwixtAccessor}, etc.) may fit your expectations more closely for +Array+ traversal.
32
+ # Other accessors ({Accessors::FirstAccessor}, {Accessors::BetwixtAccessor}, etc.) may fit your expectations more closely for +Array+ traversal.
32
33
 
33
- class Accessory::SubscriptAccessor < Accessory::Accessor
34
+ class Accessory::Accessors::SubscriptAccessor < Accessory::Accessor
34
35
  # @!visibility private
35
36
  def initialize(key, **kwargs)
36
37
  super(**kwargs)
@@ -53,12 +54,16 @@ class Accessory::SubscriptAccessor < Accessory::Accessor
53
54
  end
54
55
 
55
56
  # @!visibility private
56
- def default_fn_for_previous_step
57
- lambda{ Hash.new }
57
+ def ensure_valid(traversal_result)
58
+ if traversal_result.respond_to?(:[])
59
+ traversal_result
60
+ else
61
+ {}
62
+ end
58
63
  end
59
64
 
60
65
  # @!visibility private
61
- def value_from(data)
66
+ def traverse(data)
62
67
  data[@key]
63
68
  end
64
69
 
@@ -67,7 +72,7 @@ class Accessory::SubscriptAccessor < Accessory::Accessor
67
72
  # @param data [Enumerable] the +Enumerable+ to index into
68
73
  # @return [Object] the value derived from the rest of the accessor chain
69
74
  def get(data)
70
- value = value_or_default(data)
75
+ value = traverse_or_default(data)
71
76
 
72
77
  if block_given?
73
78
  yield(value)
@@ -84,15 +89,17 @@ class Accessory::SubscriptAccessor < Accessory::Accessor
84
89
  # @param data [Enumerable] the +Enumerable+ to index into
85
90
  # @return [Array] a two-element array containing 1. the original value found; and 2. the result value from the accessor chain
86
91
  def get_and_update(data)
87
- value = value_or_default(data)
92
+ value = traverse_or_default(data)
88
93
 
89
94
  case yield(value)
90
- in [result, new_value]
95
+ in [:clean, result, _]
96
+ [:clean, result, data]
97
+ in [:dirty, result, new_value]
91
98
  data[@key] = new_value
92
- [result, data]
99
+ [:dirty, result, data]
93
100
  in :pop
94
101
  data.delete(@key)
95
- [value, data]
102
+ [:dirty, value, data]
96
103
  end
97
104
  end
98
105
  end
@@ -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,13 +1,43 @@
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
- # Creates a Lens that will traverse +subject+ along +lens_path+.
7
- # @param subject [Object] the data-structure this Lens will traverse
8
- # @param lens_path [LensPath] the {LensPath} that will be used to traverse +subject+
9
- def self.on(subject, lens_path: Accessory::LensPath.empty)
10
- self.new(subject, path).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
11
41
  end
12
42
 
13
43
  class << self
@@ -15,88 +45,204 @@ class Accessory::Lens
15
45
  end
16
46
 
17
47
  # @!visibility private
18
- def initialize(subject, lens_path)
19
- @subject = subject
20
- @path = lens_path
21
- end
48
+ def initialize(initial_parts)
49
+ @parts = []
22
50
 
23
- # @return [LensPath] the +subject+ this Lens will traverse
24
- attr_reader :subject
51
+ for part in initial_parts
52
+ append_accessor!(part)
53
+ end
54
+ end
25
55
 
26
- # @return [LensPath] the {LensPath} for this Lens
27
- attr_reader :path
56
+ # @!visibility private
57
+ def to_a
58
+ @parts
59
+ end
28
60
 
29
61
  # @!visibility private
30
- def inspect
31
- "#<Lens on=#{@subject.inspect} #{@path.inspect(format: :short)}>"
62
+ def inspect(format: :long)
63
+ parts_desc = @parts.map{ |part| part.inspect(format: :short) }.join(', ')
64
+ parts_desc = "[#{parts_desc}]"
65
+
66
+ case format
67
+ when :long
68
+ "#Lens#{parts_desc}"
69
+ when :short
70
+ parts_desc
71
+ end
32
72
  end
33
73
 
34
- # Returns a new Lens resulting from appending +accessor+ to the receiver's
35
- # {LensPath}.
36
- #
37
- # === See also:
38
- # * {LensPath#then}
39
- #
74
+ # Returns a new {Lens} resulting from appending +accessor+ to the receiver.
40
75
  # @param accessor [Object] the accessor to append
41
- # @return [LensPath] the new Lens, containing a new joined LensPath
76
+ # @return [Lens] the new joined Lens
42
77
  def then(accessor)
43
78
  d = self.dup
44
79
  d.instance_eval do
45
- @path = @path.then(accessor)
80
+ @parts = @parts.dup
81
+ append_accessor!(accessor)
46
82
  end
47
83
  d.freeze
48
84
  end
49
85
 
50
- # Returns a new Lens resulting from concatenating +other+ to the receiver's
51
- # {LensPath}.
52
- #
53
- # === See also:
54
- # * {LensPath#+}
55
- #
56
- # @param other [Object] an accessor, an +Array+ of accessors, or a {LensPath}
57
- # @return [LensPath] the new Lens, containing a new joined LensPath
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
58
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
+
59
101
  d = self.dup
60
102
  d.instance_eval do
61
- @path = @path + other
103
+ for part in parts
104
+ append_accessor!(part)
105
+ end
62
106
  end
63
107
  d.freeze
64
108
  end
65
109
 
66
110
  alias_method :/, :+
67
111
 
68
- # (see LensPath#get_in)
69
- def get_in
70
- @path.get_in(@subject)
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
71
124
  end
72
125
 
73
- # (see LensPath#get_and_update_in)
74
- def get_and_update_in(&mutator_fn)
75
- @path.get_and_update_in(@subject, &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
76
152
  end
77
153
 
78
- # (see LensPath#update_in)
79
- def update_in(&new_value_fn)
80
- @path.update_in(@subject, &new_value_fn)
81
- end
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(subject) do |v|
168
+ case new_value_fn.call(v)
169
+ in [:set, new_value]
170
+ [:dirty, nil, new_value]
171
+ in :keep
172
+ [:clean, nil, v]
173
+ end
174
+ end
82
175
 
83
- # (see LensPath#put_in)
84
- def put_in(new_value)
85
- @path.put_in(@subject, new_value)
176
+ new_data
86
177
  end
87
178
 
88
- # (see LensPath#pop_in)
89
- def pop_in
90
- @path.pop_in(@subject)
179
+ # Traverses +subject+ using the chain of accessors held in this Lens,
180
+ # replacing the final value at the end of the traversal chain with
181
+ # +new_value+.
182
+ #
183
+ # *Equivalent* in Elixir: {https://hexdocs.pm/elixir/Kernel.html#put_in/3 +Kernel.put_in/3+}
184
+ #
185
+ # @param subject [Object] the data-structure to traverse
186
+ # @param new_value [Object] a replacement value at the traversal position.
187
+ # @return [Object] the updated +subject+
188
+ def put_in(subject, new_value)
189
+ _, _, new_data = self.get_and_update_in(subject){ [:dirty, nil, new_value] }
190
+ new_data
91
191
  end
92
- end
93
192
 
94
- class Accessory::LensPath
95
- # Returns a new {Lens} wrapping this LensPath, bound to the specified
96
- # +subject+.
193
+ # Traverses +subject+ using the chain of accessors held in this Lens,
194
+ # removing the final value at the end of the traversal chain from its position
195
+ # within its parent container.
196
+ #
197
+ # *Equivalent* in Elixir: {https://hexdocs.pm/elixir/Kernel.html#pop_in/2 +Kernel.pop_in/2+}
198
+ #
97
199
  # @param subject [Object] the data-structure to traverse
98
- # @return [Lens] a new Lens that will traverse +subject+ using this LensPath
99
- def on(subject)
100
- Accessory::Lens.on(subject, lens_path: self)
200
+ # @return [Object] the updated +subject+
201
+ def pop_in(subject)
202
+ _, popped_item, new_data = self.get_and_update_in(subject){ :pop }
203
+ [popped_item, new_data]
204
+ end
205
+
206
+ def append_accessor!(part)
207
+ accessor =
208
+ case part
209
+ when Accessory::Accessor
210
+ part
211
+ when Array
212
+ Accessory::Accessors::SubscriptAccessor.new(part[0], default: part[1])
213
+ else
214
+ Accessory::Accessors::SubscriptAccessor.new(part)
215
+ end
216
+
217
+ unless @parts.empty?
218
+ @parts.last.successor = accessor
219
+ end
220
+
221
+ @parts.push(accessor)
222
+ end
223
+ private :append_accessor!
224
+
225
+ def get_in_step(data, path)
226
+ step_accessor = path.first
227
+ rest_of_path = path[1..-1]
228
+
229
+ if rest_of_path.empty?
230
+ step_accessor.get(data)
231
+ else
232
+ step_accessor.get(data){ |v| get_in_step(v, rest_of_path) }
233
+ end
234
+ end
235
+ private :get_in_step
236
+
237
+ def get_and_update_in_step(data, path, mutator_fn)
238
+ step_accessor = path.first
239
+ rest_of_path = path[1..-1]
240
+
241
+ if rest_of_path.empty?
242
+ step_accessor.get_and_update(data, &mutator_fn)
243
+ else
244
+ step_accessor.get_and_update(data){ |v| get_and_update_in_step(v, rest_of_path, mutator_fn) }
245
+ end
101
246
  end
247
+ private :get_and_update_in_step
102
248
  end