omq-cli 0.1.0 → 0.3.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +183 -0
- data/README.md +28 -9
- data/lib/omq/cli/base_runner.rb +151 -230
- data/lib/omq/cli/cli_parser.rb +470 -0
- data/lib/omq/cli/client_server.rb +32 -59
- data/lib/omq/cli/config.rb +10 -0
- data/lib/omq/cli/expression_evaluator.rb +156 -0
- data/lib/omq/cli/formatter.rb +27 -1
- data/lib/omq/cli/pair.rb +9 -7
- data/lib/omq/cli/parallel_recv_runner.rb +150 -0
- data/lib/omq/cli/pipe.rb +175 -156
- data/lib/omq/cli/pub_sub.rb +5 -1
- data/lib/omq/cli/push_pull.rb +5 -1
- data/lib/omq/cli/radio_dish.rb +5 -1
- data/lib/omq/cli/req_rep.rb +44 -49
- data/lib/omq/cli/router_dealer.rb +13 -46
- data/lib/omq/cli/routing_helper.rb +95 -0
- data/lib/omq/cli/scatter_gather.rb +5 -1
- data/lib/omq/cli/socket_setup.rb +100 -0
- data/lib/omq/cli/transient_monitor.rb +41 -0
- data/lib/omq/cli/version.rb +1 -1
- data/lib/omq/cli.rb +150 -418
- metadata +95 -6
- data/lib/omq/cli/channel.rb +0 -8
- data/lib/omq/cli/peer.rb +0 -8
data/lib/omq/cli/base_runner.rb
CHANGED
|
@@ -2,10 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
module OMQ
|
|
4
4
|
module CLI
|
|
5
|
+
# Template runner base class for all socket-type CLI runners.
|
|
6
|
+
# Subclasses override {#run_loop} to implement socket-specific behaviour.
|
|
5
7
|
class BaseRunner
|
|
8
|
+
# @return [Config] frozen CLI configuration
|
|
9
|
+
# @return [Object] the OMQ socket instance
|
|
6
10
|
attr_reader :config, :sock
|
|
7
11
|
|
|
8
12
|
|
|
13
|
+
# @param config [Config] frozen CLI configuration
|
|
14
|
+
# @param socket_class [Class] OMQ socket class to instantiate (e.g. OMQ::PUSH)
|
|
9
15
|
def initialize(config, socket_class)
|
|
10
16
|
@config = config
|
|
11
17
|
@klass = socket_class
|
|
@@ -13,26 +19,18 @@ module OMQ
|
|
|
13
19
|
end
|
|
14
20
|
|
|
15
21
|
|
|
22
|
+
# Runs the full lifecycle: socket setup, peer wait, BEGIN/END blocks, and the main loop.
|
|
23
|
+
#
|
|
24
|
+
# @param task [Async::Task] the parent async task
|
|
25
|
+
# @return [void]
|
|
16
26
|
def call(task)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
setup_curve
|
|
20
|
-
setup_subscriptions
|
|
21
|
-
compile_expr
|
|
22
|
-
|
|
23
|
-
if config.transient
|
|
24
|
-
start_disconnect_monitor(task)
|
|
25
|
-
Async::Task.current.yield # let monitor start waiting
|
|
26
|
-
end
|
|
27
|
-
|
|
27
|
+
setup_socket
|
|
28
|
+
maybe_start_transient_monitor(task)
|
|
28
29
|
sleep(config.delay) if config.delay && config.recv_only?
|
|
29
30
|
wait_for_peer if needs_peer_wait?
|
|
30
|
-
|
|
31
|
-
@sock.instance_exec(&@send_begin_proc) if @send_begin_proc
|
|
32
|
-
@sock.instance_exec(&@recv_begin_proc) if @recv_begin_proc
|
|
31
|
+
run_begin_blocks
|
|
33
32
|
run_loop(task)
|
|
34
|
-
|
|
35
|
-
@sock.instance_exec(&@recv_end_proc) if @recv_end_proc
|
|
33
|
+
run_end_blocks
|
|
36
34
|
ensure
|
|
37
35
|
@sock&.close
|
|
38
36
|
end
|
|
@@ -46,34 +44,59 @@ module OMQ
|
|
|
46
44
|
raise NotImplementedError
|
|
47
45
|
end
|
|
48
46
|
|
|
47
|
+
|
|
49
48
|
# ── Socket creation ─────────────────────────────────────────────
|
|
50
49
|
|
|
51
50
|
|
|
51
|
+
def setup_socket
|
|
52
|
+
@sock = create_socket
|
|
53
|
+
attach_endpoints unless config.parallel
|
|
54
|
+
setup_curve
|
|
55
|
+
setup_subscriptions
|
|
56
|
+
compile_expr
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
|
|
52
60
|
def create_socket
|
|
53
|
-
|
|
54
|
-
sock_opts[:conflate] = true if config.conflate && %w[pub radio].include?(config.type_name)
|
|
55
|
-
sock = @klass.new(**sock_opts)
|
|
56
|
-
sock.recv_timeout = config.timeout if config.timeout
|
|
57
|
-
sock.send_timeout = config.timeout if config.timeout
|
|
58
|
-
sock.reconnect_interval = config.reconnect_ivl if config.reconnect_ivl
|
|
59
|
-
sock.heartbeat_interval = config.heartbeat_ivl if config.heartbeat_ivl
|
|
60
|
-
sock.identity = config.identity if config.identity
|
|
61
|
-
sock.router_mandatory = true if config.type_name == "router"
|
|
62
|
-
sock
|
|
61
|
+
SocketSetup.build(@klass, config)
|
|
63
62
|
end
|
|
64
63
|
|
|
65
64
|
|
|
66
65
|
def attach_endpoints
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
66
|
+
SocketSetup.attach(@sock, config, verbose: config.verbose)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# ── Transient disconnect monitor ────────────────────────────────
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def maybe_start_transient_monitor(task)
|
|
74
|
+
return unless config.transient
|
|
75
|
+
@transient_monitor = TransientMonitor.new(@sock, config, task, method(:log))
|
|
76
|
+
Async::Task.current.yield # let monitor start waiting
|
|
75
77
|
end
|
|
76
78
|
|
|
79
|
+
|
|
80
|
+
def transient_ready!
|
|
81
|
+
@transient_monitor&.ready!
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# ── BEGIN / END blocks ──────────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def run_begin_blocks
|
|
89
|
+
@sock.instance_exec(&@send_begin_proc) if @send_begin_proc
|
|
90
|
+
@sock.instance_exec(&@recv_begin_proc) if @recv_begin_proc
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def run_end_blocks
|
|
95
|
+
@sock.instance_exec(&@send_end_proc) if @send_end_proc
|
|
96
|
+
@sock.instance_exec(&@recv_end_proc) if @recv_end_proc
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
|
|
77
100
|
# ── Peer wait with grace period ─────────────────────────────────
|
|
78
101
|
|
|
79
102
|
|
|
@@ -86,46 +109,29 @@ module OMQ
|
|
|
86
109
|
with_timeout(config.timeout) do
|
|
87
110
|
@sock.peer_connected.wait
|
|
88
111
|
log "Peer connected"
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
log "Subscriber joined"
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
# Grace period: when multiple peers may be connecting (bind or
|
|
95
|
-
# multiple connect URLs), wait one reconnect interval so
|
|
96
|
-
# latecomers finish their handshake before we start sending.
|
|
97
|
-
if config.binds.any? || config.connects.size > 1
|
|
98
|
-
ri = @sock.options.reconnect_interval
|
|
99
|
-
sleep(ri.is_a?(Range) ? ri.begin : ri)
|
|
100
|
-
end
|
|
112
|
+
wait_for_subscriber
|
|
113
|
+
apply_grace_period
|
|
101
114
|
end
|
|
102
115
|
end
|
|
103
116
|
|
|
104
|
-
# ── Transient disconnect monitor ────────────────────────────────
|
|
105
117
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
@
|
|
109
|
-
|
|
110
|
-
@transient_barrier.wait
|
|
111
|
-
@sock.all_peers_gone.wait unless @sock.connection_count == 0
|
|
112
|
-
log "All peers disconnected, exiting"
|
|
113
|
-
@sock.reconnect_enabled = false
|
|
114
|
-
if config.send_only?
|
|
115
|
-
task.stop
|
|
116
|
-
else
|
|
117
|
-
@sock.close_read
|
|
118
|
-
end
|
|
119
|
-
end
|
|
118
|
+
def wait_for_subscriber
|
|
119
|
+
return unless %w[pub xpub].include?(config.type_name)
|
|
120
|
+
@sock.subscriber_joined.wait
|
|
121
|
+
log "Subscriber joined"
|
|
120
122
|
end
|
|
121
123
|
|
|
122
124
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
125
|
+
# Grace period: when multiple peers may be connecting (bind or
|
|
126
|
+
# multiple connect URLs), wait one reconnect interval so
|
|
127
|
+
# latecomers finish their handshake before we start sending.
|
|
128
|
+
def apply_grace_period
|
|
129
|
+
return unless config.binds.any? || config.connects.size > 1
|
|
130
|
+
ri = @sock.options.reconnect_interval
|
|
131
|
+
sleep(ri.is_a?(Range) ? ri.begin : ri)
|
|
127
132
|
end
|
|
128
133
|
|
|
134
|
+
|
|
129
135
|
# ── Timeout helper ──────────────────────────────────────────────
|
|
130
136
|
|
|
131
137
|
|
|
@@ -137,101 +143,38 @@ module OMQ
|
|
|
137
143
|
end
|
|
138
144
|
end
|
|
139
145
|
|
|
146
|
+
|
|
140
147
|
# ── Socket setup ────────────────────────────────────────────────
|
|
141
148
|
|
|
142
149
|
|
|
143
150
|
def setup_subscriptions
|
|
144
|
-
|
|
145
|
-
when "sub"
|
|
146
|
-
prefixes = config.subscribes.empty? ? [""] : config.subscribes
|
|
147
|
-
prefixes.each { |p| @sock.subscribe(p) }
|
|
148
|
-
when "dish"
|
|
149
|
-
config.joins.each { |g| @sock.join(g) }
|
|
150
|
-
end
|
|
151
|
+
SocketSetup.setup_subscriptions(@sock, config)
|
|
151
152
|
end
|
|
152
153
|
|
|
153
154
|
|
|
154
|
-
def
|
|
155
|
-
|
|
156
|
-
server_mode = config.curve_server || (ENV["OMQ_SERVER_PUBLIC"] && ENV["OMQ_SERVER_SECRET"])
|
|
157
|
-
|
|
158
|
-
return unless server_key_z85 || server_mode
|
|
159
|
-
|
|
160
|
-
crypto = load_curve_crypto(config.curve_crypto || ENV["OMQ_CURVE_CRYPTO"])
|
|
161
|
-
require "protocol/zmtp/mechanism/curve"
|
|
162
|
-
|
|
163
|
-
if server_key_z85
|
|
164
|
-
server_key = Protocol::ZMTP::Z85.decode(server_key_z85)
|
|
165
|
-
client_key = crypto::PrivateKey.generate
|
|
166
|
-
@sock.mechanism = Protocol::ZMTP::Mechanism::Curve.client(
|
|
167
|
-
client_key.public_key.to_s, client_key.to_s,
|
|
168
|
-
server_key: server_key, crypto: crypto
|
|
169
|
-
)
|
|
170
|
-
elsif server_mode
|
|
171
|
-
if ENV["OMQ_SERVER_PUBLIC"] && ENV["OMQ_SERVER_SECRET"]
|
|
172
|
-
server_pub = Protocol::ZMTP::Z85.decode(ENV["OMQ_SERVER_PUBLIC"])
|
|
173
|
-
server_sec = Protocol::ZMTP::Z85.decode(ENV["OMQ_SERVER_SECRET"])
|
|
174
|
-
else
|
|
175
|
-
key = crypto::PrivateKey.generate
|
|
176
|
-
server_pub = key.public_key.to_s
|
|
177
|
-
server_sec = key.to_s
|
|
178
|
-
end
|
|
179
|
-
@sock.mechanism = Protocol::ZMTP::Mechanism::Curve.server(
|
|
180
|
-
server_pub, server_sec, crypto: crypto
|
|
181
|
-
)
|
|
182
|
-
$stderr.puts "OMQ_SERVER_KEY='#{Protocol::ZMTP::Z85.encode(server_pub)}'"
|
|
183
|
-
end
|
|
155
|
+
def setup_subscriptions_on(sock)
|
|
156
|
+
SocketSetup.setup_subscriptions(sock, config)
|
|
184
157
|
end
|
|
185
158
|
|
|
186
159
|
|
|
187
|
-
def
|
|
188
|
-
|
|
189
|
-
when "rbnacl" then require "rbnacl"; RbNaCl
|
|
190
|
-
when "nuckle" then require "nuckle"; Nuckle
|
|
191
|
-
when nil
|
|
192
|
-
begin
|
|
193
|
-
require "rbnacl"; RbNaCl
|
|
194
|
-
rescue LoadError
|
|
195
|
-
abort "CURVE requires a crypto backend. Install rbnacl (recommended):\n" \
|
|
196
|
-
" gem install rbnacl # requires system libsodium\n" \
|
|
197
|
-
"Or use pure Ruby (not audited):\n" \
|
|
198
|
-
" --curve-crypto nuckle\n" \
|
|
199
|
-
" # or: OMQ_CURVE_CRYPTO=nuckle"
|
|
200
|
-
end
|
|
201
|
-
else
|
|
202
|
-
abort "Unknown CURVE crypto backend: #{name}. Use 'rbnacl' or 'nuckle'."
|
|
203
|
-
end
|
|
204
|
-
rescue LoadError
|
|
205
|
-
abort "Could not load #{name} gem: gem install #{name}"
|
|
160
|
+
def setup_curve
|
|
161
|
+
SocketSetup.setup_curve(@sock, config)
|
|
206
162
|
end
|
|
207
163
|
|
|
164
|
+
|
|
208
165
|
# ── Shared loop bodies ──────────────────────────────────────────
|
|
209
166
|
|
|
210
167
|
|
|
211
168
|
def run_send_logic
|
|
212
169
|
n = config.count
|
|
213
|
-
i = 0
|
|
214
170
|
sleep(config.delay) if config.delay
|
|
215
171
|
if config.interval
|
|
216
|
-
|
|
217
|
-
unless @send_tick_eof || (n && n > 0 && i >= n)
|
|
218
|
-
Async::Loop.quantized(interval: config.interval) do
|
|
219
|
-
i += send_tick
|
|
220
|
-
break if @send_tick_eof || (n && n > 0 && i >= n)
|
|
221
|
-
end
|
|
222
|
-
end
|
|
172
|
+
run_interval_send(n)
|
|
223
173
|
elsif config.data || config.file
|
|
224
174
|
parts = eval_send_expr(read_next)
|
|
225
175
|
send_msg(parts) if parts
|
|
226
176
|
elsif stdin_ready?
|
|
227
|
-
|
|
228
|
-
parts = read_next
|
|
229
|
-
break unless parts
|
|
230
|
-
parts = eval_send_expr(parts)
|
|
231
|
-
send_msg(parts) if parts
|
|
232
|
-
i += 1
|
|
233
|
-
break if n && n > 0 && i >= n
|
|
234
|
-
end
|
|
177
|
+
run_stdin_send(n)
|
|
235
178
|
elsif @send_eval_proc
|
|
236
179
|
parts = eval_send_expr(nil)
|
|
237
180
|
send_msg(parts) if parts
|
|
@@ -239,6 +182,29 @@ module OMQ
|
|
|
239
182
|
end
|
|
240
183
|
|
|
241
184
|
|
|
185
|
+
def run_interval_send(n)
|
|
186
|
+
i = send_tick
|
|
187
|
+
return if @send_tick_eof || (n && n > 0 && i >= n)
|
|
188
|
+
Async::Loop.quantized(interval: config.interval) do
|
|
189
|
+
i += send_tick
|
|
190
|
+
break if @send_tick_eof || (n && n > 0 && i >= n)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def run_stdin_send(n)
|
|
196
|
+
i = 0
|
|
197
|
+
loop do
|
|
198
|
+
parts = read_next
|
|
199
|
+
break unless parts
|
|
200
|
+
parts = eval_send_expr(parts)
|
|
201
|
+
send_msg(parts) if parts
|
|
202
|
+
i += 1
|
|
203
|
+
break if n && n > 0 && i >= n
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
|
|
242
208
|
def send_tick
|
|
243
209
|
raw = read_next_or_nil
|
|
244
210
|
if raw.nil? && !@send_eval_proc
|
|
@@ -265,6 +231,17 @@ module OMQ
|
|
|
265
231
|
end
|
|
266
232
|
|
|
267
233
|
|
|
234
|
+
# Parallel recv-eval: delegates to ParallelRecvRunner.
|
|
235
|
+
#
|
|
236
|
+
def run_parallel_recv(task)
|
|
237
|
+
# @sock was created by call() before run_loop; close it now so it doesn't
|
|
238
|
+
# steal messages from the N worker sockets ParallelRecvRunner creates.
|
|
239
|
+
@sock&.close
|
|
240
|
+
@sock = nil
|
|
241
|
+
ParallelRecvRunner.new(@klass, config, @fmt, method(:output)).run(task)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
|
|
268
245
|
def wait_for_loops(receiver, sender)
|
|
269
246
|
if config.data || config.file || config.send_expr || config.recv_expr || config.target
|
|
270
247
|
sender.wait
|
|
@@ -278,6 +255,7 @@ module OMQ
|
|
|
278
255
|
end
|
|
279
256
|
end
|
|
280
257
|
|
|
258
|
+
|
|
281
259
|
# ── Message I/O ─────────────────────────────────────────────────
|
|
282
260
|
|
|
283
261
|
|
|
@@ -307,23 +285,32 @@ module OMQ
|
|
|
307
285
|
|
|
308
286
|
|
|
309
287
|
def read_next
|
|
288
|
+
config.data || config.file ? read_inline_data : read_stdin_input
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def read_inline_data
|
|
310
293
|
if config.data
|
|
311
294
|
@fmt.decode(config.data + "\n")
|
|
312
|
-
|
|
295
|
+
else
|
|
313
296
|
@file_data ||= (config.file == "-" ? $stdin.read : File.read(config.file)).chomp
|
|
314
297
|
@fmt.decode(@file_data + "\n")
|
|
315
|
-
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def read_stdin_input
|
|
303
|
+
case config.format
|
|
304
|
+
when :msgpack
|
|
316
305
|
@fmt.decode_msgpack($stdin)
|
|
317
|
-
|
|
306
|
+
when :marshal
|
|
318
307
|
@fmt.decode_marshal($stdin)
|
|
319
|
-
|
|
308
|
+
when :raw
|
|
320
309
|
data = $stdin.read
|
|
321
|
-
|
|
322
|
-
[data]
|
|
310
|
+
data.nil? || data.empty? ? nil : [data]
|
|
323
311
|
else
|
|
324
312
|
line = $stdin.gets
|
|
325
|
-
|
|
326
|
-
@fmt.decode(line)
|
|
313
|
+
line.nil? ? nil : @fmt.decode(line)
|
|
327
314
|
end
|
|
328
315
|
end
|
|
329
316
|
|
|
@@ -355,116 +342,50 @@ module OMQ
|
|
|
355
342
|
$stdout.flush
|
|
356
343
|
end
|
|
357
344
|
|
|
358
|
-
# ── Routing helpers ─────────────────────────────────────────────
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
def display_routing_id(id)
|
|
362
|
-
if id.bytes.all? { |b| b >= 0x20 && b <= 0x7E }
|
|
363
|
-
id
|
|
364
|
-
else
|
|
365
|
-
"0x#{id.unpack1("H*")}"
|
|
366
|
-
end
|
|
367
|
-
end
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
def resolve_target(target)
|
|
371
|
-
if target.start_with?("0x")
|
|
372
|
-
[target[2..].delete(" ")].pack("H*")
|
|
373
|
-
else
|
|
374
|
-
target
|
|
375
|
-
end
|
|
376
|
-
end
|
|
377
345
|
|
|
378
346
|
# ── Eval ────────────────────────────────────────────────────────
|
|
379
347
|
|
|
380
348
|
|
|
381
349
|
def compile_expr
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
350
|
+
@send_evaluator = compile_evaluator(config.send_expr, fallback: OMQ.outgoing_proc)
|
|
351
|
+
@recv_evaluator = compile_evaluator(config.recv_expr, fallback: OMQ.incoming_proc)
|
|
352
|
+
assign_send_aliases
|
|
353
|
+
assign_recv_aliases
|
|
386
354
|
end
|
|
387
355
|
|
|
388
356
|
|
|
389
|
-
def
|
|
390
|
-
|
|
391
|
-
proc do |msg|
|
|
392
|
-
$_ = msg&.first
|
|
393
|
-
block.call(msg)
|
|
394
|
-
end
|
|
357
|
+
def compile_evaluator(src, fallback:)
|
|
358
|
+
ExpressionEvaluator.new(src, format: config.format, fallback_proc: fallback)
|
|
395
359
|
end
|
|
396
360
|
|
|
397
361
|
|
|
398
|
-
def
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
if expr && !expr.strip.empty?
|
|
404
|
-
instance_variable_set(:"@#{direction}_eval_proc", eval("proc { $_ = $F&.first; #{expr} }"))
|
|
405
|
-
end
|
|
362
|
+
def assign_send_aliases
|
|
363
|
+
# Keep ivar aliases — subclasses check these directly
|
|
364
|
+
@send_begin_proc = @send_evaluator.begin_proc
|
|
365
|
+
@send_eval_proc = @send_evaluator.eval_proc
|
|
366
|
+
@send_end_proc = @send_evaluator.end_proc
|
|
406
367
|
end
|
|
407
368
|
|
|
408
369
|
|
|
409
|
-
def
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
[expr, begin_body, end_body]
|
|
370
|
+
def assign_recv_aliases
|
|
371
|
+
@recv_begin_proc = @recv_evaluator.begin_proc
|
|
372
|
+
@recv_eval_proc = @recv_evaluator.eval_proc
|
|
373
|
+
@recv_end_proc = @recv_evaluator.end_proc
|
|
414
374
|
end
|
|
415
375
|
|
|
416
376
|
|
|
417
|
-
def extract_block(expr, keyword)
|
|
418
|
-
start = expr.index(/#{keyword}\s*\{/)
|
|
419
|
-
return [expr, nil] unless start
|
|
420
|
-
|
|
421
|
-
# Find the opening brace
|
|
422
|
-
i = expr.index("{", start)
|
|
423
|
-
depth = 1
|
|
424
|
-
j = i + 1
|
|
425
|
-
while j < expr.length && depth > 0
|
|
426
|
-
case expr[j]
|
|
427
|
-
when "{" then depth += 1
|
|
428
|
-
when "}" then depth -= 1
|
|
429
|
-
end
|
|
430
|
-
j += 1
|
|
431
|
-
end
|
|
432
|
-
|
|
433
|
-
body = expr[(i + 1)..(j - 2)]
|
|
434
|
-
trimmed = expr[0...start] + expr[j..]
|
|
435
|
-
[trimmed, body]
|
|
436
|
-
end
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
SENT = Object.new.freeze # sentinel: eval already sent the reply
|
|
440
|
-
|
|
441
377
|
def eval_send_expr(parts)
|
|
442
|
-
|
|
443
|
-
run_eval(@send_eval_proc, parts)
|
|
378
|
+
@send_evaluator.call(parts, @sock)
|
|
444
379
|
end
|
|
445
380
|
|
|
446
381
|
|
|
447
382
|
def eval_recv_expr(parts)
|
|
448
|
-
|
|
449
|
-
run_eval(@recv_eval_proc, parts)
|
|
383
|
+
@recv_evaluator.call(parts, @sock)
|
|
450
384
|
end
|
|
451
385
|
|
|
452
386
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
result = @sock.instance_exec(parts, &eval_proc)
|
|
456
|
-
return nil if result.nil?
|
|
457
|
-
return SENT if result.equal?(@sock)
|
|
458
|
-
return [result] if config.format == :marshal
|
|
459
|
-
case result
|
|
460
|
-
when Array then result
|
|
461
|
-
when String then [result]
|
|
462
|
-
else [result.to_str]
|
|
463
|
-
end
|
|
464
|
-
rescue => e
|
|
465
|
-
$stderr.puts "omq: eval error: #{e.message} (#{e.class})"
|
|
466
|
-
exit 3
|
|
467
|
-
end
|
|
387
|
+
SENT = ExpressionEvaluator::SENT
|
|
388
|
+
|
|
468
389
|
|
|
469
390
|
# ── Logging ─────────────────────────────────────────────────────
|
|
470
391
|
|