{layer.name}
+{layer.description}
++ This reference is generated directly from the libtmux source tree using the new AST-powered + pipeline. Use the module index to jump to classes, methods, and helper functions. +
++ libtmux mirrors tmux itself. Each object understands its place in the hierarchy and exposes + typed helpers that keep automation predictable. +
+{layer.description}
++Server + Session + Window + Pane ++
+ These guides mirror the tmux lifecycle. Each guide follows a workflow, from session startup + through pane automation. Use them as blueprints for your own tooling. +
+{guide.description}
++ libtmux wraps tmux with a typed ORM so you can orchestrate terminals, sessions, and panes with + confidence. This Astro site keeps the docs fast, expressive, and API-first. +
+{feature.description}
+Install libtmux and open a new tmux session programmatically.
+pip install libtmux
+ python -c "import libtmux; libtmux.Server().new_session(session_name='docs')"
+ Say hello.
', + summary: 'Say hello.', + }, + ], + variables: [], + docstringRaw: null, + docstringFormat: 'unknown', + docstringHtml: null, + summary: null, + }, + ] satisfies PyIntrospectModule[] + + const api = buildApiPackage(modules, { + name: 'libtmux', + root: fixturesRoot, + includePrivate: false, + generatedAt: '2025-01-01T00:00:00.000Z', + introspection, + }) + + const greet = api.modules[0]?.functions.find((fn) => fn.name === 'greet') + expect(greet?.signature).toBe('(name: str)') + expect(greet?.returns).toBe('str') + expect(greet?.docstringHtml).toBe('Say hello.
') + }) +}) diff --git a/astro/packages/api-model/tsconfig.json b/astro/packages/api-model/tsconfig.json new file mode 100644 index 000000000..52234f529 --- /dev/null +++ b/astro/packages/api-model/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "composite": true, + "outDir": "dist", + "rootDir": "../../", + "paths": {} + }, + "include": ["src", "tests"], + "references": [] +} diff --git a/astro/packages/api-model/vitest.config.ts b/astro/packages/api-model/vitest.config.ts new file mode 100644 index 000000000..9eef0e8a8 --- /dev/null +++ b/astro/packages/api-model/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + environment: 'node', + include: ['tests/**/*.test.ts'], + }, +}) diff --git a/astro/packages/astro-autodoc/package.json b/astro/packages/astro-autodoc/package.json new file mode 100644 index 000000000..9c898168d --- /dev/null +++ b/astro/packages/astro-autodoc/package.json @@ -0,0 +1,39 @@ +{ + "name": "@libtmux/astro-autodoc", + "version": "0.0.1", + "type": "module", + "description": "Astro components for libtmux API docs", + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "src" + ], + "scripts": { + "test": "vitest run", + "test:watch": "vitest", + "lint": "biome lint .", + "format": "pnpm biome check . --write; biome format . --write", + "type-check": "tsc --noEmit", + "type-check:watch": "tsc --noEmit --watch", + "update": "pnpm update", + "ncu": "ncu", + "clean": "rm -rf dist/ node_modules/.cache/", + "biome": "biome check . --write && biome format . --write" + }, + "dependencies": { + "@libtmux/api-model": "workspace:*", + "@libtmux/py-bridge": "workspace:*", + "@libtmux/py-introspect": "workspace:*", + "@libtmux/py-parse": "workspace:*", + "@libtmux/rst-lite": "workspace:*" + }, + "peerDependencies": { + "astro": "^5.0.0" + }, + "devDependencies": { + "@biomejs/biome": "2.3.11", + "@types/node": "^25.0.3", + "typescript": "^5.9.3", + "vitest": "^4.0.16" + } +} diff --git a/astro/packages/astro-autodoc/src/components/AutodocClass.astro b/astro/packages/astro-autodoc/src/components/AutodocClass.astro new file mode 100644 index 000000000..5aca64cbd --- /dev/null +++ b/astro/packages/astro-autodoc/src/components/AutodocClass.astro @@ -0,0 +1,48 @@ +--- +import '../styles.css' +import { slugify } from '../utils' +import AutodocFunction from './AutodocFunction.astro' +import AutodocVariable from './AutodocVariable.astro' +import Docstring from './Docstring.astro' + +const { klass } = Astro.props +const anchor = slugify(klass.qualname) +const bases = klass.bases.length > 0 ? `(${klass.bases.join(', ')})` : '' +--- + ++ Generated at {pkg.generatedAt} from {pkg.root}. +
+{block}
+ ))} +Hello world
"') + }) +}) diff --git a/astro/packages/astro-autodoc/tests/utils.test.ts b/astro/packages/astro-autodoc/tests/utils.test.ts new file mode 100644 index 000000000..ef139ea2c --- /dev/null +++ b/astro/packages/astro-autodoc/tests/utils.test.ts @@ -0,0 +1,17 @@ +import { describe, expect, it } from 'vitest' +import { formatSignature, slugify, splitDocstring } from '../src/utils' + +describe('autodoc utils', () => { + it('slugifies qualnames', () => { + expect(slugify('libtmux.Server.new_window')).toBe('libtmux-server-new-window') + }) + + it('formats signatures with return types', () => { + expect(formatSignature('(name: str)', 'Window')).toBe('(name: str) -> Window') + }) + + it('splits docstrings into blocks', () => { + const blocks = splitDocstring('Line one.\n\nLine two.') + expect(blocks).toEqual(['Line one.', 'Line two.']) + }) +}) diff --git a/astro/packages/astro-autodoc/tsconfig.json b/astro/packages/astro-autodoc/tsconfig.json new file mode 100644 index 000000000..52234f529 --- /dev/null +++ b/astro/packages/astro-autodoc/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "composite": true, + "outDir": "dist", + "rootDir": "../../", + "paths": {} + }, + "include": ["src", "tests"], + "references": [] +} diff --git a/astro/packages/astro-autodoc/vitest.config.ts b/astro/packages/astro-autodoc/vitest.config.ts new file mode 100644 index 000000000..9eef0e8a8 --- /dev/null +++ b/astro/packages/astro-autodoc/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + environment: 'node', + include: ['tests/**/*.test.ts'], + }, +}) diff --git a/astro/packages/astro-intersphinx/package.json b/astro/packages/astro-intersphinx/package.json new file mode 100644 index 000000000..2eaad39d4 --- /dev/null +++ b/astro/packages/astro-intersphinx/package.json @@ -0,0 +1,35 @@ +{ + "name": "@libtmux/astro-intersphinx", + "version": "0.0.1", + "type": "module", + "description": "Astro helpers for intersphinx resolution", + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "src" + ], + "scripts": { + "test": "vitest run", + "test:watch": "vitest", + "lint": "biome lint .", + "format": "pnpm biome check . --write; biome format . --write", + "type-check": "tsc --noEmit", + "type-check:watch": "tsc --noEmit --watch", + "update": "pnpm update", + "ncu": "ncu", + "clean": "rm -rf dist/ node_modules/.cache/", + "biome": "biome check . --write && biome format . --write" + }, + "dependencies": { + "@libtmux/intersphinx": "workspace:*" + }, + "peerDependencies": { + "astro": "^5.0.0" + }, + "devDependencies": { + "@biomejs/biome": "2.3.11", + "@types/node": "^25.0.3", + "typescript": "^5.9.3", + "vitest": "^4.0.16" + } +} diff --git a/astro/packages/astro-intersphinx/src/components/IntersphinxLink.astro b/astro/packages/astro-intersphinx/src/components/IntersphinxLink.astro new file mode 100644 index 000000000..acdddf91f --- /dev/null +++ b/astro/packages/astro-intersphinx/src/components/IntersphinxLink.astro @@ -0,0 +1,15 @@ +--- +import { resolveIntersphinx } from '../resolve' + +const { inventory, ref, label, class: className } = Astro.props +const item = resolveIntersphinx(inventory, ref) +const text = label ?? item?.displayName ?? ref.name +--- + +{item ? ( + + {text} + +) : ( + {text} +)} diff --git a/astro/packages/astro-intersphinx/src/env.d.ts b/astro/packages/astro-intersphinx/src/env.d.ts new file mode 100644 index 000000000..283b70b83 --- /dev/null +++ b/astro/packages/astro-intersphinx/src/env.d.ts @@ -0,0 +1,4 @@ +declare module '*.astro' { + const Component: (props: Record${renderInline(node.content, options)}
` + case 'heading': + return `${escapeHtml(node.text)}`
+ case 'list':
+ return renderList(node, options)
+ case 'field_list':
+ return renderFieldList(node, options)
+ case 'admonition':
+ return renderAdmonition(node, options)
+ case 'blockquote':
+ return `${node.children.map((child) => renderBlock(child, options)).join('')}` + default: + return '' + } +} + +const renderList = (node: ListNode, options: RenderOptions): string => { + const tag = node.ordered ? 'ol' : 'ul' + const items = node.items.map((item) => renderListItem(item, options)).join('') + return `<${tag}>${items}${tag}>` +} + +const renderListItem = (node: ListItemNode, options: RenderOptions): string => { + const body = node.children.map((child) => renderBlock(child, options)).join('') + return `
${escapeHtml(node.value)}`
+ case 'emphasis':
+ return `${renderInline(node.value, options)}`
+ case 'strong':
+ return `${renderInline(node.value, options)}`
+ case 'role':
+ return renderRole(node.role, node.value, options)
+ default:
+ return ''
+ }
+ })
+ .join('')
+}
+
+const renderRole = (role: string, value: string, options: RenderOptions): string => {
+ const resolver = options.roleResolver
+ if (resolver) {
+ const resolved = resolver(role, value)
+ if (resolved) {
+ const label = resolved.text ?? value
+ return `${escapeHtml(label)}`
+ }
+ }
+ return `${escapeHtml(value)}`
+}
+
+const escapeHtml = (value: string): string => {
+ return value
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''')
+}
diff --git a/astro/packages/rst-lite/tests/__snapshots__/parse-render.test.ts.snap b/astro/packages/rst-lite/tests/__snapshots__/parse-render.test.ts.snap
new file mode 100644
index 000000000..11cb09b74
--- /dev/null
+++ b/astro/packages/rst-lite/tests/__snapshots__/parse-render.test.ts.snap
@@ -0,0 +1,351 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`rst-lite parser > parses admonition > admonition-ast 1`] = `
+{
+ "children": [
+ {
+ "body": [
+ {
+ "items": [
+ {
+ "body": [],
+ "name": "Use",
+ "typeText": "meth:\`Foo.old\` instead.",
+ },
+ ],
+ "type": "field_list",
+ },
+ ],
+ "name": "deprecated",
+ "title": "Deprecated: 0.17",
+ "type": "admonition",
+ },
+ ],
+ "type": "document",
+}
+`;
+
+exports[`rst-lite parser > parses heading-field-list > heading-field-list-ast 1`] = `
+{
+ "children": [
+ {
+ "content": [
+ {
+ "type": "text",
+ "value": "Parameters",
+ },
+ ],
+ "level": 2,
+ "type": "heading",
+ },
+ {
+ "items": [
+ {
+ "body": [
+ {
+ "content": [
+ {
+ "type": "text",
+ "value": "Foo line.",
+ },
+ ],
+ "type": "paragraph",
+ },
+ ],
+ "name": "foo",
+ "typeText": "int",
+ },
+ {
+ "body": [
+ {
+ "content": [
+ {
+ "type": "text",
+ "value": "Bar line.",
+ },
+ ],
+ "type": "paragraph",
+ },
+ ],
+ "name": "bar",
+ "typeText": "str",
+ },
+ ],
+ "type": "field_list",
+ },
+ ],
+ "type": "document",
+}
+`;
+
+exports[`rst-lite parser > parses literal-block > literal-block-ast 1`] = `
+{
+ "children": [
+ {
+ "items": [
+ {
+ "body": [],
+ "name": "Example",
+ "typeText": ":",
+ },
+ ],
+ "type": "field_list",
+ },
+ ],
+ "type": "document",
+}
+`;
+
+exports[`rst-lite parser > parses mixed-blocks > mixed-blocks-ast 1`] = `
+{
+ "children": [
+ {
+ "content": [
+ {
+ "type": "text",
+ "value": "Notes",
+ },
+ ],
+ "level": 2,
+ "type": "heading",
+ },
+ {
+ "content": [
+ {
+ "type": "text",
+ "value": "This is a paragraph.",
+ },
+ ],
+ "type": "paragraph",
+ },
+ {
+ "body": [
+ {
+ "content": [
+ {
+ "type": "text",
+ "value": "A note with ",
+ },
+ {
+ "role": "class",
+ "type": "role",
+ "value": "Thing",
+ },
+ {
+ "type": "text",
+ "value": ".",
+ },
+ ],
+ "type": "paragraph",
+ },
+ ],
+ "name": "note",
+ "title": "Note",
+ "type": "admonition",
+ },
+ {
+ "items": [
+ {
+ "children": [
+ {
+ "content": [
+ {
+ "type": "text",
+ "value": "Ordered",
+ },
+ ],
+ "type": "paragraph",
+ },
+ ],
+ "type": "list_item",
+ },
+ {
+ "children": [
+ {
+ "content": [
+ {
+ "type": "text",
+ "value": "List",
+ },
+ ],
+ "type": "paragraph",
+ },
+ ],
+ "type": "list_item",
+ },
+ ],
+ "ordered": true,
+ "type": "list",
+ },
+ ],
+ "type": "document",
+}
+`;
+
+exports[`rst-lite parser > parses nested-lists > nested-lists-ast 1`] = `
+{
+ "children": [
+ {
+ "items": [
+ {
+ "children": [
+ {
+ "content": [
+ {
+ "type": "text",
+ "value": "Item one",
+ },
+ ],
+ "type": "paragraph",
+ },
+ {
+ "items": [
+ {
+ "children": [
+ {
+ "content": [
+ {
+ "type": "text",
+ "value": "Sub a",
+ },
+ ],
+ "type": "paragraph",
+ },
+ {
+ "items": [
+ {
+ "children": [
+ {
+ "content": [
+ {
+ "type": "text",
+ "value": "Deep item",
+ },
+ ],
+ "type": "paragraph",
+ },
+ ],
+ "type": "list_item",
+ },
+ ],
+ "ordered": false,
+ "type": "list",
+ },
+ ],
+ "type": "list_item",
+ },
+ {
+ "children": [
+ {
+ "content": [
+ {
+ "type": "text",
+ "value": "Sub b",
+ },
+ ],
+ "type": "paragraph",
+ },
+ ],
+ "type": "list_item",
+ },
+ ],
+ "ordered": false,
+ "type": "list",
+ },
+ ],
+ "type": "list_item",
+ },
+ {
+ "children": [
+ {
+ "content": [
+ {
+ "type": "text",
+ "value": "Item two",
+ },
+ ],
+ "type": "paragraph",
+ },
+ ],
+ "type": "list_item",
+ },
+ ],
+ "ordered": false,
+ "type": "list",
+ },
+ ],
+ "type": "document",
+}
+`;
+
+exports[`rst-lite parser > parses paragraph-inline > paragraph-inline-ast 1`] = `
+{
+ "children": [
+ {
+ "content": [
+ {
+ "type": "text",
+ "value": "Hello ",
+ },
+ {
+ "type": "emphasis",
+ "value": [
+ {
+ "type": "text",
+ "value": "world",
+ },
+ ],
+ },
+ {
+ "type": "text",
+ "value": " ",
+ },
+ {
+ "type": "strong",
+ "value": [
+ {
+ "type": "text",
+ "value": "bold",
+ },
+ ],
+ },
+ {
+ "type": "text",
+ "value": " ",
+ },
+ {
+ "type": "literal",
+ "value": "code",
+ },
+ {
+ "type": "text",
+ "value": " and ",
+ },
+ {
+ "role": "class",
+ "type": "role",
+ "value": "Foo",
+ },
+ {
+ "type": "text",
+ "value": ".",
+ },
+ ],
+ "type": "paragraph",
+ },
+ ],
+ "type": "document",
+}
+`;
+
+exports[`rst-lite parser > renders admonition > admonition-html 1`] = `""`;
+
+exports[`rst-lite parser > renders heading-field-list > heading-field-list-html 1`] = `"Foo line.
Bar line.
This is a paragraph.
Ordered
List
Item one
Sub a
Deep item
Sub b
Item two
Hello world bold code and Foo.