accessory 0.1.3 → 0.1.4
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/access.rb +31 -0
- data/lib/accessory/accessor.rb +14 -2
- data/lib/accessory/accessors/all_accessor.rb +28 -0
- data/lib/accessory/accessors/attribute_accessor.rb +41 -2
- data/lib/accessory/accessors/between_each_accessor.rb +33 -0
- data/lib/accessory/accessors/betwixt_accessor.rb +48 -2
- data/lib/accessory/accessors/filter_accessor.rb +39 -2
- data/lib/accessory/accessors/first_accessor.rb +34 -1
- data/lib/accessory/accessors/instance_variable_accessor.rb +35 -2
- data/lib/accessory/accessors/last_accessor.rb +34 -1
- data/lib/accessory/accessors/subscript_accessor.rb +46 -0
- data/lib/accessory/array_cursor_position.rb +14 -0
- data/lib/accessory/lens.rb +48 -14
- data/lib/accessory/lens_path.rb +107 -38
- data/lib/accessory/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1e6d18076731a0f1b3a28ce6ef77c75ef35dff6b7646f4880286b9bcf64ac393
|
4
|
+
data.tar.gz: '07918b78b0d0b1ae8097f22f36039a04fdefe9e59a10cae62758da84785fac4f'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ccdb07d177f3b436c91303585b921494d64aac6baa30abc32debf6ebc7ee4a6ff032b3db48f51d3c7b5d6498daeb7a298bcfd75840682bdc842e8fd70581bcf0
|
7
|
+
data.tar.gz: 2183b4b3513781e43b99490b4b86617ee8f08624c5c71a1355100fb84199bae2f27865d6b17496802c29c5571d39699e239b4dfc163983e789e618c39daf898d
|
data/lib/accessory/access.rb
CHANGED
@@ -11,88 +11,119 @@ require 'accessory/accessors/first_accessor'
|
|
11
11
|
require 'accessory/accessors/last_accessor'
|
12
12
|
|
13
13
|
module Accessory::Access
|
14
|
+
# (see Accessory::SubscriptAccessor)
|
15
|
+
def self.subscript(...)
|
16
|
+
Accessory::SubscriptAccessor.new(...)
|
17
|
+
end
|
18
|
+
|
19
|
+
# (see Accessory::AttributeAccessor)
|
14
20
|
def self.attr(...)
|
15
21
|
Accessory::AttributeAccessor.new(...)
|
16
22
|
end
|
17
23
|
|
24
|
+
# (see Accessory::InstanceVariableAccessor)
|
18
25
|
def self.ivar(...)
|
19
26
|
Accessory::InstanceVariableAccessor.new(...)
|
20
27
|
end
|
21
28
|
|
29
|
+
# (see Accessory::BetwixtAccessor)
|
22
30
|
def self.betwixt(...)
|
23
31
|
Accessory::BetwixtAccessor.new(...)
|
24
32
|
end
|
25
33
|
|
34
|
+
# Alias for +Accessory::Access.betwixt(0)+. See {Access.betwixt}
|
26
35
|
def self.before_first
|
27
36
|
self.betwixt(0)
|
28
37
|
end
|
29
38
|
|
39
|
+
# Alias for +Accessory::Access.betwixt(-1)+. See {Access.betwixt}
|
30
40
|
def self.after_last
|
31
41
|
self.betwixt(-1)
|
32
42
|
end
|
33
43
|
|
44
|
+
# (see Accessory::BetweenEachAccessor)
|
34
45
|
def self.between_each
|
35
46
|
Accessory::BetweenEachAccessor.new
|
36
47
|
end
|
37
48
|
|
49
|
+
# (see Accessory::AllAccessor)
|
38
50
|
def self.all
|
39
51
|
Accessory::AllAccessor.new
|
40
52
|
end
|
41
53
|
|
54
|
+
# (see Accessory::FirstAccessor)
|
42
55
|
def self.first
|
43
56
|
Accessory::FirstAccessor.new
|
44
57
|
end
|
45
58
|
|
59
|
+
# (see Accessory::LastAccessor)
|
46
60
|
def self.last
|
47
61
|
Accessory::LastAccessor.new
|
48
62
|
end
|
49
63
|
|
64
|
+
# (see Accessory::FilterAccessor)
|
50
65
|
def self.filter(&pred)
|
51
66
|
Accessory::FilterAccessor.new(pred)
|
52
67
|
end
|
53
68
|
end
|
54
69
|
|
55
70
|
module Accessory::Access::FluentHelpers
|
71
|
+
# (see Accessory::SubscriptAccessor)
|
72
|
+
def subscript(...)
|
73
|
+
self.then(Accessory::SubscriptAccessor.new(...))
|
74
|
+
end
|
75
|
+
|
76
|
+
# Alias for {#subscript}
|
56
77
|
def [](...)
|
57
78
|
self.then(Accessory::SubscriptAccessor.new(...))
|
58
79
|
end
|
59
80
|
|
81
|
+
# (see Accessory::AttributeAccessor)
|
60
82
|
def attr(...)
|
61
83
|
self.then(Accessory::AttributeAccessor.new(...))
|
62
84
|
end
|
63
85
|
|
86
|
+
# (see Accessory::InstanceVariableAccessor)
|
64
87
|
def ivar(...)
|
65
88
|
self.then(Accessory::InstanceVariableAccessor.new(...))
|
66
89
|
end
|
67
90
|
|
91
|
+
# (see Accessory::BetwixtAccessor)
|
68
92
|
def betwixt(...)
|
69
93
|
self.then(Accessory::BetwixtAccessor.new(...))
|
70
94
|
end
|
71
95
|
|
96
|
+
# Alias for +#betwixt(0)+. See {#betwixt}
|
72
97
|
def before_first
|
73
98
|
self.betwixt(0)
|
74
99
|
end
|
75
100
|
|
101
|
+
# Alias for +#betwixt(-1)+. See {#betwixt}
|
76
102
|
def after_last
|
77
103
|
self.betwixt(-1)
|
78
104
|
end
|
79
105
|
|
106
|
+
# (see Accessory::BetweenEachAccessor)
|
80
107
|
def between_each
|
81
108
|
self.then(Accessory::BetweenEachAccessor.new)
|
82
109
|
end
|
83
110
|
|
111
|
+
# (see Accessory::AllAccessor)
|
84
112
|
def all
|
85
113
|
self.then(Accessory::AllAccessor.new)
|
86
114
|
end
|
87
115
|
|
116
|
+
# (see Accessory::FirstAccessor)
|
88
117
|
def first
|
89
118
|
self.then(Accessory::FirstAccessor.new)
|
90
119
|
end
|
91
120
|
|
121
|
+
# (see Accessory::LastAccessor)
|
92
122
|
def last
|
93
123
|
self.then(Accessory::LastAccessor.new)
|
94
124
|
end
|
95
125
|
|
126
|
+
# (see Accessory::FilterAccessor)
|
96
127
|
def filter(&pred)
|
97
128
|
self.then(Accessory::FilterAccessor.new(pred))
|
98
129
|
end
|
data/lib/accessory/accessor.rb
CHANGED
@@ -1,14 +1,22 @@
|
|
1
1
|
module Accessory; end
|
2
2
|
|
3
|
+
##
|
4
|
+
# @!visibility private
|
3
5
|
class Accessory::Accessor
|
6
|
+
|
7
|
+
# @!visibility private
|
4
8
|
DEFAULT_NOT_SET_SENTINEL = :"98e47971-e708-42ca-bee7-0c62fe5e11c9"
|
9
|
+
|
10
|
+
# @!visibility private
|
5
11
|
TERMINAL_DEFAULT_FN = lambda{ nil }
|
6
12
|
|
7
|
-
|
8
|
-
|
13
|
+
# @!visibility private
|
14
|
+
def initialize(default = nil)
|
15
|
+
@default_value = default || DEFAULT_NOT_SET_SENTINEL
|
9
16
|
@make_default_fn = TERMINAL_DEFAULT_FN
|
10
17
|
end
|
11
18
|
|
19
|
+
# @!visibility private
|
12
20
|
def name
|
13
21
|
n = self.class.name.split('::').last.gsub(/Accessor$/, '')
|
14
22
|
n.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
|
@@ -18,6 +26,7 @@ class Accessory::Accessor
|
|
18
26
|
n
|
19
27
|
end
|
20
28
|
|
29
|
+
# @!visibility private
|
21
30
|
def inspect(format: :long)
|
22
31
|
case format
|
23
32
|
when :long
|
@@ -30,6 +39,7 @@ class Accessory::Accessor
|
|
30
39
|
end
|
31
40
|
end
|
32
41
|
|
42
|
+
# @!visibility private
|
33
43
|
HIDDEN_IVARS = [:@default_value, :@make_default_fn]
|
34
44
|
def inspect_args
|
35
45
|
(instance_variables - HIDDEN_IVARS).map do |ivar_k|
|
@@ -38,8 +48,10 @@ class Accessory::Accessor
|
|
38
48
|
end.join(' ')
|
39
49
|
end
|
40
50
|
|
51
|
+
# @!visibility private
|
41
52
|
attr_accessor :make_default_fn
|
42
53
|
|
54
|
+
# @!visibility private
|
43
55
|
def value_or_default(data)
|
44
56
|
return nil if data.nil?
|
45
57
|
|
@@ -1,12 +1,33 @@
|
|
1
1
|
require 'accessory/accessor'
|
2
2
|
|
3
|
+
##
|
4
|
+
# Traverses all elements of an +Enumerable+.
|
5
|
+
#
|
6
|
+
# *Aliases*
|
7
|
+
# * {Access.all}
|
8
|
+
# * {Access::FluentHelpers#all} (included in {LensPath} and {Lens})
|
9
|
+
#
|
10
|
+
# *Equivalents* in Elixir's {https://hexdocs.pm/elixir/Access.html +Access+} module
|
11
|
+
# * {https://hexdocs.pm/elixir/Access.html#all/0 +Access.all/0+}
|
12
|
+
# * {https://hexdocs.pm/elixir/Access.html#key/2 +Access.key/2+}
|
13
|
+
#
|
14
|
+
# <b>Default constructor</b> used by predecessor accessor
|
15
|
+
#
|
16
|
+
# * +Array.new+
|
17
|
+
|
3
18
|
class Accessory::AllAccessor < Accessory::Accessor
|
19
|
+
# @!visibility private
|
4
20
|
def default_fn_for_previous_step
|
5
21
|
lambda{ Array.new }
|
6
22
|
end
|
7
23
|
|
24
|
+
# @!visibility private
|
8
25
|
def inspect_args; nil; end
|
9
26
|
|
27
|
+
# Feeds each element of +data+ down the accessor chain, and returns
|
28
|
+
# the results.
|
29
|
+
# @param data [Enumerable] the +Enumerable+ to iterate through
|
30
|
+
# @return [Array] the values derived from the rest of the accessor chain
|
10
31
|
def get(data, &succ)
|
11
32
|
if succ
|
12
33
|
(data || []).map(&succ)
|
@@ -15,6 +36,13 @@ class Accessory::AllAccessor < Accessory::Accessor
|
|
15
36
|
end
|
16
37
|
end
|
17
38
|
|
39
|
+
# Feeds each element of +data+ down the accessor chain, overwriting
|
40
|
+
# +data+ with the results.
|
41
|
+
#
|
42
|
+
# If +:pop+ is returned from the accessor chain, the element is dropped
|
43
|
+
# from the new +data+.
|
44
|
+
# @param data [Enumerable] the +Enumerable+ to iterate through
|
45
|
+
# @return [Array] a two-element array containing 1. the original values found during iteration; and 2. the new +data+
|
18
46
|
def get_and_update(data)
|
19
47
|
results = []
|
20
48
|
new_data = []
|
@@ -1,18 +1,42 @@
|
|
1
1
|
require 'accessory/accessor'
|
2
2
|
|
3
|
+
##
|
4
|
+
# Traverses an abstract "attribute" of an arbitrary object, represented by a
|
5
|
+
# named getter/setter method pair.
|
6
|
+
#
|
7
|
+
# For example, <tt>AttributeAccessor.new(:foo)</tt> will traverse through
|
8
|
+
# the getter/setter pair <tt>.foo</tt> and <tt>.foo=</tt>.
|
9
|
+
#
|
10
|
+
# The abstract "attribute" does not have to correspond to an actual
|
11
|
+
# +attr_accessor+; the {AttributeAccessor} will work as long as the relevant
|
12
|
+
# named getter/setter methods exist on the receiver.
|
13
|
+
#
|
14
|
+
# *Aliases*
|
15
|
+
# * {Access.attr}
|
16
|
+
# * {Access::FluentHelpers#attr} (included in {LensPath} and {Lens})
|
17
|
+
#
|
18
|
+
# <b>Default constructor</b> used by predecessor accessor
|
19
|
+
#
|
20
|
+
# * +OpenStruct.new+
|
21
|
+
|
3
22
|
class Accessory::AttributeAccessor < Accessory::Accessor
|
4
|
-
|
5
|
-
|
23
|
+
# @param attr_name [Symbol] the attribute name (i.e. name of the getter method)
|
24
|
+
# @param default [Object] the default to use if the predecessor accessor passes +nil+ data
|
25
|
+
def initialize(attr_name, default: nil)
|
26
|
+
super(default)
|
6
27
|
@getter_method_name = :"#{attr_name}"
|
7
28
|
@setter_method_name = :"#{attr_name}="
|
8
29
|
end
|
9
30
|
|
31
|
+
# @!visibility private
|
10
32
|
def name; "attr"; end
|
11
33
|
|
34
|
+
# @!visibility private
|
12
35
|
def inspect_args
|
13
36
|
@getter_method_name.inspect
|
14
37
|
end
|
15
38
|
|
39
|
+
# @!visibility private
|
16
40
|
def inspect(format: :long)
|
17
41
|
case format
|
18
42
|
when :long
|
@@ -22,6 +46,7 @@ class Accessory::AttributeAccessor < Accessory::Accessor
|
|
22
46
|
end
|
23
47
|
end
|
24
48
|
|
49
|
+
# @!visibility private
|
25
50
|
def default_fn_for_previous_step
|
26
51
|
lambda do
|
27
52
|
require 'ostruct'
|
@@ -29,10 +54,15 @@ class Accessory::AttributeAccessor < Accessory::Accessor
|
|
29
54
|
end
|
30
55
|
end
|
31
56
|
|
57
|
+
# @!visibility private
|
32
58
|
def value_from(data)
|
33
59
|
data.send(@getter_method_name)
|
34
60
|
end
|
35
61
|
|
62
|
+
# Finds <tt>data.send(:"#{attr_name}")</tt>, feeds it down the accessor chain,
|
63
|
+
# and returns the result.
|
64
|
+
# @param data [Object] the object to traverse
|
65
|
+
# @return [Object] the value derived from the rest of the accessor chain
|
36
66
|
def get(data)
|
37
67
|
value = value_or_default(data)
|
38
68
|
|
@@ -43,6 +73,15 @@ class Accessory::AttributeAccessor < Accessory::Accessor
|
|
43
73
|
end
|
44
74
|
end
|
45
75
|
|
76
|
+
# Finds <tt>data.send(:"#{attr_name}")</tt>, feeds it down the accessor chain,
|
77
|
+
# and uses <tt>data.send(:"#{attr_name}=")</tt> to overwrite the stored value
|
78
|
+
# with the returned result.
|
79
|
+
#
|
80
|
+
# If +:pop+ is returned from the accessor chain, the stored value will be
|
81
|
+
# overwritten with `nil`.
|
82
|
+
#
|
83
|
+
# @param data [Object] the object to traverse
|
84
|
+
# @return [Array] a two-element array containing 1. the original value found; and 2. the result value from the accessor chain
|
46
85
|
def get_and_update(data)
|
47
86
|
value = value_or_default(data)
|
48
87
|
|
@@ -1,13 +1,31 @@
|
|
1
1
|
require 'accessory/accessor'
|
2
2
|
require 'accessory/array_cursor_position'
|
3
3
|
|
4
|
+
##
|
5
|
+
# Traverses the positions "between" the elements of an +Enumerable+, including
|
6
|
+
# the positions at the "edges" (i.e. before the first, and after the last.)
|
7
|
+
#
|
8
|
+
# {BetweenEachAccessor} can be used with {LensPath#put_in} to insert new
|
9
|
+
# elements into an Enumerable between the existing ones.
|
10
|
+
#
|
11
|
+
# *Aliases*
|
12
|
+
# * {Access.between_each}
|
13
|
+
# * {Access::FluentHelpers#between_each} (included in {LensPath} and {Lens})
|
14
|
+
#
|
15
|
+
# <b>Default constructor</b> used by predecessor accessor
|
16
|
+
#
|
17
|
+
# * +Array.new+
|
18
|
+
|
4
19
|
class Accessory::BetweenEachAccessor < Accessory::Accessor
|
20
|
+
# @!visibility private
|
5
21
|
def default_fn_for_previous_step
|
6
22
|
lambda{ Array.new }
|
7
23
|
end
|
8
24
|
|
25
|
+
# @!visibility private
|
9
26
|
def inspect_args; nil; end
|
10
27
|
|
28
|
+
# @!visibility private
|
11
29
|
def value_from(data)
|
12
30
|
data_len = data.length
|
13
31
|
|
@@ -22,6 +40,11 @@ class Accessory::BetweenEachAccessor < Accessory::Accessor
|
|
22
40
|
end
|
23
41
|
end
|
24
42
|
|
43
|
+
# Feeds {ArrayCursorPosition}s representing the positions between the elements
|
44
|
+
# of +data+ down the accessor chain.
|
45
|
+
#
|
46
|
+
# @param data [Enumerable] the +Enumerable+ to iterate through
|
47
|
+
# @return [Array] the generated {ArrayCursorPosition}s
|
25
48
|
def get(data)
|
26
49
|
positions = value_or_default(data || [])
|
27
50
|
|
@@ -32,6 +55,16 @@ class Accessory::BetweenEachAccessor < Accessory::Accessor
|
|
32
55
|
end
|
33
56
|
end
|
34
57
|
|
58
|
+
# Feeds {ArrayCursorPosition}s representing the positions between the elements
|
59
|
+
# of +data+ down the accessor chain, manipulating +data+ using the results.
|
60
|
+
#
|
61
|
+
# If a new element is returned up the accessor chain, the element is inserted
|
62
|
+
# between the existing elements.
|
63
|
+
#
|
64
|
+
# If +:pop+ is returned up the accessor chain, no new element is added.
|
65
|
+
#
|
66
|
+
# @param data [Enumerable] the +Enumerable+ to iterate through
|
67
|
+
# @return [Array] a two-element array containing 1. the {ArrayCursorPosition}s; and 2. the new {data}
|
35
68
|
def get_and_update(data)
|
36
69
|
results = []
|
37
70
|
new_data = []
|
@@ -1,20 +1,50 @@
|
|
1
1
|
require 'accessory/accessor'
|
2
2
|
require 'accessory/array_cursor_position'
|
3
3
|
|
4
|
+
##
|
5
|
+
# Traverses into a specified cursor-position "between" two elements of an
|
6
|
+
# +Enumerable+, including the positions at the "edges" (i.e. before the first,
|
7
|
+
# or after the last.)
|
8
|
+
#
|
9
|
+
# If the provided +offset+ is positive, this accessor will traverse the position
|
10
|
+
# between <tt>offset - 1</tt> and +offset+; if +offset+ is negative, this accessor
|
11
|
+
# will traverse the position _after_ +offset+.
|
12
|
+
#
|
13
|
+
# The +offset+ in this accessor has equivalent semantics to the offset in
|
14
|
+
# <tt>Array#insert(offset, obj)</tt>.
|
15
|
+
#
|
16
|
+
# {BetwixtAccessor} can be used with {LensPath#put_in} to insert new
|
17
|
+
# elements into an Enumerable between the existing ones. If you want to extend
|
18
|
+
# an +Enumerable+ as you would with <tt>#push</tt> or <tt>#unshift</tt>, this
|
19
|
+
# accessor will have better behavior than using {SubscriptAccessor} would.
|
20
|
+
#
|
21
|
+
# *Aliases*
|
22
|
+
# * {Access.betwixt}
|
23
|
+
# * {Access::FluentHelpers#betwixt} (included in {LensPath} and {Lens})
|
24
|
+
#
|
25
|
+
# <b>Default constructor</b> used by predecessor accessor
|
26
|
+
#
|
27
|
+
# * +Array.new+
|
28
|
+
|
4
29
|
class Accessory::BetwixtAccessor < Accessory::Accessor
|
5
|
-
|
6
|
-
|
30
|
+
# @param offset [Integer] the cursor position (i.e. the index of the element after the cursor)
|
31
|
+
# @param default [Object] the default to use if the predecessor accessor passes +nil+ data
|
32
|
+
def initialize(offset, default: nil)
|
33
|
+
super(default)
|
7
34
|
@offset = offset
|
8
35
|
end
|
9
36
|
|
37
|
+
# @!visibility private
|
10
38
|
def inspect_args
|
11
39
|
@offset.inspect
|
12
40
|
end
|
13
41
|
|
42
|
+
# @!visibility private
|
14
43
|
def default_fn_for_previous_step
|
15
44
|
lambda{ Array.new }
|
16
45
|
end
|
17
46
|
|
47
|
+
# @!visibility private
|
18
48
|
def value_from(data)
|
19
49
|
data_len = data.length
|
20
50
|
|
@@ -27,6 +57,11 @@ class Accessory::BetwixtAccessor < Accessory::Accessor
|
|
27
57
|
)
|
28
58
|
end
|
29
59
|
|
60
|
+
# Feeds an {ArrayCursorPosition} representing the position between the
|
61
|
+
# elements of +data+ at +@offset+ down the accessor chain.
|
62
|
+
#
|
63
|
+
# @param data [Enumerable] the +Enumerable+ to traverse into
|
64
|
+
# @return [Array] the generated {ArrayCursorPosition}
|
30
65
|
def get(data)
|
31
66
|
pos = value_or_default(data || [])
|
32
67
|
|
@@ -37,6 +72,17 @@ class Accessory::BetwixtAccessor < Accessory::Accessor
|
|
37
72
|
end
|
38
73
|
end
|
39
74
|
|
75
|
+
# Feeds an {ArrayCursorPosition} representing the position between the
|
76
|
+
# elements of +data+ at +@offset+ down the accessor chain, manipulating
|
77
|
+
# +data+ using the result.
|
78
|
+
#
|
79
|
+
# If a new element is returned up the accessor chain, the element is inserted
|
80
|
+
# at the specified position, using <tt>data.insert(@offset, e)</tt>.
|
81
|
+
#
|
82
|
+
# If +:pop+ is returned up the accessor chain, no new element is added.
|
83
|
+
#
|
84
|
+
# @param data [Enumerable] the +Enumerable+ to traverse into
|
85
|
+
# @return [Array] a two-element array containing 1. the generated {ArrayCursorPosition}; and 2. the new {data}
|
40
86
|
def get_and_update(data)
|
41
87
|
pos = value_or_default(data || [])
|
42
88
|
|
@@ -1,18 +1,47 @@
|
|
1
1
|
require 'accessory/accessor'
|
2
2
|
|
3
|
+
##
|
4
|
+
# Traverses the elements of an +Enumerable+ that return a truthy value from
|
5
|
+
# a passed-in predicate block.
|
6
|
+
#
|
7
|
+
# *Aliases*
|
8
|
+
# * {Access.filter}
|
9
|
+
# * {Access::FluentHelpers#filter} (included in {LensPath} and {Lens})
|
10
|
+
#
|
11
|
+
# *Equivalents* in Elixir's {https://hexdocs.pm/elixir/Access.html +Access+} module
|
12
|
+
# * {https://hexdocs.pm/elixir/Access.html#filter/1 +Access.filter/1+}
|
13
|
+
#
|
14
|
+
# <b>Default constructor</b> used by predecessor accessor
|
15
|
+
#
|
16
|
+
# * +Array.new+
|
17
|
+
|
3
18
|
class Accessory::FilterAccessor < Accessory::Accessor
|
4
|
-
|
5
|
-
|
19
|
+
# Returns a new instance of {FilterAccessor}.
|
20
|
+
#
|
21
|
+
# The predicate function may be passed in as either a positional argument,
|
22
|
+
# or a block.
|
23
|
+
#
|
24
|
+
# @param pred [Proc] The predicate function to use, as an object
|
25
|
+
# @param pred_blk [Proc] The predicate function to use, as a block
|
26
|
+
# @param default [Object] the default to use if the predecessor accessor passes +nil+ data
|
27
|
+
def initialize(pred = nil, default: nil, &pred_blk)
|
28
|
+
@pred = blk || pred
|
6
29
|
end
|
7
30
|
|
31
|
+
# @!visibility private
|
8
32
|
def inspect_args
|
9
33
|
@pred.inspect
|
10
34
|
end
|
11
35
|
|
36
|
+
# @!visibility private
|
12
37
|
def default_fn_for_previous_step
|
13
38
|
lambda{ Array.new }
|
14
39
|
end
|
15
40
|
|
41
|
+
# Feeds each element of +data+ matching the predicate down the accessor chain,
|
42
|
+
# returning the results.
|
43
|
+
# @param data [Enumerable] the +Enumerable+ to iterate through
|
44
|
+
# @return [Array] the values derived from the rest of the accessor chain
|
16
45
|
def get(data, &succ)
|
17
46
|
if succ
|
18
47
|
(data || []).filter(&@pred).map(&succ)
|
@@ -21,6 +50,14 @@ class Accessory::FilterAccessor < Accessory::Accessor
|
|
21
50
|
end
|
22
51
|
end
|
23
52
|
|
53
|
+
# Feeds each element of +data+ matching the predicate down the accessor chain,
|
54
|
+
# overwriting +data+ with the results.
|
55
|
+
#
|
56
|
+
# If +:pop+ is returned from the accessor chain, the element is dropped
|
57
|
+
# from the new +data+.
|
58
|
+
#
|
59
|
+
# @param data [Enumerable] the +Enumerable+ to iterate through
|
60
|
+
# @return [Array] a two-element array containing 1. the original values found during iteration; and 2. the new +data+
|
24
61
|
def get_and_update(data)
|
25
62
|
results = []
|
26
63
|
new_data = []
|
@@ -1,16 +1,37 @@
|
|
1
1
|
require 'accessory/accessor'
|
2
2
|
|
3
|
+
##
|
4
|
+
# Traverses into the "first" element within an +Enumerable+, using
|
5
|
+
# <tt>#first</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.first}
|
12
|
+
# * {Access::FluentHelpers#first} (included in {LensPath} and {Lens})
|
13
|
+
#
|
14
|
+
# <b>Default constructor</b> used by predecessor accessor
|
15
|
+
#
|
16
|
+
# * +Array.new+
|
17
|
+
|
3
18
|
class Accessory::FirstAccessor < Accessory::Accessor
|
19
|
+
# @!visibility private
|
4
20
|
def default_fn_for_previous_step
|
5
21
|
lambda{ Array.new }
|
6
22
|
end
|
7
23
|
|
24
|
+
# @!visibility private
|
8
25
|
def inspect_args; nil; end
|
9
26
|
|
27
|
+
# @!visibility private
|
10
28
|
def value_from(data)
|
11
29
|
data.first
|
12
30
|
end
|
13
31
|
|
32
|
+
# Feeds <tt>data.first</tt> down the accessor chain, returning the result.
|
33
|
+
# @param data [Object] the object to traverse
|
34
|
+
# @return [Object] the value derived from the rest of the accessor chain
|
14
35
|
def get(data)
|
15
36
|
value = value_or_default(data)
|
16
37
|
|
@@ -21,12 +42,24 @@ class Accessory::FirstAccessor < Accessory::Accessor
|
|
21
42
|
end
|
22
43
|
end
|
23
44
|
|
45
|
+
# Finds <tt>data.first</tt>, feeds it down the accessor chain, and overwrites
|
46
|
+
# the stored value with the returned result.
|
47
|
+
#
|
48
|
+
# If +:pop+ is returned from the accessor chain, the stored value will be
|
49
|
+
# removed using <tt>data.delete_at(0)</tt>.
|
50
|
+
#
|
51
|
+
# @param data [Object] the object to traverse
|
52
|
+
# @return [Array] a two-element array containing 1. the original value found; and 2. the result value from the accessor chain
|
24
53
|
def get_and_update(data)
|
25
54
|
old_value = value_or_default(data)
|
26
55
|
|
27
56
|
case yield(old_value)
|
28
57
|
in [result, new_value]
|
29
|
-
data
|
58
|
+
if data.respond_to?(:"first=")
|
59
|
+
data.first = new_value
|
60
|
+
else
|
61
|
+
data[0] = new_value
|
62
|
+
end
|
30
63
|
[result, data]
|
31
64
|
in :pop
|
32
65
|
data.delete_at(0)
|
@@ -1,8 +1,24 @@
|
|
1
1
|
require 'accessory/accessor'
|
2
2
|
|
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 {LensPath} and {Lens})
|
12
|
+
#
|
13
|
+
# <b>Default constructor</b> used by predecessor accessor
|
14
|
+
#
|
15
|
+
# * +Object.new+
|
16
|
+
|
3
17
|
class Accessory::InstanceVariableAccessor < Accessory::Accessor
|
4
|
-
|
5
|
-
|
18
|
+
# @param attr_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,18 +27,25 @@ 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
|
|
35
|
+
# @!visibility private
|
18
36
|
def default_fn_for_previous_step
|
19
37
|
lambda{ Object.new }
|
20
38
|
end
|
21
39
|
|
40
|
+
# @!visibility private
|
22
41
|
def value_from(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
50
|
value = value_or_default(data)
|
28
51
|
|
@@ -33,6 +56,16 @@ 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
70
|
value = value_or_default(data)
|
38
71
|
|
@@ -1,16 +1,37 @@
|
|
1
1
|
require 'accessory/accessor'
|
2
2
|
|
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 {LensPath} and {Lens})
|
13
|
+
#
|
14
|
+
# <b>Default constructor</b> used by predecessor accessor
|
15
|
+
#
|
16
|
+
# * +Array.new+
|
17
|
+
|
3
18
|
class Accessory::LastAccessor < Accessory::Accessor
|
19
|
+
# @!visibility private
|
4
20
|
def default_fn_for_previous_step
|
5
21
|
lambda{ Array.new }
|
6
22
|
end
|
7
23
|
|
24
|
+
# @!visibility private
|
8
25
|
def inspect_args; nil; end
|
9
26
|
|
27
|
+
# @!visibility private
|
10
28
|
def value_from(data)
|
11
29
|
data.last
|
12
30
|
end
|
13
31
|
|
32
|
+
# Feeds <tt>data.last</tt> down the accessor chain, returning the result.
|
33
|
+
# @param data [Object] the object to traverse
|
34
|
+
# @return [Object] the value derived from the rest of the accessor chain
|
14
35
|
def get(data)
|
15
36
|
value = value_or_default(data)
|
16
37
|
|
@@ -21,12 +42,24 @@ class Accessory::LastAccessor < Accessory::Accessor
|
|
21
42
|
end
|
22
43
|
end
|
23
44
|
|
45
|
+
# Finds <tt>data.last</tt>, feeds it down the accessor chain, and overwrites
|
46
|
+
# the stored value with the returned result.
|
47
|
+
#
|
48
|
+
# If +:pop+ is returned from the accessor chain, the stored value will be
|
49
|
+
# removed using <tt>data.delete_at(-1)</tt>.
|
50
|
+
#
|
51
|
+
# @param data [Object] the object to traverse
|
52
|
+
# @return [Array] a two-element array containing 1. the original value found; and 2. the result value from the accessor chain
|
24
53
|
def get_and_update(data)
|
25
54
|
old_value = value_or_default(data)
|
26
55
|
|
27
56
|
case yield(old_value)
|
28
57
|
in [result, new_value]
|
29
|
-
data
|
58
|
+
if data.respond_to?(:"last=")
|
59
|
+
data.last = new_value
|
60
|
+
else
|
61
|
+
data[-1] = new_value
|
62
|
+
end
|
30
63
|
[result, data]
|
31
64
|
in :pop
|
32
65
|
data.delete_at(-1)
|
@@ -1,11 +1,43 @@
|
|
1
1
|
require 'accessory/accessor'
|
2
2
|
|
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 {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})
|
13
|
+
#
|
14
|
+
# *Equivalents* in Elixir's {https://hexdocs.pm/elixir/Access.html +Access+} module
|
15
|
+
# * {https://hexdocs.pm/elixir/Access.html#at/1 +Access.at/1+}
|
16
|
+
# * {https://hexdocs.pm/elixir/Access.html#key/2 +Access.key/2+}
|
17
|
+
#
|
18
|
+
# <b>Default constructor</b> used by predecessor accessor
|
19
|
+
#
|
20
|
+
# * +Hash.new+
|
21
|
+
#
|
22
|
+
# == Usage Notes:
|
23
|
+
# Subscripting into an +Array+ will *work*, but may not have the results you expect:
|
24
|
+
#
|
25
|
+
# # extends the Array
|
26
|
+
# [].lens[3].put_in(1) # => [nil, nil, nil, 1]
|
27
|
+
#
|
28
|
+
# # default-constructs a Hash, not an Array
|
29
|
+
# [].lens[0][0].put_in(1) # => [{0=>1}]
|
30
|
+
#
|
31
|
+
# Other accessors ({FirstAccessor}, {BetwixtAccessor}, etc.) may fit your expectations more closely for +Array+ traversal.
|
32
|
+
|
3
33
|
class Accessory::SubscriptAccessor < Accessory::Accessor
|
34
|
+
# @!visibility private
|
4
35
|
def initialize(key, **kwargs)
|
5
36
|
super(**kwargs)
|
6
37
|
@key = key
|
7
38
|
end
|
8
39
|
|
40
|
+
# @!visibility private
|
9
41
|
def inspect(format: :long)
|
10
42
|
case format
|
11
43
|
when :long
|
@@ -15,18 +47,25 @@ class Accessory::SubscriptAccessor < Accessory::Accessor
|
|
15
47
|
end
|
16
48
|
end
|
17
49
|
|
50
|
+
# @!visibility private
|
18
51
|
def inspect_args
|
19
52
|
@key.inspect
|
20
53
|
end
|
21
54
|
|
55
|
+
# @!visibility private
|
22
56
|
def default_fn_for_previous_step
|
23
57
|
lambda{ Hash.new }
|
24
58
|
end
|
25
59
|
|
60
|
+
# @!visibility private
|
26
61
|
def value_from(data)
|
27
62
|
data[@key]
|
28
63
|
end
|
29
64
|
|
65
|
+
# Finds <tt>data[@key]</tt>, feeds it down the accessor chain, and returns
|
66
|
+
# the result.
|
67
|
+
# @param data [Enumerable] the +Enumerable+ to index into
|
68
|
+
# @return [Object] the value derived from the rest of the accessor chain
|
30
69
|
def get(data)
|
31
70
|
value = value_or_default(data)
|
32
71
|
|
@@ -37,6 +76,13 @@ class Accessory::SubscriptAccessor < Accessory::Accessor
|
|
37
76
|
end
|
38
77
|
end
|
39
78
|
|
79
|
+
# Finds <tt>data[@key]</tt>, feeds it down the accessor chain, and overwrites
|
80
|
+
# <tt>data[@key]</tt> with the returned result.
|
81
|
+
#
|
82
|
+
# If +:pop+ is returned from the accessor chain, the key is instead deleted
|
83
|
+
# from the {data} with <tt>data.delete(@key)</tt>.
|
84
|
+
# @param data [Enumerable] the +Enumerable+ to index into
|
85
|
+
# @return [Array] a two-element array containing 1. the original value found; and 2. the result value from the accessor chain
|
40
86
|
def get_and_update(data)
|
41
87
|
value = value_or_default(data)
|
42
88
|
|
@@ -1,6 +1,12 @@
|
|
1
1
|
module Accessory; end
|
2
2
|
|
3
|
+
##
|
4
|
+
# Represents a cursor-position "between" two positions in an +Array+ or other
|
5
|
+
# integer-indexed +Enumerable+.
|
6
|
+
|
3
7
|
class Accessory::ArrayCursorPosition
|
8
|
+
##
|
9
|
+
# @!visibility private
|
4
10
|
def initialize(offset, elem_before, elem_after, is_first: false, is_last: false)
|
5
11
|
@offset = offset
|
6
12
|
@elem_before = elem_before
|
@@ -9,10 +15,18 @@ class Accessory::ArrayCursorPosition
|
|
9
15
|
@is_last = is_last
|
10
16
|
end
|
11
17
|
|
18
|
+
# @return [Integer] the offset of +elem_after+ in the Enumerable
|
12
19
|
attr_reader :offset
|
20
|
+
|
21
|
+
# @return [Object] the element before the cursor, if applicable
|
13
22
|
attr_reader :elem_before
|
23
|
+
|
24
|
+
# @return [Object] the element after the cursor, if applicable
|
14
25
|
attr_reader :elem_after
|
15
26
|
|
27
|
+
# @return [Object] true when {#elem_after} is the first element of the +Enumerable+
|
16
28
|
def first?; @is_first; end
|
29
|
+
|
30
|
+
# @return [Object] true when {#elem_before} is the last element of the +Enumerable+
|
17
31
|
def last?; @is_last; end
|
18
32
|
end
|
data/lib/accessory/lens.rb
CHANGED
@@ -3,25 +3,42 @@ module Accessory; end
|
|
3
3
|
require 'accessory/lens_path'
|
4
4
|
|
5
5
|
class Accessory::Lens
|
6
|
-
|
7
|
-
|
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
|
8
11
|
end
|
9
12
|
|
10
13
|
class << self
|
11
14
|
private :new
|
12
15
|
end
|
13
16
|
|
14
|
-
|
15
|
-
|
17
|
+
# @!visibility private
|
18
|
+
def initialize(subject, lens_path)
|
19
|
+
@subject = subject
|
16
20
|
@path = lens_path
|
17
21
|
end
|
18
22
|
|
23
|
+
# @return [LensPath] the +subject+ this Lens will traverse
|
24
|
+
attr_reader :subject
|
25
|
+
|
26
|
+
# @return [LensPath] the {LensPath} for this Lens
|
19
27
|
attr_reader :path
|
20
28
|
|
29
|
+
# @!visibility private
|
21
30
|
def inspect
|
22
|
-
"#<Lens on=#{@
|
31
|
+
"#<Lens on=#{@subject.inspect} #{@path.inspect(format: :short)}>"
|
23
32
|
end
|
24
33
|
|
34
|
+
# Returns a new Lens resulting from appending +accessor+ to the receiver's
|
35
|
+
# {LensPath}.
|
36
|
+
#
|
37
|
+
# === See also:
|
38
|
+
# * {LensPath#then}
|
39
|
+
#
|
40
|
+
# @param accessor [Object] the accessor to append
|
41
|
+
# @return [LensPath] the new Lens, containing a new joined LensPath
|
25
42
|
def then(accessor)
|
26
43
|
d = self.dup
|
27
44
|
d.instance_eval do
|
@@ -30,39 +47,56 @@ class Accessory::Lens
|
|
30
47
|
d.freeze
|
31
48
|
end
|
32
49
|
|
33
|
-
|
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
|
58
|
+
def +(other)
|
34
59
|
d = self.dup
|
35
60
|
d.instance_eval do
|
36
|
-
@path = @path +
|
61
|
+
@path = @path + other
|
37
62
|
end
|
38
63
|
d.freeze
|
39
64
|
end
|
40
65
|
|
41
66
|
alias_method :/, :+
|
42
67
|
|
68
|
+
# (see LensPath#get_in)
|
43
69
|
def get_in
|
44
|
-
@path.get_in(@
|
70
|
+
@path.get_in(@subject)
|
45
71
|
end
|
46
72
|
|
73
|
+
# (see LensPath#get_and_update_in)
|
47
74
|
def get_and_update_in(&mutator_fn)
|
48
|
-
@path.get_and_update_in(@
|
75
|
+
@path.get_and_update_in(@subject, &mutator_fn)
|
49
76
|
end
|
50
77
|
|
78
|
+
# (see LensPath#update_in)
|
51
79
|
def update_in(&new_value_fn)
|
52
|
-
@path.update_in(@
|
80
|
+
@path.update_in(@subject, &new_value_fn)
|
53
81
|
end
|
54
82
|
|
83
|
+
# (see LensPath#put_in)
|
55
84
|
def put_in(new_value)
|
56
|
-
@path.put_in(@
|
85
|
+
@path.put_in(@subject, new_value)
|
57
86
|
end
|
58
87
|
|
88
|
+
# (see LensPath#pop_in)
|
59
89
|
def pop_in
|
60
|
-
@path.pop_in(@
|
90
|
+
@path.pop_in(@subject)
|
61
91
|
end
|
62
92
|
end
|
63
93
|
|
64
94
|
class Accessory::LensPath
|
65
|
-
|
66
|
-
|
95
|
+
# Returns a new {Lens} wrapping this LensPath, bound to the specified
|
96
|
+
# +subject+.
|
97
|
+
# @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)
|
67
101
|
end
|
68
102
|
end
|
data/lib/accessory/lens_path.rb
CHANGED
@@ -4,18 +4,23 @@ require 'accessory/accessor'
|
|
4
4
|
require 'accessory/accessors/subscript_accessor'
|
5
5
|
|
6
6
|
class Accessory::LensPath
|
7
|
+
# Returns the empty (identity) LensPath.
|
8
|
+
# @return [LensPath] the empty (identity) LensPath.
|
7
9
|
def self.empty
|
8
10
|
@empty_lens_path ||= (new([]).freeze)
|
9
11
|
end
|
10
12
|
|
11
|
-
|
12
|
-
|
13
|
+
# Returns a {LensPath} containing the specified +accessors+.
|
14
|
+
# @return [LensPath] a LensPath containing the specified +accessors+.
|
15
|
+
def self.[](*accessors)
|
16
|
+
new(accessors).freeze
|
13
17
|
end
|
14
18
|
|
15
19
|
class << self
|
16
20
|
private :new
|
17
21
|
end
|
18
22
|
|
23
|
+
# @!visibility private
|
19
24
|
def initialize(initial_parts)
|
20
25
|
@parts = []
|
21
26
|
|
@@ -24,10 +29,12 @@ class Accessory::LensPath
|
|
24
29
|
end
|
25
30
|
end
|
26
31
|
|
32
|
+
# @!visibility private
|
27
33
|
def to_a
|
28
34
|
@parts
|
29
35
|
end
|
30
36
|
|
37
|
+
# @!visibility private
|
31
38
|
def inspect(format: :long)
|
32
39
|
parts_desc = @parts.map{ |part| part.inspect(format: :short) }.join(', ')
|
33
40
|
parts_desc = "[#{parts_desc}]"
|
@@ -40,12 +47,16 @@ class Accessory::LensPath
|
|
40
47
|
end
|
41
48
|
end
|
42
49
|
|
50
|
+
# Returns a new {LensPath} resulting from appending +accessor+ to the receiver.
|
51
|
+
# @param accessor [Object] the accessor to append
|
52
|
+
# @return [LensPath] the new joined LensPath
|
43
53
|
def then(accessor)
|
44
54
|
d = self.dup
|
45
55
|
d.append_accessor!(accessor)
|
46
56
|
d.freeze
|
47
57
|
end
|
48
58
|
|
59
|
+
# @!visibility private
|
49
60
|
def dup
|
50
61
|
d = super
|
51
62
|
d.instance_eval do
|
@@ -54,15 +65,19 @@ class Accessory::LensPath
|
|
54
65
|
d
|
55
66
|
end
|
56
67
|
|
57
|
-
|
68
|
+
# Returns a new {LensPath} resulting from concatenating +other+ to the end
|
69
|
+
# of the receiver.
|
70
|
+
# @param other [Object] an accessor, an +Array+ of accessors, or another LensPath
|
71
|
+
# @return [LensPath] the new joined LensPath
|
72
|
+
def +(other)
|
58
73
|
parts =
|
59
|
-
case
|
74
|
+
case other
|
60
75
|
when Accessory::LensPath
|
61
|
-
|
76
|
+
other.to_a
|
62
77
|
when Array
|
63
|
-
|
78
|
+
other
|
64
79
|
else
|
65
|
-
[
|
80
|
+
[other]
|
66
81
|
end
|
67
82
|
|
68
83
|
d = self.dup
|
@@ -74,54 +89,108 @@ class Accessory::LensPath
|
|
74
89
|
|
75
90
|
alias_method :/, :+
|
76
91
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
else
|
85
|
-
Accessory::SubscriptAccessor.new(part)
|
86
|
-
end
|
87
|
-
|
88
|
-
unless @parts.empty?
|
89
|
-
@parts.last.make_default_fn = accessor.default_fn_for_previous_step
|
90
|
-
end
|
91
|
-
|
92
|
-
@parts.push(accessor)
|
93
|
-
end
|
94
|
-
|
95
|
-
protected :append_accessor!
|
96
|
-
|
97
|
-
def get_in(doc)
|
92
|
+
# Traverses +subject+ using the chain of accessors held in this LensPath,
|
93
|
+
# returning the discovered value.
|
94
|
+
#
|
95
|
+
# *Equivalent* in Elixir: {https://hexdocs.pm/elixir/Kernel.html#get_in/2 +Kernel.get_in/2+}
|
96
|
+
#
|
97
|
+
# @return [Object] the value found after all traversals.
|
98
|
+
def get_in(subject)
|
98
99
|
if @parts.empty?
|
99
|
-
|
100
|
+
subject
|
100
101
|
else
|
101
|
-
get_in_step(
|
102
|
+
get_in_step(subject, @parts)
|
102
103
|
end
|
103
104
|
end
|
104
105
|
|
105
|
-
|
106
|
+
# Traverses +subject+ using the chain of accessors held in this LensPath,
|
107
|
+
# modifying the final value at the end of the traversal chain using
|
108
|
+
# the passed +mutator_fn+, and returning the original targeted value(s)
|
109
|
+
# pre-modification.
|
110
|
+
#
|
111
|
+
# +mutator_fn+ must return one of two data "shapes":
|
112
|
+
# * a two-element +Array+, representing:
|
113
|
+
# 1. the value to surface as the "get" value of the traversal
|
114
|
+
# 2. the new value to replace at the traversal-position
|
115
|
+
# * the Symbol +:pop+ — which will remove the value from its parent, and
|
116
|
+
# return it as-is.
|
117
|
+
#
|
118
|
+
# *Equivalent* in Elixir: {https://hexdocs.pm/elixir/Kernel.html#get_and_update_in/3 +Kernel.get_and_update_in/3+}
|
119
|
+
#
|
120
|
+
# @param subject [Object] the data-structure to traverse
|
121
|
+
# @param mutator_fn [Proc] a block taking the original value derived from
|
122
|
+
# traversing +subject+, and returning a modification operation.
|
123
|
+
# @return [Array] a two-element +Array+, consisting of
|
124
|
+
# 1. the _old_ value(s) found after all traversals, and
|
125
|
+
# 2. the updated +subject+
|
126
|
+
def get_and_update_in(subject, &mutator_fn)
|
106
127
|
if @parts.empty?
|
107
|
-
|
128
|
+
subject
|
108
129
|
else
|
109
|
-
get_and_update_in_step(
|
130
|
+
get_and_update_in_step(subject, @parts, mutator_fn)
|
110
131
|
end
|
111
132
|
end
|
112
133
|
|
113
|
-
|
134
|
+
# Traverses +subject+ using the chain of accessors held in this LensPath,
|
135
|
+
# replacing the final value at the end of the traversal chain with the
|
136
|
+
# result from the passed +new_value_fn+.
|
137
|
+
#
|
138
|
+
# *Equivalent* in Elixir: {https://hexdocs.pm/elixir/Kernel.html#update_in/3 +Kernel.update_in/3+}
|
139
|
+
#
|
140
|
+
# @param subject [Object] the data-structure to traverse
|
141
|
+
# @param new_value_fn [Proc] a block taking the original value derived from
|
142
|
+
# traversing +subject+, and returning a replacement value.
|
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 update_in(subject, &new_value_fn)
|
114
147
|
_, new_data = self.get_and_update_in(data){ |v| [nil, new_value_fn.call(v)] }
|
115
148
|
new_data
|
116
149
|
end
|
117
150
|
|
118
|
-
|
119
|
-
|
151
|
+
# Traverses +subject+ using the chain of accessors held in this LensPath,
|
152
|
+
# replacing the final value at the end of the traversal chain with
|
153
|
+
# +new_value+.
|
154
|
+
#
|
155
|
+
# *Equivalent* in Elixir: {https://hexdocs.pm/elixir/Kernel.html#put_in/3 +Kernel.put_in/3+}
|
156
|
+
#
|
157
|
+
# @param subject [Object] the data-structure to traverse
|
158
|
+
# @param new_value [Object] a replacement value at the traversal position.
|
159
|
+
# @return [Object] the updated +subject+
|
160
|
+
def put_in(subject, new_value)
|
161
|
+
_, new_data = self.get_and_update_in(subject){ [nil, new_value] }
|
120
162
|
new_data
|
121
163
|
end
|
122
164
|
|
123
|
-
|
124
|
-
|
165
|
+
# Traverses +subject+ using the chain of accessors held in this LensPath,
|
166
|
+
# removing the final value at the end of the traversal chain from its position
|
167
|
+
# within its parent container.
|
168
|
+
#
|
169
|
+
# *Equivalent* in Elixir: {https://hexdocs.pm/elixir/Kernel.html#pop_in/2 +Kernel.pop_in/2+}
|
170
|
+
#
|
171
|
+
# @param subject [Object] the data-structure to traverse
|
172
|
+
# @return [Object] the updated +subject+
|
173
|
+
def pop_in(subject)
|
174
|
+
self.get_and_update_in(subject){ :pop }
|
175
|
+
end
|
176
|
+
|
177
|
+
protected
|
178
|
+
def append_accessor!(part)
|
179
|
+
accessor =
|
180
|
+
case part
|
181
|
+
when Accessory::Accessor
|
182
|
+
part
|
183
|
+
when Array
|
184
|
+
Accessory::SubscriptAccessor.new(part[0], default: part[1])
|
185
|
+
else
|
186
|
+
Accessory::SubscriptAccessor.new(part)
|
187
|
+
end
|
188
|
+
|
189
|
+
unless @parts.empty?
|
190
|
+
@parts.last.make_default_fn = accessor.default_fn_for_previous_step
|
191
|
+
end
|
192
|
+
|
193
|
+
@parts.push(accessor)
|
125
194
|
end
|
126
195
|
|
127
196
|
private
|
data/lib/accessory/version.rb
CHANGED