@observablehq/notebook-kit 2.1.6 → 2.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/package.json +2 -2
- package/dist/src/javascript/features.js +1 -1
- package/dist/src/javascript/imports/observable.d.ts +9 -2
- package/dist/src/javascript/imports/observable.js +51 -11
- package/dist/src/javascript/imports.d.ts +5 -4
- package/dist/src/javascript/imports.js +15 -3
- package/dist/src/javascript/observable.js +65 -29
- package/dist/src/runtime/define.d.ts +2 -0
- package/dist/src/runtime/define.js +3 -2
- package/dist/src/runtime/stdlib/dsv.d.ts +4 -0
- package/dist/src/runtime/stdlib/dsv.js +71 -0
- package/dist/src/runtime/stdlib/fileAttachment.d.ts +2 -6
- package/dist/src/runtime/stdlib/fileAttachment.js +6 -2
- package/dist/src/runtime/stdlib/index.d.ts +1 -5
- package/dist/src/vite/observable.js +1 -0
- package/package.json +2 -2
- package/dist/src/javascript/literal.d.ts +0 -8
- package/dist/src/javascript/literal.js +0 -42
package/dist/package.json
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/observablehq/notebook-kit.git"
|
|
7
7
|
},
|
|
8
|
-
"version": "2.1.
|
|
8
|
+
"version": "2.1.8",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"scripts": {
|
|
11
|
-
"test": "vitest",
|
|
11
|
+
"test": "TZ=America/Los_Angeles vitest",
|
|
12
12
|
"prepublishOnly": "rm -rf dist && tsc && chmod +x dist/bin/*.js && cp -r src/styles src/templates dist/src && cp src/runtime/stdlib/*.css dist/src/runtime/stdlib",
|
|
13
13
|
"lint": "tsc --noEmit && eslint bin src types",
|
|
14
14
|
"notebooks": "tsx bin/notebooks.ts",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getStringLiteralValue, isStringLiteral } from "./
|
|
1
|
+
import { getStringLiteralValue, isStringLiteral } from "./strings.js";
|
|
2
2
|
import { findReferences } from "./references.js";
|
|
3
3
|
import { syntaxError } from "./syntaxError.js";
|
|
4
4
|
import { simple } from "./walk.js";
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ImportWithDeclaration } from "@observablehq/parser";
|
|
2
|
+
import type { ImportDeclaration, ImportSpecifier } from "acorn";
|
|
3
|
+
import type { Identifier } from "acorn";
|
|
4
|
+
import type { NamedImportSpecifier } from "../imports.js";
|
|
2
5
|
/** If specifier is an observable: protocol import, resolves it. */
|
|
3
6
|
export declare function resolveObservableImport(specifier: string): string;
|
|
4
7
|
export declare function isObservableImport(node: ImportDeclaration, specifier: string): boolean;
|
|
5
8
|
/** Note: mutates inputs! */
|
|
6
|
-
export declare function renderObservableImport(source: string, node: ImportDeclaration, inputs: string[]): string;
|
|
9
|
+
export declare function renderObservableImport(source: string, node: ImportDeclaration | ImportWithDeclaration, inputs: string[]): string;
|
|
10
|
+
export declare function toSpecialIdentifier(identifier: Identifier, type: "viewof" | "mutable"): Identifier;
|
|
11
|
+
export declare function toSpecialImportSpecifier(specifier: ImportSpecifier, type: "viewof" | "mutable"): ImportSpecifier;
|
|
12
|
+
export declare function isViewImport(node: NamedImportSpecifier): node is ImportSpecifier;
|
|
13
|
+
export declare function isMutableImport(node: NamedImportSpecifier): node is ImportSpecifier;
|
|
7
14
|
/** Turns e.g. "viewof$foo" into "viewof foo", and "$$" into "$". */
|
|
8
15
|
export declare function dedollar(input: string): string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getImportedName, getLocalName } from "../imports.js";
|
|
1
|
+
import { flatMapImportSpecifiers, getImportedName, getLocalName } from "../imports.js";
|
|
2
2
|
const CODE_DOLLAR = 36;
|
|
3
3
|
/** If specifier is an observable: protocol import, resolves it. */
|
|
4
4
|
export function resolveObservableImport(specifier) {
|
|
@@ -21,20 +21,60 @@ export function renderObservableImport(source, node, inputs) {
|
|
|
21
21
|
if (!inputs.includes("@variable"))
|
|
22
22
|
inputs.push("@variable");
|
|
23
23
|
return `(import(${JSON.stringify(source)}).then((_) => {
|
|
24
|
-
const module = __variable._module._runtime.module(_.default);
|
|
25
|
-
const outputs = new Map(Array.from(__variable._outputs, (v) => [v._name, v]));${node
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
throw new SyntaxError("observable namespace imports are not supported");
|
|
29
|
-
const iname = dedollar(getImportedName(specifier));
|
|
30
|
-
const lname = getLocalName(specifier);
|
|
24
|
+
const module = __variable._module._runtime.module(_.default)${"injections" in node ? `.derive([${renderObservableInjections(node)}], __variable._module)` : ""};
|
|
25
|
+
const outputs = new Map(Array.from(__variable._outputs, (v) => [v._name, v]));${flatMapImportSpecifiers(node, (specifier) => {
|
|
26
|
+
const i = dedollar(getImportedName(specifier));
|
|
27
|
+
const l = getLocalName(specifier);
|
|
31
28
|
return `
|
|
32
|
-
outputs.get(${JSON.stringify(
|
|
33
|
-
})
|
|
34
|
-
.join("")}
|
|
29
|
+
outputs.get(${JSON.stringify(l)})?.import(${JSON.stringify(i)}${i === l ? "" : `, ${JSON.stringify(l)}`}, module);`;
|
|
30
|
+
}).join("")}
|
|
35
31
|
return {};
|
|
36
32
|
}))`;
|
|
37
33
|
}
|
|
34
|
+
function renderObservableInjections(node) {
|
|
35
|
+
return node.injections
|
|
36
|
+
.map((node) => {
|
|
37
|
+
if (node.imported.type !== "Identifier")
|
|
38
|
+
throw new SyntaxError("unexpected import specifier");
|
|
39
|
+
const imported = node.imported.name;
|
|
40
|
+
const local = node.local.name;
|
|
41
|
+
let injection;
|
|
42
|
+
if (imported === local) {
|
|
43
|
+
injection = `"${local}"`;
|
|
44
|
+
if (node.view)
|
|
45
|
+
injection += `, "viewof ${local}"`;
|
|
46
|
+
if (node.mutable)
|
|
47
|
+
injection += `, "mutable ${local}"`;
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
injection = `{name: "${imported}", alias: "${local}"}`;
|
|
51
|
+
if (node.view)
|
|
52
|
+
injection += `, {name: "viewof ${imported}", alias: "viewof ${local}"}`;
|
|
53
|
+
if (node.mutable)
|
|
54
|
+
injection += `, {name: "mutable ${imported}", alias: "mutable ${local}"}`;
|
|
55
|
+
}
|
|
56
|
+
return injection;
|
|
57
|
+
})
|
|
58
|
+
.join(", ");
|
|
59
|
+
}
|
|
60
|
+
export function toSpecialIdentifier(identifier, type) {
|
|
61
|
+
return { ...identifier, name: `${type}$${identifier.name}` };
|
|
62
|
+
}
|
|
63
|
+
export function toSpecialImportSpecifier(specifier, type) {
|
|
64
|
+
if (specifier.imported.type !== "Identifier")
|
|
65
|
+
throw new SyntaxError("unexpected import specifier");
|
|
66
|
+
return {
|
|
67
|
+
...specifier,
|
|
68
|
+
imported: toSpecialIdentifier(specifier.imported, type),
|
|
69
|
+
local: toSpecialIdentifier(specifier.local, type)
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
export function isViewImport(node) {
|
|
73
|
+
return "view" in node && !!node.view;
|
|
74
|
+
}
|
|
75
|
+
export function isMutableImport(node) {
|
|
76
|
+
return "mutable" in node && !!node.mutable;
|
|
77
|
+
}
|
|
38
78
|
/** Turns e.g. "viewof$foo" into "viewof foo", and "$$" into "$". */
|
|
39
79
|
export function dedollar(input) {
|
|
40
80
|
const start = 0;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { CallExpression, MemberExpression, Node } from "acorn";
|
|
2
|
-
import type { ImportDefaultSpecifier, ImportSpecifier } from "acorn";
|
|
1
|
+
import type { CallExpression, ImportDeclaration, MemberExpression, Node } from "acorn";
|
|
2
|
+
import type { ImportDefaultSpecifier, ImportNamespaceSpecifier, ImportSpecifier } from "acorn";
|
|
3
3
|
import type { Sourcemap } from "./sourcemap.js";
|
|
4
|
-
type NamedImportSpecifier = ImportSpecifier | ImportDefaultSpecifier;
|
|
4
|
+
export type NamedImportSpecifier = ImportSpecifier | ImportDefaultSpecifier;
|
|
5
|
+
export type AnyImportSpecifier = ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier;
|
|
5
6
|
/** Throws a syntax error if any export declarations are found. */
|
|
6
7
|
export declare function checkExports(body: Node, { input }: {
|
|
7
8
|
input: string;
|
|
@@ -21,4 +22,4 @@ export declare function rewriteImportExpressions(output: Sourcemap, body: Node,
|
|
|
21
22
|
export declare function rewriteImportDeclarations(output: Sourcemap, body: Node, inputs: string[], { resolveLocalImports }?: RewriteImportOptions): void;
|
|
22
23
|
export declare function getLocalName(node: NamedImportSpecifier): string;
|
|
23
24
|
export declare function getImportedName(node: NamedImportSpecifier): string;
|
|
24
|
-
export
|
|
25
|
+
export declare function flatMapImportSpecifiers<T>(node: ImportDeclaration, f: (node: NamedImportSpecifier) => T): T[];
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { resolveJsrImport } from "./imports/jsr.js";
|
|
2
2
|
import { resolveNpmImport } from "./imports/npm.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { isObservableImport, isMutableImport, isViewImport } from "./imports/observable.js";
|
|
4
|
+
import { resolveObservableImport, renderObservableImport } from "./imports/observable.js";
|
|
5
|
+
import { toSpecialImportSpecifier } from "./imports/observable.js";
|
|
5
6
|
import { getStringLiteralValue, isStringLiteral } from "./strings.js";
|
|
6
7
|
import { syntaxError } from "./syntaxError.js";
|
|
7
8
|
import { simple } from "./walk.js";
|
|
@@ -138,9 +139,20 @@ function isNamedSpecifier(node) {
|
|
|
138
139
|
}
|
|
139
140
|
function rewriteImportSpecifiers(node) {
|
|
140
141
|
return node.specifiers.some(isNamedSpecifier)
|
|
141
|
-
? `{${node
|
|
142
|
+
? `{${flatMapImportSpecifiers(node, rewriteImportSpecifier).join(", ")}}`
|
|
142
143
|
: (node.specifiers.find(isNamespaceSpecifier)?.local.name ?? "{}");
|
|
143
144
|
}
|
|
145
|
+
export function flatMapImportSpecifiers(node, f) {
|
|
146
|
+
return node.specifiers.flatMap((node) => {
|
|
147
|
+
if (node.type === "ImportNamespaceSpecifier")
|
|
148
|
+
throw new SyntaxError("unexpected namespace specifier");
|
|
149
|
+
return isViewImport(node)
|
|
150
|
+
? [f(node), f(toSpecialImportSpecifier(node, "viewof"))]
|
|
151
|
+
: isMutableImport(node)
|
|
152
|
+
? [f(node), f(toSpecialImportSpecifier(node, "mutable"))]
|
|
153
|
+
: f(node);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
144
156
|
function rewriteImportSpecifier(node) {
|
|
145
157
|
return getImportedName(node) === getLocalName(node)
|
|
146
158
|
? getLocalName(node)
|
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
import { parseCell } from "@observablehq/parser";
|
|
2
2
|
import { rewriteFileExpressions } from "./files.js";
|
|
3
|
+
import { flatMapImportSpecifiers, rewriteImportDeclarations } from "./imports.js";
|
|
4
|
+
import { resolveNpmImport } from "./imports/npm.js";
|
|
3
5
|
import { Sourcemap } from "./sourcemap.js";
|
|
6
|
+
import { getStringLiteralValue, isStringLiteral } from "./strings.js";
|
|
4
7
|
import { transpileJavaScript } from "./transpile.js";
|
|
5
8
|
import { simple } from "./walk.js";
|
|
6
9
|
export function transpileObservable(input, options) {
|
|
7
10
|
const cell = parseCell(input);
|
|
8
11
|
if (!cell.body)
|
|
9
12
|
return transpileJavaScript(input);
|
|
13
|
+
if (isImportCell(cell))
|
|
14
|
+
return transpileObservableImport(input, cell, options);
|
|
10
15
|
if (cell.tag)
|
|
11
16
|
throw new Error("tagged ojs cells are not supported");
|
|
12
17
|
const output = new Sourcemap(input).trim();
|
|
13
18
|
rewriteSpecialReferences(output, cell.body);
|
|
14
|
-
|
|
15
|
-
rewriteImportSource(output, cell.body);
|
|
16
|
-
return transpileJavaScript(String(output));
|
|
17
|
-
}
|
|
19
|
+
rewriteDynamicImports(output, cell.body);
|
|
18
20
|
if (options?.resolveFiles)
|
|
19
21
|
rewriteFileExpressions(output, cell.body);
|
|
20
22
|
const inputs = Array.from(new Set(cell.references.map(asReference)));
|
|
@@ -42,13 +44,66 @@ export function transpileObservable(input, options) {
|
|
|
42
44
|
secrets: new Set(cell.secrets.keys())
|
|
43
45
|
};
|
|
44
46
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
function isImportCell(cell) {
|
|
48
|
+
return cell.body.type === "ImportDeclaration";
|
|
49
|
+
}
|
|
50
|
+
function transpileObservableImport(input, cell, options) {
|
|
51
|
+
const output = new Sourcemap(input).trim();
|
|
52
|
+
const inputs = ["@variable"];
|
|
53
|
+
const declarations = flatMapImportSpecifiers(cell.body, (s) => s.local);
|
|
54
|
+
const outputs = Array.from(new Set(declarations.map(asDeclaration)));
|
|
55
|
+
transformObservableImport(cell.body);
|
|
56
|
+
rewriteImportDeclarations(output, cell.body, inputs, options);
|
|
57
|
+
output.insertLeft(0, `async (__variable) => {\n`);
|
|
58
|
+
if (outputs.length > 0)
|
|
59
|
+
output.insertRight(input.length, `\nreturn {${outputs}};`);
|
|
60
|
+
output.insertRight(input.length, "\n}");
|
|
61
|
+
const body = String(output);
|
|
62
|
+
return {
|
|
63
|
+
body,
|
|
64
|
+
inputs,
|
|
65
|
+
outputs,
|
|
66
|
+
autodisplay: false,
|
|
67
|
+
files: new Set(),
|
|
68
|
+
secrets: new Set(),
|
|
69
|
+
databases: new Set()
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/** Mutates the given import declaration to be an Observable import. */
|
|
73
|
+
function transformObservableImport(body) {
|
|
74
|
+
const source = body.source.value;
|
|
75
|
+
if (typeof source === "string" && !/^\w+:/.test(source)) {
|
|
76
|
+
body.source.value = `observable:${source}`;
|
|
50
77
|
}
|
|
51
|
-
|
|
78
|
+
body.attributes = [
|
|
79
|
+
{
|
|
80
|
+
type: "ImportAttribute",
|
|
81
|
+
key: { type: "Literal", value: "type", start: 0, end: 0 },
|
|
82
|
+
value: { type: "Literal", value: "observable", start: 0, end: 0 },
|
|
83
|
+
start: 0,
|
|
84
|
+
end: 0
|
|
85
|
+
}
|
|
86
|
+
];
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Rewrite a bare dynamic import such as import("d3") to a CDN URL, like
|
|
90
|
+
* import("https://cdn.jsdelivr.net/npm/d3/+esm"), using the same resolution as
|
|
91
|
+
* static imports. Protocol (npm:, https:, …) and local imports are left alone.
|
|
92
|
+
*/
|
|
93
|
+
function rewriteDynamicImports(output, body) {
|
|
94
|
+
simple(body, {
|
|
95
|
+
ImportExpression({ source }) {
|
|
96
|
+
if (!isStringLiteral(source))
|
|
97
|
+
return;
|
|
98
|
+
let value = getStringLiteralValue(source);
|
|
99
|
+
if (/^(\w+:|\.?\.?\/)/.test(value))
|
|
100
|
+
return;
|
|
101
|
+
if (/\.(js|mjs|cjs)$/.test(value))
|
|
102
|
+
value += "/+esm";
|
|
103
|
+
const resolution = resolveNpmImport(`npm:${value}`);
|
|
104
|
+
output.replaceLeft(source.start, source.end, JSON.stringify(resolution));
|
|
105
|
+
}
|
|
106
|
+
});
|
|
52
107
|
}
|
|
53
108
|
/** Rewrite viewof x ↦ viewof$x, and mutable x ↦ mutable$x.value. */
|
|
54
109
|
function rewriteSpecialReferences(output, body) {
|
|
@@ -58,28 +113,9 @@ function rewriteSpecialReferences(output, body) {
|
|
|
58
113
|
},
|
|
59
114
|
ViewExpression(node) {
|
|
60
115
|
output.replaceLeft(node.start, node.end, asReference(node));
|
|
61
|
-
},
|
|
62
|
-
ImportSpecifier(node) {
|
|
63
|
-
const inode = node;
|
|
64
|
-
const prefix = inode.view ? "viewof$" : inode.mutable ? "mutable$" : null;
|
|
65
|
-
if (prefix) {
|
|
66
|
-
const imported = asImportName(node.imported);
|
|
67
|
-
output.replaceLeft(node.start, node.imported.start, prefix);
|
|
68
|
-
if (node.imported === node.local) {
|
|
69
|
-
output.insertLeft(node.start, `${imported},`);
|
|
70
|
-
}
|
|
71
|
-
else {
|
|
72
|
-
const local = asImportName(node.local);
|
|
73
|
-
output.insertLeft(node.start, `${imported} as ${local},`);
|
|
74
|
-
output.insertLeft(node.local.start, prefix);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
116
|
}
|
|
78
117
|
});
|
|
79
118
|
}
|
|
80
|
-
function asImportName(ref) {
|
|
81
|
-
return ref.type === "Identifier" ? ref.name : ref.raw;
|
|
82
|
-
}
|
|
83
119
|
function asReference(ref) {
|
|
84
120
|
return ref.type === "ViewExpression"
|
|
85
121
|
? `viewof$${ref.id.name}`
|
|
@@ -22,6 +22,8 @@ export type Definition = {
|
|
|
22
22
|
autoview?: boolean;
|
|
23
23
|
/** whether this cell’s singular output is a mutable */
|
|
24
24
|
automutable?: boolean;
|
|
25
|
+
/** whether to define the display and view builtins; defaults to true */
|
|
26
|
+
display?: boolean;
|
|
25
27
|
/** an asset mapping to apply to any autodisplayed assets (e.g., images and videos) */
|
|
26
28
|
assets?: Map<string, string>;
|
|
27
29
|
};
|
|
@@ -2,12 +2,13 @@ import { clear, display, observe } from "./display.js";
|
|
|
2
2
|
import { input } from "./stdlib/generators/index.js";
|
|
3
3
|
import { Mutator } from "./stdlib/mutable.js";
|
|
4
4
|
export function define(main, state, definition, observer = observe) {
|
|
5
|
-
const { id, body, inputs = [], outputs = [], output
|
|
5
|
+
const { id, body, inputs = [], outputs = [], output } = definition;
|
|
6
|
+
const { autodisplay, autoview, automutable, display: defineDisplay = true } = definition;
|
|
6
7
|
const variables = state.variables;
|
|
7
8
|
const v = main.variable(observer(state, definition), { shadow: {} });
|
|
8
9
|
const vid = output ?? (outputs.length ? `cell ${id}` : null);
|
|
9
10
|
state.autoclear = true;
|
|
10
|
-
if (inputs.includes("display") || inputs.includes("view")) {
|
|
11
|
+
if (defineDisplay && (inputs.includes("display") || inputs.includes("view"))) {
|
|
11
12
|
let displayVersion = -1; // the variable._version of currently-displayed values
|
|
12
13
|
const vd = new v.constructor(2, v._module);
|
|
13
14
|
vd.define(inputs.filter((i) => i !== "display" && i !== "view"), () => {
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function inferTypes<T extends Record<string, string>>(rows: T[], columns: (keyof T)[]): Record<keyof T, unknown>[];
|
|
2
|
+
export declare function coerceBoolean(value: string): boolean | null | undefined;
|
|
3
|
+
export declare function coerceNumber(value: string): number;
|
|
4
|
+
export declare function coerceDate(value: string): Date | null;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unused-expressions */
|
|
2
|
+
/**
|
|
3
|
+
* Accepts dates in the following forms:
|
|
4
|
+
* - ±YYYYYY-MM-DD
|
|
5
|
+
* - YYYY-MM-DD
|
|
6
|
+
* - MM/DD/YY
|
|
7
|
+
* - MM/DD/YYYY
|
|
8
|
+
*
|
|
9
|
+
* Following a "T" or a space, dates may additionally have a time:
|
|
10
|
+
* - HH:MM
|
|
11
|
+
* - HH:MM:SS
|
|
12
|
+
* - HH:MM:SS.MMM
|
|
13
|
+
*
|
|
14
|
+
* And the time may optionally have a time zone:
|
|
15
|
+
* - Z
|
|
16
|
+
* - ±HH:MM
|
|
17
|
+
*/
|
|
18
|
+
const DATE_TEST = /^([-+]\d{2})?\d{4}-\d{2}-\d{2}|\d{1,2}\/\d{1,2}\/\d{2,4}([T ]\d{2}:\d{2}(:\d{2}(\.\d{3})?)?(Z|[-+]\d{2}:\d{2})?)?$/; // prettier-ignore
|
|
19
|
+
/** The maximum number of rows to sample (including missing values). */
|
|
20
|
+
const SAMPLE_SIZE = 100;
|
|
21
|
+
export function inferTypes(rows, columns) {
|
|
22
|
+
const output = rows;
|
|
23
|
+
const n = rows.length;
|
|
24
|
+
const k = Math.min(n, SAMPLE_SIZE);
|
|
25
|
+
for (const column of columns) {
|
|
26
|
+
let booleans = 0;
|
|
27
|
+
let numbers = 0;
|
|
28
|
+
let dates = 0;
|
|
29
|
+
let strings = 0;
|
|
30
|
+
for (let i = 0; i < k; ++i) {
|
|
31
|
+
const value = rows[i][column]?.trim();
|
|
32
|
+
if (!value)
|
|
33
|
+
continue;
|
|
34
|
+
++strings;
|
|
35
|
+
if (/^(true|false)$/i.test(value))
|
|
36
|
+
++booleans;
|
|
37
|
+
else if (!isNaN(Number(value)))
|
|
38
|
+
++numbers;
|
|
39
|
+
else if (DATE_TEST.test(value))
|
|
40
|
+
++dates;
|
|
41
|
+
}
|
|
42
|
+
// Chose the non-string type with the greatest count that is also ≥90%; or
|
|
43
|
+
// if no such type meets that criterion, use string.
|
|
44
|
+
let coerce = undefined;
|
|
45
|
+
let typeCount = Math.max(1, Math.ceil(strings * 0.9)) - 1;
|
|
46
|
+
if (booleans > typeCount)
|
|
47
|
+
((coerce = coerceBoolean), (typeCount = booleans));
|
|
48
|
+
if (numbers > typeCount)
|
|
49
|
+
((coerce = coerceNumber), (typeCount = numbers));
|
|
50
|
+
if (dates > typeCount)
|
|
51
|
+
((coerce = coerceDate), (typeCount = dates));
|
|
52
|
+
if (!coerce)
|
|
53
|
+
continue;
|
|
54
|
+
for (let i = 0; i < n; ++i) {
|
|
55
|
+
output[i][column] = coerce(rows[i][column]);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return output;
|
|
59
|
+
}
|
|
60
|
+
export function coerceBoolean(value) {
|
|
61
|
+
const trimmed = value.trim().toLowerCase();
|
|
62
|
+
return trimmed === "true" ? true : trimmed === "false" ? false : trimmed ? undefined : null;
|
|
63
|
+
}
|
|
64
|
+
export function coerceNumber(value) {
|
|
65
|
+
const trimmed = value.trim();
|
|
66
|
+
return trimmed ? Number(value) : NaN;
|
|
67
|
+
}
|
|
68
|
+
export function coerceDate(value) {
|
|
69
|
+
const trimmed = value.trim();
|
|
70
|
+
return trimmed ? new Date(DATE_TEST.test(trimmed) ? trimmed : NaN) : null;
|
|
71
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export type DsvOptions = {
|
|
2
2
|
delimiter?: string;
|
|
3
3
|
array?: boolean;
|
|
4
|
-
typed?: boolean;
|
|
4
|
+
typed?: "auto" | boolean;
|
|
5
5
|
};
|
|
6
6
|
export type DsvResult = (Record<string, any>[] | any[][]) & {
|
|
7
7
|
columns: string[];
|
|
@@ -73,11 +73,7 @@ export declare abstract class AbstractFile implements FileAttachment {
|
|
|
73
73
|
text(encoding?: string): Promise<string>;
|
|
74
74
|
json(): Promise<any>;
|
|
75
75
|
stream(): Promise<ReadableStream<Uint8Array<ArrayBufferLike>>>;
|
|
76
|
-
dsv({ delimiter, array, typed }?:
|
|
77
|
-
delimiter?: string | undefined;
|
|
78
|
-
array?: boolean | undefined;
|
|
79
|
-
typed?: boolean | undefined;
|
|
80
|
-
}): Promise<DsvResult>;
|
|
76
|
+
dsv({ delimiter, array, typed }?: DsvOptions): Promise<DsvResult>;
|
|
81
77
|
csv(options?: Omit<DsvOptions, "delimiter">): Promise<DsvResult>;
|
|
82
78
|
tsv(options?: Omit<DsvOptions, "delimiter">): Promise<DsvResult>;
|
|
83
79
|
image(props?: Partial<HTMLImageElement>): Promise<HTMLImageElement>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { inferTypes } from "./dsv.js";
|
|
1
2
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
3
|
const files = new Map();
|
|
3
4
|
let strict = false;
|
|
@@ -92,8 +93,11 @@ export class AbstractFile {
|
|
|
92
93
|
async dsv({ delimiter = ",", array = false, typed = false } = {}) {
|
|
93
94
|
const [text, d3] = await Promise.all([this.text(), import("https://cdn.jsdelivr.net/npm/d3-dsv/+esm")]); // prettier-ignore
|
|
94
95
|
const format = d3.dsvFormat(delimiter);
|
|
95
|
-
const parse = array ? format.parseRows : format.parse;
|
|
96
|
-
|
|
96
|
+
const parse = array ? ((typed && (typed = true)), format.parseRows) : format.parse;
|
|
97
|
+
const output = parse(text, typed === true ? d3.autoType : undefined);
|
|
98
|
+
if (typed === "auto")
|
|
99
|
+
inferTypes(output, output.columns);
|
|
100
|
+
return output;
|
|
97
101
|
}
|
|
98
102
|
async csv(options) {
|
|
99
103
|
return this.dsv({ ...options, delimiter: "," });
|
|
@@ -72,11 +72,7 @@ export declare const library: {
|
|
|
72
72
|
text(encoding?: string): Promise<string>;
|
|
73
73
|
json(): Promise<any>;
|
|
74
74
|
stream(): Promise<ReadableStream<Uint8Array<ArrayBufferLike>>>;
|
|
75
|
-
dsv({ delimiter, array, typed }?:
|
|
76
|
-
delimiter?: string | undefined;
|
|
77
|
-
array?: boolean | undefined;
|
|
78
|
-
typed?: boolean | undefined;
|
|
79
|
-
}): Promise<import("./fileAttachment.js").DsvResult>;
|
|
75
|
+
dsv({ delimiter, array, typed }?: import("./fileAttachment.js").DsvOptions): Promise<import("./fileAttachment.js").DsvResult>;
|
|
80
76
|
csv(options?: Omit<import("./fileAttachment.js").DsvOptions, "delimiter">): Promise<import("./fileAttachment.js").DsvResult>;
|
|
81
77
|
tsv(options?: Omit<import("./fileAttachment.js").DsvOptions, "delimiter">): Promise<import("./fileAttachment.js").DsvResult>;
|
|
82
78
|
image(props?: Partial<HTMLImageElement>): Promise<HTMLImageElement>;
|
|
@@ -173,6 +173,7 @@ define(
|
|
|
173
173
|
inputs: ${JSON.stringify(transpiled.inputs)},
|
|
174
174
|
outputs: ${JSON.stringify(transpiled.outputs)},
|
|
175
175
|
output: ${JSON.stringify(transpiled.output)},
|
|
176
|
+
display: ${cell.mode === "js" || cell.mode === "ts"},
|
|
176
177
|
assets: ${assets.size > 0 ? "assets" : "undefined"},
|
|
177
178
|
autodisplay: ${transpiled.autodisplay},
|
|
178
179
|
autoview: ${transpiled.autoview},
|
package/package.json
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/observablehq/notebook-kit.git"
|
|
7
7
|
},
|
|
8
|
-
"version": "2.1.
|
|
8
|
+
"version": "2.1.8",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"scripts": {
|
|
11
|
-
"test": "vitest",
|
|
11
|
+
"test": "TZ=America/Los_Angeles vitest",
|
|
12
12
|
"prepublishOnly": "rm -rf dist && tsc && chmod +x dist/bin/*.js && cp -r src/styles src/templates dist/src && cp src/runtime/stdlib/*.css dist/src/runtime/stdlib",
|
|
13
13
|
"lint": "tsc --noEmit && eslint bin src types",
|
|
14
14
|
"notebooks": "tsx bin/notebooks.ts",
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { BinaryExpression, Literal, Node, TemplateLiteral } from "acorn";
|
|
2
|
-
export type StringLiteral = (Literal & {
|
|
3
|
-
value: string;
|
|
4
|
-
}) | TemplateLiteral | BinaryExpression;
|
|
5
|
-
export declare function isLiteral(node: Node): node is Literal;
|
|
6
|
-
export declare function isTemplateLiteral(node: Node): node is TemplateLiteral;
|
|
7
|
-
export declare function isStringLiteral(node: Node): node is StringLiteral;
|
|
8
|
-
export declare function getStringLiteralValue(node: StringLiteral): string;
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
export function isLiteral(node) {
|
|
2
|
-
return node.type === "Literal";
|
|
3
|
-
}
|
|
4
|
-
export function isTemplateLiteral(node) {
|
|
5
|
-
return node.type === "TemplateLiteral";
|
|
6
|
-
}
|
|
7
|
-
export function isStringLiteral(node) {
|
|
8
|
-
return isLiteral(node)
|
|
9
|
-
? /^['"]/.test(node.raw)
|
|
10
|
-
: isTemplateLiteral(node)
|
|
11
|
-
? node.expressions.every(isStringLiteral)
|
|
12
|
-
: isBinaryExpression(node)
|
|
13
|
-
? node.operator === "+" && isStringLiteral(node.left) && isStringLiteral(node.right)
|
|
14
|
-
: isMemberExpression(node)
|
|
15
|
-
? "value" in node // param
|
|
16
|
-
: false;
|
|
17
|
-
}
|
|
18
|
-
export function getStringLiteralValue(node) {
|
|
19
|
-
return node.type === "TemplateLiteral"
|
|
20
|
-
? getTemplateLiteralValue(node)
|
|
21
|
-
: node.type === "BinaryExpression"
|
|
22
|
-
? getBinaryExpressionValue(node)
|
|
23
|
-
: node.value; // Literal or ParamReference
|
|
24
|
-
}
|
|
25
|
-
function getTemplateLiteralValue(node) {
|
|
26
|
-
let value = node.quasis[0].value.cooked;
|
|
27
|
-
for (let i = 0; i < node.expressions.length; ++i) {
|
|
28
|
-
value += getStringLiteralValue(node.expressions[i]);
|
|
29
|
-
value += node.quasis[i + 1].value.cooked;
|
|
30
|
-
}
|
|
31
|
-
return value;
|
|
32
|
-
}
|
|
33
|
-
function getBinaryExpressionValue(node) {
|
|
34
|
-
return (getStringLiteralValue(node.left) +
|
|
35
|
-
getStringLiteralValue(node.right));
|
|
36
|
-
}
|
|
37
|
-
function isMemberExpression(node) {
|
|
38
|
-
return node.type === "MemberExpression";
|
|
39
|
-
}
|
|
40
|
-
function isBinaryExpression(node) {
|
|
41
|
-
return node.type === "BinaryExpression";
|
|
42
|
-
}
|