beaneater 0.3.0 → 1.1.1

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.
@@ -1,14 +1,18 @@
1
- module Beaneater
1
+ class Beaneater
2
2
  # Exception to stop processing jobs during a `process!` loop.
3
3
  # Simply `raise AbortProcessingError` in any job process handler to stop the processing loop.
4
4
  class AbortProcessingError < RuntimeError; end
5
5
 
6
6
  # Represents collection of job-related commands.
7
- class Jobs < PoolCommand
7
+ class Jobs
8
8
 
9
9
  # @!attribute processors
10
10
  # @return [Array<Proc>] returns Collection of proc to handle beanstalkd jobs
11
- attr_reader :processors
11
+ # @!attribute client
12
+ # @return [Beaneater] returns the client instance
13
+ # @!attribute current_job
14
+ # @return [Beaneater] returns the currently processing job in the process loop
15
+ attr_reader :processors, :client, :current_job
12
16
 
13
17
  # Number of retries to process a job.
14
18
  MAX_RETRIES = 3
@@ -16,39 +20,44 @@ module Beaneater
16
20
  # Delay in seconds before to make job ready again.
17
21
  RELEASE_DELAY = 1
18
22
 
19
- # Peek (or find) first job from beanstalkd pool.
23
+ # Number of seconds to wait for a job before checking a different server.
24
+ RESERVE_TIMEOUT = nil
25
+
26
+ # Creates new jobs instance.
20
27
  #
21
- # @param [Integer] id Job id to find
22
- # @return [Beaneater::Job] Job matching given id
28
+ # @param [Beaneater] client The beaneater client instance.
23
29
  # @example
24
- # @beaneater_pool.jobs[123] # => <Beaneater::Job>
25
- # @beaneater_pool.jobs.find(123) # => <Beaneater::Job>
26
- # @beaneater_pool.jobs.peek(123) # => <Beaneater::Job>
30
+ # Beaneater::Jobs.new(@client)
27
31
  #
28
- # @api public
29
- def find(id)
30
- res = transmit_until_res("peek #{id}", :status => "FOUND")
31
- Job.new(res)
32
- rescue Beaneater::NotFoundError => ex
33
- nil
32
+ def initialize(client)
33
+ @client = client
34
+ end
35
+
36
+ # Delegates transmit to the connection object.
37
+ #
38
+ # @see Beaneater::Connection#transmit
39
+ def transmit(command, options={})
40
+ client.connection.transmit(command, **options)
34
41
  end
35
- alias_method :peek, :find
36
- alias_method :[], :find
37
42
 
38
- # Find all jobs with specified id fromm all beanstalkd servers in pool.
43
+ # Peek (or find) job by id from beanstalkd.
39
44
  #
40
45
  # @param [Integer] id Job id to find
41
- # @return [Array<Beaneater::Job>] Jobs matching given id
46
+ # @return [Beaneater::Job] Job matching given id
42
47
  # @example
43
- # @beaneater_pool.jobs.find_all(123) # => [<Beaneater::Job>, <Beaneater::Job>]
48
+ # @beaneater.jobs[123] # => <Beaneater::Job>
49
+ # @beaneater.jobs.find(123) # => <Beaneater::Job>
50
+ # @beaneater.jobs.peek(123) # => <Beaneater::Job>
44
51
  #
45
52
  # @api public
46
- def find_all(id)
47
- res = transmit_to_all("peek #{id}")
48
- res.compact.map { |r| Job.new(r) }
49
- rescue Beaneater::NotFoundError => ex
50
- []
53
+ def find(id)
54
+ res = transmit("peek #{id}")
55
+ Job.new(client, res)
56
+ rescue Beaneater::NotFoundError
57
+ nil
51
58
  end
59
+ alias_method :peek, :find
60
+ alias_method :[], :find
52
61
 
53
62
  # Register a processor to handle beanstalkd job on particular tube.
54
63
  #
@@ -75,31 +84,52 @@ module Beaneater
75
84
  @processors[tube_name.to_s] = { :block => block, :retry_on => retry_on, :max_retries => max_retries }
76
85
  end
77
86
 
87
+ # Sets flag to indicate that process loop should stop after current job
88
+ def stop!
89
+ @stop = true
90
+ end
91
+
92
+ # Returns whether the process loop should stop
93
+ #
94
+ # @return [Boolean] if true the loop should stop after current processing
95
+ def stop?
96
+ !!@stop
97
+ end
98
+
78
99
  # Watch, reserve, process and delete or bury or release jobs.
79
100
  #
80
101
  # @param [Hash{String => Integer}] options Settings for processing
81
102
  # @option options [Integer] release_delay Delay in seconds before to make job ready again
103
+ # @option options [Integer] reserve_timeout Number of seconds to wait for a job before checking a different server
82
104
  #
83
105
  # @api public
84
106
  def process!(options={})
85
107
  release_delay = options.delete(:release_delay) || RELEASE_DELAY
86
- tubes.watch!(*processors.keys)
87
- loop do
88
- job = tubes.reserve
89
- processor = processors[job.tube]
108
+ reserve_timeout = options.delete(:reserve_timeout) || RESERVE_TIMEOUT
109
+ client.tubes.watch!(*processors.keys)
110
+ while !stop? do
90
111
  begin
91
- processor[:block].call(job)
92
- job.delete
112
+ @current_job = client.tubes.reserve(reserve_timeout)
113
+ processor = processors[@current_job.tube]
114
+ begin
115
+ processor[:block].call(@current_job)
116
+ @current_job.delete
117
+ rescue *processor[:retry_on]
118
+ if @current_job.stats.releases < processor[:max_retries]
119
+ @current_job.release(:delay => release_delay)
120
+ end
121
+ end
93
122
  rescue AbortProcessingError
94
123
  break
95
- rescue *processor[:retry_on]
96
- job.release(:delay => release_delay) if job.stats.releases < processor[:max_retries]
97
- rescue StandardError => e # handles unspecified errors
98
- job.bury
124
+ rescue Beaneater::JobNotReserved, Beaneater::NotFoundError, Beaneater::TimedOutError
125
+ retry
126
+ rescue StandardError # handles unspecified errors
127
+ @current_job.bury if @current_job
99
128
  ensure # bury if still reserved
100
- job.bury if job.exists? && job.reserved?
129
+ @current_job.bury if @current_job && @current_job.exists? && @current_job.reserved?
130
+ @current_job = nil
101
131
  end
102
132
  end
103
133
  end # process!
104
134
  end # Jobs
105
- end # Beaneater
135
+ end # Beaneater
@@ -1,4 +1,4 @@
1
- module Beaneater
1
+ class Beaneater
2
2
  # Represents job related commands.
3
3
  class Job
4
4
 
@@ -6,21 +6,21 @@ module Beaneater
6
6
  # @return [Integer] id for the job.
7
7
  # @!attribute body
8
8
  # @return [String] the job's body.
9
- # @!attribute connection
10
- # @return [Beaneater::Connection] connection which has retrieved job.
11
9
  # @!attribute reserved
12
10
  # @return [Boolean] whether the job has been reserved.
13
- attr_reader :id, :body, :connection, :reserved
11
+ # @!attribute client
12
+ # @return [Beaneater] returns the client instance
13
+ attr_reader :id, :body, :reserved, :client
14
14
 
15
15
 
16
16
  # Initializes a new job object.
17
17
  #
18
18
  # @param [Hash{Symbol => String,Number}] res Result from beanstalkd response
19
19
  #
20
- def initialize(res)
20
+ def initialize(client, res)
21
+ @client = client
21
22
  @id = res[:id]
22
23
  @body = res[:body]
23
- @connection = res[:connection]
24
24
  @reserved = res[:status] == 'RESERVED'
25
25
  end
26
26
 
@@ -28,9 +28,11 @@ module Beaneater
28
28
  #
29
29
  # @param [Hash{Symbol => Integer}] options Settings to bury job
30
30
  # @option options [Integer] pri Assign new priority to job
31
+ # @return [Hash{Symbol => String,Number}] Beanstalkd response for the command.
31
32
  #
32
33
  # @example
33
- # @beaneater_connection.bury({:pri => 100})
34
+ # @job.bury({:pri => 100})
35
+ # # => {:status=>"BURIED", :body=>nil}
34
36
  #
35
37
  # @api public
36
38
  def bury(options={})
@@ -45,8 +47,10 @@ module Beaneater
45
47
  # @param [Hash{String => Integer}] options Settings to release job
46
48
  # @option options [Integer] pri Assign new priority to job
47
49
  # @option options [Integer] delay Assign new delay to job
50
+ # @return [Hash{Symbol => String,Number}] Beanstalkd response for the command.
48
51
  # @example
49
- # @beaneater_connection.jobs.find(123).release(:pri => 10, :delay => 5)
52
+ # @beaneater.jobs.find(123).release(:pri => 10, :delay => 5)
53
+ # # => {:status=>"RELEASED", :body=>nil}
50
54
  #
51
55
  # @api public
52
56
  def release(options={})
@@ -58,8 +62,10 @@ module Beaneater
58
62
 
59
63
  # Sends command to touch job which extends the ttr.
60
64
  #
65
+ # @return [Hash{Symbol => String,Number}] Beanstalkd response for the command.
61
66
  # @example
62
- # @beaneater_connection.jobs.find(123).touch
67
+ # @beaneater.jobs.find(123).touch
68
+ # # => {:status=>"TOUCHED", :body=>nil}
63
69
  #
64
70
  # @api public
65
71
  def touch
@@ -68,8 +74,10 @@ module Beaneater
68
74
 
69
75
  # Sends command to delete a job.
70
76
  #
77
+ # @return [Hash{Symbol => String,Number}] Beanstalkd response for the command.
71
78
  # @example
72
- # @beaneater_connection.jobs.find(123).delete
79
+ # @beaneater.jobs.find(123).delete
80
+ # # => {:status=>"DELETED", :body=>nil}
73
81
  #
74
82
  # @api public
75
83
  def delete
@@ -78,8 +86,10 @@ module Beaneater
78
86
 
79
87
  # Sends command to kick a buried job.
80
88
  #
89
+ # @return [Hash{Symbol => String,Number}] Beanstalkd response for the command.
81
90
  # @example
82
- # @beaneater_connection.jobs.find(123).kick
91
+ # @beaneater.jobs.find(123).kick
92
+ # # => {:status=>"KICKED", :body=>nil}
83
93
  #
84
94
  # @api public
85
95
  def kick
@@ -90,7 +100,7 @@ module Beaneater
90
100
  #
91
101
  # @return [Beaneater::StatStruct] struct filled with relevant job stats
92
102
  # @example
93
- # @beaneater_connection.jobs.find(123).stats
103
+ # @beaneater.jobs.find(123).stats
94
104
  # @job.stats.tube # => "some-tube"
95
105
  #
96
106
  # @api public
@@ -103,7 +113,7 @@ module Beaneater
103
113
  #
104
114
  # @return [Boolean] Returns true if the job is in a reserved state
105
115
  # @example
106
- # @beaneater_connection.jobs.find(123).reserved?
116
+ # @beaneater.jobs.find(123).reserved?
107
117
  #
108
118
  # @api public
109
119
  def reserved?
@@ -114,7 +124,7 @@ module Beaneater
114
124
  #
115
125
  # @return [Boolean] Returns true if the job still exists
116
126
  # @example
117
- # @beaneater_connection.jobs.find(123).exists?
127
+ # @beaneater.jobs.find(123).exists?
118
128
  #
119
129
  # @api public
120
130
  def exists?
@@ -127,52 +137,54 @@ module Beaneater
127
137
  #
128
138
  # @return [String] The name of the tube for this job
129
139
  # @example
130
- # @beaneater_connection.jobs.find(123).tube
140
+ # @beaneater.jobs.find(123).tube
131
141
  # # => "some-tube"
132
142
  #
143
+ # @api public
133
144
  def tube
134
- self.stats && self.stats.tube
145
+ @tube ||= self.stats.tube
135
146
  end
136
147
 
137
148
  # Returns the ttr of this job
138
149
  #
139
- # @return [String] The ttr of this job
150
+ # @return [Integer] The ttr of this job
140
151
  # @example
141
- # @beaneater_connection.jobs.find(123).ttr
152
+ # @beaneater.jobs.find(123).ttr
142
153
  # # => 123
143
154
  #
155
+ # @api public
144
156
  def ttr
145
- self.stats && self.stats.ttr
157
+ @ttr ||= self.stats.ttr
146
158
  end
147
159
 
148
160
  # Returns the pri of this job
149
161
  #
150
- # @return [String] The pri of this job
162
+ # @return [Integer] The pri of this job
151
163
  # @example
152
- # @beaneater_connection.jobs.find(123).pri
164
+ # @beaneater.jobs.find(123).pri
153
165
  # # => 1
154
166
  #
155
167
  def pri
156
- self.stats && self.stats.pri
168
+ self.stats.pri
157
169
  end
158
170
 
159
171
  # Returns the delay of this job
160
172
  #
161
- # @return [String] The delay of this job
173
+ # @return [Integer] The delay of this job
162
174
  # @example
163
- # @beaneater_connection.jobs.find(123).delay
175
+ # @beaneater.jobs.find(123).delay
164
176
  # # => 5
165
177
  #
166
178
  def delay
167
- self.stats && self.stats.delay
179
+ self.stats.delay
168
180
  end
169
181
 
170
182
  # Returns string representation of job
171
183
  #
172
184
  # @return [String] string representation
173
185
  # @example
174
- # @beaneater_connection.jobs.find(123).to_s
175
- # @beaneater_connection.jobs.find(123).inspect
186
+ # @beaneater.jobs.find(123).to_s
187
+ # @beaneater.jobs.find(123).inspect
176
188
  #
177
189
  def to_s
178
190
  "#<Beaneater::Job id=#{id} body=#{body.inspect}>"
@@ -181,7 +193,7 @@ module Beaneater
181
193
 
182
194
  protected
183
195
 
184
- # Transmit command to beanstalkd instances and fetch response.
196
+ # Transmit command to beanstalkd instance and fetch response.
185
197
  #
186
198
  # @param [String] cmd Beanstalkd command to send.
187
199
  # @return [Hash{Symbol => String,Number}] Beanstalkd response for the command.
@@ -190,7 +202,7 @@ module Beaneater
190
202
  # transmit('stats') { 'success' }
191
203
  #
192
204
  def transmit(cmd, &block)
193
- res = connection.transmit(cmd)
205
+ res = client.connection.transmit(cmd)
194
206
  yield if block_given?
195
207
  res
196
208
  end
@@ -209,4 +221,4 @@ module Beaneater
209
221
  end
210
222
 
211
223
  end # Job
212
- end # Beaneater
224
+ end # Beaneater
@@ -1,4 +1,4 @@
1
- module Beaneater
1
+ class Beaneater
2
2
  #
3
3
  # Borrowed from:
4
4
  # https://github.com/dolzenko/faster_open_struct/blob/master/lib/faster_open_struct.rb
@@ -1,4 +1,4 @@
1
- module Beaneater
1
+ class Beaneater
2
2
  # Represents a stats hash with proper underscored keys
3
3
  class StatStruct < FasterOpenStruct
4
4
  # Convert a stats hash into a struct.
@@ -35,5 +35,11 @@ module Beaneater
35
35
  def keys
36
36
  @hash.keys.map { |k| k.to_s }
37
37
  end
38
+
39
+ # Returns the initialization hash
40
+ #
41
+ def to_h
42
+ @hash
43
+ end
38
44
  end # StatStruct
39
- end # Beaneater
45
+ end # Beaneater
@@ -1,9 +1,24 @@
1
1
  require 'beaneater/stats/fast_struct'
2
2
  require 'beaneater/stats/stat_struct'
3
3
 
4
- module Beaneater
4
+ class Beaneater
5
5
  # Represents stats related to the beanstalkd pool.
6
- class Stats < PoolCommand
6
+ class Stats
7
+
8
+ # @!attribute client
9
+ # @return [Beaneater] returns the client instance
10
+ attr_reader :client
11
+
12
+ # Creates new stats instance.
13
+ #
14
+ # @param [Beaneater] client The beaneater client instance.
15
+ # @example
16
+ # Beaneater::Stats.new(@client)
17
+ #
18
+ def initialize(client)
19
+ @client = client
20
+ end
21
+
7
22
  # Returns keys for stats data
8
23
  #
9
24
  # @return [Array<String>] Set of keys for stats.
@@ -26,6 +41,14 @@ module Beaneater
26
41
  data[key]
27
42
  end
28
43
 
44
+ # Delegates inspection to the real data structure
45
+ #
46
+ # @return [String] returns a string containing a detailed stats summary
47
+ def inspect
48
+ data.to_s
49
+ end
50
+ alias :to_s :inspect
51
+
29
52
  # Defines a cached method for looking up data for specified key
30
53
  # Protects against infinite loops by checking stacktrace
31
54
  # @api public
@@ -42,15 +65,15 @@ module Beaneater
42
65
 
43
66
  protected
44
67
 
45
- # Returns struct based on stats data merged from all connections.
68
+ # Returns struct based on stats data from response.
46
69
  #
47
- # @return [Beaneater::StatStruct] the combined stats for all beanstalk connections in the pool
70
+ # @return [Beaneater::StatStruct] the stats
48
71
  # @example
49
72
  # self.data # => { 'version' : 1.7, 'total_connections' : 23 }
50
73
  # self.data.total_connections # => 23
51
74
  #
52
75
  def data
53
- StatStruct.from_hash(transmit_to_all('stats', :merge => true)[:body])
76
+ StatStruct.from_hash(client.connection.transmit('stats')[:body])
54
77
  end
55
78
  end # Stats
56
79
  end # Beaneater
@@ -1,16 +1,36 @@
1
- module Beaneater
1
+ class Beaneater
2
2
  # Represents collection of tube related commands.
3
- class Tubes < PoolCommand
3
+
4
+ class Tubes
5
+ include Enumerable
6
+
7
+ # @!attribute client
8
+ # @return [Beaneater] returns the client instance
9
+ attr_reader :client
4
10
 
5
11
  # Creates new tubes instance.
6
12
  #
7
- # @param [Beaneater::Pool] pool The beaneater pool for this tube.
13
+ # @param [Beaneater] client The beaneater client instance.
8
14
  # @example
9
- # Beaneater::Tubes.new(@pool)
15
+ # Beaneater::Tubes.new(@client)
16
+ #
17
+ def initialize(client)
18
+ @client = client
19
+ end
20
+
21
+ def last_used
22
+ client.connection.tube_used
23
+ end
24
+
25
+ def last_used=(tube_name)
26
+ client.connection.tube_used = tube_name
27
+ end
28
+
29
+ # Delegates transmit to the connection object.
10
30
  #
11
- def initialize(pool)
12
- @last_used = 'default'
13
- super
31
+ # @see Beaneater::Connection#transmit
32
+ def transmit(command, **options)
33
+ client.connection.transmit(command, **options)
14
34
  end
15
35
 
16
36
  # Finds the specified beanstalk tube.
@@ -24,7 +44,7 @@ module Beaneater
24
44
  #
25
45
  # @api public
26
46
  def find(tube_name)
27
- Tube.new(self.pool, tube_name)
47
+ Tube.new(client, tube_name)
28
48
  end
29
49
  alias_method :[], :find
30
50
 
@@ -35,13 +55,14 @@ module Beaneater
35
55
  # @yield [job] Reserved beaneater job.
36
56
  # @return [Beaneater::Job] Reserved beaneater job.
37
57
  # @example
38
- # @conn.tubes.reserve { |job| process(job) }
58
+ # @client.tubes.reserve { |job| process(job) }
39
59
  # # => <Beaneater::Job id=5 body="foo">
40
60
  #
41
61
  # @api public
42
62
  def reserve(timeout=nil, &block)
43
- res = transmit_to_rand(timeout ? "reserve-with-timeout #{timeout}" : 'reserve')
44
- job = Job.new(res)
63
+ res = transmit(
64
+ timeout ? "reserve-with-timeout #{timeout}" : 'reserve')
65
+ job = Job.new(client, res)
45
66
  block.call(job) if block_given?
46
67
  job
47
68
  end
@@ -50,36 +71,54 @@ module Beaneater
50
71
  #
51
72
  # @return [Array<Beaneater::Tube>] List of all beanstalk tubes.
52
73
  # @example
53
- # @pool.tubes.all
74
+ # @client.tubes.all
54
75
  # # => [<Beaneater::Tube name="tube2">, <Beaneater::Tube name="tube3">]
55
76
  #
56
77
  # @api public
57
78
  def all
58
- transmit_to_all('list-tubes', :merge => true)[:body].map { |tube_name| Tube.new(self.pool, tube_name) }
79
+ transmit('list-tubes')[:body].map do |tube_name|
80
+ Tube.new(client, tube_name)
81
+ end
82
+ end
83
+
84
+ # Calls the given block once for each known beanstalk tube, passing that element as a parameter.
85
+ #
86
+ # @return An Enumerator is returned if no block is given.
87
+ # @example
88
+ # @pool.tubes.each {|t| puts t.name}
89
+ #
90
+ # @api public
91
+ def each(&block)
92
+ all.each(&block)
59
93
  end
60
94
 
61
95
  # List of watched beanstalk tubes.
62
96
  #
63
97
  # @return [Array<Beaneater::Tube>] List of watched beanstalk tubes.
64
98
  # @example
65
- # @pool.tubes.watched
99
+ # @client.tubes.watched
66
100
  # # => [<Beaneater::Tube name="tube2">, <Beaneater::Tube name="tube3">]
67
101
  #
68
102
  # @api public
69
103
  def watched
70
- transmit_to_all('list-tubes-watched', :merge => true)[:body].map { |tube_name| Tube.new(self.pool, tube_name) }
104
+ last_watched = transmit('list-tubes-watched')[:body]
105
+ client.connection.tubes_watched = last_watched.dup
106
+ last_watched.map do |tube_name|
107
+ Tube.new(client, tube_name)
108
+ end
71
109
  end
72
110
 
73
111
  # Currently used beanstalk tube.
74
112
  #
75
113
  # @return [Beaneater::Tube] Currently used beanstalk tube.
76
114
  # @example
77
- # @pool.tubes.used
115
+ # @client.tubes.used
78
116
  # # => <Beaneater::Tube name="tube2">
79
117
  #
80
118
  # @api public
81
119
  def used
82
- Tube.new(self.pool, transmit_to_rand('list-tube-used')[:id])
120
+ last_used = transmit('list-tube-used')[:id]
121
+ Tube.new(client, last_used)
83
122
  end
84
123
 
85
124
  # Add specified beanstalkd tubes as watched.
@@ -87,12 +126,13 @@ module Beaneater
87
126
  # @param [*String] names Name of tubes to watch
88
127
  # @raise [Beaneater::InvalidTubeName] Tube to watch was invalid.
89
128
  # @example
90
- # @pool.tubes.watch('foo', 'bar')
129
+ # @client.tubes.watch('foo', 'bar')
91
130
  #
92
131
  # @api public
93
132
  def watch(*names)
94
133
  names.each do |t|
95
- transmit_to_all "watch #{t}"
134
+ transmit "watch #{t}"
135
+ client.connection.add_to_watched(t)
96
136
  end
97
137
  rescue BadFormatError => ex
98
138
  raise InvalidTubeName, "Tube in '#{ex.cmd}' is invalid!"
@@ -103,7 +143,7 @@ module Beaneater
103
143
  # @param [*String] names Name of tubes to watch
104
144
  # @raise [Beaneater::InvalidTubeName] Tube to watch was invalid.
105
145
  # @example
106
- # @pool.tubes.watch!('foo', 'bar')
146
+ # @client.tubes.watch!('foo', 'bar')
107
147
  #
108
148
  # @api public
109
149
  def watch!(*names)
@@ -116,12 +156,13 @@ module Beaneater
116
156
  #
117
157
  # @param [*String] names Name of tubes to ignore
118
158
  # @example
119
- # @pool.tubes.ignore('foo', 'bar')
159
+ # @client.tubes.ignore('foo', 'bar')
120
160
  #
121
161
  # @api public
122
162
  def ignore(*names)
123
163
  names.each do |w|
124
- transmit_to_all "ignore #{w}"
164
+ transmit "ignore #{w}"
165
+ client.connection.remove_from_watched(w)
125
166
  end
126
167
  end
127
168
 
@@ -132,11 +173,11 @@ module Beaneater
132
173
  # @conn.tubes.use("some-tube")
133
174
  #
134
175
  def use(tube)
135
- return tube if @last_used == tube
136
- res = transmit_to_all("use #{tube}")
137
- @last_used = tube
176
+ return tube if last_used == tube
177
+ transmit("use #{tube}")
178
+ self.last_used = tube
138
179
  rescue BadFormatError
139
180
  raise InvalidTubeName, "Tube cannot be named '#{tube}'"
140
181
  end
141
182
  end # Tubes
142
- end # Beaneater
183
+ end # Beaneater