Skip to content

Commit 009c228

Browse files
committed
Simple embed support for notebooks
1 parent 4ed990a commit 009c228

File tree

4 files changed

+134
-6
lines changed

4 files changed

+134
-6
lines changed

src/core/handlers/embed.ts

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
* embed.ts
3+
*
4+
* Copyright (C) 2022 by RStudio, PBC
5+
*
6+
*/
7+
8+
import { LanguageCellHandlerContext, LanguageHandler } from "./types.ts";
9+
import { baseHandler, install } from "./base.ts";
10+
import {
11+
EitherString,
12+
mappedConcat,
13+
MappedString,
14+
} from "../lib/mapped-text.ts";
15+
16+
import { DirectiveCell } from "../lib/break-quarto-md-types.ts";
17+
import { jupyterAssets } from "../jupyter/jupyter.ts";
18+
19+
import {
20+
notebookForAddress,
21+
notebookMarkdown,
22+
parseNotebookPath,
23+
} from "./include-notebook.ts";
24+
import { JupyterCell } from "../jupyter/types.ts";
25+
26+
interface EmbedHandler {
27+
name: string;
28+
handle(
29+
filename: string,
30+
handlerContext: LanguageCellHandlerContext,
31+
): Promise<{
32+
handled: boolean;
33+
markdownFragments: EitherString[];
34+
}>;
35+
}
36+
37+
const kHandlers: EmbedHandler[] = [
38+
{
39+
name: "Jupyter Notebook Embed",
40+
async handle(filename: string, handlerContext: LanguageCellHandlerContext) {
41+
const markdownFragments: EitherString[] = [];
42+
43+
// Resolve the filename into a path
44+
const path = handlerContext.resolvePath(filename);
45+
46+
const notebookAddress = parseNotebookPath(path);
47+
if (notebookAddress) {
48+
const nb = notebookForAddress(
49+
notebookAddress,
50+
(cell: JupyterCell) => {
51+
cell.metadata["echo"] = false;
52+
cell.metadata["warning"] = false;
53+
cell.metadata["output"] = "asis";
54+
return cell;
55+
},
56+
);
57+
const assets = jupyterAssets(
58+
handlerContext.options.context.target.source,
59+
handlerContext.options.context.format.pandoc.to,
60+
);
61+
62+
// Render the notebook markdown and inject it
63+
const markdown = await notebookMarkdown(
64+
nb,
65+
assets,
66+
handlerContext.options.context,
67+
handlerContext.options.flags,
68+
);
69+
if (markdown) {
70+
markdownFragments.push(markdown);
71+
}
72+
return {
73+
handled: true,
74+
markdownFragments,
75+
};
76+
} else {
77+
return {
78+
handled: false,
79+
markdownFragments: [],
80+
};
81+
}
82+
},
83+
},
84+
];
85+
86+
const embedHandler: LanguageHandler = {
87+
...baseHandler,
88+
89+
languageName: "embed",
90+
91+
type: "directive",
92+
stage: "pre-engine",
93+
94+
async directive(
95+
handlerContext: LanguageCellHandlerContext,
96+
directive: DirectiveCell,
97+
): Promise<MappedString> {
98+
const textFragments: EitherString[] = [];
99+
100+
// The first parameter is a path to a file
101+
const filename = directive.shortcode.params[0];
102+
if (!filename) {
103+
throw new Error("Embed directive needs filename as a parameter");
104+
}
105+
106+
// Go through handlers until one handles the embed by returning markdown
107+
for (const handler of kHandlers) {
108+
const result = await handler.handle(filename, handlerContext);
109+
if (result.handled) {
110+
textFragments.push(...result.markdownFragments);
111+
break;
112+
}
113+
}
114+
115+
// Return the markdown
116+
return mappedConcat(textFragments);
117+
},
118+
};
119+
120+
install(embedHandler);

src/core/handlers/handlers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@
88
import "./mermaid.ts";
99
import "./include.ts";
1010
import "./dot.ts";
11+
import "./embed.ts";

src/core/handlers/include-notebook.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@ import {
3333
} from "../jupyter/types.ts";
3434

3535
import { dirname, extname } from "path/mod.ts";
36+
import { Cell } from "https://deno.land/x/[email protected]/table/cell.ts";
3637

37-
export interface NotebookInclude {
38+
export interface NotebookAddress {
3839
path: string;
3940
cellIds: string[] | undefined;
4041
params: Record<string, string>;
@@ -59,14 +60,15 @@ export function parseNotebookPath(path: string) {
5960
return {
6061
path,
6162
cellIds,
62-
} as NotebookInclude;
63+
} as NotebookAddress;
6364
} else {
6465
return undefined;
6566
}
6667
}
6768

68-
export function notebookForInclude(
69-
nbInclude: NotebookInclude,
69+
export function notebookForAddress(
70+
nbInclude: NotebookAddress,
71+
filter?: (cell: JupyterCell) => JupyterCell,
7072
) {
7173
try {
7274
const nb = jupyterFromFile(nbInclude.path);
@@ -96,6 +98,11 @@ export function notebookForInclude(
9698
}
9799
nb.cells = cells;
98100
}
101+
102+
if (filter) {
103+
nb.cells = nb.cells.map(filter);
104+
}
105+
99106
return nb;
100107
} catch (ex) {
101108
throw new Error(`Failed to read included notebook ${nbInclude.path}`, ex);

src/core/handlers/include.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { DirectiveCell } from "../lib/break-quarto-md-types.ts";
2121
import { jupyterAssets } from "../jupyter/jupyter.ts";
2222

2323
import {
24-
notebookForInclude,
24+
notebookForAddress,
2525
notebookMarkdown,
2626
parseNotebookPath,
2727
} from "./include-notebook.ts";
@@ -58,7 +58,7 @@ const includeHandler: LanguageHandler = {
5858
if (notebookInclude) {
5959
// This is a notebook include, so read the notebook (including only
6060
// the cells that are specified in the include and include them)
61-
const nb = notebookForInclude(
61+
const nb = notebookForAddress(
6262
notebookInclude,
6363
);
6464
const assets = jupyterAssets(

0 commit comments

Comments
 (0)