omq-cli 0.1.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 +7 -0
- data/LICENSE +15 -0
- data/README.md +464 -0
- data/exe/omq +6 -0
- data/lib/omq/cli/base_runner.rb +477 -0
- data/lib/omq/cli/channel.rb +8 -0
- data/lib/omq/cli/client_server.rb +111 -0
- data/lib/omq/cli/config.rb +55 -0
- data/lib/omq/cli/formatter.rb +75 -0
- data/lib/omq/cli/pair.rb +31 -0
- data/lib/omq/cli/peer.rb +8 -0
- data/lib/omq/cli/pipe.rb +265 -0
- data/lib/omq/cli/pub_sub.rb +14 -0
- data/lib/omq/cli/push_pull.rb +14 -0
- data/lib/omq/cli/radio_dish.rb +27 -0
- data/lib/omq/cli/req_rep.rb +83 -0
- data/lib/omq/cli/router_dealer.rb +76 -0
- data/lib/omq/cli/scatter_gather.rb +14 -0
- data/lib/omq/cli/version.rb +7 -0
- data/lib/omq/cli.rb +544 -0
- metadata +78 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
module CLI
|
|
5
|
+
# Handles encoding/decoding messages in the configured format,
|
|
6
|
+
# plus optional Zstandard compression.
|
|
7
|
+
class Formatter
|
|
8
|
+
def initialize(format, compress: false)
|
|
9
|
+
@format = format
|
|
10
|
+
@compress = compress
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def encode(parts)
|
|
15
|
+
case @format
|
|
16
|
+
when :ascii
|
|
17
|
+
parts.map { |p| p.b.gsub(/[^[:print:]\t]/, ".") }.join("\t") + "\n"
|
|
18
|
+
when :quoted
|
|
19
|
+
parts.map { |p| p.b.dump[1..-2] }.join("\t") + "\n"
|
|
20
|
+
when :raw
|
|
21
|
+
parts.each_with_index.map do |p, i|
|
|
22
|
+
ZMTP::Codec::Frame.new(p.to_s, more: i < parts.size - 1).to_wire
|
|
23
|
+
end.join
|
|
24
|
+
when :jsonl
|
|
25
|
+
JSON.generate(parts) + "\n"
|
|
26
|
+
when :msgpack
|
|
27
|
+
MessagePack.pack(parts)
|
|
28
|
+
when :marshal
|
|
29
|
+
parts.map(&:inspect).join("\t") + "\n"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def decode(line)
|
|
35
|
+
case @format
|
|
36
|
+
when :ascii, :marshal
|
|
37
|
+
line.chomp.split("\t")
|
|
38
|
+
when :quoted
|
|
39
|
+
line.chomp.split("\t").map { |p| "\"#{p}\"".undump }
|
|
40
|
+
when :raw
|
|
41
|
+
[line]
|
|
42
|
+
when :jsonl
|
|
43
|
+
arr = JSON.parse(line.chomp)
|
|
44
|
+
abort "JSON Lines input must be an array of strings" unless arr.is_a?(Array) && arr.all? { |e| e.is_a?(String) }
|
|
45
|
+
arr
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def decode_marshal(io)
|
|
51
|
+
Marshal.load(io)
|
|
52
|
+
rescue EOFError, TypeError
|
|
53
|
+
nil
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def decode_msgpack(io)
|
|
58
|
+
@msgpack_unpacker ||= MessagePack::Unpacker.new(io)
|
|
59
|
+
@msgpack_unpacker.read
|
|
60
|
+
rescue EOFError
|
|
61
|
+
nil
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def compress(parts)
|
|
66
|
+
@compress ? parts.map { |p| Zstd.compress(p) } : parts
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def decompress(parts)
|
|
71
|
+
@compress ? parts.map { |p| Zstd.decompress(p) } : parts
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
data/lib/omq/cli/pair.rb
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
module CLI
|
|
5
|
+
class PairRunner < BaseRunner
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def run_loop(task)
|
|
10
|
+
receiver = task.async do
|
|
11
|
+
n = config.count
|
|
12
|
+
i = 0
|
|
13
|
+
loop do
|
|
14
|
+
parts = recv_msg
|
|
15
|
+
break if parts.nil?
|
|
16
|
+
parts = eval_recv_expr(parts)
|
|
17
|
+
output(parts)
|
|
18
|
+
i += 1
|
|
19
|
+
break if n && n > 0 && i >= n
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
sender = task.async do
|
|
24
|
+
run_send_logic
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
wait_for_loops(receiver, sender)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
data/lib/omq/cli/peer.rb
ADDED
data/lib/omq/cli/pipe.rb
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
module CLI
|
|
5
|
+
class PipeRunner
|
|
6
|
+
attr_reader :config
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def initialize(config)
|
|
10
|
+
@config = config
|
|
11
|
+
@fmt = Formatter.new(config.format, compress: config.compress)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def call(task)
|
|
16
|
+
if config.parallel
|
|
17
|
+
run_parallel
|
|
18
|
+
else
|
|
19
|
+
run_sequential(task)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def resolve_endpoints
|
|
28
|
+
if config.in_endpoints.any?
|
|
29
|
+
[config.in_endpoints, config.out_endpoints]
|
|
30
|
+
else
|
|
31
|
+
[[config.endpoints[0]], [config.endpoints[1]]]
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def attach_endpoints(sock, endpoints)
|
|
37
|
+
endpoints.each { |ep| ep.bind? ? sock.bind(ep.url) : sock.connect(ep.url) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def run_sequential(task)
|
|
42
|
+
in_eps, out_eps = resolve_endpoints
|
|
43
|
+
|
|
44
|
+
@pull = OMQ::PULL.new(linger: config.linger, recv_timeout: config.timeout)
|
|
45
|
+
@push = OMQ::PUSH.new(linger: config.linger, send_timeout: config.timeout)
|
|
46
|
+
@pull.reconnect_interval = config.reconnect_ivl if config.reconnect_ivl
|
|
47
|
+
@push.reconnect_interval = config.reconnect_ivl if config.reconnect_ivl
|
|
48
|
+
@pull.heartbeat_interval = config.heartbeat_ivl if config.heartbeat_ivl
|
|
49
|
+
@push.heartbeat_interval = config.heartbeat_ivl if config.heartbeat_ivl
|
|
50
|
+
|
|
51
|
+
attach_endpoints(@pull, in_eps)
|
|
52
|
+
attach_endpoints(@push, out_eps)
|
|
53
|
+
|
|
54
|
+
compile_expr
|
|
55
|
+
@sock = @pull # for eval instance_exec
|
|
56
|
+
|
|
57
|
+
with_timeout(config.timeout) do
|
|
58
|
+
@push.peer_connected.wait
|
|
59
|
+
@pull.peer_connected.wait
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
if config.transient
|
|
63
|
+
task.async do
|
|
64
|
+
@pull.all_peers_gone.wait
|
|
65
|
+
@pull.reconnect_enabled = false
|
|
66
|
+
@pull.close_read
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
@sock.instance_exec(&@recv_begin_proc) if @recv_begin_proc
|
|
71
|
+
|
|
72
|
+
n = config.count
|
|
73
|
+
i = 0
|
|
74
|
+
loop do
|
|
75
|
+
parts = @pull.receive
|
|
76
|
+
break if parts.nil?
|
|
77
|
+
parts = @fmt.decompress(parts)
|
|
78
|
+
parts = eval_recv_expr(parts)
|
|
79
|
+
if parts && !parts.empty?
|
|
80
|
+
@push.send(@fmt.compress(parts))
|
|
81
|
+
end
|
|
82
|
+
i += 1
|
|
83
|
+
break if n && n > 0 && i >= n
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
@sock.instance_exec(&@recv_end_proc) if @recv_end_proc
|
|
87
|
+
ensure
|
|
88
|
+
@pull&.close
|
|
89
|
+
@push&.close
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def run_parallel
|
|
94
|
+
workers = config.parallel.times.map do
|
|
95
|
+
Ractor.new(config) do |cfg|
|
|
96
|
+
$VERBOSE = nil
|
|
97
|
+
Console.logger = Console::Logger.new(Console::Output::Null.new)
|
|
98
|
+
|
|
99
|
+
Sync do |task|
|
|
100
|
+
# Parse BEGIN/END blocks and per-message expression
|
|
101
|
+
begin_proc = end_proc = eval_proc = nil
|
|
102
|
+
if cfg.recv_expr
|
|
103
|
+
extract = ->(src, kw) {
|
|
104
|
+
s = src.index(/#{kw}\s*\{/)
|
|
105
|
+
return [src, nil] unless s
|
|
106
|
+
i = src.index("{", s); d = 1; j = i + 1
|
|
107
|
+
while j < src.length && d > 0
|
|
108
|
+
d += 1 if src[j] == "{"; d -= 1 if src[j] == "}"
|
|
109
|
+
j += 1
|
|
110
|
+
end
|
|
111
|
+
[src[0...s] + src[j..], src[(i + 1)..(j - 2)]]
|
|
112
|
+
}
|
|
113
|
+
expr, begin_body = extract.(cfg.recv_expr, "BEGIN")
|
|
114
|
+
expr, end_body = extract.(expr, "END")
|
|
115
|
+
begin_proc = eval("proc { #{begin_body} }") if begin_body
|
|
116
|
+
end_proc = eval("proc { #{end_body} }") if end_body
|
|
117
|
+
if expr && !expr.strip.empty?
|
|
118
|
+
ractor_expr = expr.gsub(/\$F\b/, "__F")
|
|
119
|
+
eval_proc = eval("proc { |__F| $_ = __F&.first; #{ractor_expr} }")
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
formatter = OMQ::CLI::Formatter.new(cfg.format, compress: cfg.compress)
|
|
124
|
+
|
|
125
|
+
pull = OMQ::PULL.new(linger: cfg.linger, recv_timeout: cfg.timeout)
|
|
126
|
+
push = OMQ::PUSH.new(linger: cfg.linger, send_timeout: cfg.timeout)
|
|
127
|
+
pull.reconnect_interval = cfg.reconnect_ivl if cfg.reconnect_ivl
|
|
128
|
+
push.reconnect_interval = cfg.reconnect_ivl if cfg.reconnect_ivl
|
|
129
|
+
pull.heartbeat_interval = cfg.heartbeat_ivl if cfg.heartbeat_ivl
|
|
130
|
+
push.heartbeat_interval = cfg.heartbeat_ivl if cfg.heartbeat_ivl
|
|
131
|
+
in_eps = cfg.in_endpoints.any? ? cfg.in_endpoints : [cfg.endpoints[0]]
|
|
132
|
+
out_eps = cfg.out_endpoints.any? ? cfg.out_endpoints : [cfg.endpoints[1]]
|
|
133
|
+
in_eps.each { |ep| pull.connect(ep.url) }
|
|
134
|
+
out_eps.each { |ep| push.connect(ep.url) }
|
|
135
|
+
|
|
136
|
+
if cfg.timeout
|
|
137
|
+
task.with_timeout(cfg.timeout) do
|
|
138
|
+
push.peer_connected.wait
|
|
139
|
+
pull.peer_connected.wait
|
|
140
|
+
end
|
|
141
|
+
else
|
|
142
|
+
push.peer_connected.wait
|
|
143
|
+
pull.peer_connected.wait
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
if cfg.transient
|
|
147
|
+
task.async do
|
|
148
|
+
pull.all_peers_gone.wait
|
|
149
|
+
pull.reconnect_enabled = false
|
|
150
|
+
pull.close_read
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
begin_proc&.call
|
|
155
|
+
|
|
156
|
+
i = 0
|
|
157
|
+
loop do
|
|
158
|
+
parts = pull.receive
|
|
159
|
+
break if parts.nil?
|
|
160
|
+
parts = formatter.decompress(parts)
|
|
161
|
+
if eval_proc
|
|
162
|
+
result = eval_proc.call(parts)
|
|
163
|
+
parts = case result
|
|
164
|
+
when nil then nil
|
|
165
|
+
when Array then result
|
|
166
|
+
when String then [result]
|
|
167
|
+
else [result.to_s]
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
if parts && !parts.empty?
|
|
171
|
+
push.send(formatter.compress(parts))
|
|
172
|
+
end
|
|
173
|
+
i += 1
|
|
174
|
+
break if cfg.count && cfg.count > 0 && i >= cfg.count
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
end_proc&.call
|
|
178
|
+
rescue Async::TimeoutError
|
|
179
|
+
# exit cleanly on timeout
|
|
180
|
+
ensure
|
|
181
|
+
pull&.close
|
|
182
|
+
push&.close
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
workers.each do |w|
|
|
188
|
+
w.value
|
|
189
|
+
rescue Ractor::RemoteError => e
|
|
190
|
+
$stderr.puts "omq: Ractor error: #{e.cause&.message || e.message}"
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def with_timeout(seconds)
|
|
196
|
+
if seconds
|
|
197
|
+
Async::Task.current.with_timeout(seconds) { yield }
|
|
198
|
+
else
|
|
199
|
+
yield
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def compile_expr
|
|
205
|
+
src = config.recv_expr
|
|
206
|
+
return unless src
|
|
207
|
+
expr, begin_body, end_body = extract_blocks(src)
|
|
208
|
+
@recv_begin_proc = eval("proc { #{begin_body} }") if begin_body
|
|
209
|
+
@recv_end_proc = eval("proc { #{end_body} }") if end_body
|
|
210
|
+
@recv_eval_proc = eval("proc { $_ = $F&.first; #{expr} }") if expr && !expr.strip.empty?
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def extract_blocks(expr)
|
|
215
|
+
begin_body = end_body = nil
|
|
216
|
+
expr, begin_body = extract_block(expr, "BEGIN")
|
|
217
|
+
expr, end_body = extract_block(expr, "END")
|
|
218
|
+
[expr, begin_body, end_body]
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def extract_block(expr, keyword)
|
|
223
|
+
start = expr.index(/#{keyword}\s*\{/)
|
|
224
|
+
return [expr, nil] unless start
|
|
225
|
+
|
|
226
|
+
i = expr.index("{", start)
|
|
227
|
+
depth = 1
|
|
228
|
+
j = i + 1
|
|
229
|
+
while j < expr.length && depth > 0
|
|
230
|
+
case expr[j]
|
|
231
|
+
when "{" then depth += 1
|
|
232
|
+
when "}" then depth -= 1
|
|
233
|
+
end
|
|
234
|
+
j += 1
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
body = expr[(i + 1)..(j - 2)]
|
|
238
|
+
trimmed = expr[0...start] + expr[j..]
|
|
239
|
+
[trimmed, body]
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def eval_recv_expr(parts)
|
|
244
|
+
return parts unless @recv_eval_proc
|
|
245
|
+
$F = parts
|
|
246
|
+
result = @sock.instance_exec(&@recv_eval_proc)
|
|
247
|
+
return nil if result.nil? || result.equal?(@sock)
|
|
248
|
+
return [result] if config.format == :marshal
|
|
249
|
+
case result
|
|
250
|
+
when Array then result
|
|
251
|
+
when String then [result]
|
|
252
|
+
else [result.to_str]
|
|
253
|
+
end
|
|
254
|
+
rescue => e
|
|
255
|
+
$stderr.puts "omq: eval error: #{e.message} (#{e.class})"
|
|
256
|
+
exit 3
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def log(msg)
|
|
261
|
+
$stderr.puts(msg) if config.verbose
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
module CLI
|
|
5
|
+
class RadioRunner < BaseRunner
|
|
6
|
+
def run_loop(task) = run_send_logic
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def send_msg(parts)
|
|
13
|
+
return if parts.empty?
|
|
14
|
+
parts = [Marshal.dump(parts)] if config.format == :marshal
|
|
15
|
+
parts = @fmt.compress(parts)
|
|
16
|
+
group = config.group || parts.shift
|
|
17
|
+
@sock.publish(group, parts.first || "")
|
|
18
|
+
transient_ready!
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class DishRunner < BaseRunner
|
|
24
|
+
def run_loop(task) = run_recv_logic
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
module CLI
|
|
5
|
+
class ReqRunner < BaseRunner
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def run_loop(task)
|
|
10
|
+
n = config.count
|
|
11
|
+
i = 0
|
|
12
|
+
sleep(config.delay) if config.delay
|
|
13
|
+
if config.interval
|
|
14
|
+
loop do
|
|
15
|
+
parts = read_next
|
|
16
|
+
break unless parts
|
|
17
|
+
parts = eval_send_expr(parts)
|
|
18
|
+
next unless parts
|
|
19
|
+
send_msg(parts)
|
|
20
|
+
reply = recv_msg
|
|
21
|
+
break if reply.nil?
|
|
22
|
+
reply = eval_recv_expr(reply)
|
|
23
|
+
output(reply)
|
|
24
|
+
i += 1
|
|
25
|
+
break if n && n > 0 && i >= n
|
|
26
|
+
interval = config.interval
|
|
27
|
+
wait = interval - (Time.now.to_f % interval)
|
|
28
|
+
sleep(wait) if wait > 0
|
|
29
|
+
end
|
|
30
|
+
else
|
|
31
|
+
loop do
|
|
32
|
+
parts = read_next
|
|
33
|
+
break unless parts
|
|
34
|
+
parts = eval_send_expr(parts)
|
|
35
|
+
next unless parts
|
|
36
|
+
send_msg(parts)
|
|
37
|
+
reply = recv_msg
|
|
38
|
+
break if reply.nil?
|
|
39
|
+
reply = eval_recv_expr(reply)
|
|
40
|
+
output(reply)
|
|
41
|
+
i += 1
|
|
42
|
+
break if n && n > 0 && i >= n
|
|
43
|
+
break if config.data || config.file
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class RepRunner < BaseRunner
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def run_loop(task)
|
|
55
|
+
n = config.count
|
|
56
|
+
i = 0
|
|
57
|
+
loop do
|
|
58
|
+
msg = recv_msg
|
|
59
|
+
break if msg.nil?
|
|
60
|
+
if config.recv_expr || @recv_eval_proc
|
|
61
|
+
reply = eval_recv_expr(msg)
|
|
62
|
+
unless reply.equal?(SENT)
|
|
63
|
+
output(reply)
|
|
64
|
+
send_msg(reply || [""])
|
|
65
|
+
end
|
|
66
|
+
elsif config.echo
|
|
67
|
+
output(msg)
|
|
68
|
+
send_msg(msg)
|
|
69
|
+
elsif config.data || config.file || !config.stdin_is_tty
|
|
70
|
+
reply = read_next
|
|
71
|
+
break unless reply
|
|
72
|
+
output(msg)
|
|
73
|
+
send_msg(reply)
|
|
74
|
+
else
|
|
75
|
+
abort "REP needs a reply source: --echo, --data, --file, -e, or stdin pipe"
|
|
76
|
+
end
|
|
77
|
+
i += 1
|
|
78
|
+
break if n && n > 0 && i >= n
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
module CLI
|
|
5
|
+
class DealerRunner < PairRunner
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RouterRunner < BaseRunner
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def run_loop(task)
|
|
14
|
+
receiver = task.async do
|
|
15
|
+
n = config.count
|
|
16
|
+
i = 0
|
|
17
|
+
loop do
|
|
18
|
+
parts = recv_msg_raw
|
|
19
|
+
break if parts.nil?
|
|
20
|
+
identity = parts.shift
|
|
21
|
+
parts.shift if parts.first == ""
|
|
22
|
+
parts = @fmt.decompress(parts)
|
|
23
|
+
result = eval_recv_expr([display_routing_id(identity), *parts])
|
|
24
|
+
output(result)
|
|
25
|
+
i += 1
|
|
26
|
+
break if n && n > 0 && i >= n
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
sender = task.async do
|
|
31
|
+
n = config.count
|
|
32
|
+
i = 0
|
|
33
|
+
sleep(config.delay) if config.delay
|
|
34
|
+
if config.interval
|
|
35
|
+
Async::Loop.quantized(interval: config.interval) do
|
|
36
|
+
parts = read_next
|
|
37
|
+
break unless parts
|
|
38
|
+
send_targeted_or_eval(parts)
|
|
39
|
+
i += 1
|
|
40
|
+
break if n && n > 0 && i >= n
|
|
41
|
+
end
|
|
42
|
+
elsif config.data || config.file
|
|
43
|
+
parts = read_next
|
|
44
|
+
send_targeted_or_eval(parts) if parts
|
|
45
|
+
else
|
|
46
|
+
loop do
|
|
47
|
+
parts = read_next
|
|
48
|
+
break unless parts
|
|
49
|
+
send_targeted_or_eval(parts)
|
|
50
|
+
i += 1
|
|
51
|
+
break if n && n > 0 && i >= n
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
wait_for_loops(receiver, sender)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def send_targeted_or_eval(parts)
|
|
61
|
+
if @send_eval_proc
|
|
62
|
+
parts = eval_send_expr(parts)
|
|
63
|
+
return unless parts
|
|
64
|
+
identity = resolve_target(parts.shift)
|
|
65
|
+
payload = @fmt.compress(parts)
|
|
66
|
+
@sock.send([identity, "", *payload])
|
|
67
|
+
elsif config.target
|
|
68
|
+
payload = @fmt.compress(parts)
|
|
69
|
+
@sock.send([resolve_target(config.target), "", *payload])
|
|
70
|
+
else
|
|
71
|
+
send_msg(parts)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|