accessory 0.1.1 → 0.1.6

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.
@@ -0,0 +1,99 @@
1
+ require 'accessory/accessor'
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 {Lens} and {BoundLens})
17
+ #
18
+ # <b>Default constructor</b> used by predecessor accessor
19
+ #
20
+ # * +OpenStruct.new+
21
+
22
+ class Accessory::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)
27
+ @getter_method_name = :"#{attr_name}"
28
+ @setter_method_name = :"#{attr_name}="
29
+ end
30
+
31
+ # @!visibility private
32
+ def name; "attr"; end
33
+
34
+ # @!visibility private
35
+ def inspect_args
36
+ @getter_method_name.inspect
37
+ end
38
+
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
54
+ require 'ostruct'
55
+ OpenStruct.new
56
+ end
57
+ end
58
+
59
+ # @!visibility private
60
+ def traverse(data)
61
+ data.send(@getter_method_name)
62
+ end
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
68
+ def get(data)
69
+ value = traverse_or_default(data)
70
+
71
+ if block_given?
72
+ yield(value)
73
+ else
74
+ value
75
+ end
76
+ end
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
87
+ def get_and_update(data)
88
+ value = traverse_or_default(data)
89
+
90
+ case yield(value)
91
+ in [result, new_value]
92
+ data.send(@setter_method_name, new_value)
93
+ [result, data]
94
+ in :pop
95
+ data.send(@setter_method_name, nil)
96
+ [value, data]
97
+ end
98
+ end
99
+ end
@@ -1,14 +1,36 @@
1
1
  require 'accessory/accessor'
2
- require 'accessory/array_cursor_position'
2
+ require 'accessory/traversal_position/enumerable_before_offset'
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 {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+
3
18
 
4
19
  class Accessory::BetweenEachAccessor < Accessory::Accessor
5
- def default_fn_for_previous_step
6
- lambda{ Array.new }
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
- def value_from(data)
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::ArrayCursorPosition.new(i, b, a, is_first: i == 0, is_last: i == data_len)
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 = value_or_default(data || [])
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 = value_or_default(data || [])
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/array_cursor_position'
2
+ require 'accessory/traversal_position/enumerable_before_offset'
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 {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+
3
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
 
14
- def default_fn_for_previous_step
15
- lambda{ Array.new }
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
- def value_from(data)
19
- data_len = data.length
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
- Accessory::ArrayCursorPosition.new(
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 = value_or_default(data || [])
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 = value_or_default(data || [])
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
+ # 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
+
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
 
12
- def default_fn_for_previous_step
13
- lambda{ Array.new }
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
+ # 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
+
3
18
  class Accessory::FirstAccessor < Accessory::Accessor
4
- def default_fn_for_previous_step
5
- lambda{ Array.new }
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
- def value_from(data)
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 = value_or_default(data)
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 = value_or_default(data)
58
+ old_value = traverse_or_default(data)
26
59
 
27
60
  case yield(old_value)
28
61
  in [result, new_value]
29
- data[0] = new_value
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)
@@ -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 {Lens} and {BoundLens})
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 ivar_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,20 +27,27 @@ 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
 
18
- def default_fn_for_previous_step
19
- lambda{ Object.new }
35
+ # @!visibility private
36
+ def ensure_valid(traversal_result)
37
+ traversal_result || Object.new
20
38
  end
21
39
 
22
- def value_from(data)
40
+ # @!visibility private
41
+ def traverse(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
- value = value_or_default(data)
50
+ value = traverse_or_default(data)
28
51
 
29
52
  if block_given?
30
53
  yield(value)
@@ -33,8 +56,18 @@ 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
- value = value_or_default(data)
70
+ value = traverse_or_default(data)
38
71
 
39
72
  case yield(value)
40
73
  in [result, new_value]