libtmux-workspace 0.1.0.alpha.1 → 0.1.0.alpha.3
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/README.md +356 -157
- data/assets/logo.png +0 -0
- data/assets/logo.svg +2 -0
- data/lib/libtmux/workspace/cli.rb +13 -2
- data/lib/libtmux/workspace/version.rb +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0c27c9bbe4d2cf44b5cbee027902519d4443858717ac679f12d879b426869d1c
|
|
4
|
+
data.tar.gz: b8da758f2977f2a1b4fb15230951db6e094704b24f117b54d0eae4aa42e2b821
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4688b7848b4408a2a7f397d8468ddbb0581f8e330dbea1472d414b37f12ebf32be957b43a3a3b386b758f3b51ebd1416255ddd2714978441978f3c50e5c909aa
|
|
7
|
+
data.tar.gz: d6fcee3d35cd4261025cd62e6a9beef1b353ccab5bc08c1ceb56f05cd5535a9f02746371f2136e558c662741e4cfa4490395b732d85174543ca26f4f82745aac
|
data/README.md
CHANGED
|
@@ -1,24 +1,54 @@
|
|
|
1
|
+
<!-- libtmux-logo -->
|
|
2
|
+
<div align="center">
|
|
3
|
+
<picture>
|
|
4
|
+
<source srcset="assets/logo.svg" type="image/svg+xml">
|
|
5
|
+
<img src="assets/logo.png" width="128" height="128" alt="libtmux for Ruby workspace">
|
|
6
|
+
</picture>
|
|
7
|
+
</div>
|
|
8
|
+
<!-- /libtmux-logo -->
|
|
9
|
+
|
|
10
|
+
<div align="center">
|
|
11
|
+
|
|
1
12
|
# libtmux-workspace
|
|
2
13
|
|
|
3
|
-
Load bounded YAML or JSON, inspect an immutable
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
14
|
+
[Load][workspace-load] bounded YAML or JSON, inspect an immutable
|
|
15
|
+
[creation plan][workspace-plan-class], then explicitly
|
|
16
|
+
[apply][workspace-plan-apply] it to an open libtmux server.
|
|
17
|
+
|
|
18
|
+
[API reference](https://libtmux.org/en/ruby/latest/workspace/reference/) ·
|
|
19
|
+
[Command reference](https://libtmux.org/en/ruby/latest/workspace/cli/) ·
|
|
20
|
+
[Guides](https://libtmux.org/en/ruby/latest/workspace/guides/) ·
|
|
21
|
+
[RubyGems](https://rubygems.org/gems/libtmux-workspace)
|
|
22
|
+
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
Part of [libtmux for Ruby](https://github.com/libtmux/libtmux-ruby#readme).
|
|
26
|
+
Built on [libtmux](https://github.com/libtmux/libtmux-ruby/tree/master/gems/libtmux).
|
|
27
|
+
|
|
28
|
+
Check a workspace file, see the ordered steps a load would run, and create the
|
|
29
|
+
session on a running tmux server, from the `libtmux-workspace` command or from
|
|
30
|
+
Ruby.
|
|
8
31
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
[contribution guide](../../.github/CONTRIBUTING.md) for local builds and checks.
|
|
12
|
-
Imports and planning do not start tmux or run commands.
|
|
32
|
+
Validation and offline planning need no running daemon, and imports start no
|
|
33
|
+
tmux process.
|
|
13
34
|
|
|
14
|
-
|
|
35
|
+
**Contents** — [Quick start](#quick-start) ·
|
|
36
|
+
[Write the file](#write-the-file) ·
|
|
37
|
+
[Create the session](#create-the-session) ·
|
|
38
|
+
[Failed apply](#recover-from-a-failed-apply) ·
|
|
39
|
+
[Attach or switch](#attach-or-switch-to-the-new-session) ·
|
|
40
|
+
[Scripts](#script-it) · [Leaves out](#what-it-leaves-out) ·
|
|
41
|
+
[Support](#support-and-status) · [Documentation](#documentation)
|
|
42
|
+
|
|
43
|
+
## Quick start
|
|
44
|
+
|
|
45
|
+
Install the alpha and its `libtmux-workspace` executable. It needs Ruby 3.3 or
|
|
46
|
+
newer, and tmux 3.3a or newer to load a plan:
|
|
15
47
|
|
|
16
48
|
```console
|
|
17
49
|
$ gem install libtmux-workspace --pre
|
|
18
50
|
```
|
|
19
51
|
|
|
20
|
-
## Example
|
|
21
|
-
|
|
22
52
|
Save this declared subset of tmuxp-style data as `workspace.yaml`. Relative
|
|
23
53
|
directories resolve against the configuration file's directory.
|
|
24
54
|
|
|
@@ -37,127 +67,235 @@ windows:
|
|
|
37
67
|
```
|
|
38
68
|
<!-- /example -->
|
|
39
69
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
70
|
+
Check the file. [`validate`][cli-validate] reads no tmux state:
|
|
71
|
+
|
|
72
|
+
```console
|
|
73
|
+
$ libtmux-workspace validate workspace.yaml
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
```text
|
|
77
|
+
Workspace configuration is valid.
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Print the ordered operations a load would run. Offline [`plan`][cli-plan] does
|
|
81
|
+
not contact tmux either:
|
|
82
|
+
|
|
83
|
+
```console
|
|
84
|
+
$ libtmux-workspace plan workspace.yaml
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
```text
|
|
88
|
+
offline_create: 11 ordered steps
|
|
89
|
+
1. create_session session (creation)
|
|
90
|
+
2. set_session_option session (configuration)
|
|
91
|
+
3. set_window_option window:0 (configuration)
|
|
92
|
+
4. move_initial_window window:0 (configuration)
|
|
93
|
+
5. split_pane pane:0:0 (creation)
|
|
94
|
+
6. select_layout window:0 (configuration)
|
|
95
|
+
7. send_command pane:0:0 (dispatch_only)
|
|
96
|
+
8. unset_window_option window:0 (configuration)
|
|
97
|
+
9. select_pane pane:0:0 (configuration)
|
|
98
|
+
10. select_window window:0 (configuration)
|
|
99
|
+
11. unset_session_option session (configuration)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Write the file
|
|
44
103
|
|
|
45
|
-
|
|
104
|
+
### [`Workspace.load`][workspace-load]: the declared subset
|
|
105
|
+
|
|
106
|
+
`load` reads a regular file of bounded size and infers JSON or YAML from its
|
|
107
|
+
extension. [`Workspace.parse`][workspace-parse] takes the text and a base
|
|
108
|
+
directory instead. Both return an immutable workspace:
|
|
109
|
+
|
|
110
|
+
- Root: `session_name`, nonempty `windows`, `options`, `window_options`.
|
|
111
|
+
- Window: `window_name`, nonempty `panes`, `window_index`, `focus`, `layout`,
|
|
112
|
+
`options`.
|
|
113
|
+
- Pane: a command string, or a mapping with `focus`, `split` and `size`.
|
|
114
|
+
- Root, windows and panes accept `start_directory`, `environment`,
|
|
115
|
+
`shell_command` and `shell_command_before`.
|
|
116
|
+
- Layouts are `even-horizontal`, `even-vertical`, `main-horizontal`,
|
|
117
|
+
`main-vertical` and `tiled`.
|
|
118
|
+
- At most one window and one pane per window declare focus.
|
|
119
|
+
- Without a filename, the command looks for exactly one `.tmuxp.yaml`,
|
|
120
|
+
`.tmuxp.yml` or `.tmuxp.json` in the current directory.
|
|
121
|
+
- An unknown field or an unsupported feature fails with a
|
|
122
|
+
[`ConfigError`][workspace-configerror] that names its position in the file.
|
|
123
|
+
- YAML tags, anchors and aliases are rejected, and no Ruby, ERB, plugins or
|
|
124
|
+
callbacks run.
|
|
125
|
+
- `${NAME}` substitution is opt-in: pass
|
|
126
|
+
`expand_environment: true, environment: {"NAME" => "value"}` to `load` or
|
|
127
|
+
`parse`, or `--expand-environment --env NAME=VALUE` to the command.
|
|
128
|
+
- [`workspace.to_h`][workspace-to_h] exports an immutable, normalized and
|
|
129
|
+
reloadable configuration, with expanded values and absolute directories.
|
|
130
|
+
|
|
131
|
+
The full field, option and limit lists are in
|
|
132
|
+
[Workspace parsing][behavior-workspace-parsing].
|
|
133
|
+
|
|
134
|
+
## Create the session
|
|
135
|
+
|
|
136
|
+
### [`libtmux-workspace load`][cli-load]: build it on a running server
|
|
137
|
+
|
|
138
|
+
`load` needs a running server. It never starts one, so start a private server
|
|
139
|
+
with a keepalive session and no user configuration:
|
|
140
|
+
|
|
141
|
+
```console
|
|
142
|
+
$ WORKSPACE_TMP=$(mktemp -d)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
```console
|
|
146
|
+
$ tmux -S "$WORKSPACE_TMP/tmux.sock" -f /dev/null new-session -d -s keepalive
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Create the workspace's session on that socket. It stays detached:
|
|
150
|
+
|
|
151
|
+
```console
|
|
152
|
+
$ libtmux-workspace load \
|
|
153
|
+
--socket "$WORKSPACE_TMP/tmux.sock" \
|
|
154
|
+
workspace.yaml
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
```text
|
|
158
|
+
Workspace created; 11 steps completed. Shell commands were dispatched.
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
The new `work` session holds the `editor` window:
|
|
162
|
+
|
|
163
|
+
```console
|
|
164
|
+
$ tmux -S "$WORKSPACE_TMP/tmux.sock" list-windows -t work
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Stop only the private server and remove its directory:
|
|
168
|
+
|
|
169
|
+
```console
|
|
170
|
+
$ tmux -S "$WORKSPACE_TMP/tmux.sock" kill-server
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
```console
|
|
174
|
+
$ rm -rf "$WORKSPACE_TMP"
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Loading the same file again fails with status 1: a plan creates a new session
|
|
178
|
+
and never reconciles an existing one.
|
|
179
|
+
|
|
180
|
+
### [`Plan#apply`][workspace-plan-apply]: the same from Ruby
|
|
181
|
+
|
|
182
|
+
[`Workspace.load`][workspace-load] reads the file,
|
|
183
|
+
[`workspace.plan`][workspace-plan-method] plans it against a snapshot, and
|
|
184
|
+
`apply` creates the session. Calling `apply` authorizes the configuration's
|
|
185
|
+
shell commands:
|
|
186
|
+
|
|
187
|
+
<!-- example: api_workspace_apply/main -->
|
|
46
188
|
```ruby
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
crowded.plan.apply(server: server, compensate: true)
|
|
189
|
+
require "libtmux"
|
|
190
|
+
require "libtmux/workspace"
|
|
191
|
+
|
|
192
|
+
LibTmux::Server.start do |server|
|
|
193
|
+
workspace = LibTmux::Workspace.load(File.expand_path("../workspace.yaml", __dir__))
|
|
194
|
+
plan = workspace.plan(snapshot: server.snapshot)
|
|
195
|
+
puts "planned steps: #{plan.steps.length}"
|
|
196
|
+
result = plan.apply(server: server)
|
|
197
|
+
puts "applied: #{result.success?}"
|
|
198
|
+
puts "sessions: #{server.list_sessions.map { |session| session.snapshot.name }.join(', ')}"
|
|
58
199
|
end
|
|
59
|
-
Example.check(!error.result.created_refs.empty?, "failure lost partial creation ledger")
|
|
60
|
-
Example.check(error.result.compensation == :completed, "owned compensation failed")
|
|
61
|
-
Example.check(server.list_sessions.map(&:ref).include?(borrowed.ref), "borrowed session was removed")
|
|
62
200
|
```
|
|
63
201
|
<!-- /example -->
|
|
64
202
|
|
|
65
|
-
|
|
203
|
+
The program runs next to the `workspace.yaml` above, on a private server, and
|
|
204
|
+
prints:
|
|
66
205
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
206
|
+
```text
|
|
207
|
+
planned steps: 11
|
|
208
|
+
applied: true
|
|
209
|
+
sessions: work
|
|
210
|
+
```
|
|
72
211
|
|
|
73
|
-
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
`
|
|
160
|
-
`.tmuxp.yaml`, `.tmuxp.yml` or `.tmuxp.json` in the current directory.
|
|
212
|
+
- A snapshot makes the plan reject a name that already exists, and every
|
|
213
|
+
`apply` takes a fresh snapshot and checks again.
|
|
214
|
+
- The plan creates a new session. It does not reconcile, replace or remove an
|
|
215
|
+
existing one.
|
|
216
|
+
- Success shows that tmux accepted the dispatch of each shell command. It does
|
|
217
|
+
not show that the command finished.
|
|
218
|
+
|
|
219
|
+
The [complete program](../../examples/workspace_apply.rb) also runs the
|
|
220
|
+
installed command with `--switch`. The contract is in
|
|
221
|
+
[Workspace apply][behavior-workspace-apply].
|
|
222
|
+
|
|
223
|
+
## Recover from a failed apply
|
|
224
|
+
|
|
225
|
+
### [`ApplyError#result`][workspace-applyerror-result]: what exists after a failure
|
|
226
|
+
|
|
227
|
+
A failed apply raises [`ApplyError`][workspace-applyerror]. Its immutable
|
|
228
|
+
`result` holds the completed step IDs, the
|
|
229
|
+
[`created_refs`][workspace-applyresult-created_refs] tmux returned, the
|
|
230
|
+
effects it observed, and the failed action. Pass `compensate: true` to remove
|
|
231
|
+
only the session the apply created:
|
|
232
|
+
|
|
233
|
+
<!-- example: api_workspace_failure/main -->
|
|
234
|
+
```ruby
|
|
235
|
+
require "json"
|
|
236
|
+
require "libtmux"
|
|
237
|
+
require "libtmux/workspace"
|
|
238
|
+
|
|
239
|
+
LibTmux::Server.start do |server|
|
|
240
|
+
server.new_session(name: "borrowed", command: ["/bin/cat"])
|
|
241
|
+
crowded = LibTmux::Workspace.parse(
|
|
242
|
+
JSON.generate({session_name: "crowded", windows: [{window_name: "small", panes: Array.new(40) { {} }}]}),
|
|
243
|
+
format: :json, base_directory: __dir__
|
|
244
|
+
)
|
|
245
|
+
begin
|
|
246
|
+
crowded.plan.apply(server: server, compensate: true)
|
|
247
|
+
rescue LibTmux::Workspace::ApplyError => error
|
|
248
|
+
puts "failed action: #{error.result.failed_action}"
|
|
249
|
+
puts "created a session: #{error.result.created_refs.key?('session')}"
|
|
250
|
+
puts "compensation: #{error.result.compensation}"
|
|
251
|
+
end
|
|
252
|
+
puts "sessions: #{server.list_sessions.map { |session| session.snapshot.name }.join(', ')}"
|
|
253
|
+
end
|
|
254
|
+
```
|
|
255
|
+
<!-- /example -->
|
|
256
|
+
|
|
257
|
+
That many panes do not fit in one window, so tmux refuses a split after the
|
|
258
|
+
session exists. It prints:
|
|
259
|
+
|
|
260
|
+
```text
|
|
261
|
+
failed action: split_pane
|
|
262
|
+
created a session: true
|
|
263
|
+
compensation: completed
|
|
264
|
+
sessions: borrowed
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
- Compensation removes the new session only after a tmux guard shows that
|
|
268
|
+
every window and pane in it has a positively identified created reference.
|
|
269
|
+
- An unknown initial entity, or a borrowed entity moved into the session,
|
|
270
|
+
makes it refuse.
|
|
271
|
+
- Compensation cannot undo what shell commands did. Without it, the partial
|
|
272
|
+
state stays for you to inspect.
|
|
273
|
+
- Diagnostics omit command payloads and paths. Names are never used to guess
|
|
274
|
+
ownership.
|
|
275
|
+
|
|
276
|
+
The command takes `--compensate`. The contracts are in
|
|
277
|
+
[Workspace results][behavior-workspace-results].
|
|
278
|
+
|
|
279
|
+
## Attach or switch to the new session
|
|
280
|
+
|
|
281
|
+
### [`load --switch`][cli-load]: move a client to the session
|
|
282
|
+
|
|
283
|
+
- `load --attach` opens the CLI's `/dev/tty` after creation and runs an owned
|
|
284
|
+
terminal client until you detach. It requires a valid `TERM`.
|
|
285
|
+
- `load --switch CLIENT` switches a current tmux client to the created session
|
|
286
|
+
after apply, keeping the session's environment.
|
|
287
|
+
- `CLIENT` is a client name, a full TTY path or a TTY path without `/dev/`.
|
|
288
|
+
Use `--switch=VALUE` for a selector that begins with `-`.
|
|
289
|
+
- A missing client, or a failure to open or attach the terminal, keeps the
|
|
290
|
+
created session and returns status 3.
|
|
291
|
+
- Attach and switch are mutually exclusive, and neither infers a latest
|
|
292
|
+
client. The library's [`Plan#apply`][workspace-plan-apply] does neither.
|
|
293
|
+
|
|
294
|
+
The selector rules are in [Client switching][behavior-client-switching].
|
|
295
|
+
|
|
296
|
+
## Script it
|
|
297
|
+
|
|
298
|
+
### [`--json`][cli-load]: one object on stdout
|
|
161
299
|
|
|
162
300
|
Check the installed gem version without reading a configuration or contacting
|
|
163
301
|
tmux:
|
|
@@ -166,12 +304,9 @@ tmux:
|
|
|
166
304
|
$ libtmux-workspace --version
|
|
167
305
|
```
|
|
168
306
|
|
|
169
|
-
```console
|
|
170
|
-
$ libtmux-workspace validate workspace.yaml
|
|
171
|
-
```
|
|
172
|
-
|
|
173
307
|
Human plans list ordered operations and their effects. `--json` prints the
|
|
174
|
-
same plan data as `Plan#to_h
|
|
308
|
+
same plan data as [`Plan#to_h`][workspace-plan-to_h], including configured
|
|
309
|
+
command text and paths:
|
|
175
310
|
|
|
176
311
|
```console
|
|
177
312
|
$ libtmux-workspace plan \
|
|
@@ -179,8 +314,9 @@ $ libtmux-workspace plan \
|
|
|
179
314
|
workspace.yaml
|
|
180
315
|
```
|
|
181
316
|
|
|
182
|
-
`load` and `plan --live` require `--socket` for an existing
|
|
183
|
-
detached creation example uses an explicitly supplied
|
|
317
|
+
[`load`][cli-load] and `plan --live` require `--socket` for an existing
|
|
318
|
+
server. This detached creation example uses an explicitly supplied
|
|
319
|
+
`TMUX_SOCKET` value:
|
|
184
320
|
|
|
185
321
|
```console
|
|
186
322
|
$ libtmux-workspace load \
|
|
@@ -189,27 +325,20 @@ $ libtmux-workspace load \
|
|
|
189
325
|
workspace.yaml
|
|
190
326
|
```
|
|
191
327
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
Missing or invalid selector arguments fail before creation. Use
|
|
207
|
-
`--switch=VALUE` for a selector beginning with `-`.
|
|
208
|
-
|
|
209
|
-
The selector is resolved at dispatch; a reconnect matching it is eligible.
|
|
210
|
-
It is not a captured client reference or proof of terminal ownership. Attach
|
|
211
|
-
and switch are mutually exclusive. Neither operation infers a latest client,
|
|
212
|
-
and library `Plan#apply` performs neither operation.
|
|
328
|
+
`libtmux-workspace --help` lists every option. The
|
|
329
|
+
[command reference][ws-cli] has a page for each command.
|
|
330
|
+
|
|
331
|
+
| Option | Effect |
|
|
332
|
+
| --- | --- |
|
|
333
|
+
| `--json` | Write one structured result or error object to stdout |
|
|
334
|
+
| `--socket PATH` | Select the existing server's socket for `load` or `plan --live` |
|
|
335
|
+
| `--live` | Capture a snapshot before planning |
|
|
336
|
+
| `--timeout SECONDS` | Bound each apply, live capture or switch operation; the default is 5 |
|
|
337
|
+
| `--compensate` | Run guarded cleanup after an apply failure |
|
|
338
|
+
| `--attach` | Attach this CLI's terminal after a successful `load` |
|
|
339
|
+
| `--switch CLIENT` | Switch an explicit current client to the new session after `load` |
|
|
340
|
+
| `--expand-environment` | Expand `${NAME}` in directories and environment values; requires `--env` |
|
|
341
|
+
| `--env NAME=VALUE` | Supply a value for expansion; ambient environment variables are not copied |
|
|
213
342
|
|
|
214
343
|
| Exit status | Meaning |
|
|
215
344
|
| --- | --- |
|
|
@@ -219,7 +348,77 @@ and library `Plan#apply` performs neither operation.
|
|
|
219
348
|
| 3 | Application was partial or uncertain, or a later attach/switch/cleanup failed |
|
|
220
349
|
| 130 | Interrupted; available effect ledger is retained |
|
|
221
350
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
351
|
+
Apply errors include [`ApplyResult#to_h`][workspace-applyresult-to_h]. Human
|
|
352
|
+
errors write the diagnostic and any ledger to stderr. Diagnostics omit
|
|
353
|
+
configuration payloads; plan rendering and canonical export contain those
|
|
354
|
+
values by design.
|
|
355
|
+
|
|
356
|
+
## What it leaves out
|
|
357
|
+
|
|
358
|
+
- **Reconciliation.** A plan creates a new session and never replaces or
|
|
359
|
+
removes an existing workspace. The contract is in
|
|
360
|
+
[Workspace plans][behavior-workspace-plans].
|
|
361
|
+
- **Shell command completion.** Apply records a shell command as dispatched.
|
|
362
|
+
Embedded newlines can execute while the text is inserted. See
|
|
363
|
+
[Workspace apply][behavior-workspace-apply].
|
|
364
|
+
- **Rollback of shell effects.** Compensation removes only tmux objects the
|
|
365
|
+
apply created. See [Workspace results][behavior-workspace-results].
|
|
366
|
+
- **Features beyond the declared subset.** Plugins, ERB and callbacks are
|
|
367
|
+
rejected, and a [`ConfigError`][workspace-configerror] names the position.
|
|
368
|
+
- **A guessed client.** Attach and switch need an explicit client. See
|
|
369
|
+
[Client switching][behavior-client-switching].
|
|
370
|
+
|
|
371
|
+
## Support and status
|
|
372
|
+
|
|
373
|
+
**Alpha.** APIs may change between releases.
|
|
374
|
+
|
|
375
|
+
- **Ruby 3.3 or newer.** The gem depends on the same version of `libtmux`,
|
|
376
|
+
JSON 3.0 and Psych 5.5.
|
|
377
|
+
- **tmux 3.3a or newer** to load or apply a plan.
|
|
378
|
+
- **Older daemons.** `load` creates nothing and prints the refusal with the
|
|
379
|
+
floor and the remedy. With `--json`, the refusal is in `error.message` and
|
|
380
|
+
`error.kind` is `tmux_unsupported`.
|
|
381
|
+
- **Linux, including WSL, and macOS.** The library and the command share the
|
|
382
|
+
core [compatibility workflow][compat]: Ruby 3.3, 3.4 and 4.0 with tmux 3.3a
|
|
383
|
+
through 3.8 on Ubuntu 24.04, with per-revision results. macOS is supported on
|
|
384
|
+
a best-effort basis, and CI coverage for it is highly limited.
|
|
385
|
+
- **History limit.** On tmux 3.3a to 3.6 the reused initial pane keeps the
|
|
386
|
+
global `history-limit`. Set the server's global value before you apply to
|
|
387
|
+
get one history size.
|
|
388
|
+
|
|
389
|
+
## Documentation
|
|
390
|
+
|
|
391
|
+
| | |
|
|
392
|
+
| --- | --- |
|
|
393
|
+
| [Reference](https://libtmux.org/en/ruby/latest/workspace/reference/) | Every public workspace class and method |
|
|
394
|
+
| [Command reference][ws-cli] | A page for `validate`, `plan` and `load` |
|
|
395
|
+
| [Guides][workspace-guides] | Validate, inspect and explicitly apply a plan |
|
|
396
|
+
| [Topics][workspace-topics] | Format, planning boundaries, effects and failure ledgers |
|
|
397
|
+
| [Public behavior][behavior-workspace-parsing] | Parsing, plans, apply, results and the command |
|
|
398
|
+
| [Recipes][doc-recipes] | The workspace plan, load and failure program |
|
|
399
|
+
|
|
400
|
+
[behavior-client-switching]: ../../docs/reference/behavior.md#client-switching
|
|
401
|
+
[behavior-workspace-apply]: ../../docs/reference/behavior.md#workspace-apply
|
|
402
|
+
[behavior-workspace-parsing]: ../../docs/reference/behavior.md#workspace-parsing
|
|
403
|
+
[behavior-workspace-plans]: ../../docs/reference/behavior.md#workspace-plans
|
|
404
|
+
[behavior-workspace-results]: ../../docs/reference/behavior.md#workspace-results
|
|
405
|
+
[cli-load]: https://libtmux.org/en/ruby/latest/workspace/cli/load/
|
|
406
|
+
[cli-plan]: https://libtmux.org/en/ruby/latest/workspace/cli/plan/
|
|
407
|
+
[cli-validate]: https://libtmux.org/en/ruby/latest/workspace/cli/validate/
|
|
408
|
+
[compat]: https://github.com/libtmux/libtmux-ruby/actions/workflows/compatibility.yml
|
|
409
|
+
[doc-recipes]: ../../docs/recipes.md
|
|
410
|
+
[workspace-applyerror]: https://libtmux.org/en/ruby/latest/workspace/reference/libtmux-workspace-applyerror/
|
|
411
|
+
[workspace-applyerror-result]: https://libtmux.org/en/ruby/latest/workspace/reference/libtmux-workspace-applyerror-result/
|
|
412
|
+
[workspace-applyresult-created_refs]: https://libtmux.org/en/ruby/latest/workspace/reference/libtmux-workspace-applyresult-created_refs/
|
|
413
|
+
[workspace-applyresult-to_h]: https://libtmux.org/en/ruby/latest/workspace/reference/libtmux-workspace-applyresult-to_h/
|
|
414
|
+
[workspace-configerror]: https://libtmux.org/en/ruby/latest/workspace/reference/libtmux-workspace-configerror/
|
|
415
|
+
[workspace-guides]: https://libtmux.org/en/ruby/latest/workspace/guides/
|
|
416
|
+
[workspace-load]: https://libtmux.org/en/ruby/latest/workspace/reference/libtmux-workspace-load/
|
|
417
|
+
[workspace-parse]: https://libtmux.org/en/ruby/latest/workspace/reference/libtmux-workspace-parse/
|
|
418
|
+
[workspace-plan-apply]: https://libtmux.org/en/ruby/latest/workspace/reference/libtmux-workspace-plan-apply/
|
|
419
|
+
[workspace-plan-class]: https://libtmux.org/en/ruby/latest/workspace/reference/libtmux-workspace-plan-1qko/
|
|
420
|
+
[workspace-plan-method]: https://libtmux.org/en/ruby/latest/workspace/reference/libtmux-workspace-plan-1c5c/
|
|
421
|
+
[workspace-plan-to_h]: https://libtmux.org/en/ruby/latest/workspace/reference/libtmux-workspace-plan-to_h/
|
|
422
|
+
[workspace-to_h]: https://libtmux.org/en/ruby/latest/workspace/reference/libtmux-workspace-to_h/
|
|
423
|
+
[workspace-topics]: https://libtmux.org/en/ruby/latest/workspace/topics/
|
|
424
|
+
[ws-cli]: https://libtmux.org/en/ruby/latest/workspace/cli/
|
data/assets/logo.png
ADDED
|
Binary file
|
data/assets/logo.svg
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 135.46667 135.46666" role="img" aria-label="libtmux for Ruby — workspace CLI" class="libtmux-logo" data-language="ruby" data-variant="workspace" focusable="false"><title>libtmux for Ruby — workspace CLI</title><g transform="translate(-210.28959,-409.98108)" data-part="artwork"><path d="m 334.66493,533.88598 v 11.56176 h 3.76501 c 4.04619,2.1e-4 7.32623,-3.28012 7.32632,-7.32631 v -4.23545 z" fill="#333333" data-part="base-edge"></path><g transform="matrix(1.024,0,0,1.024,-5.0469422,-9.8395302)" data-part="primary-tile"><path d="M 227.78906,0 213.98242,3.6992188 V 460.62109 H 241.4082 V 15.564453 Z" transform="matrix(0.26458333,0,0,0.26458333,210.28959,409.98108)" fill="#2D0002" data-part="primary-edge"></path><path d="m 210.28981,417.13553 v 113.86425 l 60.26885,2.60089 V 409.98109 h -53.11441 c -3.95129,0 -7.15444,3.20315 -7.15444,7.15444 z" fill="#701516" data-part="primary"></path></g><g transform="matrix(1.024,0,0,1.024,-5.0469422,-9.8395302)" data-part="lower-tile"><path d="m 486.38086,246.03906 -5.24805,4.99805 v 207.2207 l 5.24805,-0.93164 L 500,465.18945 V 261.60352 Z" transform="matrix(0.26458333,0,0,0.26458333,210.28959,409.98108)" fill="#67221F" data-part="lower-edge"></path><path d="m 278.70874,475.07917 v 64.8875 l 60.26883,-8.98455 v -55.90295 z" fill="#984640" data-part="lower"></path></g><path d="m 210.28959,533.88598 v 4.23545 c 9e-5,4.04619 3.28012,7.32651 7.32631,7.32631 h 117.12417 c 4.04619,2e-4 7.32621,-3.28012 7.32631,-7.32631 v -4.23545 z" fill="#231F20" data-part="base"></path><g transform="matrix(1.024,0,0,1.024,-5.0469422,-9.8395302)" data-part="upper-tile"><path d="m 459.33984,0 -25.33593,2.1074219 h 21.38281 c 14.93397,0 27.04101,12.1070061 27.04101,27.0410161 l 1.51727,183.649512 2.43542,2.38046 -1.84468,1.91057 15.46424,13.65284 L 500,42.603516 C 499.99913,8.8176763 474.27408,-6.8031496e-4 459.33984,0 Z" transform="matrix(0.26458333,0,0,0.26458333,210.28959,409.98108)" fill="#854A46" data-part="upper-side"></path><path d="m 331.82331,409.98109 h -53.11442 v 56.93261 h 60.26885 v -49.77817 c 0,-3.95129 -3.20315,-7.15444 -7.15443,-7.15444 z" fill="#B6706A" data-part="upper"></path><path d="m 278.7089,466.9137 h 60.26885 l 3.6035,4.11782 h -60.26932 z" fill="#733A36" data-part="upper-bottom"></path></g></g></svg>
|
|
@@ -76,7 +76,7 @@ module LibTmux
|
|
|
76
76
|
rescue Interrupt, LibTmux::Cancelled => error
|
|
77
77
|
report(error, kind: "interrupted", status: 130)
|
|
78
78
|
rescue LibTmux::Error, SystemCallError, IOError => error
|
|
79
|
-
report(error, kind: "execution", status: @result ? 3 : 1)
|
|
79
|
+
report(error, kind: version_refusal?(error) ? "tmux_unsupported" : "execution", status: @result ? 3 : 1)
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
private
|
|
@@ -157,10 +157,21 @@ module LibTmux
|
|
|
157
157
|
@out.puts(@json ? JSON.generate(value) : human)
|
|
158
158
|
end
|
|
159
159
|
|
|
160
|
+
# A refusal before dispatch sent nothing, and its message says what to
|
|
161
|
+
# change, so it replaces the generic failure line.
|
|
162
|
+
def pre_dispatch_refusal?(error)
|
|
163
|
+
error.is_a?(LibTmux::UnsupportedFeatureError) && error.phase == :admission && error.delivery == :not_sent
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Only a refusal that names the tmux floor carries the machine code.
|
|
167
|
+
def version_refusal?(error)
|
|
168
|
+
LibTmux::Internal::TmuxVersion.refusal?(error)
|
|
169
|
+
end
|
|
170
|
+
|
|
160
171
|
def report(error, kind:, status:)
|
|
161
172
|
message = if kind == "arguments"
|
|
162
173
|
@argument_message || "Invalid arguments; use --help for supported options."
|
|
163
|
-
elsif error.is_a?(Workspace::ConfigError) || error.is_a?(Workspace::ApplyError)
|
|
174
|
+
elsif error.is_a?(Workspace::ConfigError) || error.is_a?(Workspace::ApplyError) || pre_dispatch_refusal?(error)
|
|
164
175
|
error.message
|
|
165
176
|
else
|
|
166
177
|
"Workspace #{kind} failed#{error ? " (#{error.class})" : ''}."
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: libtmux-workspace
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.0.alpha.
|
|
4
|
+
version: 0.1.0.alpha.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- libtmux contributors
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - '='
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.1.0.alpha.
|
|
18
|
+
version: 0.1.0.alpha.3
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - '='
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.1.0.alpha.
|
|
25
|
+
version: 0.1.0.alpha.3
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: json
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -60,6 +60,8 @@ extra_rdoc_files: []
|
|
|
60
60
|
files:
|
|
61
61
|
- LICENSE
|
|
62
62
|
- README.md
|
|
63
|
+
- assets/logo.png
|
|
64
|
+
- assets/logo.svg
|
|
63
65
|
- exe/libtmux-workspace
|
|
64
66
|
- lib/libtmux/workspace.rb
|
|
65
67
|
- lib/libtmux/workspace/apply.rb
|
|
@@ -74,6 +76,7 @@ licenses:
|
|
|
74
76
|
- MIT
|
|
75
77
|
metadata:
|
|
76
78
|
source_code_uri: https://github.com/libtmux/libtmux-ruby
|
|
79
|
+
documentation_uri: https://libtmux.org/en/ruby/v0.1.0.alpha.3/workspace/
|
|
77
80
|
rdoc_options: []
|
|
78
81
|
require_paths:
|
|
79
82
|
- lib
|