accessory 0.1.3 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- 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