Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

- Docs: add complete common API examples with private tmux setup and cleanup.

## 0.1.0alpha2-1

- Types: declare `libtmux.Session`, `Window`, `WindowLink`, `Pane`, `Client`
Expand Down
39 changes: 39 additions & 0 deletions examples/api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Complete API examples

Each Lua file is a standalone program. Save it beside [run.sh](run.sh), which
starts a private tmux daemon and stops it after the program finishes or fails.
No existing session is required. The connection closes without stopping the
daemon; the launcher checks that its bootstrap session still exists before
cleanup. A failed daemon shutdown keeps the socket directory and reports it.

Use Lua 5.5.1, LuaRocks, Git, tmux 3.2a or newer, a C compiler, and CMake on
Linux. From the checked-out repository root, install the library and its
standalone runtime dependency into a local tree:

```console
$ luarocks --tree ./rocks install luv 1.52.1-0 && \
luarocks --tree ./rocks make rockspecs/libtmux-scm-1.rockspec && \
eval "$(luarocks --tree ./rocks path)"
```

Run one complete program:

```console
$ sh examples/api/run.sh examples/api/connect.lua
```

| Program | Task | Expected output |
| --- | --- | --- |
| [connect.lua](connect.lua) | Connect to the launcher's daemon | `connected` |
| [snapshot.lua](snapshot.lua) | List sessions, windows, and panes | Two of each |
| [new_session.lua](new_session.lua) | Create a session | `session: demo` |
| [new_window.lua](new_window.lua) | Create a window in that session | `window: logs` |
| [query.lua](query.lua) | Select sessions by name | `matched: demo` |
| [send_keys.lua](send_keys.lua) | Send text and press Enter | `lua input ready` |
| [capture.lua](capture.lua) | Read a completed command's output | `lua capture ready` |

The [manifest](manifest.json) attaches whole files to existing API symbols and
records exact expected output. The native documentation export validates the
targets and includes both files needed for each program. Installed-package
checks run those files outside the checkout; no test helper is imported by
the examples.
42 changes: 42 additions & 0 deletions examples/api/capture.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
local adapter = require("libtmux.runtime.luv")

local function must(value, err)
if err ~= nil then
error(tostring(err), 0)
end
return value
end

local binary = assert(os.getenv("TMUX_BIN"), "set TMUX_BIN to an absolute tmux executable")
local socket = assert(os.getenv("TMUX_SOCKET"), "set TMUX_SOCKET to the private socket")

must(adapter.run(function(runtime)
local server = must(runtime:connect({ binary = binary, socket_path = socket }):await())
local created = must(server:new_session({ name = "demo", argv = { "/bin/sh" } }):await())
local pane = created.pane
local function quote(text)
return "'" .. text:gsub("'", "'\\''") .. "'"
end

-- Signal completion on this socket; do not guess when the shell has printed.
local command = "printf '\\nlua capture ready\\n'; "
.. quote(binary)
.. " -S "
.. quote(socket)
.. " wait-for -S example-ready"
must(pane:send_text(command):await())
must(pane:send_keys({ "Enter" }):await())
must(server:command({ "wait-for", "example-ready" }, { timeout = 1000 }):await())
local capture = must(pane:capture({ history_lines = 20 }):await())
local found = false
for line in must(capture:text()):gmatch("[^\r\n]+") do
if line == "lua capture ready" then
found = true
end
end
assert(found, "completed command did not print the expected line")
print("lua capture ready")

must(server:close():await())
return true
end))
21 changes: 21 additions & 0 deletions examples/api/connect.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
local adapter = require("libtmux.runtime.luv")

local function must(value, err)
if err ~= nil then
error(tostring(err), 0)
end
return value
end

local binary = assert(os.getenv("TMUX_BIN"), "set TMUX_BIN to an absolute tmux executable")
local socket = assert(os.getenv("TMUX_SOCKET"), "set TMUX_SOCKET to the private socket")

must(adapter.run(function(runtime)
local server = must(runtime:connect({ binary = binary, socket_path = socket }):await())
local snapshot = must(server:snapshot({ strict = true }):await())
assert(#snapshot.sessions:where({ name = "bootstrap" }) == 1, "bootstrap session is missing")
print("connected")

must(server:close():await())
return true
end))
77 changes: 77 additions & 0 deletions examples/api/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
{
"schema": 1,
"setup": {
"lua": "5.5.1",
"luv": "1.52.1-0",
"launcher": "examples/api/run.sh"
},
"examples": [
{
"id": "connect",
"symbols": [
"libtmux.Runtime:connect"
],
"file": "examples/api/connect.lua",
"description": "Connect to an existing tmux daemon without taking ownership of it.",
"stdout": "connected\n"
},
{
"id": "snapshot",
"symbols": [
"libtmux.Server:snapshot",
"libtmux.Snapshot.sessions",
"libtmux.Snapshot.windows",
"libtmux.Snapshot.panes"
],
"file": "examples/api/snapshot.lua",
"description": "List sessions, windows, and panes from one captured server snapshot.",
"stdout": "sessions: bootstrap, demo\nwindows: bootstrap, main\npanes: 2\n"
},
{
"id": "new_session",
"symbols": [
"libtmux.Server:new_session"
],
"file": "examples/api/new_session.lua",
"description": "Create a session and use the session, window, and pane handles returned with it.",
"stdout": "session: demo\n"
},
{
"id": "new_window",
"symbols": [
"libtmux.Session:new_window"
],
"file": "examples/api/new_window.lua",
"description": "Create a named window in a particular session.",
"stdout": "window: logs\n"
},
{
"id": "query",
"symbols": [
"libtmux.Server:query"
],
"file": "examples/api/query.lua",
"description": "Find sessions by name with a live query and inspect its captured rows.",
"stdout": "matched: demo\n"
},
{
"id": "send_keys",
"symbols": [
"libtmux.Pane:send_text",
"libtmux.Pane:send_keys"
],
"file": "examples/api/send_keys.lua",
"description": "Send literal text and press Enter in a pane.",
"stdout": "lua input ready\n"
},
{
"id": "capture",
"symbols": [
"libtmux.Pane:capture"
],
"file": "examples/api/capture.lua",
"description": "Capture a completed command's output from a pane.",
"stdout": "lua capture ready\n"
}
]
}
29 changes: 29 additions & 0 deletions examples/api/new_session.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
local adapter = require("libtmux.runtime.luv")

local function must(value, err)
if err ~= nil then
error(tostring(err), 0)
end
return value
end

local binary = assert(os.getenv("TMUX_BIN"), "set TMUX_BIN to an absolute tmux executable")
local socket = assert(os.getenv("TMUX_SOCKET"), "set TMUX_SOCKET to the private socket")

must(adapter.run(function(runtime)
local server = must(runtime:connect({ binary = binary, socket_path = socket }):await())
local created = must(server
:new_session({
name = "demo",
window_name = "main",
argv = { "/bin/cat" },
})
:await())
assert(created.session and created.window and created.pane, "creation handles are missing")
local snapshot = must(created.session:snapshot():await())
assert(snapshot.name == "demo", "created session has the wrong name")
print("session: " .. snapshot.name)

must(server:close():await())
return true
end))
29 changes: 29 additions & 0 deletions examples/api/new_window.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
local adapter = require("libtmux.runtime.luv")

local function must(value, err)
if err ~= nil then
error(tostring(err), 0)
end
return value
end

local binary = assert(os.getenv("TMUX_BIN"), "set TMUX_BIN to an absolute tmux executable")
local socket = assert(os.getenv("TMUX_SOCKET"), "set TMUX_SOCKET to the private socket")

must(adapter.run(function(runtime)
local server = must(runtime:connect({ binary = binary, socket_path = socket }):await())
local created = must(server
:new_session({
name = "demo",
window_name = "main",
argv = { "/bin/cat" },
})
:await())
local logs = must(created.session:new_window({ name = "logs", argv = { "/bin/cat" } }):await())
local snapshot = must(logs.window:snapshot():await())
assert(snapshot.name == "logs", "created window has the wrong name")
print("window: " .. snapshot.name)

must(server:close():await())
return true
end))
36 changes: 36 additions & 0 deletions examples/api/query.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
local adapter = require("libtmux.runtime.luv")

local function must(value, err)
if err ~= nil then
error(tostring(err), 0)
end
return value
end

local binary = assert(os.getenv("TMUX_BIN"), "set TMUX_BIN to an absolute tmux executable")
local socket = assert(os.getenv("TMUX_SOCKET"), "set TMUX_SOCKET to the private socket")

must(adapter.run(function(runtime)
local server = must(runtime:connect({ binary = binary, socket_path = socket }):await())
must(server
:new_session({
name = "demo",
window_name = "main",
argv = { "/bin/cat" },
})
:await())
must(server:new_session({ name = "worker", argv = { "/bin/cat" } }):await())
local result = must(server
:query({
kind = "session",
where = { name = "demo" },
snapshot = { strict = true },
})
:await())
assert(result.complete, "query observed a topology change")
assert(#result.rows == 1 and result.rows[1].name == "demo", "query selected the wrong session")
print("matched: " .. result.rows[1].name)

must(server:close():await())
return true
end))
30 changes: 30 additions & 0 deletions examples/api/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/bin/sh
set -eu

program=${1:?Pass the complete Lua example filename}
binary=$(command -v "${TMUX_BIN:-tmux}")
case "$binary" in
/*) ;;
*) printf '%s\n' 'tmux must resolve to an absolute path' >&2; exit 1 ;;
esac
directory=$(mktemp -d "${TMPDIR:-/tmp}/libtmux-lua-api.XXXXXX")
socket="$directory/tmux.sock"

cleanup() {
status=$?
trap - 0 HUP INT TERM
if [ -S "$socket" ] && ! "$binary" -S "$socket" kill-server; then
printf 'Cannot stop tmux; kept %s\n' "$directory" >&2
exit 1
fi
rm -rf "$directory" || exit 1
exit "$status"
}
trap cleanup 0
trap 'exit 1' HUP INT TERM

unset TMUX TMUX_PANE
export TMUX_BIN="$binary" TMUX_SOCKET="$socket" ENV=/dev/null BASH_ENV=/dev/null
"$binary" -S "$socket" -f /dev/null new-session -d -s bootstrap -n bootstrap /bin/cat
"${LUA_BIN:-lua}" "$program"
"$binary" -S "$socket" has-session -t '=bootstrap'
42 changes: 42 additions & 0 deletions examples/api/send_keys.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
local adapter = require("libtmux.runtime.luv")

local function must(value, err)
if err ~= nil then
error(tostring(err), 0)
end
return value
end

local binary = assert(os.getenv("TMUX_BIN"), "set TMUX_BIN to an absolute tmux executable")
local socket = assert(os.getenv("TMUX_SOCKET"), "set TMUX_SOCKET to the private socket")

must(adapter.run(function(runtime)
local server = must(runtime:connect({ binary = binary, socket_path = socket }):await())
local created = must(server:new_session({ name = "demo", argv = { "/bin/sh" } }):await())
local pane = created.pane
local function quote(text)
return "'" .. text:gsub("'", "'\\''") .. "'"
end

-- Signal completion on this socket; do not guess when the shell has printed.
local command = "printf '\\nlua input ready\\n'; "
.. quote(binary)
.. " -S "
.. quote(socket)
.. " wait-for -S example-ready"
must(pane:send_text(command):await())
must(pane:send_keys({ "Enter" }):await())
must(server:command({ "wait-for", "example-ready" }, { timeout = 1000 }):await())
local capture = must(pane:capture({ history_lines = 20 }):await())
local found = false
for line in must(capture:text()):gmatch("[^\r\n]+") do
if line == "lua input ready" then
found = true
end
end
assert(found, "completed command did not print the expected line")
print("lua input ready")

must(server:close():await())
return true
end))
Loading
Loading