omq-cli 0.2.0 → 0.3.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +170 -0
- data/README.md +14 -9
- data/lib/omq/cli/base_runner.rb +196 -221
- 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 +72 -426
- metadata +94 -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,50 +143,22 @@ 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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
end
|
|
151
|
+
SocketSetup.setup_subscriptions(@sock, config)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def setup_subscriptions_on(sock)
|
|
156
|
+
SocketSetup.setup_subscriptions(sock, config)
|
|
151
157
|
end
|
|
152
158
|
|
|
153
159
|
|
|
154
160
|
def setup_curve
|
|
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 = CLI.load_curve_crypto(config.curve_crypto || ENV["OMQ_CURVE_CRYPTO"], verbose: config.verbose)
|
|
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
|
|
161
|
+
SocketSetup.setup_curve(@sock, config)
|
|
184
162
|
end
|
|
185
163
|
|
|
186
164
|
|
|
@@ -189,28 +167,14 @@ module OMQ
|
|
|
189
167
|
|
|
190
168
|
def run_send_logic
|
|
191
169
|
n = config.count
|
|
192
|
-
i = 0
|
|
193
170
|
sleep(config.delay) if config.delay
|
|
194
171
|
if config.interval
|
|
195
|
-
|
|
196
|
-
unless @send_tick_eof || (n && n > 0 && i >= n)
|
|
197
|
-
Async::Loop.quantized(interval: config.interval) do
|
|
198
|
-
i += send_tick
|
|
199
|
-
break if @send_tick_eof || (n && n > 0 && i >= n)
|
|
200
|
-
end
|
|
201
|
-
end
|
|
172
|
+
run_interval_send(n)
|
|
202
173
|
elsif config.data || config.file
|
|
203
174
|
parts = eval_send_expr(read_next)
|
|
204
175
|
send_msg(parts) if parts
|
|
205
176
|
elsif stdin_ready?
|
|
206
|
-
|
|
207
|
-
parts = read_next
|
|
208
|
-
break unless parts
|
|
209
|
-
parts = eval_send_expr(parts)
|
|
210
|
-
send_msg(parts) if parts
|
|
211
|
-
i += 1
|
|
212
|
-
break if n && n > 0 && i >= n
|
|
213
|
-
end
|
|
177
|
+
run_stdin_send(n)
|
|
214
178
|
elsif @send_eval_proc
|
|
215
179
|
parts = eval_send_expr(nil)
|
|
216
180
|
send_msg(parts) if parts
|
|
@@ -218,9 +182,38 @@ module OMQ
|
|
|
218
182
|
end
|
|
219
183
|
|
|
220
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
|
+
|
|
221
208
|
def send_tick
|
|
222
209
|
raw = read_next_or_nil
|
|
223
|
-
if raw.nil?
|
|
210
|
+
if raw.nil?
|
|
211
|
+
if @send_eval_proc && !@stdin_ready
|
|
212
|
+
# Pure generator mode: no stdin, eval produces output from nothing.
|
|
213
|
+
parts = eval_send_expr(nil)
|
|
214
|
+
send_msg(parts) if parts
|
|
215
|
+
return 1
|
|
216
|
+
end
|
|
224
217
|
@send_tick_eof = true
|
|
225
218
|
return 0
|
|
226
219
|
end
|
|
@@ -233,14 +226,52 @@ module OMQ
|
|
|
233
226
|
def run_recv_logic
|
|
234
227
|
n = config.count
|
|
235
228
|
i = 0
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
229
|
+
if config.interval
|
|
230
|
+
run_interval_recv(n)
|
|
231
|
+
else
|
|
232
|
+
loop do
|
|
233
|
+
parts = recv_msg
|
|
234
|
+
break if parts.nil?
|
|
235
|
+
parts = eval_recv_expr(parts)
|
|
236
|
+
output(parts)
|
|
237
|
+
i += 1
|
|
238
|
+
break if n && n > 0 && i >= n
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def run_interval_recv(n)
|
|
245
|
+
i = recv_tick
|
|
246
|
+
return if i == 0
|
|
247
|
+
return if n && n > 0 && i >= n
|
|
248
|
+
Async::Loop.quantized(interval: config.interval) do
|
|
249
|
+
i += recv_tick
|
|
250
|
+
break if @recv_tick_eof || (n && n > 0 && i >= n)
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def recv_tick
|
|
256
|
+
parts = recv_msg
|
|
257
|
+
if parts.nil?
|
|
258
|
+
@recv_tick_eof = true
|
|
259
|
+
return 0
|
|
243
260
|
end
|
|
261
|
+
parts = eval_recv_expr(parts)
|
|
262
|
+
output(parts)
|
|
263
|
+
1
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
# Parallel recv-eval: delegates to ParallelRecvRunner.
|
|
268
|
+
#
|
|
269
|
+
def run_parallel_recv(task)
|
|
270
|
+
# @sock was created by call() before run_loop; close it now so it doesn't
|
|
271
|
+
# steal messages from the N worker sockets ParallelRecvRunner creates.
|
|
272
|
+
@sock&.close
|
|
273
|
+
@sock = nil
|
|
274
|
+
ParallelRecvRunner.new(@klass, config, @fmt, method(:output)).run(task)
|
|
244
275
|
end
|
|
245
276
|
|
|
246
277
|
|
|
@@ -257,6 +288,7 @@ module OMQ
|
|
|
257
288
|
end
|
|
258
289
|
end
|
|
259
290
|
|
|
291
|
+
|
|
260
292
|
# ── Message I/O ─────────────────────────────────────────────────
|
|
261
293
|
|
|
262
294
|
|
|
@@ -286,23 +318,32 @@ module OMQ
|
|
|
286
318
|
|
|
287
319
|
|
|
288
320
|
def read_next
|
|
321
|
+
config.data || config.file ? read_inline_data : read_stdin_input
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def read_inline_data
|
|
289
326
|
if config.data
|
|
290
327
|
@fmt.decode(config.data + "\n")
|
|
291
|
-
|
|
328
|
+
else
|
|
292
329
|
@file_data ||= (config.file == "-" ? $stdin.read : File.read(config.file)).chomp
|
|
293
330
|
@fmt.decode(@file_data + "\n")
|
|
294
|
-
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def read_stdin_input
|
|
336
|
+
case config.format
|
|
337
|
+
when :msgpack
|
|
295
338
|
@fmt.decode_msgpack($stdin)
|
|
296
|
-
|
|
339
|
+
when :marshal
|
|
297
340
|
@fmt.decode_marshal($stdin)
|
|
298
|
-
|
|
341
|
+
when :raw
|
|
299
342
|
data = $stdin.read
|
|
300
|
-
|
|
301
|
-
[data]
|
|
343
|
+
data.nil? || data.empty? ? nil : [data]
|
|
302
344
|
else
|
|
303
345
|
line = $stdin.gets
|
|
304
|
-
|
|
305
|
-
@fmt.decode(line)
|
|
346
|
+
line.nil? ? nil : @fmt.decode(line)
|
|
306
347
|
end
|
|
307
348
|
end
|
|
308
349
|
|
|
@@ -320,10 +361,10 @@ module OMQ
|
|
|
320
361
|
def read_next_or_nil
|
|
321
362
|
if config.data || config.file
|
|
322
363
|
read_next
|
|
323
|
-
elsif
|
|
324
|
-
|
|
364
|
+
elsif stdin_ready?
|
|
365
|
+
read_stdin_input
|
|
325
366
|
else
|
|
326
|
-
|
|
367
|
+
nil
|
|
327
368
|
end
|
|
328
369
|
end
|
|
329
370
|
|
|
@@ -334,116 +375,50 @@ module OMQ
|
|
|
334
375
|
$stdout.flush
|
|
335
376
|
end
|
|
336
377
|
|
|
337
|
-
# ── Routing helpers ─────────────────────────────────────────────
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
def display_routing_id(id)
|
|
341
|
-
if id.bytes.all? { |b| b >= 0x20 && b <= 0x7E }
|
|
342
|
-
id
|
|
343
|
-
else
|
|
344
|
-
"0x#{id.unpack1("H*")}"
|
|
345
|
-
end
|
|
346
|
-
end
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
def resolve_target(target)
|
|
350
|
-
if target.start_with?("0x")
|
|
351
|
-
[target[2..].delete(" ")].pack("H*")
|
|
352
|
-
else
|
|
353
|
-
target
|
|
354
|
-
end
|
|
355
|
-
end
|
|
356
378
|
|
|
357
379
|
# ── Eval ────────────────────────────────────────────────────────
|
|
358
380
|
|
|
359
381
|
|
|
360
382
|
def compile_expr
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
end
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
def wrap_registered_proc(block)
|
|
369
|
-
return unless block
|
|
370
|
-
proc do |msg|
|
|
371
|
-
$_ = msg&.first
|
|
372
|
-
block.call(msg)
|
|
373
|
-
end
|
|
383
|
+
@send_evaluator = compile_evaluator(config.send_expr, fallback: OMQ.outgoing_proc)
|
|
384
|
+
@recv_evaluator = compile_evaluator(config.recv_expr, fallback: OMQ.incoming_proc)
|
|
385
|
+
assign_send_aliases
|
|
386
|
+
assign_recv_aliases
|
|
374
387
|
end
|
|
375
388
|
|
|
376
389
|
|
|
377
|
-
def
|
|
378
|
-
|
|
379
|
-
expr, begin_body, end_body = extract_blocks(src)
|
|
380
|
-
instance_variable_set(:"@#{direction}_begin_proc", eval("proc { #{begin_body} }")) if begin_body
|
|
381
|
-
instance_variable_set(:"@#{direction}_end_proc", eval("proc { #{end_body} }")) if end_body
|
|
382
|
-
if expr && !expr.strip.empty?
|
|
383
|
-
instance_variable_set(:"@#{direction}_eval_proc", eval("proc { $_ = $F&.first; #{expr} }"))
|
|
384
|
-
end
|
|
390
|
+
def compile_evaluator(src, fallback:)
|
|
391
|
+
ExpressionEvaluator.new(src, format: config.format, fallback_proc: fallback)
|
|
385
392
|
end
|
|
386
393
|
|
|
387
394
|
|
|
388
|
-
def
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
395
|
+
def assign_send_aliases
|
|
396
|
+
# Keep ivar aliases — subclasses check these directly
|
|
397
|
+
@send_begin_proc = @send_evaluator.begin_proc
|
|
398
|
+
@send_eval_proc = @send_evaluator.eval_proc
|
|
399
|
+
@send_end_proc = @send_evaluator.end_proc
|
|
393
400
|
end
|
|
394
401
|
|
|
395
402
|
|
|
396
|
-
def
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
# Find the opening brace
|
|
401
|
-
i = expr.index("{", start)
|
|
402
|
-
depth = 1
|
|
403
|
-
j = i + 1
|
|
404
|
-
while j < expr.length && depth > 0
|
|
405
|
-
case expr[j]
|
|
406
|
-
when "{" then depth += 1
|
|
407
|
-
when "}" then depth -= 1
|
|
408
|
-
end
|
|
409
|
-
j += 1
|
|
410
|
-
end
|
|
411
|
-
|
|
412
|
-
body = expr[(i + 1)..(j - 2)]
|
|
413
|
-
trimmed = expr[0...start] + expr[j..]
|
|
414
|
-
[trimmed, body]
|
|
403
|
+
def assign_recv_aliases
|
|
404
|
+
@recv_begin_proc = @recv_evaluator.begin_proc
|
|
405
|
+
@recv_eval_proc = @recv_evaluator.eval_proc
|
|
406
|
+
@recv_end_proc = @recv_evaluator.end_proc
|
|
415
407
|
end
|
|
416
408
|
|
|
417
409
|
|
|
418
|
-
SENT = Object.new.freeze # sentinel: eval already sent the reply
|
|
419
|
-
|
|
420
410
|
def eval_send_expr(parts)
|
|
421
|
-
|
|
422
|
-
run_eval(@send_eval_proc, parts)
|
|
411
|
+
@send_evaluator.call(parts, @sock)
|
|
423
412
|
end
|
|
424
413
|
|
|
425
414
|
|
|
426
415
|
def eval_recv_expr(parts)
|
|
427
|
-
|
|
428
|
-
run_eval(@recv_eval_proc, parts)
|
|
416
|
+
@recv_evaluator.call(parts, @sock)
|
|
429
417
|
end
|
|
430
418
|
|
|
431
419
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
result = @sock.instance_exec(parts, &eval_proc)
|
|
435
|
-
return nil if result.nil?
|
|
436
|
-
return SENT if result.equal?(@sock)
|
|
437
|
-
return [result] if config.format == :marshal
|
|
438
|
-
case result
|
|
439
|
-
when Array then result
|
|
440
|
-
when String then [result]
|
|
441
|
-
else [result.to_str]
|
|
442
|
-
end
|
|
443
|
-
rescue => e
|
|
444
|
-
$stderr.puts "omq: eval error: #{e.message} (#{e.class})"
|
|
445
|
-
exit 3
|
|
446
|
-
end
|
|
420
|
+
SENT = ExpressionEvaluator::SENT
|
|
421
|
+
|
|
447
422
|
|
|
448
423
|
# ── Logging ─────────────────────────────────────────────────────
|
|
449
424
|
|