Skip to content

Commit 30ccadc

Browse files
quarto api: quarto.system.pandoc(), breakQuartoMd cell regex
pandoc execution wrapper add optional startCodeCellRegex param to breakQuartoMd update llm docs for quarto api Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent b323477 commit 30ccadc

7 files changed

Lines changed: 137 additions & 96 deletions

File tree

llm-docs/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# LLM documentation for Quarto
2+
3+
This directory contains documents providing context and instructions for LLMs
4+
to execute tasks in the `quarto-cli` codebase.

llm-docs/quarto-api.md

Lines changed: 86 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Quarto API and @quarto/types
22

3+
## Scope of this document
4+
5+
This document covers **adding or modifying methods in existing namespaces** of the Quarto API.
6+
7+
**Out of scope:** Creating new namespaces, renaming namespaces, or restructuring the API architecture. These operations require reading `src/core/api/*.ts` in depth and should be planned and executed with a human in the loop.
8+
9+
---
10+
311
## Building @quarto/types
412

513
To build the @quarto/types package:
@@ -18,160 +26,144 @@ This runs typecheck and then bundles all type definitions into `dist/index.d.ts`
1826
The Quarto API is how external execution engines access Quarto's core functionality. The API exists in two places:
1927

2028
1. **Type definitions** in `packages/quarto-types/` - consumed by external engines (TypeScript)
21-
2. **Implementation** in `src/core/quarto-api.ts` - used within quarto-cli
29+
2. **Implementation** in `src/core/api/` - used within quarto-cli
2230

23-
### Step-by-step: Adding to the Quarto API
31+
### Existing namespaces
2432

25-
Follow these steps in order when adding new functionality to the API:
33+
The API has these namespaces (each has a corresponding file in `src/core/api/`):
2634

27-
#### 1. Update quarto-types type definitions
35+
- `system` - Process execution, environment detection, temp files
36+
- `console` - Logging, spinners, user feedback
37+
- `path` - Path manipulation and resource locations
38+
- `format` - Format detection utilities
39+
- `jupyter` - Jupyter notebook operations
40+
- `text` - Text processing utilities
41+
- `mappedString` - Source-mapped string operations
42+
- `markdownRegex` - Markdown parsing with regex
43+
- `crypto` - Cryptographic utilities
2844

29-
**Add auxiliary types** (if needed):
45+
### Step-by-step: Adding a method to an existing namespace
3046

31-
- Types belong in `packages/quarto-types/src/`
32-
- Follow the existing file organization:
33-
- `system.ts` - System/process types (ProcessResult, TempContext, etc.)
34-
- `console.ts` - Console/UI types (SpinnerOptions, etc.)
35-
- `jupyter.ts` - Jupyter-specific types
36-
- `check.ts` - Check command types
37-
- `execution.ts` - Execution engine types
38-
- etc.
39-
- Create new files if needed for logical grouping
47+
Follow these steps when adding a new method to an existing namespace:
4048

41-
**Export types from index.ts:**
49+
#### 1. Add to quarto-types type definitions
4250

43-
```typescript
44-
// In packages/quarto-types/src/index.ts
45-
export type * from "./your-new-file.ts";
46-
```
47-
48-
**Add to QuartoAPI interface:**
51+
In `packages/quarto-types/src/quarto-api.ts`, find the namespace and add your method:
4952

5053
```typescript
51-
// In packages/quarto-types/src/quarto-api.ts
52-
53-
// 1. Import any new types at the top
54-
import type { YourNewType } from "./your-file.ts";
55-
56-
// 2. Add to the QuartoAPI interface
57-
export interface QuartoAPI {
58-
// ... existing namespaces
59-
60-
yourNamespace: {
61-
yourMethod: (param: YourNewType) => ReturnType;
62-
};
63-
}
54+
system: {
55+
// ... existing methods
56+
57+
/**
58+
* Your method description
59+
* @param arg - Argument description
60+
* @returns Return value description
61+
*/
62+
yourMethod: (arg: ArgType) => ReturnType;
63+
};
6464
```
6565

66+
If your method needs new types, add them to the appropriate file in `packages/quarto-types/src/` (e.g., `system.ts` for system-related types).
67+
6668
#### 2. Test the type definitions
6769

6870
```bash
6971
cd packages/quarto-types
7072
npm run build
7173
```
7274

73-
This will:
74-
75-
- Run `tsc --noEmit` to typecheck
76-
- Bundle types into `dist/index.d.ts`
77-
- Show any type errors
78-
79-
Fix any errors before proceeding.
75+
This will typecheck and bundle. Fix any errors before proceeding.
8076

81-
#### 3. Update the internal QuartoAPI interface
77+
#### 3. Add to internal types
8278

83-
The file `src/core/quarto-api.ts` contains a **duplicate** QuartoAPI interface definition used for the internal implementation. Update it to match:
79+
In `src/core/api/types.ts`, find the namespace interface and add your method:
8480

8581
```typescript
86-
// In src/core/quarto-api.ts (near top of file)
87-
export interface QuartoAPI {
88-
// ... existing namespaces
89-
90-
yourNamespace: {
91-
yourMethod: (param: YourNewType) => ReturnType;
92-
};
82+
export interface SystemNamespace {
83+
// ... existing methods
84+
yourMethod: (arg: ArgType) => ReturnType;
9385
}
9486
```
9587

96-
**Note:** This interface must match the one in quarto-types, but uses internal types.
97-
98-
#### 4. Wire up the implementation
99-
100-
Still in `src/core/quarto-api.ts`:
101-
102-
**Add imports** (near top):
88+
#### 4. Implement the method
10389

104-
```typescript
105-
import { yourMethod } from "./your-module.ts";
106-
```
90+
In the namespace's implementation file (e.g., `src/core/api/system.ts`):
10791

108-
**Add to quartoAPI object** (at bottom):
92+
1. Import any needed functions at the top
93+
2. Add the method to the returned object
10994

11095
```typescript
111-
export const quartoAPI: QuartoAPI = {
112-
// ... existing namespaces
96+
import { yourImplementation } from "../your-module.ts";
11397

114-
yourNamespace: {
115-
yourMethod,
116-
},
117-
};
98+
globalRegistry.register("system", (): SystemNamespace => {
99+
return {
100+
// ... existing methods
101+
yourMethod: yourImplementation,
102+
};
103+
});
118104
```
119105

120106
#### 5. Verify with typecheck
121107

122-
Run the quarto typecheck:
123-
124108
```bash
125109
package/dist/bin/quarto
126110
```
127111

128-
No output means success! Fix any type errors.
112+
No output means success.
129113

130114
#### 6. Commit with built artifact
131115

132116
**Always commit the built `dist/index.d.ts` file** along with source changes:
133117

134118
```bash
135-
git add packages/quarto-types/src/your-file.ts \
136-
packages/quarto-types/src/index.ts \
137-
packages/quarto-types/src/quarto-api.ts \
119+
git add packages/quarto-types/src/quarto-api.ts \
138120
packages/quarto-types/dist/index.d.ts \
139-
src/core/quarto-api.ts
121+
src/core/api/types.ts \
122+
src/core/api/system.ts
140123

141-
git commit -m "Add yourNamespace to Quarto API"
124+
git commit -m "Add yourMethod to system namespace"
142125
```
143126

144127
### Using the Quarto API in source files
145128

146-
#### Inside quarto-cli (internal modules)
147-
148-
```typescript
149-
// Import the quartoAPI instance
150-
import { quartoAPI as quarto } from "../../core/quarto-api.ts";
151-
152-
// Use it
153-
const caps = await quarto.jupyter.capabilities();
154-
await quarto.console.withSpinner({ message: "Working..." }, async () => {
155-
// do work
156-
});
157-
```
158-
159-
#### External engines
129+
#### Execution engines (preferred)
160130

161-
External engines receive the API via their `init()` method:
131+
Execution engines receive the API via their `init()` method. Store it for use throughout the engine:
162132

163133
```typescript
134+
import type { QuartoAPI } from "../../core/api/index.ts";
135+
164136
let quarto: QuartoAPI;
165137

166138
export const myEngineDiscovery: ExecutionEngineDiscovery = {
167139
init: (quartoAPI: QuartoAPI) => {
168-
quarto = quartoAPI; // Store for later use
140+
quarto = quartoAPI;
169141
},
170142

171-
// ... other methods can now use quarto
143+
launch: (context) => {
144+
return {
145+
// ... use quarto throughout
146+
markdownForFile(file) {
147+
return quarto.mappedString.fromFile(file);
148+
},
149+
};
150+
},
172151
};
173152
```
174153

154+
#### When init() API is not available
155+
156+
For helper modules that don't have access to the API via `init()`, use `getQuartoAPI()`:
157+
158+
```typescript
159+
import { getQuartoAPI } from "../../core/api/index.ts";
160+
161+
function someHelper() {
162+
const quarto = getQuartoAPI();
163+
const caps = await quarto.jupyter.capabilities();
164+
}
165+
```
166+
175167
#### Removing old imports
176168

177169
When moving functionality to the API, **remove direct imports** from internal modules:
@@ -180,8 +172,8 @@ When moving functionality to the API, **remove direct imports** from internal mo
180172
// ❌ OLD - direct import
181173
import { withSpinner } from "../../core/console.ts";
182174

183-
// ✅ NEW - use API
184-
import { quartoAPI as quarto } from "../../core/quarto-api.ts";
175+
// ✅ NEW - use API via init() or getQuartoAPI()
176+
const quarto = getQuartoAPI();
185177
const result = await quarto.console.withSpinner(...);
186178
```
187179

packages/quarto-types/dist/index.d.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -837,9 +837,12 @@ export interface QuartoAPI {
837837
* @param src - Markdown string or MappedString
838838
* @param validate - Whether to validate cells (default: false)
839839
* @param lenient - Whether to use lenient parsing (default: false)
840+
* @param startCodeCellRegex - Optional custom regex for detecting code cell starts.
841+
* Must have capture group 1 for backticks and group 2 for language.
842+
* Default matches `{language}` syntax.
840843
* @returns Promise resolving to chunks with cells
841844
*/
842-
breakQuartoMd: (src: string | MappedString, validate?: boolean, lenient?: boolean) => Promise<QuartoMdChunks>;
845+
breakQuartoMd: (src: string | MappedString, validate?: boolean, lenient?: boolean, startCodeCellRegex?: RegExp) => Promise<QuartoMdChunks>;
843846
};
844847
/**
845848
* MappedString utilities for source location tracking
@@ -1300,6 +1303,17 @@ export interface QuartoAPI {
13001303
* @returns Promise resolving to render result with success status
13011304
*/
13021305
checkRender: (options: CheckRenderOptions) => Promise<CheckRenderResult>;
1306+
/**
1307+
* Execute pandoc with the given arguments
1308+
*
1309+
* Runs the bundled pandoc binary (or QUARTO_PANDOC override) with
1310+
* the specified arguments. Path to pandoc is automatically resolved.
1311+
*
1312+
* @param args - Command line arguments to pass to pandoc
1313+
* @param stdin - Optional stdin content to pipe to pandoc
1314+
* @returns Promise resolving to process result with stdout/stderr
1315+
*/
1316+
pandoc: (args: string[], stdin?: string) => Promise<ProcessResult>;
13031317
};
13041318
/**
13051319
* Text processing utilities

packages/quarto-types/src/quarto-api.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,16 @@ export interface QuartoAPI {
7272
* @param src - Markdown string or MappedString
7373
* @param validate - Whether to validate cells (default: false)
7474
* @param lenient - Whether to use lenient parsing (default: false)
75+
* @param startCodeCellRegex - Optional custom regex for detecting code cell starts.
76+
* Must have capture group 1 for backticks and group 2 for language.
77+
* Default matches `{language}` syntax.
7578
* @returns Promise resolving to chunks with cells
7679
*/
7780
breakQuartoMd: (
7881
src: string | MappedString,
7982
validate?: boolean,
8083
lenient?: boolean,
84+
startCodeCellRegex?: RegExp,
8185
) => Promise<QuartoMdChunks>;
8286
};
8387

@@ -637,6 +641,18 @@ export interface QuartoAPI {
637641
* @returns Promise resolving to render result with success status
638642
*/
639643
checkRender: (options: CheckRenderOptions) => Promise<CheckRenderResult>;
644+
645+
/**
646+
* Execute pandoc with the given arguments
647+
*
648+
* Runs the bundled pandoc binary (or QUARTO_PANDOC override) with
649+
* the specified arguments. Path to pandoc is automatically resolved.
650+
*
651+
* @param args - Command line arguments to pass to pandoc
652+
* @param stdin - Optional stdin content to pipe to pandoc
653+
* @returns Promise resolving to process result with stdout/stderr
654+
*/
655+
pandoc: (args: string[], stdin?: string) => Promise<ProcessResult>;
640656
};
641657

642658
/**

src/core/api/system.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { runExternalPreviewServer } from "../../preview/preview-server.ts";
1111
import { onCleanup } from "../cleanup.ts";
1212
import { globalTempContext } from "../temp.ts";
1313
import { checkRender } from "../../command/check/check-render.ts";
14+
import { pandocBinaryPath } from "../resources.ts";
1415

1516
// Register system namespace
1617
globalRegistry.register("system", (): SystemNamespace => {
@@ -22,5 +23,16 @@ globalRegistry.register("system", (): SystemNamespace => {
2223
onCleanup,
2324
tempContext: globalTempContext,
2425
checkRender,
26+
pandoc: (args: string[], stdin?: string) => {
27+
return execProcess(
28+
{
29+
cmd: pandocBinaryPath(),
30+
args,
31+
stdout: "piped",
32+
stderr: "piped",
33+
},
34+
stdin,
35+
);
36+
},
2537
};
2638
});

src/core/api/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export interface MarkdownRegexNamespace {
4242
src: string | MappedString,
4343
validate?: boolean,
4444
lenient?: boolean,
45+
startCodeCellRegex?: RegExp,
4546
) => Promise<QuartoMdChunks>;
4647
}
4748

@@ -179,6 +180,7 @@ export interface SystemNamespace {
179180
language: string;
180181
services: RenderServiceWithLifetime;
181182
}) => Promise<{ success: boolean; error?: Error }>;
183+
pandoc: (args: string[], stdin?: string) => Promise<ProcessResult>;
182184
}
183185

184186
/**

src/core/lib/break-quarto-md.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export async function breakQuartoMd(
2929
src: EitherString,
3030
validate = false,
3131
lenient = false,
32+
startCodeCellRegex?: RegExp,
3233
) {
3334
if (typeof src === "string") {
3435
src = asMappedString(src);
@@ -42,7 +43,7 @@ export async function breakQuartoMd(
4243

4344
// regexes
4445
const yamlRegEx = /^---\s*$/;
45-
const startCodeCellRegEx = new RegExp(
46+
const startCodeCellRegEx = startCodeCellRegex ?? new RegExp(
4647
"^\\s*(```+)\\s*\\{([=A-Za-z]+)( *[ ,].*)?\\}\\s*$",
4748
);
4849
const startCodeRegEx = /^```/;

0 commit comments

Comments
 (0)