accessory 0.1.2 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/accessory.rb +3 -3
- data/lib/accessory/access.rb +67 -21
- data/lib/accessory/accessor.rb +173 -13
- data/lib/accessory/accessors/all_accessor.rb +35 -3
- data/lib/accessory/accessors/attribute_accessor.rb +60 -8
- data/lib/accessory/accessors/between_each_accessor.rb +48 -8
- data/lib/accessory/accessors/betwixt_accessor.rb +75 -17
- data/lib/accessory/accessors/filter_accessor.rb +46 -5
- data/lib/accessory/accessors/first_accessor.rb +44 -7
- data/lib/accessory/accessors/instance_variable_accessor.rb +41 -8
- data/lib/accessory/accessors/last_accessor.rb +44 -7
- data/lib/accessory/accessors/subscript_accessor.rb +57 -6
- data/lib/accessory/bound_lens.rb +139 -0
- data/lib/accessory/lens.rb +197 -26
- data/lib/accessory/traversal_position/enumerable_at_offset.rb +28 -0
- data/lib/accessory/traversal_position/enumerable_before_offset.rb +55 -0
- data/lib/accessory/version.rb +1 -1
- metadata +5 -4
- data/lib/accessory/array_cursor_position.rb +0 -18
- data/lib/accessory/lens_path.rb +0 -150
@@ -1,29 +1,72 @@
|
|
1
1
|
require 'accessory/accessor'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
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 {Lens} and {BoundLens})
|
17
|
+
#
|
18
|
+
# <b>Default constructor</b> used by predecessor accessor
|
19
|
+
#
|
20
|
+
# * +OpenStruct.new+
|
21
|
+
|
22
|
+
class Accessory::Accessors::AttributeAccessor < Accessory::Accessor
|
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
|
32
|
+
def name; "attr"; end
|
33
|
+
|
34
|
+
# @!visibility private
|
10
35
|
def inspect_args
|
11
36
|
@getter_method_name.inspect
|
12
37
|
end
|
13
38
|
|
14
|
-
|
15
|
-
|
39
|
+
# @!visibility private
|
40
|
+
def inspect(format: :long)
|
41
|
+
case format
|
42
|
+
when :long
|
43
|
+
super()
|
44
|
+
when :short
|
45
|
+
".#{@getter_method_name}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# @!visibility private
|
50
|
+
def ensure_valid(traversal_result)
|
51
|
+
if traversal_result
|
52
|
+
traversal_result
|
53
|
+
else
|
16
54
|
require 'ostruct'
|
17
55
|
OpenStruct.new
|
18
56
|
end
|
19
57
|
end
|
20
58
|
|
21
|
-
|
59
|
+
# @!visibility private
|
60
|
+
def traverse(data)
|
22
61
|
data.send(@getter_method_name)
|
23
62
|
end
|
24
63
|
|
64
|
+
# Finds <tt>data.send(:"#{attr_name}")</tt>, feeds it down the accessor chain,
|
65
|
+
# and returns the result.
|
66
|
+
# @param data [Object] the object to traverse
|
67
|
+
# @return [Object] the value derived from the rest of the accessor chain
|
25
68
|
def get(data)
|
26
|
-
value =
|
69
|
+
value = traverse_or_default(data)
|
27
70
|
|
28
71
|
if block_given?
|
29
72
|
yield(value)
|
@@ -32,8 +75,17 @@ class Accessory::AttributeAccessor < Accessory::Accessor
|
|
32
75
|
end
|
33
76
|
end
|
34
77
|
|
78
|
+
# Finds <tt>data.send(:"#{attr_name}")</tt>, feeds it down the accessor chain,
|
79
|
+
# and uses <tt>data.send(:"#{attr_name}=")</tt> to overwrite the stored value
|
80
|
+
# with the returned result.
|
81
|
+
#
|
82
|
+
# If +:pop+ is returned from the accessor chain, the stored value will be
|
83
|
+
# overwritten with `nil`.
|
84
|
+
#
|
85
|
+
# @param data [Object] the object to traverse
|
86
|
+
# @return [Array] a two-element array containing 1. the original value found; and 2. the result value from the accessor chain
|
35
87
|
def get_and_update(data)
|
36
|
-
value =
|
88
|
+
value = traverse_or_default(data)
|
37
89
|
|
38
90
|
case yield(value)
|
39
91
|
in [result, new_value]
|
@@ -1,14 +1,36 @@
|
|
1
1
|
require 'accessory/accessor'
|
2
|
-
require 'accessory/
|
2
|
+
require 'accessory/traversal_position/enumerable_before_offset'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
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 {Lens#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 {Lens} and {BoundLens})
|
14
|
+
#
|
15
|
+
# <b>Default constructor</b> used by predecessor accessor
|
16
|
+
#
|
17
|
+
# * +Array.new+
|
18
|
+
|
19
|
+
class Accessory::Accessors::BetweenEachAccessor < Accessory::Accessor
|
20
|
+
# @!visibility private
|
21
|
+
def ensure_valid(traversal_result)
|
22
|
+
if traversal_result.kind_of?(Enumerable)
|
23
|
+
traversal_result
|
24
|
+
else
|
25
|
+
[]
|
26
|
+
end
|
7
27
|
end
|
8
28
|
|
29
|
+
# @!visibility private
|
9
30
|
def inspect_args; nil; end
|
10
31
|
|
11
|
-
|
32
|
+
# @!visibility private
|
33
|
+
def traverse(data)
|
12
34
|
data_len = data.length
|
13
35
|
|
14
36
|
positions = [
|
@@ -18,12 +40,17 @@ class Accessory::BetweenEachAccessor < Accessory::Accessor
|
|
18
40
|
]
|
19
41
|
|
20
42
|
positions.transpose.map do |(i, b, a)|
|
21
|
-
Accessory::
|
43
|
+
Accessory::TraversalPosition::EnumerableBeforeOffset.new(i, b, a, is_first: i == 0, is_last: i == data_len)
|
22
44
|
end
|
23
45
|
end
|
24
46
|
|
47
|
+
# Feeds {TraversalPosition::EnumerableBeforeOffset}s representing the
|
48
|
+
# positions between the elements of +data+ down the accessor chain.
|
49
|
+
#
|
50
|
+
# @param data [Enumerable] the +Enumerable+ to iterate through
|
51
|
+
# @return [Array] the generated {TraversalPosition::EnumerableBeforeOffset}s
|
25
52
|
def get(data)
|
26
|
-
positions =
|
53
|
+
positions = traverse_or_default(data || [])
|
27
54
|
|
28
55
|
if block_given?
|
29
56
|
positions.map{ |rec| yield(rec) }
|
@@ -32,11 +59,24 @@ class Accessory::BetweenEachAccessor < Accessory::Accessor
|
|
32
59
|
end
|
33
60
|
end
|
34
61
|
|
62
|
+
# Feeds {TraversalPosition::EnumerableBeforeOffset}s representing the
|
63
|
+
# positions between the elements of +data+ down the accessor chain,
|
64
|
+
# manipulating +data+ using the results.
|
65
|
+
#
|
66
|
+
# If a new element is returned up the accessor chain, the element is inserted
|
67
|
+
# between the existing elements.
|
68
|
+
#
|
69
|
+
# If +:pop+ is returned up the accessor chain, no new element is added.
|
70
|
+
#
|
71
|
+
# @param data [Enumerable] the +Enumerable+ to iterate through
|
72
|
+
# @return [Array] a two-element array containing
|
73
|
+
# 1. the {TraversalPosition::EnumerableBeforeOffset}s
|
74
|
+
# 2. the new {data}
|
35
75
|
def get_and_update(data)
|
36
76
|
results = []
|
37
77
|
new_data = []
|
38
78
|
|
39
|
-
positions =
|
79
|
+
positions = traverse_or_default(data || [])
|
40
80
|
|
41
81
|
positions.each do |pos|
|
42
82
|
case yield(pos)
|
@@ -1,34 +1,79 @@
|
|
1
1
|
require 'accessory/accessor'
|
2
|
-
require 'accessory/
|
2
|
+
require 'accessory/traversal_position/enumerable_before_offset'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
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 {Lens#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 {Lens} and {BoundLens})
|
24
|
+
#
|
25
|
+
# <b>Default constructor</b> used by predecessor accessor
|
26
|
+
#
|
27
|
+
# * +Array.new+
|
28
|
+
|
29
|
+
class Accessory::Accessors::BetwixtAccessor < Accessory::Accessor
|
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
|
|
14
|
-
|
15
|
-
|
42
|
+
# @!visibility private
|
43
|
+
def ensure_valid(traversal_result)
|
44
|
+
if traversal_result.kind_of?(Enumerable)
|
45
|
+
traversal_result
|
46
|
+
else
|
47
|
+
[]
|
48
|
+
end
|
16
49
|
end
|
17
50
|
|
18
|
-
|
19
|
-
|
51
|
+
# @!visibility private
|
52
|
+
def traverse(data)
|
53
|
+
nil
|
54
|
+
# return :error unless data.kind_of?(Enumerable)
|
55
|
+
|
56
|
+
# data_len = data.length
|
57
|
+
|
58
|
+
# ebo = Accessory::TraversalPosition::EnumerableBeforeOffset.new(
|
59
|
+
# @offset,
|
60
|
+
# (@offset > 0) ? data[@offset - 1] : nil,
|
61
|
+
# (@offset < (data_len - 1)) ? data[@offset + 1] : nil,
|
62
|
+
# is_first: @offset == 0,
|
63
|
+
# is_last: @offset == data_len
|
64
|
+
# )
|
20
65
|
|
21
|
-
|
22
|
-
@offset,
|
23
|
-
(@offset > 0) ? data[@offset - 1] : nil,
|
24
|
-
(@offset < (data_len - 1)) ? data[@offset + 1] : nil,
|
25
|
-
is_first: @offset == 0,
|
26
|
-
is_last: @offset == data_len
|
27
|
-
)
|
66
|
+
# [:ok, ebo]
|
28
67
|
end
|
29
68
|
|
69
|
+
# Feeds a {TraversalPosition::EnumerableBeforeOffset} representing the
|
70
|
+
# position between the elements of +data+ at +@offset+ down the accessor
|
71
|
+
# chain.
|
72
|
+
#
|
73
|
+
# @param data [Enumerable] the +Enumerable+ to traverse into
|
74
|
+
# @return [Array] the generated {TraversalPosition::EnumerableBeforeOffset}
|
30
75
|
def get(data)
|
31
|
-
pos =
|
76
|
+
pos = traverse_or_default(data || [])
|
32
77
|
|
33
78
|
if block_given?
|
34
79
|
yield(pos)
|
@@ -37,8 +82,21 @@ class Accessory::BetwixtAccessor < Accessory::Accessor
|
|
37
82
|
end
|
38
83
|
end
|
39
84
|
|
85
|
+
# Feeds a {TraversalPosition::EnumerableBeforeOffset} representing the
|
86
|
+
# position between the elements of +data+ at +@offset+ down the accessor
|
87
|
+
# chain, manipulating +data+ using the result.
|
88
|
+
#
|
89
|
+
# If a new element is returned up the accessor chain, the element is inserted
|
90
|
+
# at the specified position, using <tt>data.insert(@offset, e)</tt>.
|
91
|
+
#
|
92
|
+
# If +:pop+ is returned up the accessor chain, no new element is added.
|
93
|
+
#
|
94
|
+
# @param data [Enumerable] the +Enumerable+ to traverse into
|
95
|
+
# @return [Array] a two-element array containing
|
96
|
+
# 1. the generated {TraversalPosition::EnumerableBeforeOffset}
|
97
|
+
# 2. the new {data}
|
40
98
|
def get_and_update(data)
|
41
|
-
pos =
|
99
|
+
pos = traverse_or_default(data || [])
|
42
100
|
|
43
101
|
case yield(pos)
|
44
102
|
in [result, new_value]
|
@@ -1,18 +1,51 @@
|
|
1
1
|
require 'accessory/accessor'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
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 {Lens} and {BoundLens})
|
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
|
+
|
18
|
+
class Accessory::Accessors::FilterAccessor < Accessory::Accessor
|
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
|
|
12
|
-
|
13
|
-
|
36
|
+
# @!visibility private
|
37
|
+
def ensure_valid(traversal_result)
|
38
|
+
if traversal_result.kind_of?(Enumerable)
|
39
|
+
traversal_result
|
40
|
+
else
|
41
|
+
[]
|
42
|
+
end
|
14
43
|
end
|
15
44
|
|
45
|
+
# Feeds each element of +data+ matching the predicate down the accessor chain,
|
46
|
+
# returning the results.
|
47
|
+
# @param data [Enumerable] the +Enumerable+ to iterate through
|
48
|
+
# @return [Array] the values derived from the rest of the accessor chain
|
16
49
|
def get(data, &succ)
|
17
50
|
if succ
|
18
51
|
(data || []).filter(&@pred).map(&succ)
|
@@ -21,6 +54,14 @@ class Accessory::FilterAccessor < Accessory::Accessor
|
|
21
54
|
end
|
22
55
|
end
|
23
56
|
|
57
|
+
# Feeds each element of +data+ matching the predicate down the accessor chain,
|
58
|
+
# overwriting +data+ with the results.
|
59
|
+
#
|
60
|
+
# If +:pop+ is returned from the accessor chain, the element is dropped
|
61
|
+
# from the new +data+.
|
62
|
+
#
|
63
|
+
# @param data [Enumerable] the +Enumerable+ to iterate through
|
64
|
+
# @return [Array] a two-element array containing 1. the original values found during iteration; and 2. the new +data+
|
24
65
|
def get_and_update(data)
|
25
66
|
results = []
|
26
67
|
new_data = []
|
@@ -1,18 +1,43 @@
|
|
1
1
|
require 'accessory/accessor'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
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 {Lens} and {BoundLens})
|
13
|
+
#
|
14
|
+
# <b>Default constructor</b> used by predecessor accessor
|
15
|
+
#
|
16
|
+
# * +Array.new+
|
17
|
+
|
18
|
+
class Accessory::Accessors::FirstAccessor < Accessory::Accessor
|
19
|
+
# @!visibility private
|
20
|
+
def ensure_valid(traversal_result)
|
21
|
+
if traversal_result.kind_of?(Enumerable)
|
22
|
+
traversal_result
|
23
|
+
else
|
24
|
+
[]
|
25
|
+
end
|
6
26
|
end
|
7
27
|
|
28
|
+
# @!visibility private
|
8
29
|
def inspect_args; nil; end
|
9
30
|
|
10
|
-
|
31
|
+
# @!visibility private
|
32
|
+
def traverse(data)
|
11
33
|
data.first
|
12
34
|
end
|
13
35
|
|
36
|
+
# Feeds <tt>data.first</tt> down the accessor chain, returning the result.
|
37
|
+
# @param data [Object] the object to traverse
|
38
|
+
# @return [Object] the value derived from the rest of the accessor chain
|
14
39
|
def get(data)
|
15
|
-
value =
|
40
|
+
value = traverse_or_default(data)
|
16
41
|
|
17
42
|
if block_given?
|
18
43
|
yield(value)
|
@@ -21,12 +46,24 @@ class Accessory::FirstAccessor < Accessory::Accessor
|
|
21
46
|
end
|
22
47
|
end
|
23
48
|
|
49
|
+
# Finds <tt>data.first</tt>, feeds it down the accessor chain, and overwrites
|
50
|
+
# the stored value with the returned result.
|
51
|
+
#
|
52
|
+
# If +:pop+ is returned from the accessor chain, the stored value will be
|
53
|
+
# removed using <tt>data.delete_at(0)</tt>.
|
54
|
+
#
|
55
|
+
# @param data [Object] the object to traverse
|
56
|
+
# @return [Array] a two-element array containing 1. the original value found; and 2. the result value from the accessor chain
|
24
57
|
def get_and_update(data)
|
25
|
-
old_value =
|
58
|
+
old_value = traverse_or_default(data)
|
26
59
|
|
27
60
|
case yield(old_value)
|
28
61
|
in [result, new_value]
|
29
|
-
data
|
62
|
+
if data.respond_to?(:"first=")
|
63
|
+
data.first = new_value
|
64
|
+
else
|
65
|
+
data[0] = new_value
|
66
|
+
end
|
30
67
|
[result, data]
|
31
68
|
in :pop
|
32
69
|
data.delete_at(0)
|