gemerald_beanstalk 0.0.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.
- 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
|