Skip to content

Commit 8b7ef2c

Browse files
committed
python cells
1 parent 9ca8d5e commit 8b7ef2c

File tree

9 files changed

+48
-16
lines changed

9 files changed

+48
-16
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"@lezer/html": "^1.3.10",
5252
"@lezer/javascript": "^1.5.1",
5353
"@lezer/markdown": "^1.4.3",
54+
"@lezer/python": "^1.1.18",
5455
"@observablehq/inspector": "^5.0.1",
5556
"@observablehq/parser": "^6.1.0",
5657
"@observablehq/runtime": "^6.0.0",

src/interpreters/index.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,14 @@ export async function getInterpreterCachePath(
1313
const cacheName = `${await nameHash(interpreter)}-${await stringHash(input)}${getInterpreterExtension(format)}`; // TODO avoid conflict with database cache?
1414
return join(sourceDir, ".observable", "cache", cacheName);
1515
}
16+
17+
export function getInterpreterCommand(interpreter: string): [command: string, args: string[]] {
18+
switch (interpreter) {
19+
case "node":
20+
return ["node", ["--input-type=module", "--permission", "--allow-fs-read=."]];
21+
case "python":
22+
return ["python3", []];
23+
default:
24+
throw new Error(`unknown interpreter: ${interpreter}`);
25+
}
26+
}

src/javascript/template.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export function transpileTemplate(input: string | Cell, tag = "", raw = false):
9090
if (!input) return input;
9191
const source = new Sourcemap(input);
9292
let node: Node;
93-
if (cell && isInterpreter(cell)) {
93+
if (cell && isInterpreter(cell.mode)) {
9494
node = {type: "Literal", start: 0, end: input.length};
9595
escapeBacktick(source, node);
9696
escapeBackslash(source, node);
@@ -115,7 +115,7 @@ function getTag(cell: Cell): string {
115115
? "tex.block"
116116
: cell.mode === "sql"
117117
? getSqlTag(cell)
118-
: isInterpreter(cell)
118+
: isInterpreter(cell.mode)
119119
? getInterpreterTag(cell)
120120
: cell.mode;
121121
}
@@ -135,7 +135,7 @@ function getInterpreterTag(cell: Cell): string {
135135
function getSuffix(cell: Cell): string {
136136
return cell.mode === "sql" && !cell.hidden
137137
? ".then(Inputs.table)"
138-
: isInterpreter(cell)
138+
: isInterpreter(cell.mode)
139139
? getInterpreterSuffix(cell)
140140
: "";
141141
}

src/lib/interpreters.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type {Cell} from "./notebook.js";
22

3-
export function isInterpreter(cell: Cell): boolean {
4-
return cell.mode === "node";
3+
export function isInterpreter(mode: Cell["mode"]): boolean {
4+
return mode === "node" || mode === "python";
55
}
66

77
export function getInterpreterExtension(format: Cell["format"]): string {

src/lib/notebook.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import {isInterpreter} from "./interpreters.js";
2+
13
export type NotebookTheme =
24
| "air"
35
| "coffee"
@@ -37,7 +39,7 @@ export interface CellSpec {
3739
/** the committed cell value; defaults to empty */
3840
value?: string;
3941
/** the mode; affects how the value is evaluated; defaults to js */
40-
mode?: "js" | "ojs" | "md" | "html" | "tex" | "dot" | "sql" | "node";
42+
mode?: "js" | "ojs" | "md" | "html" | "tex" | "dot" | "sql" | "node" | "python";
4143
/** if true, the editor will stay open when not focused; defaults to false */
4244
pinned?: boolean;
4345
/** if true, implicit display will be suppressed; defaults to false */
@@ -94,7 +96,7 @@ export function toCell({
9496
pinned = defaultPinned(mode),
9597
hidden = false,
9698
output,
97-
format = mode === "node" ? "buffer" : undefined,
99+
format = isInterpreter(mode) ? "buffer" : undefined,
98100
database = mode === "sql" ? "var:db" : undefined,
99101
since
100102
}: CellSpec): Cell {
@@ -105,7 +107,7 @@ export function toCell({
105107
pinned,
106108
hidden,
107109
output,
108-
format: mode === "node" ? format : undefined,
110+
format: isInterpreter(mode) ? format : undefined,
109111
database: mode === "sql" ? database : undefined,
110112
since: since !== undefined ? asDate(since) : undefined
111113
};
@@ -116,5 +118,5 @@ function asDate(date: Date | string | number): Date {
116118
}
117119

118120
export function defaultPinned(mode: Cell["mode"]): boolean {
119-
return mode === "js" || mode === "sql" || mode === "node" || mode === "ojs";
121+
return mode === "js" || mode === "sql" || isInterpreter(mode) || mode === "ojs";
120122
}

src/lib/serialize.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ function serializeMode(mode: Cell["mode"]): string {
6969
return "text/vnd.graphviz";
7070
case "node":
7171
return "application/vnd.node.javascript";
72+
case "python":
73+
return "text/x-python";
7274
case "ojs":
7375
return "application/vnd.observable.javascript";
7476
default:
@@ -90,6 +92,8 @@ function deserializeMode(mode: string | null): Cell["mode"] {
9092
return "dot";
9193
case "application/vnd.node.javascript":
9294
return "node";
95+
case "text/x-python":
96+
return "python";
9397
case "application/vnd.observable.javascript":
9498
return "ojs";
9599
default:

src/runtime/stdlib/highlight.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,14 @@ async function getParser(language: string): Promise<Parser | undefined> {
5757
case "js":
5858
case "ts":
5959
case "jsx":
60-
case "node":
6160
return (await import("@lezer/javascript")).parser.configure({dialect: language});
61+
case "py":
62+
return (await import("@lezer/python")).parser;
6263
case "html":
6364
return (await import("@lezer/html")).parser;
6465
case "css":
6566
return (await import("@lezer/css")).parser;
6667
case "md":
67-
case "markdown":
6868
return (await import("@lezer/markdown")).parser;
6969
}
7070
}
@@ -75,10 +75,15 @@ function getLanguage(code: HTMLElement): string | undefined {
7575
?.slice("language-".length)
7676
?.toLowerCase();
7777
switch (language) {
78+
case "node":
7879
case "javascript":
7980
return "js";
8081
case "typescript":
8182
return "ts";
83+
case "python":
84+
return "py";
85+
case "markdown":
86+
return "md";
8287
}
8388
return language;
8489
}

src/vite/observable.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import type {TemplateLiteral} from "acorn";
88
import {JSDOM} from "jsdom";
99
import type {PluginOption, IndexHtmlTransformContext} from "vite";
1010
import {getQueryCachePath} from "../databases/index.js";
11-
import {getInterpreterCachePath} from "../interpreters/index.js";
12-
import {getInterpreterMethod} from "../lib/interpreters.js";
11+
import {getInterpreterCachePath, getInterpreterCommand} from "../interpreters/index.js";
12+
import {getInterpreterMethod, isInterpreter} from "../lib/interpreters.js";
1313
import type {Cell, Notebook} from "../lib/notebook.js";
1414
import {deserialize} from "../lib/serialize.js";
1515
import {Sourcemap} from "../javascript/sourcemap.js";
@@ -130,14 +130,14 @@ export function observable({
130130
cell.mode = "js";
131131
cell.value = `FileAttachment(${JSON.stringify(relative(dir, cachePath))}).json().then(DatabaseClient.revive)${hidden ? "" : `.then(Inputs.table)${cell.output ? ".then(view)" : ""}`}`;
132132
}
133-
} else if (mode === "node") {
133+
} else if (isInterpreter(mode)) {
134134
const {filename: sourcePath} = context;
135135
const sourceDir = dirname(sourcePath);
136136
const cachePath = await getInterpreterCachePath(sourcePath, mode, format, value);
137137
if (!existsSync(cachePath)) {
138138
await mkdir(dirname(cachePath), {recursive: true});
139-
const args = ["--input-type=module", "--permission", "--allow-fs-read=."];
140-
const child = spawn("node", args, {cwd: sourceDir});
139+
const [command, args] = getInterpreterCommand(mode);
140+
const child = spawn(command, args, {cwd: sourceDir});
141141
child.stdin.end(value);
142142
child.stderr.pipe(process.stderr);
143143
child.stdout.pipe(createWriteStream(cachePath));

yarn.lock

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1303,6 +1303,15 @@
13031303
"@lezer/common" "^1.0.0"
13041304
"@lezer/highlight" "^1.0.0"
13051305

1306+
"@lezer/python@^1.1.18":
1307+
version "1.1.18"
1308+
resolved "https://registry.yarnpkg.com/@lezer/python/-/python-1.1.18.tgz#fa02fbf492741c82dc2dc98a0a042bd0d4d7f1d3"
1309+
integrity sha512-31FiUrU7z9+d/ElGQLJFXl+dKOdx0jALlP3KEOsGTex8mvj+SoE1FgItcHWK/axkxCHGUSpqIHt6JAWfWu9Rhg==
1310+
dependencies:
1311+
"@lezer/common" "^1.2.0"
1312+
"@lezer/highlight" "^1.0.0"
1313+
"@lezer/lr" "^1.0.0"
1314+
13061315
"@nodelib/fs.scandir@2.1.5":
13071316
version "2.1.5"
13081317
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"

0 commit comments

Comments
 (0)