gemerald_beanstalk 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/.travis.yml +27 -0
- data/Gemfile +8 -0
- data/LICENSE +22 -0
- data/README.md +67 -0
- data/Rakefile +19 -0
- data/gemerald_beanstalk.gemspec +25 -0
- data/lib/gemerald_beanstalk.rb +10 -0
- data/lib/gemerald_beanstalk/beanstalk.rb +289 -0
- data/lib/gemerald_beanstalk/beanstalk_helper.rb +300 -0
- data/lib/gemerald_beanstalk/command.rb +365 -0
- data/lib/gemerald_beanstalk/connection.rb +170 -0
- data/lib/gemerald_beanstalk/job.rb +229 -0
- data/lib/gemerald_beanstalk/jobs.rb +39 -0
- data/lib/gemerald_beanstalk/server.rb +54 -0
- data/lib/gemerald_beanstalk/tube.rb +164 -0
- data/lib/gemerald_beanstalk/version.rb +3 -0
- data/test/beanstalk_integration_tests_test.rb +2 -0
- data/test/test_helper.rb +8 -0
- metadata +133 -0
@@ -0,0 +1,300 @@
|
|
1
|
+
module GemeraldBeanstalk::BeanstalkHelper
|
2
|
+
|
3
|
+
BAD_FORMAT = "BAD_FORMAT\r\n"
|
4
|
+
BURIED = "BURIED\r\n"
|
5
|
+
CRLF = "\r\n"
|
6
|
+
DEADLINE_SOON = "DEADLINE_SOON\r\n"
|
7
|
+
DELETED = "DELETED\r\n"
|
8
|
+
EXPECTED_CRLF = "EXPECTED_CRLF\r\n"
|
9
|
+
JOB_TOO_BIG = "JOB_TOO_BIG\r\n"
|
10
|
+
KICKED = "KICKED\r\n"
|
11
|
+
NOT_FOUND = "NOT_FOUND\r\n"
|
12
|
+
NOT_IGNORED = "NOT_IGNORED\r\n"
|
13
|
+
PAUSED = "PAUSED\r\n"
|
14
|
+
RELEASED = "RELEASED\r\n"
|
15
|
+
TIMED_OUT = "TIMED_OUT\r\n"
|
16
|
+
TOUCHED = "TOUCHED\r\n"
|
17
|
+
UNKNOWN_COMMAND = "UNKNOWN_COMMAND\r\n"
|
18
|
+
|
19
|
+
JOB_INACTIVE_STATES = GemeraldBeanstalk::Job::INACTIVE_STATES
|
20
|
+
JOB_RESERVED_STATES = GemeraldBeanstalk::Job::RESERVED_STATES
|
21
|
+
|
22
|
+
# ease handling of odd case where put can return BAD_FORMAT but increment stats
|
23
|
+
def adjust_stats_cmd_put
|
24
|
+
adjust_stats_key(:'cmd-put')
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
def connect(connection = nil)
|
29
|
+
beanstalk_connection = GemeraldBeanstalk::Connection.new(self, connection)
|
30
|
+
@connections << beanstalk_connection
|
31
|
+
adjust_stats_key(:'total-connections')
|
32
|
+
return beanstalk_connection
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
def disconnect(connection)
|
37
|
+
connection.close_connection
|
38
|
+
tube(connection.tube_used).stop_use
|
39
|
+
connection.tubes_watched.each do |watched_tube|
|
40
|
+
tube(watched_tube).ignore
|
41
|
+
connection.ignore(watched_tube, :force)
|
42
|
+
end
|
43
|
+
@reserved[connection].each do |job|
|
44
|
+
job.release(connection, job.priority, 0, false)
|
45
|
+
end
|
46
|
+
@reserved.delete(connection)
|
47
|
+
@connections.delete(connection)
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
def execute(command)
|
52
|
+
return send(command.method_name, *command.arguments)
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
def register_job_timeout(connection, job)
|
57
|
+
@reserved[connection].delete(job)
|
58
|
+
adjust_stats_key(:'job-timeouts')
|
59
|
+
honor_reservations(job)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def active_tubes
|
65
|
+
tubes = {}
|
66
|
+
@tubes.each_pair { |tube_name, tube| tubes[tube_name] = tube if tube.active? }
|
67
|
+
return tubes
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
def adjust_stats_key(key, adjustment = 1)
|
72
|
+
@stats[key] += adjustment
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
def cancel_reservations(connection)
|
77
|
+
connection.tubes_watched.each do |tube_name|
|
78
|
+
tube(tube_name).cancel_reservation(connection)
|
79
|
+
end
|
80
|
+
return connection
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
def deadline_pending?(connection)
|
85
|
+
return @reserved[connection].any?(&:deadline_pending?)
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
def find_job(job_id, options = {})
|
90
|
+
return unless (job_id = job_id.to_i) > 0
|
91
|
+
only = Array(options[:only])
|
92
|
+
except = Array(options[:except]).unshift(:deleted)
|
93
|
+
|
94
|
+
job = @jobs[job_id - 1]
|
95
|
+
|
96
|
+
return nil if job.nil? || except.include?(job.state)
|
97
|
+
return (only.empty? || only.include?(job.state)) ? job : nil
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
def honor_reservations(job_or_tube)
|
102
|
+
if job_or_tube.is_a?(GemeraldBeanstalk::Job)
|
103
|
+
job = job_or_tube
|
104
|
+
tube = tube(job.tube_name)
|
105
|
+
elsif job_or_tube.is_a?(GemeraldBeanstalk::Tube)
|
106
|
+
tube = job_or_tube
|
107
|
+
job = tube.next_job
|
108
|
+
end
|
109
|
+
|
110
|
+
while job && (next_reservation = tube.next_reservation)
|
111
|
+
next unless try_dispatch(next_reservation, job)
|
112
|
+
job = tube.next_job
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
def next_job(connection, state = :ready)
|
118
|
+
best_candidate = nil
|
119
|
+
connection.tubes_watched.each do |tube_name|
|
120
|
+
candidate = tube(tube_name).next_job(state)
|
121
|
+
next if candidate.nil?
|
122
|
+
|
123
|
+
best_candidate = candidate if best_candidate.nil? || candidate < best_candidate
|
124
|
+
end
|
125
|
+
|
126
|
+
return best_candidate
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
def peek_by_state(connection, state)
|
131
|
+
adjust_stats_key(:"cmd-peek-#{state}")
|
132
|
+
return peek_message(tube(connection.tube_used).next_job(state, :peek))
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
def peek_message(job)
|
137
|
+
job.nil? ? NOT_FOUND : "FOUND #{job.id} #{job.bytes}\r\n#{job.body}\r\n"
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
def reserve_job(connection, timeout = 0)
|
142
|
+
connection.worker = true
|
143
|
+
|
144
|
+
if deadline_pending?(connection)
|
145
|
+
connection.transmit(DEADLINE_SOON)
|
146
|
+
return true
|
147
|
+
end
|
148
|
+
|
149
|
+
connection.tubes_watched.each do |tube_name|
|
150
|
+
tube(tube_name).reserve(connection)
|
151
|
+
end
|
152
|
+
connection.wait(timeout <= 0 ? nil : Time.now.to_f + timeout)
|
153
|
+
|
154
|
+
dispatched = false
|
155
|
+
while !dispatched
|
156
|
+
break if (job = next_job(connection)).nil?
|
157
|
+
dispatched = try_dispatch(connection, job)
|
158
|
+
end
|
159
|
+
|
160
|
+
return dispatched
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
def stats_commands
|
165
|
+
return {
|
166
|
+
'cmd-put' => @stats[:'cmd-put'],
|
167
|
+
'cmd-peek' => @stats[:'cmd-peek'],
|
168
|
+
'cmd-peek-ready' => @stats[:'cmd-peek-ready'],
|
169
|
+
'cmd-peek-delayed' => @stats[:'cmd-peek-delayed'],
|
170
|
+
'cmd-peek-buried' => @stats[:'cmd-peek-buried'],
|
171
|
+
'cmd-reserve' => @stats[:'cmd-reserve'],
|
172
|
+
'cmd-reserve-with-timeout' => @stats[:'cmd-reserve-with-timeout'],
|
173
|
+
'cmd-delete' => @stats[:'cmd-delete'],
|
174
|
+
'cmd-release' => @stats[:'cmd-release'],
|
175
|
+
'cmd-use' => @stats[:'cmd-use'],
|
176
|
+
'cmd-watch' => @stats[:'cmd-watch'],
|
177
|
+
'cmd-ignore' => @stats[:'cmd-ignore'],
|
178
|
+
'cmd-bury' => @stats[:'cmd-bury'],
|
179
|
+
'cmd-kick' => @stats[:'cmd-kick'],
|
180
|
+
'cmd-touch' => @stats[:'cmd-touch'],
|
181
|
+
'cmd-stats' => @stats[:'cmd-stats'],
|
182
|
+
'cmd-stats-job' => @stats[:'cmd-stats-job'],
|
183
|
+
'cmd-stats-tube' => @stats[:'cmd-stats-tube'],
|
184
|
+
'cmd-list-tubes' => @stats[:'cmd-list-tubes'],
|
185
|
+
'cmd-list-tube-used' => @stats[:'cmd-list-tube-used'],
|
186
|
+
'cmd-list-tubes-watched' => @stats[:'cmd-list-tubes-watched'],
|
187
|
+
'cmd-pause-tube' => @stats[:'cmd-pause-tube'],
|
188
|
+
}
|
189
|
+
end
|
190
|
+
|
191
|
+
|
192
|
+
def stats_connections
|
193
|
+
conn_stats = {
|
194
|
+
'current-connections' => @connections.length,
|
195
|
+
'current-producers' => 0,
|
196
|
+
'current-workers' => 0,
|
197
|
+
'current-waiting' => 0,
|
198
|
+
'total-connections' => @stats[:'total-connections']
|
199
|
+
}
|
200
|
+
@connections.each do |connection|
|
201
|
+
conn_stats['current-producers'] += 1 if connection.producer?
|
202
|
+
conn_stats['current-waiting'] += 1 if connection.waiting?
|
203
|
+
conn_stats['current-workers'] += 1 if connection.worker?
|
204
|
+
end
|
205
|
+
return conn_stats
|
206
|
+
end
|
207
|
+
|
208
|
+
|
209
|
+
def try_dispatch(connection, job)
|
210
|
+
connection.mutex.synchronize do
|
211
|
+
# Make sure connection still waiting and job not claimed
|
212
|
+
return false unless connection.waiting? && job.reserve(connection)
|
213
|
+
connection.transmit("RESERVED #{job.id} #{job.bytes}\r\n#{job.body}\r\n")
|
214
|
+
cancel_reservations(connection)
|
215
|
+
end
|
216
|
+
@reserved[connection] << job
|
217
|
+
return true
|
218
|
+
end
|
219
|
+
|
220
|
+
|
221
|
+
def tube(tube_name, create_if_missing = false)
|
222
|
+
tube = @tubes[tube_name]
|
223
|
+
|
224
|
+
return tube unless tube.nil? || tube.deactivated?
|
225
|
+
|
226
|
+
return @tubes[tube_name] = GemeraldBeanstalk::Tube.new(tube_name) if create_if_missing
|
227
|
+
|
228
|
+
@tubes.delete(tube_name) unless tube.nil?
|
229
|
+
return nil
|
230
|
+
end
|
231
|
+
|
232
|
+
|
233
|
+
def tube_list(tube_list)
|
234
|
+
return yaml_response(tube_list.map { |key| "- #{key}" })
|
235
|
+
end
|
236
|
+
|
237
|
+
|
238
|
+
def update_state
|
239
|
+
update_waiting
|
240
|
+
update_timeouts
|
241
|
+
end
|
242
|
+
|
243
|
+
|
244
|
+
def update_timeouts
|
245
|
+
@reserved.values.flatten.each(&:state)
|
246
|
+
@delayed.keep_if do |job|
|
247
|
+
case job.state
|
248
|
+
when :delayed
|
249
|
+
true
|
250
|
+
when :ready
|
251
|
+
honor_reservations(job)
|
252
|
+
false
|
253
|
+
else
|
254
|
+
false
|
255
|
+
end
|
256
|
+
end
|
257
|
+
@paused.keep_if do |tube|
|
258
|
+
if tube.paused?
|
259
|
+
true
|
260
|
+
else
|
261
|
+
honor_reservations(tube)
|
262
|
+
false
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
|
268
|
+
def update_waiting
|
269
|
+
waiting_connections.each do |connection|
|
270
|
+
if connection.waiting? && deadline_pending?(connection)
|
271
|
+
message_for_connection = DEADLINE_SOON
|
272
|
+
elsif connection.timed_out?
|
273
|
+
message_for_connection = TIMED_OUT
|
274
|
+
else
|
275
|
+
next
|
276
|
+
end
|
277
|
+
|
278
|
+
cancel_reservations(connection)
|
279
|
+
connection.transmit(message_for_connection)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
|
284
|
+
def uptime
|
285
|
+
(Time.now.to_f - @up_at).to_i
|
286
|
+
end
|
287
|
+
|
288
|
+
|
289
|
+
def waiting_connections
|
290
|
+
return @connections.select {|connection| connection.waiting? || connection.timed_out? }
|
291
|
+
end
|
292
|
+
|
293
|
+
|
294
|
+
def yaml_response(data)
|
295
|
+
response = %w[---].concat(data).join("\n")
|
296
|
+
return "OK #{response.bytesize}\r\n#{response}\r\n"
|
297
|
+
end
|
298
|
+
|
299
|
+
|
300
|
+
end
|
@@ -0,0 +1,365 @@
|
|
1
|
+
class GemeraldBeanstalk::Command
|
2
|
+
|
3
|
+
COMMAND_PARSER_REGEX = /(?:(?<command>.*?)\r\n)(?<body>.*\r\n)?\z/m
|
4
|
+
TRAILING_SPACE_REGEX = /\s+\z/
|
5
|
+
WHITE_SPACE_REGEX = / /
|
6
|
+
ZERO_STRING_REGEX = /^0+[^1-9]*/
|
7
|
+
|
8
|
+
VALID_TUBE_NAME_REGEX = /\A[a-zA-Z0-9_+\/;.$()]{1}[a-zA-Z0-9_\-+\/;.$()]*\z/
|
9
|
+
|
10
|
+
BAD_FORMAT = GemeraldBeanstalk::Beanstalk::BAD_FORMAT
|
11
|
+
UNKNOWN_COMMAND = GemeraldBeanstalk::Beanstalk::UNKNOWN_COMMAND
|
12
|
+
|
13
|
+
COMMANDS = [
|
14
|
+
:bury, :delete, :ignore, :kick, :'kick-job', :'list-tubes', :'list-tube-used', :'list-tubes-watched',
|
15
|
+
:'pause-tube', :peek, :'peek-buried', :'peek-delayed', :'peek-ready', :put, :quit, :release, :reserve,
|
16
|
+
:'reserve-with-timeout', :stats, :'stats-job', :'stats-tube', :touch, :use, :watch,
|
17
|
+
]
|
18
|
+
|
19
|
+
COMMAND_METHOD_NAMES = Hash[COMMANDS.zip(COMMANDS)].merge!({
|
20
|
+
:'kick-job' => 'kick_job', :'list-tubes' => 'list_tubes', :'list-tube-used' => 'list_tube_used',
|
21
|
+
:'list-tubes-watched' => :'list_tubes_watched', :'pause-tube' => 'pause_tube', :'peek-buried' => 'peek_buried',
|
22
|
+
:'peek-delayed' => 'peek_delayed', :'peek-ready' => 'peek_ready', :'reserve-with-timeout' => 'reserve_with_timeout',
|
23
|
+
:'stats-job' => 'stats_job', :'stats-tube' => 'stats_tube',
|
24
|
+
})
|
25
|
+
|
26
|
+
COMMANDS_RECOGNIZED_WITHOUT_SPACE_AFTER_COMMAND = [
|
27
|
+
:'list-tubes', :'list-tube-used', :'list-tubes-watched', :'peek-buried', :'peek-delayed', :'peek-ready',
|
28
|
+
:'pause-tube', :quit, :reserve, :'reserve-with-timeout', :stats, :'stats-job', :'stats-tube',
|
29
|
+
]
|
30
|
+
|
31
|
+
COMMANDS_REQUIRING_SPACE_AFTER_COMMAND = COMMANDS - COMMANDS_RECOGNIZED_WITHOUT_SPACE_AFTER_COMMAND
|
32
|
+
|
33
|
+
attr_reader :command, :connection, :error
|
34
|
+
attr_accessor :body
|
35
|
+
|
36
|
+
|
37
|
+
def arguments
|
38
|
+
if command == :put
|
39
|
+
return @args[0, @argument_cardnality].unshift(connection).push(body)
|
40
|
+
else
|
41
|
+
return @args[0, @argument_cardnality].unshift(connection)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
def initialize(raw_command, connection)
|
47
|
+
@connection = connection
|
48
|
+
parse(raw_command)
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
def method_name
|
53
|
+
return COMMAND_METHOD_NAMES[command]
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
def multi_part_request?
|
58
|
+
return command == :put && body.nil?
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
def to_s
|
63
|
+
return "#{command} #{@args.join(' ')}"
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
def valid?
|
68
|
+
return @valid
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def bad_format!
|
74
|
+
invalidate(BAD_FORMAT)
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
def bury
|
79
|
+
@argument_cardnality = 2
|
80
|
+
return requires_no_space_after_line &&
|
81
|
+
requires_exact_argument_count &&
|
82
|
+
requires_valid_integer(@args[0]) &&
|
83
|
+
requires_valid_integer(@args[1], :positive => true)
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
def delete
|
88
|
+
@argument_cardnality = 1
|
89
|
+
return true
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
def ignore
|
94
|
+
@argument_cardnality = 1
|
95
|
+
return requires_no_space_after_line &&
|
96
|
+
requires_exact_argument_count &&
|
97
|
+
requires_valid_tube_name(@args[0])
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
def invalidate(error)
|
102
|
+
@error = error
|
103
|
+
@value = false
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
def kick
|
108
|
+
@argument_cardnality = 1
|
109
|
+
return requires(@args[0]) &&
|
110
|
+
requires_valid_integer(@args[0], :allow_trailing => true)
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
def kick_job
|
115
|
+
@argument_cardnality = 1
|
116
|
+
return true
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
def list_tubes
|
121
|
+
@argument_cardnality = 0
|
122
|
+
return requires_only_command
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
def list_tube_used
|
127
|
+
@argument_cardnality = 0
|
128
|
+
return requires_only_command
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
def list_tubes_watched
|
133
|
+
@argument_cardnality = 0
|
134
|
+
return requires_only_command
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
def parse(raw_command)
|
139
|
+
@command_lines = raw_command.match(COMMAND_PARSER_REGEX)
|
140
|
+
if @command_lines.nil?
|
141
|
+
return unknown_command!
|
142
|
+
end
|
143
|
+
|
144
|
+
@args = @command_lines[:command].split(WHITE_SPACE_REGEX)
|
145
|
+
@command = @args.shift.to_sym rescue nil
|
146
|
+
|
147
|
+
@space_after_command = @command && !!(raw_command[@command.length] =~ WHITE_SPACE_REGEX)
|
148
|
+
@body = @command_lines[:body]
|
149
|
+
|
150
|
+
return unknown_command! unless valid_command?
|
151
|
+
|
152
|
+
return bad_format! unless send(method_name)
|
153
|
+
@valid = true
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
def pause_tube
|
158
|
+
@argument_cardnality = 2
|
159
|
+
return requires_no_space_after_line &&
|
160
|
+
requires(@args[0]) &&
|
161
|
+
requires_valid_integer(@args[1]) &&
|
162
|
+
requires_valid_tube_name(@args[0])
|
163
|
+
end
|
164
|
+
|
165
|
+
|
166
|
+
def peek
|
167
|
+
@argument_cardnality = 1
|
168
|
+
return true
|
169
|
+
end
|
170
|
+
|
171
|
+
|
172
|
+
def peek_buried
|
173
|
+
@argument_cardnality = 0
|
174
|
+
return requires_only_command
|
175
|
+
end
|
176
|
+
|
177
|
+
|
178
|
+
def peek_delayed
|
179
|
+
@argument_cardnality = 0
|
180
|
+
return requires_only_command
|
181
|
+
end
|
182
|
+
|
183
|
+
|
184
|
+
def peek_ready
|
185
|
+
@argument_cardnality = 0
|
186
|
+
return requires_only_command
|
187
|
+
end
|
188
|
+
|
189
|
+
|
190
|
+
def put
|
191
|
+
@argument_cardnality = 5
|
192
|
+
return false unless @args.all? do |arg|
|
193
|
+
requires_valid_integer(arg, :positive => true)
|
194
|
+
end
|
195
|
+
|
196
|
+
# Handle weird case where BAD_FORMAT, but increments stats
|
197
|
+
is_valid = requires_no_space_after_line
|
198
|
+
unless is_valid
|
199
|
+
connection.beanstalk.adjust_stats_cmd_put
|
200
|
+
end
|
201
|
+
return is_valid
|
202
|
+
end
|
203
|
+
|
204
|
+
|
205
|
+
def quit
|
206
|
+
@argument_cardnality = 0
|
207
|
+
return requires_only_command
|
208
|
+
end
|
209
|
+
|
210
|
+
|
211
|
+
def release
|
212
|
+
@argument_cardnality = 3
|
213
|
+
return requires_no_space_after_line &&
|
214
|
+
requires_exact_argument_count &&
|
215
|
+
requires_valid_integer(@args[0]) &&
|
216
|
+
requires_valid_integer(@args[1], :positive => true) &&
|
217
|
+
requires_valid_integer(@args[2], :positive => true)
|
218
|
+
end
|
219
|
+
|
220
|
+
|
221
|
+
def requires(arg)
|
222
|
+
return true unless arg.nil?
|
223
|
+
|
224
|
+
bad_format!
|
225
|
+
return false
|
226
|
+
end
|
227
|
+
|
228
|
+
|
229
|
+
def requires_exact_argument_count
|
230
|
+
return true if @args.length == @argument_cardnality
|
231
|
+
|
232
|
+
bad_format!
|
233
|
+
return false
|
234
|
+
end
|
235
|
+
|
236
|
+
|
237
|
+
def requires_no_space_after_command
|
238
|
+
return true unless @space_after_command
|
239
|
+
|
240
|
+
bad_format!
|
241
|
+
return false
|
242
|
+
end
|
243
|
+
|
244
|
+
|
245
|
+
def requires_no_space_after_line
|
246
|
+
return true unless @command_lines[:command] =~ TRAILING_SPACE_REGEX
|
247
|
+
|
248
|
+
bad_format!
|
249
|
+
return false
|
250
|
+
end
|
251
|
+
|
252
|
+
|
253
|
+
def requires_only_command
|
254
|
+
return requires_no_space_after_command && requires_exact_argument_count
|
255
|
+
end
|
256
|
+
|
257
|
+
|
258
|
+
def requires_space_after_command
|
259
|
+
return true if @space_after_command
|
260
|
+
|
261
|
+
bad_format!
|
262
|
+
return false
|
263
|
+
end
|
264
|
+
|
265
|
+
|
266
|
+
def requires_valid_integer(arg, opts = {})
|
267
|
+
arg_to_i = arg.to_i
|
268
|
+
if opts[:allow_trailing]
|
269
|
+
if arg_to_i == 0 && arg !~ ZERO_STRING_REGEX
|
270
|
+
bad_format!
|
271
|
+
return false
|
272
|
+
end
|
273
|
+
else
|
274
|
+
if arg_to_i.to_s != arg
|
275
|
+
bad_format!
|
276
|
+
return false
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
return true if opts[:positive] ? arg_to_i >= 0 : true
|
281
|
+
|
282
|
+
bad_format!
|
283
|
+
return false
|
284
|
+
end
|
285
|
+
|
286
|
+
|
287
|
+
def requires_valid_tube_name(tube_name)
|
288
|
+
return true if valid_tube_name?(tube_name)
|
289
|
+
|
290
|
+
bad_format!
|
291
|
+
return false
|
292
|
+
end
|
293
|
+
|
294
|
+
|
295
|
+
def reserve
|
296
|
+
@argument_cardnality = 0
|
297
|
+
return requires_only_command
|
298
|
+
end
|
299
|
+
|
300
|
+
|
301
|
+
def reserve_with_timeout
|
302
|
+
@argument_cardnality = 1
|
303
|
+
return requires_space_after_command
|
304
|
+
end
|
305
|
+
|
306
|
+
|
307
|
+
def stats
|
308
|
+
@argument_cardnality = 0
|
309
|
+
return requires_only_command
|
310
|
+
end
|
311
|
+
|
312
|
+
|
313
|
+
def stats_job
|
314
|
+
@argument_cardnality = 1
|
315
|
+
return requires_space_after_command
|
316
|
+
end
|
317
|
+
|
318
|
+
|
319
|
+
def stats_tube
|
320
|
+
@argument_cardnality = 1
|
321
|
+
return requires_space_after_command &&
|
322
|
+
requires_no_space_after_line &&
|
323
|
+
requires_valid_tube_name(@args[0])
|
324
|
+
end
|
325
|
+
|
326
|
+
|
327
|
+
def touch
|
328
|
+
@argument_cardnality = 1
|
329
|
+
return true
|
330
|
+
end
|
331
|
+
|
332
|
+
|
333
|
+
def use
|
334
|
+
@argument_cardnality = 1
|
335
|
+
return requires_no_space_after_line &&
|
336
|
+
requires_exact_argument_count &&
|
337
|
+
requires_valid_tube_name(@args[0])
|
338
|
+
end
|
339
|
+
|
340
|
+
|
341
|
+
def unknown_command!
|
342
|
+
invalidate(UNKNOWN_COMMAND)
|
343
|
+
end
|
344
|
+
|
345
|
+
|
346
|
+
def valid_command?
|
347
|
+
return COMMANDS_RECOGNIZED_WITHOUT_SPACE_AFTER_COMMAND.include?(command) || (
|
348
|
+
COMMANDS_REQUIRING_SPACE_AFTER_COMMAND.include?(command) && @space_after_command
|
349
|
+
)
|
350
|
+
end
|
351
|
+
|
352
|
+
|
353
|
+
def valid_tube_name?(tube_name)
|
354
|
+
return !tube_name.nil? && tube_name.bytesize <= 200 && VALID_TUBE_NAME_REGEX =~ tube_name
|
355
|
+
end
|
356
|
+
|
357
|
+
|
358
|
+
def watch
|
359
|
+
@argument_cardnality = 1
|
360
|
+
return requires_no_space_after_line &&
|
361
|
+
requires_exact_argument_count &&
|
362
|
+
requires_valid_tube_name(@args[0])
|
363
|
+
end
|
364
|
+
|
365
|
+
end
|