omq-cli 0.15.1 → 0.15.2

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: '08825b7e8c6acff6b7c8847cd1966def9bfa751a06cfb0a69c36f0bdc8f71b0e'
4
- data.tar.gz: 2d060c1e4bdfea82a807f053ff5457eb392d372fd73e1c4f962b4b90cbbcfa06
3
+ metadata.gz: f6f38a5924268358a3b48021d597248a341e3590ea36a13fc9ee8cc078d2a826
4
+ data.tar.gz: fc3e4b1b13cb85b9e492f6f75e4a6409e5fe2357ee1806bd68abe27954117e41
5
5
  SHA512:
6
- metadata.gz: 5784667a548da85592c289fa905c70290e8e90426815a2fafbf5b833f15159c4d0bdaf1ebb561d50dfe4ed4e801f2e90ffd44c04541a2e62a59e6d17da6f44ef
7
- data.tar.gz: ff42a632e0360a1b926d7833cbbeaa3331a2db0d10a47d0d4b5b5f7ef863d948a58cf5972643872566478c2ca0706b9394f75e49518887dc66ad54b67140d9bc
6
+ metadata.gz: c8b10c0dc4aa93ab2676c8df93cc62026fd08b17cc76d2cb3da73059e0fba443263e9200a9eb65835bd7985bf48e5409fd04dabd0574cbe1b5c1735e14ce0c88
7
+ data.tar.gz: 7899ea0cbfac91674f8bc750cd31faf957bcb9f6b9996261d606d6859322b7c74fdbc2311f3d7dc9ec72355add2f9734f1493d36a4eb86af3bffb6eed110a874
data/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.15.2 — 2026-04-15
4
+
5
+ ### Fixed
6
+
7
+ - **`-vvv` trace output for REQ/REP/PAIR.** `recv_msg` now calls
8
+ `trace_recv` itself, so every runner logs received messages under
9
+ `-vvv` (previously only `run_recv_logic` / `recv_tick` runners did,
10
+ leaving REQ/REP/PAIR receive-side traces silent).
11
+
12
+ ### Tests
13
+
14
+ - **System test for REQ/REP verbose trace order.** Asserts REQ logs
15
+ `>> hi` then `<< HI`, and REP (with `-e'it.first.upcase'`) logs
16
+ `<< hi` then `>> HI`.
17
+ - **Test suite aborts on background-thread exceptions.**
18
+ `Thread.abort_on_exception = true` in `test_helper.rb` — a raising
19
+ spawned thread now re-raises in the main thread immediately instead
20
+ of leaving the test hanging on a receive from a dead peer.
21
+ - **`connect_before_bind_test`**: drop `linger: 1` from
22
+ `OMQ::PULL.new` — PULL is read-only and doesn't accept `linger`.
23
+
3
24
  ## 0.15.0 — 2026-04-14
4
25
 
5
26
  ### Changed
data/README.md CHANGED
@@ -1,33 +1,58 @@
1
- # omq — ZeroMQ CLI
1
+ # omq — Swiss army knife for ØMQ
2
2
 
3
3
  [![Gem Version](https://img.shields.io/gem/v/omq-cli?color=e9573f)](https://rubygems.org/gems/omq-cli)
4
4
  [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](LICENSE)
5
5
  [![Ruby](https://img.shields.io/badge/Ruby-%3E%3D%203.3-CC342D?logo=ruby&logoColor=white)](https://www.ruby-lang.org)
6
6
 
7
- Command-line tool for sending and receiving ZeroMQ messages on any socket type.
8
- Like `nngcat` from libnng, but with Ruby eval, Ractor parallelism, and message handlers.
9
-
10
- Built on [omq](https://github.com/zeromq/omq) — pure Ruby ZeroMQ, no C dependencies.
11
-
12
- ## Install
7
+ A command-line tool that speaks every ZeroMQ socket pattern, transforms
8
+ messages with inline Ruby, parallelizes across Ractors, encrypts with CURVE,
9
+ and reshapes stdin/stdout through three I/O formats on top of three
10
+ on-the-wire formats. Built on [ØMQ](https://github.com/zeromq/omq) — pure
11
+ Ruby, no C dependencies.
13
12
 
14
13
  ```sh
15
14
  gem install omq-cli
16
15
  ```
17
16
 
18
- ## Quick Start
17
+ Think of it as `nngcat` with a scripting engine bolted on. A few things it can do out of the box:
19
18
 
20
19
  ```sh
21
- # Echo server
20
+ # an echo server in one line
22
21
  omq rep -b tcp://:5555 --echo
23
22
 
24
- # Client
25
- echo "hello" | omq req -c tcp://localhost:5555
26
-
27
- # Upcase server — -e evals Ruby on each incoming message
23
+ # upcase every incoming message
28
24
  omq rep -b tcp://:5555 -e 'it.map(&:upcase)'
25
+
26
+ # publish a live timestamp every second
27
+ omq pub -c tcp://localhost:5556 -E 'Time.now.to_s' -i 1
28
+
29
+ # CPU-parallel PULL → transform → PUSH pipeline across all cores
30
+ omq pipe -c@work -c@sink -P0 -r./fib.rb -e 'fib(Integer(it.first)).to_s'
31
+
32
+ # JSON over the wire, filter by field
33
+ omq sub -c tcp://localhost:5556 -rjson -e 'JSON.parse(it.first)["temperature"]'
29
34
  ```
30
35
 
36
+ ## Highlights
37
+
38
+ - **Every socket pattern** — req/rep, pub/sub, push/pull, dealer/router,
39
+ xpub/xsub, pair, and draft types (client/server, radio/dish, scatter/gather,
40
+ peer, channel)
41
+ - **Inline Ruby transforms** — `-e` rewrites incoming messages, `-E` rewrites
42
+ outgoing. `next` / `break` / `nil` for flow control, `BEGIN{}` / `END{}` for
43
+ awk-style aggregation, `-r` to load helper scripts
44
+ - **Ractor-parallel `pipe`** — `-P0` spawns one worker Ractor per core, each
45
+ with its own PULL/PUSH pair. CPU-bound transforms actually scale
46
+ - **I/O formats** — ASCII, quoted, or JSONL for stdin/stdout (display only;
47
+ wire stays plain)
48
+ - **Wire formats** — raw ZMTP, MessagePack, or Ruby Marshal shape the frame
49
+ payload itself, so arbitrary Ruby objects round-trip end-to-end. Optional
50
+ Zstandard compression (`-z`) on top
51
+ - **CURVE encryption** — end-to-end encrypted sockets via libsodium (or nuckle,
52
+ pure Ruby). `omq keygen` generates a persistent keypair
53
+ - **Transient mode** — `--transient` exits cleanly when peers disconnect,
54
+ perfect for pipeline workers and one-shot sinks
55
+
31
56
  ```
32
57
  Usage: omq TYPE [options]
33
58
 
@@ -61,7 +86,8 @@ Pipe takes two positional endpoints (input, output) or uses `--in`/`--out` for m
61
86
  | `scatter` | `gather` | Pipeline (draft, single-frame only) |
62
87
  | `radio` | `dish` | Group messaging (draft, single-frame only) |
63
88
 
64
- Send-only sockets read from stdin (or `--data`/`--file`) and send. Recv-only sockets receive and write to stdout.
89
+ Send-only sockets read from stdin (or `--data`/`--file`) and send. Recv-only
90
+ sockets receive and write to stdout.
65
91
 
66
92
  ```sh
67
93
  echo "task" | omq push -c tcp://worker:5557
@@ -96,8 +122,8 @@ echo "hello" | omq req -c tcp://localhost:5555
96
122
  | `dealer` | Like `pair` but round-robin send to multiple peers |
97
123
  | `channel` | Like `pair` (draft, single-frame) |
98
124
 
99
- These spawn two concurrent tasks: a receiver (prints incoming) and a sender (reads stdin).
100
- `-e` transforms incoming, `-E` transforms outgoing.
125
+ These spawn two concurrent tasks: a receiver (prints incoming) and a sender
126
+ (reads stdin). `-e` transforms incoming, `-E` transforms outgoing.
101
127
 
102
128
  ### Routing sockets
103
129
 
@@ -212,8 +238,7 @@ echo hello | omq push -c tcp://localhost:5557 -E 'it.map(&:upcase)'
212
238
  omq pull -b tcp://:5557 -e 'it.first.include?("error") ? it : nil'
213
239
 
214
240
  # REQ: different transforms per direction
215
- echo hello | omq req -c tcp://localhost:5555 \
216
- -E 'it.map(&:upcase)' -e 'it.map(&:reverse)'
241
+ echo hello | omq req -c tcp://localhost:5555 -E 'it.map(&:upcase)' -e 'it.map(&:reverse)'
217
242
 
218
243
  # generate messages without stdin
219
244
  omq pub -c tcp://localhost:5556 -E 'Time.now.to_s' -i 1
@@ -305,17 +330,32 @@ OMQ.outgoing { |msg| [*msg, Time.now.iso8601] }
305
330
 
306
331
  ## Formats
307
332
 
333
+ Two distinct axes: **I/O formats** reshape how messages are read from stdin
334
+ and printed to stdout, while **wire formats** shape the frame payload that
335
+ actually goes over ZMTP. Pick one of each — they compose freely.
336
+
337
+ ### I/O formats (stdin/stdout only)
338
+
308
339
  | Flag | Format |
309
340
  |------|--------|
310
341
  | `-A` / `--ascii` | Tab-separated frames, non-printable → dots (default) |
311
342
  | `-Q` / `--quoted` | C-style escapes, lossless round-trip |
312
- | `--raw` | Raw ZMTP binary (pipe to `hexdump -C` for debugging) |
313
343
  | `-J` / `--jsonl` | JSON Lines — `["frame1","frame2"]` per line |
314
- | `--msgpack` | MessagePack arrays (binary stream) |
315
- | `-M` / `--marshal` | Ruby Marshal — one arbitrary Ruby object per message |
316
344
 
317
- Multipart messages: in ASCII/quoted mode, frames are tab-separated. In JSONL mode,
318
- each message is a JSON array.
345
+ Display-only: the wire carries plain ZMTP frames. Multipart messages are
346
+ tab-separated in ASCII/quoted mode and encoded as JSON arrays in JSONL.
347
+
348
+ ### Wire formats (on the ZMTP frame payload)
349
+
350
+ | Flag | Format |
351
+ |------|--------|
352
+ | `--raw` | Raw ZMTP binary (pipe to `hexdump -C` for debugging) |
353
+ | `--msgpack` | MessagePack — each frame is one packed object |
354
+ | `-M` / `--marshal` | Ruby Marshal — each frame is one arbitrary Ruby object |
355
+
356
+ Wire formats reshape the payload end-to-end: inside `-e`/`-E`, `it` is the
357
+ decoded object (not an Array of frames), so scalars, hashes, and custom
358
+ classes flow through transparently between peers speaking the same format.
319
359
 
320
360
  ```sh
321
361
  # send multipart via tabs
@@ -326,12 +366,8 @@ echo '["key","value"]' | omq push -c tcp://localhost:5557 -J
326
366
  omq pull -b tcp://:5557 -J
327
367
  ```
328
368
 
329
- Under `-M`, each wire frame is one Marshal-dumped Ruby object. Inside `-e` / `-E`,
330
- `it` is that raw object — not an Array of frames — so scalars, hashes, custom
331
- classes, or any Marshal-safe value flow through transparently:
332
-
333
369
  ```sh
334
- # send a bare String, receive a { string => encoding } Hash
370
+ # send a bare String with Marshal, receive a { string => encoding } Hash
335
371
  omq push -b tcp://:5557 -ME '"foo"'
336
372
  omq pull -c tcp://:5557 -M -e '{it => it.encoding}'
337
373
  # => {"foo" => #<Encoding:UTF-8>}
@@ -413,8 +449,7 @@ omq rep -b tcp://:5555 --echo --curve-server --crypto nuckle
413
449
  omq rep -b tcp://:5555 --echo --curve-server
414
450
 
415
451
  # client (paste the key)
416
- echo "secret" | omq req -c tcp://localhost:5555 \
417
- --curve-server-key '<key from server>'
452
+ echo "secret" | omq req -c tcp://localhost:5555 --curve-server-key '<key from server>'
418
453
  ```
419
454
 
420
455
  Persistent keys via env vars: `OMQ_SERVER_PUBLIC` + `OMQ_SERVER_SECRET` (server), `OMQ_SERVER_KEY` (client).
@@ -289,7 +289,6 @@ module OMQ
289
289
  loop do
290
290
  parts = recv_msg
291
291
  break if parts.nil?
292
- trace_recv(parts)
293
292
  parts = eval_recv_expr(parts)
294
293
  output(parts)
295
294
  i += 1
@@ -316,7 +315,6 @@ module OMQ
316
315
  @recv_tick_eof = true
317
316
  return 0
318
317
  end
319
- trace_recv(parts)
320
318
  parts = eval_recv_expr(parts)
321
319
  output(parts)
322
320
  1
@@ -405,6 +403,7 @@ module OMQ
405
403
  parts = Marshal.load(parts.first)
406
404
  end
407
405
 
406
+ trace_recv(parts)
408
407
  transient_ready!
409
408
  parts
410
409
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module OMQ
4
4
  module CLI
5
- VERSION = "0.15.1"
5
+ VERSION = "0.15.2"
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.15.1
4
+ version: 0.15.2
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.19'
19
- - - ">="
20
- - !ruby/object:Gem::Version
21
- version: 0.19.3
18
+ version: '0.22'
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.19'
29
- - - ">="
30
- - !ruby/object:Gem::Version
31
- version: 0.19.3
25
+ version: '0.22'
32
26
  - !ruby/object:Gem::Dependency
33
27
  name: omq-ffi
34
28
  requirement: !ruby/object:Gem::Requirement
@@ -49,84 +43,84 @@ dependencies:
49
43
  requirements:
50
44
  - - "~>"
51
45
  - !ruby/object:Gem::Version
52
- version: '0.1'
46
+ version: '0.2'
53
47
  type: :runtime
54
48
  prerelease: false
55
49
  version_requirements: !ruby/object:Gem::Requirement
56
50
  requirements:
57
51
  - - "~>"
58
52
  - !ruby/object:Gem::Version
59
- version: '0.1'
53
+ version: '0.2'
60
54
  - !ruby/object:Gem::Dependency
61
55
  name: omq-rfc-radiodish
62
56
  requirement: !ruby/object:Gem::Requirement
63
57
  requirements:
64
58
  - - "~>"
65
59
  - !ruby/object:Gem::Version
66
- version: '0.1'
60
+ version: '0.2'
67
61
  type: :runtime
68
62
  prerelease: false
69
63
  version_requirements: !ruby/object:Gem::Requirement
70
64
  requirements:
71
65
  - - "~>"
72
66
  - !ruby/object:Gem::Version
73
- version: '0.1'
67
+ version: '0.2'
74
68
  - !ruby/object:Gem::Dependency
75
69
  name: omq-rfc-scattergather
76
70
  requirement: !ruby/object:Gem::Requirement
77
71
  requirements:
78
72
  - - "~>"
79
73
  - !ruby/object:Gem::Version
80
- version: '0.1'
74
+ version: '0.2'
81
75
  type: :runtime
82
76
  prerelease: false
83
77
  version_requirements: !ruby/object:Gem::Requirement
84
78
  requirements:
85
79
  - - "~>"
86
80
  - !ruby/object:Gem::Version
87
- version: '0.1'
81
+ version: '0.2'
88
82
  - !ruby/object:Gem::Dependency
89
83
  name: omq-rfc-channel
90
84
  requirement: !ruby/object:Gem::Requirement
91
85
  requirements:
92
86
  - - "~>"
93
87
  - !ruby/object:Gem::Version
94
- version: '0.1'
88
+ version: '0.2'
95
89
  type: :runtime
96
90
  prerelease: false
97
91
  version_requirements: !ruby/object:Gem::Requirement
98
92
  requirements:
99
93
  - - "~>"
100
94
  - !ruby/object:Gem::Version
101
- version: '0.1'
95
+ version: '0.2'
102
96
  - !ruby/object:Gem::Dependency
103
97
  name: omq-rfc-p2p
104
98
  requirement: !ruby/object:Gem::Requirement
105
99
  requirements:
106
100
  - - "~>"
107
101
  - !ruby/object:Gem::Version
108
- version: '0.1'
102
+ version: '0.2'
109
103
  type: :runtime
110
104
  prerelease: false
111
105
  version_requirements: !ruby/object:Gem::Requirement
112
106
  requirements:
113
107
  - - "~>"
114
108
  - !ruby/object:Gem::Version
115
- version: '0.1'
109
+ version: '0.2'
116
110
  - !ruby/object:Gem::Dependency
117
111
  name: omq-rfc-zstd
118
112
  requirement: !ruby/object:Gem::Requirement
119
113
  requirements:
120
114
  - - "~>"
121
115
  - !ruby/object:Gem::Version
122
- version: '0.2'
116
+ version: '0.3'
123
117
  type: :runtime
124
118
  prerelease: false
125
119
  version_requirements: !ruby/object:Gem::Requirement
126
120
  requirements:
127
121
  - - "~>"
128
122
  - !ruby/object:Gem::Version
129
- version: '0.2'
123
+ version: '0.3'
130
124
  - !ruby/object:Gem::Dependency
131
125
  name: msgpack
132
126
  requirement: !ruby/object:Gem::Requirement