linked 0.4.0 → 0.5.1

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
- SHA1:
3
- metadata.gz: 05e94a861ba2e494ea20dc84572c5dac578421eb
4
- data.tar.gz: 849a8b134e95bcd6d444782c167f83ff6f982be4
2
+ SHA256:
3
+ metadata.gz: 44eaaab09ac6111dd607d3cd52e35d2d072315bbc4614b7805254c8a7bab311a
4
+ data.tar.gz: 4672f6d56f0dde64a6fe12ecaefdf82d39795a957d97ccb55eb0558ffe23285d
5
5
  SHA512:
6
- metadata.gz: 255a18d48ba2d395e12394aa8a6a72880100a1c2677ad3f5e3005c7412ea74011c4b902947b390b85077675c6a0447c112b6bed36ba4da8bf03dcd5769bf7f73
7
- data.tar.gz: e2e9b8f9a72c06074c1aa3af9cbecff9c3fbb8adc2bc6674f50f86d3a089abec934ca5ee91815e9156f4647c374ec3bea87fec583e80236a5b010c9939c0cbf8
6
+ metadata.gz: c94e42bee0a6717053ff3c33de9f36c03ab4ba24d35ddb3b387ed78a3e4a54898269eb62237117a05db4cf4c96b5555c376cc507cb2d8cc02aed1a80059f69a3
7
+ data.tar.gz: 479b9a24c6dc2a785993072e620695bebe35e9599cfb9f8842fd1a767d6783c5b0838f6ccd0abce247a155e416930fa76cc6c164cb6ae85e2e9aac23c7948c9f
data/.rubocop.yml ADDED
@@ -0,0 +1,71 @@
1
+ AllCops:
2
+ Exclude:
3
+ - 'vendor/**/*'
4
+ - 'tmp/**/*'
5
+ TargetRubyVersion: 2.3
6
+
7
+ Layout/EndOfLine:
8
+ EnforcedStyle: lf
9
+
10
+ Layout/ClassStructure:
11
+ Enabled: true
12
+ Categories:
13
+ module_inclusion:
14
+ - include
15
+ - prepend
16
+ - extend
17
+ ExpectedOrder:
18
+ - module_inclusion
19
+ - constants
20
+ - public_class_methods
21
+ - initializer
22
+ - instance_methods
23
+ - protected_methods
24
+ - private_methods
25
+
26
+ Layout/IndentHeredoc:
27
+ EnforcedStyle: squiggly
28
+
29
+ Lint/AmbiguousBlockAssociation:
30
+ Exclude:
31
+ - 'test/**/*.rb'
32
+
33
+ Lint/InterpolationCheck:
34
+ Exclude:
35
+ - 'test/**/*.rb'
36
+
37
+ Metrics/BlockLength:
38
+ Exclude:
39
+ - 'Rakefile'
40
+ - '**/*.rake'
41
+ - 'test/**/*.rb'
42
+
43
+ Metrics/ModuleLength:
44
+ Exclude:
45
+ - 'test/**/*.rb'
46
+
47
+ Metrics/ParameterLists:
48
+ CountKeywordArgs: false
49
+
50
+ Naming/UncommunicativeMethodParamName:
51
+ AllowedNames:
52
+ - "_"
53
+ - x
54
+ - y
55
+ - i
56
+ - p
57
+ - n
58
+ - t
59
+ - r
60
+ - g
61
+ - b
62
+ - to
63
+
64
+ Style/FrozenStringLiteralComment:
65
+ EnforcedStyle: always
66
+
67
+ Style/FormatStringToken:
68
+ Enabled: false
69
+
70
+ Style/MultipleComparison:
71
+ Enabled: false
data/.travis.yml CHANGED
@@ -3,3 +3,4 @@ language: ruby
3
3
  rvm:
4
4
  - 2.3.0
5
5
  before_install: gem install bundler -v 1.12.5
6
+ script: bundle exec rake test
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in linked.gemspec
data/Rakefile CHANGED
@@ -1,10 +1,23 @@
1
- require "bundler/gem_tasks"
2
- require "rake/testtask"
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rake/testtask'
5
+ require 'rubocop/rake_task'
6
+ require 'yard'
3
7
 
4
8
  Rake::TestTask.new(:test) do |t|
5
- t.libs << "test"
6
- t.libs << "lib"
9
+ t.libs << 'test'
10
+ t.libs << 'lib'
7
11
  t.test_files = FileList['test/**/*_test.rb']
8
12
  end
9
13
 
10
- task :default => :test
14
+ RuboCop::RakeTask.new(:rubocop)
15
+
16
+ YARD::Rake::YardocTask.new(:yard) do |t|
17
+ t.stats_options = %w[--list-undoc]
18
+ end
19
+
20
+ desc 'Generate Ruby documentation'
21
+ task doc: %w[yard]
22
+
23
+ task default: %w[test rubocop:auto_correct]
data/bin/console CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require "bundler/setup"
4
- require "linked"
4
+ require 'bundler/setup'
5
+ require 'linked'
5
6
 
6
7
  # You can add fixtures and/or initialization code here to make experimenting
7
8
  # with your gem easier. You can also use a different console, if you like.
@@ -10,5 +11,5 @@ require "linked"
10
11
  # require "pry"
11
12
  # Pry.start
12
13
 
13
- require "irb"
14
+ require 'irb'
14
15
  IRB.start
data/lib/linked.rb CHANGED
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'linked/version'
4
+ require 'linked/util'
4
5
  require 'linked/listable'
5
6
  require 'linked/item'
6
- require 'linked/list/eol'
7
+ require 'linked/list_enumerable'
7
8
  require 'linked/list'
8
9
 
10
+ # Linked List implementation.
9
11
  module Linked
10
-
12
+ private_constant :Util, :ListEnumerable
11
13
  end
data/lib/linked/item.rb CHANGED
@@ -1,37 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Linked
4
- # Item
4
+ # This is the default implementation of a listable object
5
5
  #
6
6
  # This class implements a listable value object that wraps an arbitrary value
7
- # an can be stored in a list.
8
-
7
+ # an can be with other listable items.
9
8
  class Item
10
9
  include Listable
11
10
 
12
11
  # The Item can hold an arbitrary object as its value and it will stay with
13
12
  # the item.
14
-
13
+ # @return [Object] any object that is stored in the item.
15
14
  attr_accessor :value
16
15
 
17
16
  # Creates a new item. If a list is given the item will be considered a part
18
17
  # of that list and appended to the end of it.
19
18
  #
20
- # value - an arbitrary object to store with the item.
21
- # list - an object responding to #head and #tail.
22
- #
23
- # Returns a new Item.
24
-
19
+ # @param value [Object] an arbitrary object to store with the item.
25
20
  def initialize(value = nil)
26
21
  @value = value
27
22
  super()
28
23
  end
29
24
 
30
25
  # Calling #dup on an item returns a copy that is no longer connected to the
31
- # original item chain, or the list. The value will also be copied.
26
+ # original item chain. The value will also be copied.
32
27
  #
33
- # Returns a new Item.
34
-
28
+ # @param source [Item] the item to copy.
29
+ # @return [item] a new Item.
35
30
  def initialize_dup(source)
36
31
  @value = begin
37
32
  source.value.dup
@@ -45,10 +40,9 @@ module Linked
45
40
  # responds to #value, and its value is equal (#==) to this value, the
46
41
  # objects are considered equal.
47
42
  #
48
- # other - any object.
49
- #
50
- # Returns true if the objects are considered equal.
51
-
43
+ # @param other [#value, Object] any object.
44
+ # @return [true, false] true if the value of the given object is equal to
45
+ # the item value.
52
46
  def ==(other)
53
47
  return false unless other.respond_to? :value
54
48
  value == other.value
@@ -58,28 +52,27 @@ module Linked
58
52
 
59
53
  # Uses the hash value of the item value.
60
54
  #
61
- # Returns a fixnum that can be used by Hash to identify the item.
62
-
55
+ # @return [Integer] a fixnum that can be used by Hash to identify the item.
63
56
  def hash
64
57
  value.hash
65
58
  end
66
59
 
67
- # Freezes the value, as well as making the list item itself immutable.
68
-
60
+ # Freezes the value, as well as making the item itself immutable.
61
+ #
62
+ # @return [self]
69
63
  def freeze
70
64
  value.freeze
71
65
  super
72
66
  end
73
67
 
74
68
  # The default #inspect method becomes very cluttered the moment you start
75
- # liking objects together. This implementation fixes that and only shows the
76
- # class name, object id and value (if set).
77
-
69
+ # linking objects together. This implementation fixes that and only shows
70
+ # the class name, object id and value (if set).
71
+ #
72
+ # @return [String] a string representation of the list item.
78
73
  def inspect
79
- return yield self if block_given?
80
-
81
- output = format '%s:0x%0x', self.class.name, object_id
82
- value ? output + " value=#{value.inspect}" : output
74
+ return yield(self).to_s if block_given?
75
+ value ? object_identifier + " value=#{value.inspect}" : object_identifier
83
76
  end
84
77
  end
85
78
  end
data/lib/linked/list.rb CHANGED
@@ -1,70 +1,52 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Linked
4
- # List
4
+ # This class provides a way extend the regular chain of listable items with
5
+ # the concept of an empty chain.
5
6
  #
6
- # This class implements a linked list. Most importantly, the methods #head,
7
- # #tail, #grow, #shrink and #create_item are implemented to comply with the
8
- # requirements defined by Listable.
9
-
7
+ # Lists are ment to behave more like arrays, and respond to many of the same
8
+ # methods.
10
9
  class List
11
- include Enumerable
12
-
13
- # Initializes the list. It is important that this method be called during
14
- # the initialization of the including class, and that the instance variables
15
- # @_item_count and @_eol never be accessed directly.
10
+ include ListEnumerable
11
+ include Util
16
12
 
13
+ # Initializes the list.
17
14
  def initialize
18
- @_eol = EOL.new self
19
- @_item_count = 0
20
-
15
+ reset_list
21
16
  super
22
17
  end
23
18
 
24
19
  # When copying a list its entire item chain needs to be copied as well.
25
20
  # Therefore #dup will be called on each of the original lists items, making
26
21
  # this operation quite expensive.
27
-
22
+ #
23
+ # @param source [List] the list to copy.
28
24
  def initialize_dup(source)
29
- @_eol = EOL.new self
30
- @_item_count = 0
31
-
32
- source.each_item { |item| push item.dup }
25
+ reset_list
26
+ source.each_item { |item| push item.dup }
33
27
 
34
28
  super
35
29
  end
36
30
 
37
- # Identity method that simply return the list. This method mirrors Item#list
38
- # and allows other methods that work on List objects to easily and
39
- # interchangebly accept both lists and items as arguments.
40
- #
41
- # Returns the list itself.
42
-
43
- def list
44
- self
45
- end
46
-
47
31
  # Access the first item in the list. If the list is empty a NoMethodError
48
32
  # will be raised. This mirrors the behaviour of Item#item and allows other
49
33
  # methods that work on List objects to easily and interchangeably accept
50
34
  # both lists and items as arguments.
51
35
  #
52
- # Returns the first item in the list.
53
-
36
+ # @return [Listable] the first item in the list.
54
37
  def item
55
38
  raise NoMethodError if empty?
56
- head.next
39
+ @_chain
57
40
  end
58
41
 
59
42
  # Two lists are considered equal if the n:th item from each list are equal.
60
43
  #
61
- # other - any object.
62
- #
63
- # Returns true if the given object is a list and the items are equal.
64
-
44
+ # @param other [Object] the object to compare with.
45
+ # @return [true] if the given object is a list and the items are equal.
46
+ # @return [false] otherwise.
65
47
  def ==(other)
66
48
  return false unless other.is_a? self.class
67
- return false unless other.count == @_item_count
49
+ return false unless other.count == count
68
50
 
69
51
  other_items = other.each_item
70
52
  each_item.all? { |item| item == other_items.next }
@@ -72,86 +54,10 @@ module Linked
72
54
 
73
55
  alias eql? ==
74
56
 
75
- # Access the first n item(s) in the list. If a block is given each item will
76
- # be yielded to it. The first item, starting from the first in the list, for
77
- # which the block returns true and the n - 1 items directly following it
78
- # will be returned.
79
- #
80
- # n - the number of items to return.
81
- #
82
- # Returns, for different values of n:
83
- # n == 0) nil
84
- # n == 1) an item if the list contains one, or nil
85
- # n > 1) an array of between 0 and n items, depending on how many are in
86
- # the list
87
-
88
- def first(n = 1)
89
- raise ArgumentError, 'n cannot be negative' if n < 0
90
-
91
- return first_item_after head, n, count unless block_given?
92
-
93
- item = head
94
- items_left = count
95
-
96
- items_left.times do
97
- break if yield next_item = item.next
98
- item = next_item
99
- items_left -= 1
100
- end
101
-
102
- first_item_after item, n, items_left
103
- end
104
-
105
- # Access the last n item(s) in the list. The items will retain thier order.
106
- # If a block is given each item, starting with the last in the list, will be
107
- # yielded to it. The first item for which the block returns true and the
108
- # n - 1 items directly preceding it will be returned.
109
- #
110
- # n - the number of items to return.
111
- #
112
- # Returns, for different values of n:
113
- # n == 0) nil
114
- # n == 1) an item if the list contains one, or nil
115
- # n > 1) an array of between 0 and n items, depending on how many are in
116
- # the list
117
-
118
- def last(n = 1)
119
- raise ArgumentError, 'n cannot be negative' if n < 0
120
-
121
- return last_item_before tail, n, count unless block_given?
122
-
123
- item = tail
124
- items_left = count
125
-
126
- items_left.times do
127
- break if yield prev_item = item.prev
128
- item = prev_item
129
- items_left -= 1
130
- end
131
-
132
- last_item_before item, n, items_left
133
- end
134
-
135
- # Overrides the Enumerable#count method when given no argument to provide a
136
- # fast item count. Instead of iterating over each item, the internal item
137
- # count is returned.
138
- #
139
- # args - see Enumerable#count
140
- #
141
- # Returns the number of items counted.
142
-
143
- def count(*args)
144
- if args.empty? && !block_given?
145
- @_item_count
146
- else
147
- super
148
- end
149
- end
150
-
151
- # Returns true if the list does not contain any items.
152
-
57
+ # @return [true] if the list does not contain any items.
58
+ # @return [false] otherwise.
153
59
  def empty?
154
- @_item_count == 0
60
+ nil.eql? @_chain
155
61
  end
156
62
 
157
63
  # Insert an item at the end of the list. If the given object is not an
@@ -160,12 +66,17 @@ module Linked
160
66
  #
161
67
  # See Item#append for more details.
162
68
  #
163
- # object - the item to insert, or an arbitrary object.
164
- #
165
- # Returns self.
166
-
69
+ # @param object [#item, Object] the item to insert, or an arbitrary object.
70
+ # @return [self]
167
71
  def push(object)
168
- tail.append object
72
+ item = coerce_item object
73
+
74
+ if empty?
75
+ @_chain = item
76
+ else
77
+ list_tail.append item
78
+ end
79
+
169
80
  self
170
81
  end
171
82
 
@@ -173,11 +84,18 @@ module Linked
173
84
 
174
85
  # Pop the last item off the list.
175
86
  #
176
- # Returns the last item in the list, or nil if the list is empty.
177
-
87
+ # @return [Listable, nil] the last item in the list, or nil if the list is
88
+ # empty.
178
89
  def pop
179
90
  return nil if empty?
180
- last.delete
91
+
92
+ if list_tail.first?
93
+ item = last
94
+ @_chain = nil
95
+ item
96
+ else
97
+ list_tail.delete
98
+ end
181
99
  end
182
100
 
183
101
  # Insert an item at the beginning of the list. If the given object is not an
@@ -186,67 +104,49 @@ module Linked
186
104
  #
187
105
  # See Item#prepend for more details.
188
106
  #
189
- # object - the item to insert, or an arbitrary object.
190
- #
191
- # Returns self.
192
-
107
+ # @param object [#item, Object] the item to insert, or an arbitrary object.
108
+ # @return [self]
193
109
  def unshift(object)
194
- head.prepend object
110
+ item = coerce_item object
111
+ @_chain = empty? ? item.chain : @_chain.prepend(item)
112
+
195
113
  self
196
114
  end
197
115
 
198
116
  # Shift the first item off the list.
199
117
  #
200
- # Returns the first item in the list, or nil if the list is empty.
201
-
118
+ # @return [Listable, nil] the first item in the list, or nil if the list is
119
+ # empty.
202
120
  def shift
203
121
  return nil if empty?
204
- first.delete
122
+
123
+ if list_head.last?
124
+ item = @_chain
125
+ @_chain = nil
126
+ item
127
+ else
128
+ old_head = list_head
129
+ @_chain = list_head.next
130
+ old_head.delete
131
+ end
205
132
  end
206
133
 
207
134
  # Check if an item is in the list.
208
135
  #
209
- # item - Item, or any object that may be in the list.
210
- #
211
- # Returns true if the given item is in the list, otherwise false.
212
-
136
+ # @param item [Object] any object that may be in the list.
137
+ # @return [true] if the given item is in the list.
138
+ # @return [false] otherwise.
213
139
  def include?(item)
214
- item.in? self
215
- rescue NoMethodError
216
- false
217
- end
218
-
219
- # Iterates over each item in the list If a block is not given an enumerator
220
- # is returned.
221
-
222
- def each_item
223
- return to_enum(__method__) { count } unless block_given?
224
- return if empty?
225
-
226
- item = head
227
- loop { yield item = item.next }
228
- end
229
-
230
- alias each each_item
231
-
232
- # Iterates over each item in the list in reverse order. If a block is not
233
- # given an enumerator is returned.
234
-
235
- def reverse_each_item
236
- return to_enum(__method__) { count } unless block_given?
237
- return if empty?
238
-
239
- item = tail
240
- loop { yield item = item.prev }
140
+ return false if empty?
141
+ # TODO: This works fine, but looks wrong.
142
+ @_chain.in_chain? item
241
143
  end
242
144
 
243
- alias reverse_each reverse_each_item
244
-
245
145
  # Calls #freeze on all items in the list, as well as the head and the tail
246
146
  # (eol).
247
-
147
+ #
148
+ # @return [self]
248
149
  def freeze
249
- @_eol.freeze
250
150
  each_item(&:freeze)
251
151
  super
252
152
  end
@@ -257,14 +157,13 @@ module Linked
257
157
  # Importantly this implementation supports nested lists and will return a
258
158
  # tree like structure.
259
159
 
260
- def inspect(&block)
261
- # Get the parents inspect output
262
- res = [super]
160
+ def inspect_list(&block)
161
+ res = [block_given? ? yield(self) : object_identifier]
263
162
 
264
163
  each_item do |item|
265
164
  lines = item.inspect(&block).split "\n"
266
165
 
267
- res.push (item.last? ? '└─╴' : '├─╴') + lines.shift
166
+ res.push((item.last? ? '└─╴' : '├─╴') + lines.shift)
268
167
  padding = item.last? ? '   ' : '│  '
269
168
  lines.each { |line| res.push padding + line }
270
169
  end
@@ -272,125 +171,49 @@ module Linked
272
171
  res.join("\n")
273
172
  end
274
173
 
174
+ alias inspect inspect_list
175
+
275
176
  # Protected factory method for creating items compatible with the list. This
276
177
  # method is called whenever an arbitrary object is pushed or unshifted onto
277
178
  # the list and need to be wraped inside an Item.
278
179
  #
279
180
  # This method can be overridden to support different Item types.
280
181
  #
281
- # args - any arguments will be passed on to Item.new.
282
- #
283
- # Returns a new Item.
284
-
182
+ # @param args [Array<Object>] the arguments that are to be passed on to
183
+ # `Item.new`.
184
+ # @return [Item] a new `Listable` item.
285
185
  protected def create_item(*args)
286
186
  Item.new(*args)
287
187
  end
288
188
 
289
- # Returns an object that responds to #next= and #prepend.
290
-
291
- protected def head
292
- @_eol
293
- end
294
-
295
- # Returns an object that responds to #prev= and #append.
296
-
297
- alias tail head
298
-
299
- # Internal method to grow the list with n elements. Never call this method
300
- # without also inserting the n elements.
189
+ # Takes an arbitrary object and coerces it into an item compliant with the
190
+ # list. If the object is already an item it will be used as is. Otherwise
191
+ # #create_item will be called with the object as an argument.
301
192
  #
302
- # n - the number of items that has been/will be added to the list.
303
- #
304
- # Returns updated the item count.
305
-
306
- private def grow(n = 1)
307
- @_item_count += n
308
- end
309
-
310
- # Internal method to shrink the list with n elements. Never call this method
311
- # without also deleting the n elements.
312
- #
313
- # n - the number of items that has been/will be removed from the list.
314
- #
315
- # Returns updated the item count.
316
-
317
- private def shrink(n = 1)
318
- @_item_count -= n
193
+ # @param [#item, Object] the object to coerce.
194
+ # @return [Listable] see `#create_item`.
195
+ private def coerce_item(object)
196
+ if object.respond_to? :item
197
+ object.item
198
+ else
199
+ create_item object
200
+ end
319
201
  end
320
202
 
321
- # Private method to clear the list. Never call this method without also
322
- # modifying the items in the list, as this operation leavs them in an
323
- # inconsistant state. If the list items are kept, make sure to
324
- # a) clear the `prev` pointer of the first item and
325
- # b) clear the `next` pointer of the last item.
326
-
327
- private def clear
328
- @_eol.send :reset
329
- @_item_count = 0
203
+ # Private method for clearing the list and bringing it to a pristine
204
+ # state.
205
+ private def reset_list
206
+ @_chain = nil
330
207
  end
331
208
 
332
- # Private helper method that returns the first n items, starting just
333
- # after item, given that there are items_left items left. Knowing the exact
334
- # number of items left is not cruicial but does impact speed. The number
335
- # should not be lower than the actual ammount. The following must
336
- # hold for the output to be valid:
337
- # a) n > 0
338
- # b) there are at least items_left items left
339
- #
340
- # item - the Item just before the item to start from.
341
- # n - the number of items to return.
342
- # items_left - the number of items left.
343
- #
344
- # Returns, for different values of n:
345
- # n == 0) nil
346
- # n == 1) an item if items_left > 0 or nil
347
- # n > 1) an array of items if items_left > 0 or an empty array
348
-
349
- private def first_item_after(item, n, items_left = @_item_count)
350
- # Optimize for these cases
351
- return nil if n == 0
352
- return n > 1 ? [] : nil if item.last?
353
- return item.next if n == 1
354
-
355
- n = items_left if n > items_left
356
-
357
- arr = Array.new n
358
- n.times { |i| arr[i] = item = item.next }
359
- arr
360
- rescue StopIteration
361
- arr.compact! || arr
209
+ # Returns the first item item in the list, or nil if empty.
210
+ private def list_head
211
+ @_chain
362
212
  end
363
213
 
364
- # Private helper method that returns the last n items, ending just before
365
- # item, given that there are items_left items left. Knowing the exact
366
- # number of items left is not cruicial but does impact speed. The number
367
- # should not be lower than the actual ammount. The following must hold for
368
- # the output to be valid:
369
- # a) n > 0
370
- # b) there are at least items_left items left
371
- #
372
- # item - the Item just after the item to start from.
373
- # n - the number of items to return.
374
- # items_left - the number of items left.
375
- #
376
- # Returns, for different values of n:
377
- # n == 0) nil
378
- # n == 1) an item if items_left > 0 or nil
379
- # n > 1) an array of items if items_left > 0 or an empty array
380
-
381
- private def last_item_before(item, n, items_left = @_item_count)
382
- # Optimize for these cases
383
- return nil if n == 0
384
- return n > 1 ? [] : nil if item.first?
385
- return item.prev if n == 1
386
-
387
- n = items_left if n > items_left
388
-
389
- arr = Array.new n
390
- (n - 1).downto(0) { |i| arr[i] = item = item.prev }
391
- arr
392
- rescue StopIteration
393
- arr.compact! || arr
214
+ # Returns an the last item in the list, or nil if empty.
215
+ private def list_tail
216
+ @_chain.last_in_chain
394
217
  end
395
218
  end
396
219
  end