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: 1 addition & 1 deletion packages/api-model/src/concepts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4053,7 +4053,7 @@ for (const [id, binding] of Object.entries(ERLANG_CONCEPTS)) {
CONCEPTS['list-session-panes']!.absent!.julia = 'Traverse `windows(session)` and `panes(window)` in the captured snapshot. `panes(snapshot)` has server-wide scope.'

const OCAML_CONCEPTS: Record<string, { symbol: string; note?: string; variants?: string[] }> = {
server: { symbol: 'Libtmux_unix.Client.t', variants: ['Libtmux_eio.Client.t'], note: 'A client is a live, generation-bound server connection. Endpoint describes a socket without connecting; closing a client releases its resources.' },
server: { symbol: 'Libtmux_unix.Client', variants: ['Libtmux_eio.Client'], note: 'A client is a live, generation-bound server connection. Endpoint describes a socket without connecting; closing a client releases its resources.' },
session: { symbol: 'Libtmux.Session.t' },
window: { symbol: 'Libtmux.Window.t', note: 'A global window can have several session placements. Window_link holds the session, index and current flag.' },
pane: { symbol: 'Libtmux.Pane.t' },
Expand Down
10 changes: 8 additions & 2 deletions packages/api-model/src/languages/lisp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,13 +239,19 @@ export function extractCommonLisp(input: unknown, revision?: string): ApiModel {
kind: 'module', modifiers: [], signatures: [], product: pkg.product, package: pkg.system,
publicOwner: pkg.name, apiScope: 'exported', doc: documentation(pkg.documentation), source: source(pkg.source),
})
// A function whose first required parameter is the client operates on it, so the client's page lists it as a
// member, as other ports' server pages list their methods.
const client = pkg.symbols.find((symbol) => symbol.kind === 'class' && symbol.name === 'client')?.id
for (const symbol of pkg.symbols) {
const params = symbol.signature ? parameters(symbol.signature) : []
const operation = client && symbol.kind === 'function' && !symbol.modifiers.includes('macro') &&
params[0]?.name.toLowerCase() === 'client' && !params[0].optional && !params[0].variadic
symbols.push({
id: symbol.id, publicId: symbol.id, qualifiedName: `${pkg.name}:${symbol.name}`,
namespace: pkg.name, name: symbol.name, kind: symbol.kind as SymbolKind,
modifiers: symbol.modifiers as Modifier[], parent: pkg.name,
modifiers: symbol.modifiers as Modifier[], parent: operation ? client : pkg.name,
signatures: [
...(symbol.signature ? [{ raw: symbol.signature, params: parameters(symbol.signature) }] : []),
...(symbol.signature ? [{ raw: symbol.signature, params }] : []),
...(symbol.methods ?? []).map((method) => methodSignature(symbol.id, method, names)),
],
product: pkg.product, package: pkg.system, publicOwner: pkg.name, apiScope: 'exported',
Expand Down
2 changes: 1 addition & 1 deletion packages/api-model/test/concepts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ describe('concept map', () => {
['powershell', 'LibTmux\\New-TmuxServer', 'function'],
['julia', 'LibTmux.Server', 'struct'],
['fsharp', 'LibTmux.FSharp.Server', 'module'],
['ocaml', 'Libtmux_unix.Client.t', 'typealias'],
['ocaml', 'Libtmux_unix.Client', 'module'],
]
for (const [port, id, kind] of expected) {
const symbol = models.get(port)!.symbols.find((symbol) => (symbol.publicId ?? symbol.id) === id)!
Expand Down
21 changes: 21 additions & 0 deletions packages/api-model/test/lisp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,27 @@ describe('Common Lisp native API adapter', () => {
.toEqual(['libtmux.workspace:result-outcomes'])
})

it('lists the operations that take a client as members of its class', () => {
const operation = (name: string, signature: string) => ({ ...functionSymbol(`libtmux:${name}`, name, 'src/client.lisp'), signature })
const withClient = structuredClone(fixture)
withClient.source.files.push({ path: 'src/client.lisp', sha256: '9'.repeat(64) })
withClient.packages[0].symbols.push(
{ id: 'libtmux:client', name: 'client', kind: 'class', modifiers: [], supertypes: [],
documentation: documentation('A connection scope.'), source: source('src/client.lisp', 1) },
operation('fetch-snapshot', '(fetch-snapshot client &key timeout)'),
operation('open-client', '(open-client &key socket-path)'),
operation('client-label', '(client-label value client)'),
)
withClient.packages[1].symbols.push(functionSymbol('libtmux.json:write-client', 'write-client', 'src/json.lisp'))
const parents = Object.fromEntries(extractCommonLisp(withClient, revision).symbols.map((entry) => [entry.id, entry.parent]))
expect(parents['libtmux:fetch-snapshot']).toBe('libtmux:client')
// The controls: no client, a client that is not the first parameter, and another package's function.
expect(parents['libtmux:open-client']).toBe('libtmux')
expect(parents['libtmux:client-label']).toBe('libtmux')
expect(parents['libtmux:session/function']).toBe('libtmux')
expect(parents['libtmux.json:write-client']).toBe('libtmux.json')
})

it('extracts the optional public control package alongside existing systems', () => {
const input = structuredClone(fixture)
input.source.files.push({ path: 'src/control.lisp', sha256: '1'.repeat(64) })
Expand Down
25 changes: 22 additions & 3 deletions scripts/lib/badges.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const BADGE_DIRECTORY = 'badges'
* a Maven group).
*/
const VERSION_SERVICES = {
cpan: { label: 'cpan', qualifiers: 0 },
gem: { label: 'gem', qualifiers: 0 },
crates: { label: 'crates.io', qualifiers: 0 },
hexpm: { label: 'hex', qualifiers: 0 },
Expand All @@ -38,6 +39,9 @@ const VERSION_SERVICES = {

const OFF_ORIGIN = /^https?:\/\/(?:img\.shields\.io|badgen\.net|badge\.fury\.io|(?:www\.)?github\.com\/[^/]+\/[^/]+\/(?:actions\/workflows\/[^/]+|workflows\/[^/]+)\/badge\.svg|github\.com\/[^/]+\/[^/]+\/workflows\/.*\/badge\.svg)/i

/** An `<img>` with its source, and the `<a>` around it when it is a link: groups are the opening tag, quote, source and closing tag. */
const HTML_IMAGE = /(<a\b[^>]*>\s*)?<img\b[^>]*?\bsrc=(["'])([^"']+)\2[^>]*>(\s*<\/a>)?/gi

export function isOffOriginBadge(url) {
return OFF_ORIGIN.test(url)
}
Expand Down Expand Up @@ -114,17 +118,32 @@ export function readRegistry() {
export function localizeBadges(content, port, registry) {
const files = new Map()
const edits = []
const publish = (spec) => {
const svg = makeBadge({ label: spec.label, message: spec.message, color: spec.color })
const name = `${BADGE_DIRECTORY}/${createHash('sha256').update(svg).digest('hex').slice(0, 12)}.svg`
files.set(name, svg)
return name
}
const visit = (node, parent) => {
if (node.type === 'html') {
// A README written as HTML, as the Erlang one is for ex_doc, holds `<a><img></a>` badges.
const value = node.value.replace(HTML_IMAGE, (image, open, quote, url, close) => {
if (!isOffOriginBadge(url)) return image
const spec = badgeSpec(url, { port, registry })
if (!spec) return open && close ? '' : (open ?? '')
return image.replace(`src=${quote}${url}${quote}`, `src=${quote}/${publish(spec)}${quote}`)
})
if (value !== node.value) edits.push({ start: node.position.start.offset, end: node.position.end.offset, text: value })
return
}
if (node.type === 'image' && isOffOriginBadge(node.url)) {
const spec = badgeSpec(node.url, { port, registry })
const droppable = parent?.type === 'link' && parent.children.length === 1 ? parent : node
if (!spec) {
edits.push({ start: droppable.position.start.offset, end: droppable.position.end.offset, text: '' })
return
}
const svg = makeBadge({ label: spec.label, message: spec.message, color: spec.color })
const name = `${BADGE_DIRECTORY}/${createHash('sha256').update(svg).digest('hex').slice(0, 12)}.svg`
files.set(name, svg)
const name = publish(spec)
node.url = `/${name}`
node.alt = node.alt || `${spec.label}: ${spec.message}`
edits.push({ start: node.position.start.offset, end: node.position.end.offset, text: toMarkdown(node).trimEnd() })
Expand Down
7 changes: 5 additions & 2 deletions scripts/stage-port-docs.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,11 @@ function titleAndBody(content, sourcePath) {
// GitHub README headers may center the title and intro in a presentation
// wrapper. The site supplies its own heading; later HTML stays intact.
const centered = /^<div align=["']center["']>\s*\n([\s\S]*?)\n<\/div>\s*/.exec(content)
if (centered && /^#\s/.test(centered[1])) {
content = `${centered[1]}\n\n${content.slice(centered[0].length)}`
if (centered) {
// The Erlang README writes the centered title as <h1> so ex_doc renders it.
const html = /^<h1>([^<\n]+)<\/h1>[ \t]*(?:\n|$)/.exec(centered[1])
const inner = html ? `# ${html[1]}\n${centered[1].slice(html[0].length)}` : centered[1]
if (/^#\s/.test(inner)) content = `${inner}\n\n${content.slice(centered[0].length)}`
}
const match = /^#\s+(.+)\n+/.exec(content)
return {
Expand Down
35 changes: 19 additions & 16 deletions site/src/content/docs/ports/erlang/examples/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
port: erlang
route: examples
title: Examples
description: Run the fifteen tested Erlang programs against private tmux servers and read each one's guide.
description: Run the eighteen tested Erlang programs against private tmux servers and read each one's guide.
---

Each program below is a module in the repository's `examples/` directory. The
Expand All @@ -15,28 +15,31 @@ adapting a program to an existing server.

| Program | What it demonstrates | Guide |
| --- | --- | --- |
| [inspect_demo.erl](https://github.com/libtmux/libtmux-erlang/blob/539c7297ceae65a1cf683e0836eae0a1ccd34d64/examples/inspect_demo.erl) | Inspect an existing tmux socket without changing its sessions. | [Installation](../guides/installation/) |
| [readme_demo.erl](https://github.com/libtmux/libtmux-erlang/blob/539c7297ceae65a1cf683e0836eae0a1ccd34d64/examples/readme_demo.erl) | Create a workspace, query its graph and preserve captured values. | [Getting started](../guides/getting-started/) |
| [linked_graph.erl](https://github.com/libtmux/libtmux-erlang/blob/539c7297ceae65a1cf683e0836eae0a1ccd34d64/examples/linked_graph.erl) | Distinguish physical windows from their session memberships. | [Object graph](../concepts/object-graph/) |
| [filter_demo.erl](https://github.com/libtmux/libtmux-erlang/blob/539c7297ceae65a1cf683e0836eae0a1ccd34d64/examples/filter_demo.erl) | Validate portable JSON and filter captured rows without tmux I/O. | [Selections and queries](../concepts/queries/) |
| [control_demo.erl](https://github.com/libtmux/libtmux-erlang/blob/539c7297ceae65a1cf683e0836eae0a1ccd34d64/examples/control_demo.erl) | Attach explicitly, inspect captured values and prove detachment. | [Control connections](../topics/control/) |
| [native_consumer.erl](https://github.com/libtmux/libtmux-erlang/blob/539c7297ceae65a1cf683e0836eae0a1ccd34d64/examples/native_consumer.erl) | Construct native values and exercise a borrowed endpoint through public APIs. | Installed-consumer check |
| [inspect_demo.erl](https://github.com/libtmux/libtmux-erlang/blob/33fd7fe985454e375d5ac8533043cabf4a0b2fa5/examples/inspect_demo.erl) | Inspect an existing tmux socket without changing its sessions. | [Installation](../guides/installation/) |
| [readme_demo.erl](https://github.com/libtmux/libtmux-erlang/blob/33fd7fe985454e375d5ac8533043cabf4a0b2fa5/examples/readme_demo.erl) | Create a workspace, query its graph and preserve captured values. | [Getting started](../guides/getting-started/) |
| [linked_graph.erl](https://github.com/libtmux/libtmux-erlang/blob/33fd7fe985454e375d5ac8533043cabf4a0b2fa5/examples/linked_graph.erl) | Distinguish physical windows from their session memberships. | [Object graph](../concepts/object-graph/) |
| [filter_demo.erl](https://github.com/libtmux/libtmux-erlang/blob/33fd7fe985454e375d5ac8533043cabf4a0b2fa5/examples/filter_demo.erl) | Validate portable JSON and filter captured rows without tmux I/O. | [Selections and queries](../concepts/queries/) |
| [plan_demo.erl](https://github.com/libtmux/libtmux-erlang/blob/33fd7fe985454e375d5ac8533043cabf4a0b2fa5/examples/plan_demo.erl) | Explain a query, let tmux exclude candidates, then fetch it. | [Selections and queries](../concepts/queries/) |
| [control_demo.erl](https://github.com/libtmux/libtmux-erlang/blob/33fd7fe985454e375d5ac8533043cabf4a0b2fa5/examples/control_demo.erl) | Attach explicitly, inspect captured values and prove detachment. | [Control connections](../topics/control/) |
| [native_consumer.erl](https://github.com/libtmux/libtmux-erlang/blob/33fd7fe985454e375d5ac8533043cabf4a0b2fa5/examples/native_consumer.erl) | Construct native values and exercise a borrowed endpoint through public APIs. | Installed-consumer check |

## Run, send and capture

| Program | What it demonstrates | Guide |
| --- | --- | --- |
| [job_demo.erl](https://github.com/libtmux/libtmux-erlang/blob/539c7297ceae65a1cf683e0836eae0a1ccd34d64/examples/job_demo.erl) | Capture a job that acknowledges terminal parsing before it exits. | [Owned operations](../guides/operations/) |
| [pipe_demo.erl](https://github.com/libtmux/libtmux-erlang/blob/539c7297ceae65a1cf683e0836eae0a1ccd34d64/examples/pipe_demo.erl) | Save terminal bytes after a consumer acknowledgement. | [Pipe pane output](../guides/piping/) |
| [pane_demo.erl](https://github.com/libtmux/libtmux-erlang/blob/539c7297ceae65a1cf683e0836eae0a1ccd34d64/examples/pane_demo.erl) | Isolate a running pane, then join it back without respawning. | [Panes](../guides/panes/) |
| [command_demo.erl](https://github.com/libtmux/libtmux-erlang/blob/539c7297ceae65a1cf683e0836eae0a1ccd34d64/examples/command_demo.erl) | Build captured-target commands, then execute and inspect them. | [Commands](../guides/commands/) |
| [job_demo.erl](https://github.com/libtmux/libtmux-erlang/blob/33fd7fe985454e375d5ac8533043cabf4a0b2fa5/examples/job_demo.erl) | Capture a job that acknowledges terminal parsing before it exits. | [Owned operations](../guides/operations/) |
| [pipe_demo.erl](https://github.com/libtmux/libtmux-erlang/blob/33fd7fe985454e375d5ac8533043cabf4a0b2fa5/examples/pipe_demo.erl) | Save terminal bytes after a consumer acknowledgement. | [Pipe pane output](../guides/piping/) |
| [pane_demo.erl](https://github.com/libtmux/libtmux-erlang/blob/33fd7fe985454e375d5ac8533043cabf4a0b2fa5/examples/pane_demo.erl) | Isolate a running pane, then join it back without respawning. | [Panes](../guides/panes/) |
| [command_demo.erl](https://github.com/libtmux/libtmux-erlang/blob/33fd7fe985454e375d5ac8533043cabf4a0b2fa5/examples/command_demo.erl) | Build captured-target commands, then execute and inspect them. | [Commands](../guides/commands/) |
| [typing_demo.erl](https://github.com/libtmux/libtmux-erlang/blob/33fd7fe985454e375d5ac8533043cabf4a0b2fa5/examples/typing_demo.erl) | Type a command into a shell pane, wait for it, then read the screen. | [Getting started](../guides/getting-started/) |
| [batch_demo.erl](https://github.com/libtmux/libtmux-erlang/blob/33fd7fe985454e375d5ac8533043cabf4a0b2fa5/examples/batch_demo.erl) | Run several commands in one tmux client and read an option batch. | [Getting started](../guides/getting-started/) |

## Options, hooks, keys and buffers

| Program | What it demonstrates | Guide |
| --- | --- | --- |
| [options_demo.erl](https://github.com/libtmux/libtmux-erlang/blob/539c7297ceae65a1cf683e0836eae0a1ccd34d64/examples/options_demo.erl) | Use local options to mark workspace ownership without inheritance. | [Options](../guides/options/) |
| [config_demo.erl](https://github.com/libtmux/libtmux-erlang/blob/539c7297ceae65a1cf683e0836eae0a1ccd34d64/examples/config_demo.erl) | Apply session configuration and wait for its explicit signal. | [Configuration](../guides/configuration/) |
| [hook_demo.erl](https://github.com/libtmux/libtmux-erlang/blob/539c7297ceae65a1cf683e0836eae0a1ccd34d64/examples/hook_demo.erl) | Acknowledge a registered split hook before reading its effect. | [Hooks](../guides/hooks/) |
| [key_demo.erl](https://github.com/libtmux/libtmux-erlang/blob/539c7297ceae65a1cf683e0836eae0a1ccd34d64/examples/key_demo.erl) | Install and remove an application-owned key table. | [Keys](../guides/keys/) |
| [buffer_demo.erl](https://github.com/libtmux/libtmux-erlang/blob/539c7297ceae65a1cf683e0836eae0a1ccd34d64/examples/buffer_demo.erl) | Store a binary artifact without terminal or text conversion. | [Buffers](../guides/buffers/) |
| [options_demo.erl](https://github.com/libtmux/libtmux-erlang/blob/33fd7fe985454e375d5ac8533043cabf4a0b2fa5/examples/options_demo.erl) | Use local options to mark workspace ownership without inheritance. | [Options](../guides/options/) |
| [config_demo.erl](https://github.com/libtmux/libtmux-erlang/blob/33fd7fe985454e375d5ac8533043cabf4a0b2fa5/examples/config_demo.erl) | Apply session configuration and wait for its explicit signal. | [Configuration](../guides/configuration/) |
| [hook_demo.erl](https://github.com/libtmux/libtmux-erlang/blob/33fd7fe985454e375d5ac8533043cabf4a0b2fa5/examples/hook_demo.erl) | Acknowledge a registered split hook before reading its effect. | [Hooks](../guides/hooks/) |
| [key_demo.erl](https://github.com/libtmux/libtmux-erlang/blob/33fd7fe985454e375d5ac8533043cabf4a0b2fa5/examples/key_demo.erl) | Install and remove an application-owned key table. | [Keys](../guides/keys/) |
| [buffer_demo.erl](https://github.com/libtmux/libtmux-erlang/blob/33fd7fe985454e375d5ac8533043cabf4a0b2fa5/examples/buffer_demo.erl) | Store a binary artifact without terminal or text conversion. | [Buffers](../guides/buffers/) |
Loading
Loading