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 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