omq-cli 0.12.3 → 0.13.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bf29033480f27f1a249d6f22b85a2f569687e79ed6ddde06a0ef74eacafa0863
4
- data.tar.gz: 4d51ae13241f047758223a2b4f055721d2b1a8809c5ba7c1154868141b1e22e8
3
+ metadata.gz: 0135d1ab507a4e82186cae115a477ccbd4b231e12a7450cb8989a5c0c1479e50
4
+ data.tar.gz: efc832159dc103ec4b18ba5f65acb5976a09c7530b246e5e433795357ca21181
5
5
  SHA512:
6
- metadata.gz: 254ca00deef4d9bd9e4bbafd3cd44f785c27f63446437367b178e76711cdeda33ab779f7880e71181d3d60198458bd1f5741f1890e0d9125d797282dbef96189
7
- data.tar.gz: 642c4eebaedc3d3b606693e40c3cf6112ee4c06b60432c60831bd75b0d3be171d81a2f4232c27a49fbed9c287a061e490dc3a62ebdfd618cf0f7fa3ea9624fff
6
+ metadata.gz: a8093420e5209f9e4672dd15a343c60167959532d90fda0fce7f108042807a65d27f033eb39a3abfee80059e8832d2e90680a624f36575c8b608f2da5095865c
7
+ data.tar.gz: 30dc24c50221b726130e638764d891d2736044b358b364f2bd987a21eceac85bea44ed0821ab601d1070d2e60bc10492a61ffd8baa8129588a0dce06b72ec192
data/CHANGELOG.md CHANGED
@@ -1,5 +1,42 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.13.0 — 2026-04-12
4
+
5
+ ### Added
6
+
7
+ - **`--timestamps[=PRECISION]` flag.** Prefix log lines with UTC
8
+ timestamps. Accepts `s`, `ms` (default), or `us`. Replaces the
9
+ former `-vvvv` special meaning, which has been removed.
10
+ - **`-M` / `--marshal` preserves arbitrary objects on the wire.**
11
+ Eval results in `--format marshal` mode (e.g. `Time.now`, hashes,
12
+ UTF-16LE strings) are now passed through unchanged instead of
13
+ being coerced via `#to_s`. Affects both the main runner and
14
+ Ractor workers (`-P`).
15
+ - **`-P0` ⇒ `nproc`.** `-P0` (or bare `-P`) spawns one Ractor worker
16
+ per CPU, capped at 16. Short-option clustering works: `-P0zvv`
17
+ expands to `-P 0 -z -v -v`.
18
+
19
+ ### Changed
20
+
21
+ - **`-vvv` shows decompressed message previews.** When `--compress`
22
+ is active, message traces now log the decompressed parts and
23
+ include the on-wire size: `(5B wire=21B) hello`. Engine-level
24
+ message events are suppressed in compressed mode to avoid
25
+ double-logging compressed bytes.
26
+ - **Truncation marker uses `…` (U+2026).** `Formatter.preview`
27
+ truncation now uses the real horizontal ellipsis character
28
+ instead of three ASCII dots.
29
+
30
+ ### Fixed
31
+
32
+ - **`FrozenError` in eval handlers returning received parts.**
33
+ `ExpressionEvaluator` no longer mutates the result array with
34
+ `#map!`, which crashed when a script handler returned the frozen
35
+ parts array received from the socket.
36
+ - **README:** fixed broken `if /regex/` examples (no implicit `$_`
37
+ match in `instance_exec`), broken `OMQ.incoming`/`OMQ.outgoing`
38
+ handler table, and `-P` examples that no longer parsed.
39
+
3
40
  ## 0.12.3 — 2026-04-10
4
41
 
5
42
  ### Fixed
data/README.md CHANGED
@@ -127,8 +127,8 @@ Pipe creates an internal PULL → eval → PUSH pipeline:
127
127
  ```sh
128
128
  omq pipe -c@work -c@sink -e 'it.map(&:upcase)'
129
129
 
130
- # with Ractor workers for CPU parallelism
131
- omq pipe -c@work -c@sink -P 4 -r./fib.rb -e 'fib(Integer(it.first)).to_s'
130
+ # with Ractor workers for CPU parallelism (-P0 = nproc)
131
+ omq pipe -c@work -c@sink -P0 -r./fib.rb -e 'fib(Integer(it.first)).to_s'
132
132
  ```
133
133
 
134
134
  The first endpoint is the pull-side (input), the second is the push-side (output).
@@ -171,10 +171,10 @@ omq pull -b tcp://:5557 -e '|(key, value)| "#{key}=#{value}"'
171
171
 
172
172
  ```sh
173
173
  # skip messages matching a pattern
174
- omq pull -b tcp://:5557 -e 'next if /^#/; it'
174
+ omq pull -b tcp://:5557 -e 'next if it.first.start_with?("#"); it'
175
175
 
176
176
  # stop on "quit"
177
- omq pull -b tcp://:5557 -e 'break if /quit/; it'
177
+ omq pull -b tcp://:5557 -e 'break if it.first == "quit"; it'
178
178
  ```
179
179
 
180
180
  ### BEGIN/END blocks
@@ -244,8 +244,8 @@ omq req -c tcp://localhost:5555 -r./handler.rb
244
244
 
245
245
  | Method | Effect |
246
246
  |--------|--------|
247
- | `OMQ.outgoing { |msg| ... }` | Register outgoing message transform |
248
- | `OMQ.incoming { |msg| ... }` | Register incoming message transform |
247
+ | `OMQ.outgoing { \|msg\| ... }` | Register outgoing message transform |
248
+ | `OMQ.incoming { \|msg\| ... }` | Register incoming message transform |
249
249
 
250
250
  - use explicit block variable (like `msg`) or `it`
251
251
  - Setup: use local variables and closures at the top of the script
@@ -441,11 +441,11 @@ Pipe creates an in-process PULL → eval → PUSH pipeline:
441
441
  # basic pipe (positional: first = input, second = output)
442
442
  omq pipe -c@work -c@sink -e 'it.map(&:upcase)'
443
443
 
444
- # parallel Ractor workers (default: all CPUs)
445
- omq pipe -c@work -c@sink -P -r./fib.rb -e 'fib(Integer(it.first)).to_s'
444
+ # parallel Ractor workers (-P0 = nproc, also combinable: -P0zvv)
445
+ omq pipe -c@work -c@sink -P0 -r./fib.rb -e 'fib(Integer(it.first)).to_s'
446
446
 
447
447
  # fixed number of workers
448
- omq pipe -c@work -c@sink -P 4 -e 'it.map(&:upcase)'
448
+ omq pipe -c@work -c@sink -P4 -e 'it.map(&:upcase)'
449
449
 
450
450
  # exit when producer disconnects
451
451
  omq pipe -c@work -c@sink --transient -e 'it.map(&:upcase)'
@@ -467,7 +467,7 @@ omq pipe --in -b tcp://:5555 --out -c@sink1 -c@sink2 -e 'it'
467
467
  omq pipe --in -b tcp://:5555 -b tcp://:5556 --out -c tcp://sink:5557 -e 'it'
468
468
 
469
469
  # parallel workers with fan-in (all must be -c)
470
- omq pipe --in -c@a -c@b --out -c@sink -P 4 -e 'it'
470
+ omq pipe --in -c@a -c@b --out -c@sink -P4 -e 'it'
471
471
  ```
472
472
 
473
473
  `-P`/`--parallel` requires all endpoints to be `--connect`. In parallel mode, each Ractor worker
@@ -99,7 +99,7 @@ module OMQ
99
99
 
100
100
 
101
101
  def attach_endpoints
102
- SocketSetup.attach(@sock, config, verbose: config.verbose)
102
+ SocketSetup.attach(@sock, config, verbose: config.verbose, timestamps: config.timestamps)
103
103
  end
104
104
 
105
105
 
@@ -313,9 +313,15 @@ module OMQ
313
313
 
314
314
  def send_msg(parts)
315
315
  return if parts.empty?
316
+ log_parts = parts
316
317
  parts = [Marshal.dump(parts)] if config.format == :marshal
317
- parts = @fmt.compress(parts)
318
- @sock.send(parts)
318
+ if config.compress
319
+ wire = @fmt.compress(parts)
320
+ trace_msg(">>", log_parts, wire)
321
+ @sock.send(wire)
322
+ else
323
+ @sock.send(parts)
324
+ end
319
325
  transient_ready!
320
326
  end
321
327
 
@@ -324,12 +330,24 @@ module OMQ
324
330
  raw = @sock.receive
325
331
  return nil if raw.nil?
326
332
  parts = @fmt.decompress(raw)
333
+ trace_msg("<<", parts, raw) if config.compress
327
334
  parts = Marshal.load(parts.first) if config.format == :marshal
328
335
  transient_ready!
329
336
  parts
330
337
  end
331
338
 
332
339
 
340
+ # Writes an explicit -vvv message trace line. Used when compression
341
+ # is active: the engine-level monitor would see compressed bytes,
342
+ # so we log post-decompression (recv) / pre-compression (send)
343
+ # and annotate the compressed wire size.
344
+ def trace_msg(marker, parts, wire)
345
+ return unless config.verbose >= 3
346
+ wire_size = wire.sum(&:bytesize)
347
+ $stderr.write("#{Term.log_prefix(config.timestamps)}omq: #{marker} #{Formatter.preview(parts, wire_size: wire_size)}\n")
348
+ end
349
+
350
+
333
351
  def recv_msg_raw
334
352
  msg = @sock.receive
335
353
  msg&.dup
@@ -464,10 +482,13 @@ module OMQ
464
482
 
465
483
  # -vv: log connect/disconnect/retry/timeout events via Socket#monitor
466
484
  # -vvv: also log message sent/received traces
467
- # -vvvv: also prepend ISO8601 µs-precision timestamps
485
+ # --timestamps[=s|ms|us]: prepend UTC timestamps to log lines
468
486
  def start_event_monitor
469
- @sock.monitor(verbose: config.verbose >= 3) do |event|
470
- Term.write_event(event, config.verbose)
487
+ # When compress is on, BaseRunner#trace_msg logs post-
488
+ # decompression, so we suppress the engine-level message trace.
489
+ trace = config.verbose >= 3 && !config.compress
490
+ @sock.monitor(verbose: trace) do |event|
491
+ Term.write_event(event, config.timestamps)
471
492
  end
472
493
  end
473
494
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "socket"
4
+ require "etc"
4
5
 
5
6
  module OMQ
6
7
  module CLI
@@ -231,6 +232,7 @@ module OMQ
231
232
  parallel: nil,
232
233
  transient: false,
233
234
  verbose: 0,
235
+ timestamps: nil,
234
236
  quiet: false,
235
237
  echo: false,
236
238
  scripts: [],
@@ -249,6 +251,25 @@ module OMQ
249
251
  end
250
252
 
251
253
 
254
+ # Splits short-option clusters of the form `-P[digits][letters]`
255
+ # so OptionParser sees `-P[digits]` followed by `-[letters]`.
256
+ # Lets `-P0zvv` mean `-P0 -z -v -v` (portable & combinable).
257
+ # Also rewrites bare `--timestamps` to `--timestamps=ms` so
258
+ # OptionParser doesn't consume the next positional token as its
259
+ # argument.
260
+ #
261
+ def split_parallel_cluster(argv)
262
+ argv.flat_map { |a|
263
+ if a =~ /\A-P(\d*)([a-zA-Z].*)\z/
264
+ n, rest = $1, $2
265
+ n.empty? ? ["-P", "-#{rest}"] : ["-P#{n}", "-#{rest}"]
266
+ else
267
+ a
268
+ end
269
+ }.map { |a| a == "--timestamps" ? "--timestamps=ms" : a }
270
+ end
271
+
272
+
252
273
  # Validates option combinations, aborting on bad combos.
253
274
  #
254
275
  def self.validate!(opts)
@@ -384,8 +405,9 @@ module OMQ
384
405
  require "omq" unless defined?(OMQ::VERSION)
385
406
  opts[:scripts] << (v == "-" ? :stdin : (v.start_with?("./", "../") ? File.expand_path(v) : v))
386
407
  }
387
- o.on("-P", "--parallel N", Integer, "Parallel Ractor workers (max 16)") { |v|
388
- opts[:parallel] = [v, 16].min
408
+ o.on("-P", "--parallel [N]", Integer, "Parallel Ractor workers (0 = nproc, max 16)") { |v|
409
+ n = v.nil? || v.zero? ? Etc.nprocessors : v
410
+ opts[:parallel] = [n, 16].min
389
411
  }
390
412
 
391
413
  o.separator "\nCURVE encryption (requires system libsodium):"
@@ -397,7 +419,10 @@ module OMQ
397
419
  o.separator " OMQ_CRYPTO (backend: rbnacl or nuckle)"
398
420
 
399
421
  o.separator "\nOther:"
400
- o.on("-v", "--verbose", "Verbosity: -v endpoints, -vv events, -vvv messages, -vvvv timestamps") { opts[:verbose] += 1 }
422
+ o.on("-v", "--verbose", "Verbosity: -v endpoints, -vv events, -vvv messages") { opts[:verbose] += 1 }
423
+ o.on( "--timestamps PRECISION", %w[s ms us], "Prefix log lines with UTC timestamp (s/ms/us, default ms)") { |v|
424
+ opts[:timestamps] = v.to_sym
425
+ }
401
426
  o.on("-q", "--quiet", "Suppress message output") { opts[:quiet] = true }
402
427
  o.on( "--transient", "Exit when all peers disconnect") { opts[:transient] = true }
403
428
  o.on( "--ffi", "Use libzmq FFI backend (requires omq-ffi gem + system libzmq 4.x)") do
@@ -427,6 +452,8 @@ module OMQ
427
452
  o.separator "\nExit codes: 0 = success, 1 = error, 2 = timeout"
428
453
  end
429
454
 
455
+ argv = split_parallel_cluster(argv)
456
+
430
457
  begin
431
458
  parser.parse!(argv)
432
459
  rescue OptionParser::ParseError => e
@@ -51,6 +51,7 @@ module OMQ
51
51
  :parallel,
52
52
  :transient,
53
53
  :verbose,
54
+ :timestamps,
54
55
  :quiet,
55
56
  :echo,
56
57
  :scripts,
@@ -49,20 +49,23 @@ module OMQ
49
49
  return [result] if @format == :marshal
50
50
 
51
51
  result = result.is_a?(Array) ? result : [result]
52
- result.map!(&:to_s)
52
+ result.map(&:to_s)
53
53
  rescue => e
54
54
  $stderr.puts "omq: eval error: #{e.message} (#{e.class})"
55
55
  exit 3
56
56
  end
57
57
 
58
58
 
59
- # Normalises an eval result to nil (skip) or an Array of strings.
59
+ # Normalises an eval result to nil (skip) or an Array.
60
60
  # Used inside Ractor worker blocks where instance methods are unavailable.
61
+ # When +format+ is :marshal, arbitrary objects are preserved (wrapped
62
+ # in a one-element Array so the wire path can Marshal.dump them).
61
63
  #
62
- def self.normalize_result(result)
64
+ def self.normalize_result(result, format: nil)
63
65
  return nil if result.nil?
66
+ return [result] if format == :marshal
64
67
  result = result.is_a?(Array) ? result : [result]
65
- result.map!(&:to_s)
68
+ result.map(&:to_s)
66
69
  end
67
70
 
68
71
 
@@ -105,15 +105,19 @@ module OMQ
105
105
 
106
106
 
107
107
  # Formats message parts for human-readable preview (logging).
108
+ # When +wire_size+ is given, the header also shows the
109
+ # compressed on-the-wire size: "(29B wire=12B)".
108
110
  #
109
- # @param parts [Array<String>] message frames
111
+ # @param parts [Array<String>] message frames (decompressed)
112
+ # @param wire_size [Integer, nil] compressed bytes on the wire
110
113
  # @return [String] truncated preview of each frame joined by |
111
- def self.preview(parts)
114
+ def self.preview(parts, wire_size: nil)
112
115
  total = parts.sum(&:bytesize)
113
116
  nparts = parts.size
114
117
  shown = parts.first(3).map { |p| preview_frame(p) }
115
- tail = nparts > 3 ? "|..." : ""
116
- header = nparts > 1 ? "(#{total}B #{nparts}F)" : "(#{total}B)"
118
+ tail = nparts > 3 ? "|…" : ""
119
+ size = wire_size ? "#{total}B wire=#{wire_size}B" : "#{total}B"
120
+ header = nparts > 1 ? "(#{size} #{nparts}F)" : "(#{size})"
117
121
 
118
122
  "#{header} #{shown.join("|")}#{tail}"
119
123
  end
@@ -133,7 +137,7 @@ module OMQ
133
137
  if printable < sample.bytesize / 2
134
138
  "[#{bytes.bytesize}B]"
135
139
  elsif bytes.bytesize > 12
136
- "#{sample.gsub(/[^[:print:]]/, ".")}..."
140
+ "#{sample.gsub(/[^[:print:]]/, ".")}"
137
141
  else
138
142
  sample.gsub(/[^[:print:]]/, ".")
139
143
  end
@@ -51,20 +51,35 @@ module OMQ
51
51
  def log_endpoints
52
52
  return unless @config.verbose >= 1
53
53
  @endpoints.each do |ep|
54
- @log_port.send(OMQ::CLI::Term.format_attach(ep.bind? ? :bind : :connect, ep.url, @config.verbose))
54
+ @log_port.send(OMQ::CLI::Term.format_attach(ep.bind? ? :bind : :connect, ep.url, @config.timestamps))
55
55
  end
56
56
  end
57
57
 
58
58
 
59
59
  def start_monitors
60
60
  return unless @config.verbose >= 2
61
- trace = @config.verbose >= 3
61
+ # When compress is on, messages are traced explicitly post-
62
+ # decompression; suppress engine-level message trace to avoid
63
+ # showing compressed bytes.
64
+ trace = @config.verbose >= 3 && !@config.compress
62
65
  @sock.monitor(verbose: trace) do |event|
63
- @log_port.send(OMQ::CLI::Term.format_event(event, @config.verbose))
66
+ @log_port.send(OMQ::CLI::Term.format_event(event, @config.timestamps))
64
67
  end
65
68
  end
66
69
 
67
70
 
71
+ def trace_in(parts, wire)
72
+ return unless @config.verbose >= 3 && @config.compress
73
+ @log_port.send("#{OMQ::CLI::Term.log_prefix(@config.timestamps)}omq: << #{OMQ::CLI::Formatter.preview(parts, wire_size: wire.sum(&:bytesize))}")
74
+ end
75
+
76
+
77
+ def trace_out(parts, wire)
78
+ return unless @config.verbose >= 3 && @config.compress
79
+ @log_port.send("#{OMQ::CLI::Term.log_prefix(@config.timestamps)}omq: >> #{OMQ::CLI::Formatter.preview(parts, wire_size: wire.sum(&:bytesize))}")
80
+ end
81
+
82
+
68
83
  def wait_for_peer
69
84
  if @config.timeout
70
85
  Fiber.scheduler.with_timeout(@config.timeout) do
@@ -104,9 +119,10 @@ module OMQ
104
119
  n = @config.count
105
120
  i = 0
106
121
  loop do
107
- parts = @sock.receive
108
- break if parts.nil?
109
- parts = @fmt.decompress(parts)
122
+ wire = @sock.receive
123
+ break if wire.nil?
124
+ parts = @fmt.decompress(wire)
125
+ trace_in(parts, wire)
110
126
  if @eval_proc
111
127
  parts = normalize(
112
128
  @ctx.instance_exec(parts, &@eval_proc)
@@ -129,12 +145,15 @@ module OMQ
129
145
  n = @config.count
130
146
  i = 0
131
147
  loop do
132
- parts = @sock.receive
133
- break if parts.nil?
134
- parts = @fmt.decompress(parts)
148
+ wire = @sock.receive
149
+ break if wire.nil?
150
+ parts = @fmt.decompress(wire)
151
+ trace_in(parts, wire)
135
152
  reply = compute_reply(parts)
136
153
  output(reply)
137
- @sock.send(@fmt.compress(reply))
154
+ reply_wire = @fmt.compress(reply)
155
+ trace_out(reply, reply_wire)
156
+ @sock.send(reply_wire)
138
157
  i += 1
139
158
  break if n && n > 0 && i >= n
140
159
  end
@@ -166,7 +185,7 @@ module OMQ
166
185
 
167
186
 
168
187
  def normalize(result)
169
- OMQ::CLI::ExpressionEvaluator.normalize_result(result)
188
+ OMQ::CLI::ExpressionEvaluator.normalize_result(result, format: @config.format)
170
189
  end
171
190
 
172
191
 
data/lib/omq/cli/pipe.rb CHANGED
@@ -85,8 +85,8 @@ module OMQ
85
85
  push = OMQ::PUSH.new(**kwargs)
86
86
  SocketSetup.apply_options(pull, config)
87
87
  SocketSetup.apply_options(push, config)
88
- SocketSetup.attach_endpoints(pull, in_eps, verbose: config.verbose)
89
- SocketSetup.attach_endpoints(push, out_eps, verbose: config.verbose)
88
+ SocketSetup.attach_endpoints(pull, in_eps, verbose: config.verbose, timestamps: config.timestamps)
89
+ SocketSetup.attach_endpoints(push, out_eps, verbose: config.verbose, timestamps: config.timestamps)
90
90
  [pull, push]
91
91
  end
92
92
 
@@ -196,7 +196,7 @@ module OMQ
196
196
  trace = config.verbose >= 3
197
197
  [@pull, @push].each do |sock|
198
198
  sock.monitor(verbose: trace) do |event|
199
- Term.write_event(event, config.verbose)
199
+ Term.write_event(event, config.timestamps)
200
200
  end
201
201
  end
202
202
  end
@@ -49,21 +49,37 @@ module OMQ
49
49
 
50
50
  def log_endpoints
51
51
  (@in_eps + @out_eps).each do |ep|
52
- @log_port.send(OMQ::CLI::Term.format_attach(ep.bind? ? :bind : :connect, ep.url, @config.verbose))
52
+ @log_port.send(OMQ::CLI::Term.format_attach(ep.bind? ? :bind : :connect, ep.url, @config.timestamps))
53
53
  end
54
54
  end
55
55
 
56
56
 
57
57
  def start_monitors
58
- trace = @config.verbose >= 3
58
+ # When compress is on, messages are traced explicitly in
59
+ # run_message_loop post-decompression; keep engine-level
60
+ # traces off to avoid showing compressed bytes.
61
+ compress = @config.compress || @config.compress_in || @config.compress_out
62
+ trace = @config.verbose >= 3 && !compress
59
63
  [@pull, @push].each do |sock|
60
64
  sock.monitor(verbose: trace) do |event|
61
- @log_port.send(OMQ::CLI::Term.format_event(event, @config.verbose))
65
+ @log_port.send(OMQ::CLI::Term.format_event(event, @config.timestamps))
62
66
  end
63
67
  end
64
68
  end
65
69
 
66
70
 
71
+ def trace_in(parts, wire)
72
+ return unless @config.verbose >= 3 && (@config.compress || @config.compress_in)
73
+ @log_port.send("#{OMQ::CLI::Term.log_prefix(@config.timestamps)}omq: << #{OMQ::CLI::Formatter.preview(parts, wire_size: wire.sum(&:bytesize))}")
74
+ end
75
+
76
+
77
+ def trace_out(parts, wire)
78
+ return unless @config.verbose >= 3 && (@config.compress || @config.compress_out)
79
+ @log_port.send("#{OMQ::CLI::Term.log_prefix(@config.timestamps)}omq: >> #{OMQ::CLI::Formatter.preview(parts, wire_size: wire.sum(&:bytesize))}")
80
+ end
81
+
82
+
67
83
  # With --timeout set, fail fast if peers never show up. Without
68
84
  # it, there's no point waiting: PULL#receive blocks naturally
69
85
  # and PUSH buffers up to send_hwm when no peer is present.
@@ -91,39 +107,15 @@ module OMQ
91
107
  n = @config.count
92
108
  if @eval_proc
93
109
  if n && n > 0
94
- n.times do
95
- parts = @pull.receive
96
- break if parts.nil?
97
- parts = OMQ::CLI::ExpressionEvaluator.normalize_result(
98
- @ctx.instance_exec(@fmt_in.decompress(parts), &@eval_proc)
99
- )
100
- next if parts.nil?
101
- @push << @fmt_out.compress(parts) unless parts.empty?
102
- end
110
+ n.times { break unless process_one_eval }
103
111
  else
104
- loop do
105
- parts = @pull.receive
106
- break if parts.nil?
107
- parts = OMQ::CLI::ExpressionEvaluator.normalize_result(
108
- @ctx.instance_exec(@fmt_in.decompress(parts), &@eval_proc)
109
- )
110
- next if parts.nil?
111
- @push << @fmt_out.compress(parts) unless parts.empty?
112
- end
112
+ loop { break unless process_one_eval }
113
113
  end
114
114
  else
115
115
  if n && n > 0
116
- n.times do
117
- parts = @pull.receive
118
- break if parts.nil?
119
- @push << @fmt_out.compress(@fmt_in.decompress(parts))
120
- end
116
+ n.times { break unless process_one_passthrough }
121
117
  else
122
- loop do
123
- parts = @pull.receive
124
- break if parts.nil?
125
- @push << @fmt_out.compress(@fmt_in.decompress(parts))
126
- end
118
+ loop { break unless process_one_passthrough }
127
119
  end
128
120
  end
129
121
  rescue IO::TimeoutError, Async::TimeoutError
@@ -131,10 +123,38 @@ module OMQ
131
123
  end
132
124
 
133
125
 
126
+ def process_one_eval
127
+ wire_in = @pull.receive
128
+ return false if wire_in.nil?
129
+ parts_in = @fmt_in.decompress(wire_in)
130
+ trace_in(parts_in, wire_in)
131
+ parts_out = OMQ::CLI::ExpressionEvaluator.normalize_result(
132
+ @ctx.instance_exec(parts_in, &@eval_proc), format: @config.format
133
+ )
134
+ return true if parts_out.nil? || parts_out.empty?
135
+ wire_out = @fmt_out.compress(parts_out)
136
+ trace_out(parts_out, wire_out)
137
+ @push << wire_out
138
+ true
139
+ end
140
+
141
+
142
+ def process_one_passthrough
143
+ wire_in = @pull.receive
144
+ return false if wire_in.nil?
145
+ parts_in = @fmt_in.decompress(wire_in)
146
+ trace_in(parts_in, wire_in)
147
+ wire_out = @fmt_out.compress(parts_in)
148
+ trace_out(parts_in, wire_out)
149
+ @push << wire_out
150
+ true
151
+ end
152
+
153
+
134
154
  def run_end_block
135
155
  return unless @end_proc
136
156
  out = OMQ::CLI::ExpressionEvaluator.normalize_result(
137
- @ctx.instance_exec(&@end_proc)
157
+ @ctx.instance_exec(&@end_proc), format: @config.format
138
158
  )
139
159
  @push << @fmt_out.compress(out) if out && !out.empty?
140
160
  end
@@ -58,32 +58,32 @@ module OMQ
58
58
 
59
59
 
60
60
  # Bind/connect +sock+ using URL strings from +config.binds+ / +config.connects+.
61
- # +verbose+ is the integer verbosity level (0 = silent).
61
+ # +verbose+ gates logging (>= 1), +timestamps+ controls prefix.
62
62
  #
63
- def self.attach(sock, config, verbose: 0)
63
+ def self.attach(sock, config, verbose: 0, timestamps: nil)
64
64
  config.binds.each do |url|
65
65
  sock.bind(url)
66
- CLI::Term.write_attach(:bind, sock.last_endpoint, verbose) if verbose >= 1
66
+ CLI::Term.write_attach(:bind, sock.last_endpoint, timestamps) if verbose >= 1
67
67
  end
68
68
  config.connects.each do |url|
69
69
  sock.connect(url)
70
- CLI::Term.write_attach(:connect, url, verbose) if verbose >= 1
70
+ CLI::Term.write_attach(:connect, url, timestamps) if verbose >= 1
71
71
  end
72
72
  end
73
73
 
74
74
 
75
75
  # Bind/connect +sock+ from an Array of Endpoint objects.
76
76
  # Used by PipeRunner, which works with structured endpoint lists.
77
- # +verbose+ is the integer verbosity level (0 = silent).
77
+ # +verbose+ gates logging (>= 1), +timestamps+ controls prefix.
78
78
  #
79
- def self.attach_endpoints(sock, endpoints, verbose: 0)
79
+ def self.attach_endpoints(sock, endpoints, verbose: 0, timestamps: nil)
80
80
  endpoints.each do |ep|
81
81
  if ep.bind?
82
82
  sock.bind(ep.url)
83
- CLI::Term.write_attach(:bind, sock.last_endpoint, verbose) if verbose >= 1
83
+ CLI::Term.write_attach(:bind, sock.last_endpoint, timestamps) if verbose >= 1
84
84
  else
85
85
  sock.connect(ep.url)
86
- CLI::Term.write_attach(:connect, ep.url, verbose) if verbose >= 1
86
+ CLI::Term.write_attach(:connect, ep.url, timestamps) if verbose >= 1
87
87
  end
88
88
  end
89
89
  end
data/lib/omq/cli/term.rb CHANGED
@@ -15,15 +15,18 @@ module OMQ
15
15
  module_function
16
16
 
17
17
 
18
- # Returns a stderr log line prefix. At verbose >= 4, prepends an
19
- # ISO8601 UTC timestamp with µs precision so log traces become
20
- # time-correlatable. Otherwise returns the empty string.
18
+ # Returns a stderr log line prefix with a UTC ISO8601 timestamp
19
+ # at the requested precision (:s/:ms/:us), or "" when nil.
21
20
  #
22
- # @param verbose [Integer]
21
+ # @param timestamps [Symbol, nil] :s, :ms, :us, or nil (disabled)
23
22
  # @return [String]
24
- def log_prefix(verbose)
25
- return "" unless verbose && verbose >= 4
26
- "#{Time.now.utc.strftime("%FT%T.%6N")}Z "
23
+ def log_prefix(timestamps)
24
+ case timestamps
25
+ when nil then ""
26
+ when :s then "#{Time.now.utc.strftime("%FT%T")}Z "
27
+ when :ms then "#{Time.now.utc.strftime("%FT%T.%3N")}Z "
28
+ when :us then "#{Time.now.utc.strftime("%FT%T.%6N")}Z "
29
+ end
27
30
  end
28
31
 
29
32
 
@@ -31,10 +34,10 @@ module OMQ
31
34
  # trailing newline).
32
35
  #
33
36
  # @param event [OMQ::MonitorEvent]
34
- # @param verbose [Integer]
37
+ # @param timestamps [Symbol, nil]
35
38
  # @return [String]
36
- def format_event(event, verbose)
37
- prefix = log_prefix(verbose)
39
+ def format_event(event, timestamps)
40
+ prefix = log_prefix(timestamps)
38
41
  case event.type
39
42
  when :message_sent
40
43
  "#{prefix}omq: >> #{Formatter.preview(event.detail[:parts])}"
@@ -52,22 +55,22 @@ module OMQ
52
55
  #
53
56
  # @param kind [:bind, :connect]
54
57
  # @param url [String]
55
- # @param verbose [Integer]
58
+ # @param timestamps [Symbol, nil]
56
59
  # @return [String]
57
- def format_attach(kind, url, verbose)
60
+ def format_attach(kind, url, timestamps)
58
61
  verb = kind == :bind ? "Bound to" : "Connecting to"
59
- "#{log_prefix(verbose)}omq: #{verb} #{url}"
62
+ "#{log_prefix(timestamps)}omq: #{verb} #{url}"
60
63
  end
61
64
 
62
65
 
63
66
  # Writes one formatted event line to +io+ (default $stderr).
64
67
  #
65
68
  # @param event [OMQ::MonitorEvent]
66
- # @param verbose [Integer]
69
+ # @param timestamps [Symbol, nil]
67
70
  # @param io [#write] writable sink, default $stderr
68
71
  # @return [void]
69
- def write_event(event, verbose, io: $stderr)
70
- io.write("#{format_event(event, verbose)}\n")
72
+ def write_event(event, timestamps, io: $stderr)
73
+ io.write("#{format_event(event, timestamps)}\n")
71
74
  end
72
75
 
73
76
 
@@ -76,11 +79,11 @@ module OMQ
76
79
  #
77
80
  # @param kind [:bind, :connect]
78
81
  # @param url [String]
79
- # @param verbose [Integer]
82
+ # @param timestamps [Symbol, nil]
80
83
  # @param io [#write]
81
84
  # @return [void]
82
- def write_attach(kind, url, verbose, io: $stderr)
83
- io.write("#{format_attach(kind, url, verbose)}\n")
85
+ def write_attach(kind, url, timestamps, io: $stderr)
86
+ io.write("#{format_attach(kind, url, timestamps)}\n")
84
87
  end
85
88
  end
86
89
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module OMQ
4
4
  module CLI
5
- VERSION = "0.12.3"
5
+ VERSION = "0.13.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omq-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.3
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrik Wenger
@@ -15,20 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '0.17'
19
- - - ">="
20
- - !ruby/object:Gem::Version
21
- version: 0.17.8
18
+ version: '0.18'
22
19
  type: :runtime
23
20
  prerelease: false
24
21
  version_requirements: !ruby/object:Gem::Requirement
25
22
  requirements:
26
23
  - - "~>"
27
24
  - !ruby/object:Gem::Version
28
- version: '0.17'
29
- - - ">="
30
- - !ruby/object:Gem::Version
31
- version: 0.17.8
25
+ version: '0.18'
32
26
  - !ruby/object:Gem::Dependency
33
27
  name: omq-ffi
34
28
  requirement: !ruby/object:Gem::Requirement