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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b3276e22b0eb2ad764f2a1d742962c2b3c2e066f8949b5328c00d4bae0f08edf
4
- data.tar.gz: c90330dc2daf7065405406e25b1feed10f3b224a822013af44e4645195d8d76d
3
+ metadata.gz: 1e6d18076731a0f1b3a28ce6ef77c75ef35dff6b7646f4880286b9bcf64ac393
4
+ data.tar.gz: '07918b78b0d0b1ae8097f22f36039a04fdefe9e59a10cae62758da84785fac4f'
5
5
  SHA512:
6
- metadata.gz: 6a98028a9b9e2e1913c098a8858e4994c4793f8451ea6e15bfca2cb5dd54c1ae54b31a5320094632e329767a1ee3cf0baa5e84fc3571c3b104914cbeaf2ab767
7
- data.tar.gz: b7b10ab902d4841090f1df7d5c770a16d02400f0d7b12761274928aefaca411f216c61f6a8436e7ee243fb20b45ff1feaa77fb22f9d2a16e1245c3506c61eb65
6
+ metadata.gz: ccdb07d177f3b436c91303585b921494d64aac6baa30abc32debf6ebc7ee4a6ff032b3db48f51d3c7b5d6498daeb7a298bcfd75840682bdc842e8fd70581bcf0
7
+ data.tar.gz: 2183b4b3513781e43b99490b4b86617ee8f08624c5c71a1355100fb84199bae2f27865d6b17496802c29c5571d39699e239b4dfc163983e789e618c39daf898d
@@ -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
@@ -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
- def initialize(default: DEFAULT_NOT_SET_SENTINEL)
8
- @default_value = default
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
- def initialize(attr_name, **kwargs)
5
- super(**kwargs)
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
- def initialize(offset, **kwargs)
6
- super(**kwargs)
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
- def initialize(pred)
5
- @pred = pred
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[0] = new_value
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
- def initialize(ivar_name, **kwargs)
5
- super(**kwargs)
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[-1] = new_value
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
@@ -3,25 +3,42 @@ module Accessory; end
3
3
  require 'accessory/lens_path'
4
4
 
5
5
  class Accessory::Lens
6
- def self.on(doc, path: nil)
7
- self.new(doc, path || Accessory::LensPath.empty).freeze
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
- def initialize(doc, lens_path)
15
- @doc = doc
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=#{@doc.inspect} #{@path.inspect(format: :short)}>"
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
- def +(lens_path)
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 + lens_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(@doc)
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(@doc, &mutator_fn)
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(@doc, &new_value_fn)
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(@doc, new_value)
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(@doc)
90
+ @path.pop_in(@subject)
61
91
  end
62
92
  end
63
93
 
64
94
  class Accessory::LensPath
65
- def on(doc)
66
- Accessory::Lens.on(doc, path: self)
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
@@ -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
- def self.[](*path)
12
- new(path).freeze
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
- def +(lp_b)
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 lp_b
74
+ case other
60
75
  when Accessory::LensPath
61
- lp_b.to_a
76
+ other.to_a
62
77
  when Array
63
- lp_b
78
+ other
64
79
  else
65
- [lp_b]
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
- def append_accessor!(part)
78
- accessor =
79
- case part
80
- when Accessory::Accessor
81
- part
82
- when Array
83
- Accessory::SubscriptAccessor.new(part[0], default: part[1])
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
- doc
100
+ subject
100
101
  else
101
- get_in_step(doc, @parts)
102
+ get_in_step(subject, @parts)
102
103
  end
103
104
  end
104
105
 
105
- def get_and_update_in(doc, &mutator_fn)
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
- doc
128
+ subject
108
129
  else
109
- get_and_update_in_step(doc, @parts, mutator_fn)
130
+ get_and_update_in_step(subject, @parts, mutator_fn)
110
131
  end
111
132
  end
112
133
 
113
- def update_in(data, &new_value_fn)
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
- def put_in(data, new_value)
119
- _, new_data = self.get_and_update_in(data){ [nil, new_value] }
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
- def pop_in(data)
124
- self.get_and_update_in(data){ :pop }
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
@@ -1,3 +1,3 @@
1
1
  module Accessory
2
- VERSION = "0.1.3"
2
+ VERSION = "0.1.4"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: accessory
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Levi Aul