@nteract/commutable
Advanced tools
Comparing version 6.0.4-alpha.0 to 7.0.0
@@ -6,4 +6,4 @@ /** | ||
import { ExecutionCount } from "./primitives"; | ||
import { Map as ImmutableMap, List as ImmutableList, Record, RecordOf } from "immutable"; | ||
declare type CodeCellParams = { | ||
import { List as ImmutableList, Map as ImmutableMap, Record, RecordOf } from "immutable"; | ||
export interface CodeCellParams { | ||
cell_type: "code"; | ||
@@ -14,17 +14,17 @@ metadata: ImmutableMap<string, any>; | ||
outputs: ImmutableList<ImmutableOutput>; | ||
}; | ||
} | ||
export declare const makeCodeCell: Record.Factory<CodeCellParams>; | ||
export declare type ImmutableCodeCell = RecordOf<CodeCellParams>; | ||
declare type MarkdownCellParams = { | ||
export interface MarkdownCellParams { | ||
cell_type: "markdown"; | ||
source: string; | ||
metadata: ImmutableMap<string, any>; | ||
}; | ||
} | ||
export declare const makeMarkdownCell: Record.Factory<MarkdownCellParams>; | ||
export declare type ImmutableMarkdownCell = RecordOf<MarkdownCellParams>; | ||
declare type RawCellParams = { | ||
export interface RawCellParams { | ||
cell_type: "raw"; | ||
source: string; | ||
metadata: ImmutableMap<string, any>; | ||
}; | ||
} | ||
export declare const makeRawCell: Record.Factory<RawCellParams>; | ||
@@ -34,2 +34,1 @@ export declare type ImmutableRawCell = RecordOf<RawCellParams>; | ||
export declare type CellType = "raw" | "markdown" | "code"; | ||
export {}; |
@@ -5,5 +5,3 @@ "use strict"; | ||
*/ | ||
// ..................................... | ||
// API Exports | ||
// | ||
function __export(m) { | ||
@@ -18,49 +16,1 @@ for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; | ||
__export(require("./notebook")); | ||
/* | ||
// from structures | ||
export { | ||
emptyCodeCell, | ||
emptyMarkdownCell, | ||
emptyNotebook, | ||
monocellNotebook, | ||
createCodeCell, | ||
insertCellAt, | ||
insertCellAfter, | ||
deleteCell, | ||
appendCellToNotebook | ||
} from "./structures"; | ||
// v4 | ||
export { StreamOutput, Output, createImmutableOutput } from "./v4"; | ||
export { | ||
createImmutableMimeBundle, | ||
makeDisplayData, | ||
makeErrorOutput, | ||
makeStreamOutput, | ||
makeExecuteResult, | ||
MimeBundle | ||
} from "./outputs"; | ||
export { | ||
makeRawCell, | ||
makeCodeCell, | ||
makeMarkdownCell, | ||
ImmutableCodeCell, | ||
ImmutableMarkdownCell, | ||
ImmutableRawCell, | ||
ImmutableCell, | ||
CellType | ||
} from "./cells"; | ||
export { | ||
toJS, | ||
stringifyNotebook, | ||
fromJS, | ||
parseNotebook, | ||
makeNotebookRecord, | ||
Notebook, | ||
ImmutableNotebook | ||
} from "./notebook"; | ||
*/ |
@@ -10,8 +10,8 @@ /** | ||
*/ | ||
import * as v3 from "./v3"; | ||
import * as v4 from "./v4"; | ||
import * as v3 from "./v3"; | ||
import { Map as ImmutableMap, List as ImmutableList, Record } from "immutable"; | ||
import { List as ImmutableList, Map as ImmutableMap, Record } from "immutable"; | ||
import { ImmutableCell } from "./cells"; | ||
import { CellId } from "./primitives"; | ||
export declare type NotebookRecordParams = { | ||
export interface NotebookRecordParams { | ||
cellOrder: ImmutableList<CellId>; | ||
@@ -22,6 +22,6 @@ cellMap: ImmutableMap<CellId, ImmutableCell>; | ||
metadata: ImmutableMap<string, any>; | ||
}; | ||
} | ||
export declare const makeNotebookRecord: Record.Factory<NotebookRecordParams>; | ||
export declare type ImmutableNotebook = Record<NotebookRecordParams> & Readonly<NotebookRecordParams>; | ||
export declare type Notebook = v4.Notebook | v3.Notebook; | ||
export declare type Notebook = v4.NotebookV4 | v3.NotebookV3; | ||
/** | ||
@@ -34,4 +34,4 @@ * Converts a string representation of a notebook into a JSON representation. | ||
*/ | ||
export declare const parseNotebook: (notebookString: string) => Notebook; | ||
export declare const fromJS: (notebook: v4.Notebook | ImmutableNotebook | v3.Notebook) => ImmutableNotebook; | ||
export declare function parseNotebook(notebookString: string): Notebook; | ||
export declare function fromJS(notebook: Notebook | ImmutableNotebook): Record<NotebookRecordParams> & Readonly<NotebookRecordParams>; | ||
/** | ||
@@ -45,3 +45,3 @@ * Converts an immutable representation of a notebook to a JSON representation of the | ||
*/ | ||
export declare const toJS: (immnb: ImmutableNotebook) => v4.Notebook; | ||
export declare function toJS(immnb: ImmutableNotebook): v4.NotebookV4; | ||
/** | ||
@@ -54,2 +54,2 @@ * Converts a JSON representation of a notebook into a string representation. | ||
*/ | ||
export declare const stringifyNotebook: (notebook: v4.Notebook) => string; | ||
export declare function stringifyNotebook(notebook: v4.NotebookV4): string; |
@@ -19,4 +19,4 @@ "use strict"; | ||
*/ | ||
const v3 = __importStar(require("./v3")); | ||
const v4 = __importStar(require("./v4")); | ||
const v3 = __importStar(require("./v3")); | ||
const immutable_1 = require("immutable"); | ||
@@ -30,3 +30,5 @@ exports.makeNotebookRecord = immutable_1.Record({ | ||
}); | ||
const freezeReviver = (_k, v) => Object.freeze(v); | ||
function freezeReviver(_k, v) { | ||
return Object.freeze(v); | ||
} | ||
/** | ||
@@ -39,4 +41,7 @@ * Converts a string representation of a notebook into a JSON representation. | ||
*/ | ||
exports.parseNotebook = (notebookString) => JSON.parse(notebookString, freezeReviver); | ||
exports.fromJS = (notebook) => { | ||
function parseNotebook(notebookString) { | ||
return JSON.parse(notebookString, freezeReviver); | ||
} | ||
exports.parseNotebook = parseNotebook; | ||
function fromJS(notebook) { | ||
if (immutable_1.Record.isRecord(notebook)) { | ||
@@ -46,12 +51,11 @@ if (notebook.has("cellOrder") && notebook.has("cellMap")) { | ||
} | ||
throw new TypeError(`commutable was passed an Immutable.Record structure that is not a notebook`); | ||
throw new TypeError("commutable was passed an Immutable.Record structure that is not a notebook"); | ||
} | ||
if (notebook.nbformat === 4 && notebook.nbformat_minor >= 0) { | ||
var v4Notebook = notebook; | ||
if (Array.isArray(v4Notebook.cells) && | ||
if (v4.isNotebookV4(notebook)) { | ||
if (Array.isArray(notebook.cells) && | ||
typeof notebook.metadata === "object") { | ||
return v4.fromJS(v4Notebook); | ||
return v4.fromJS(notebook); | ||
} | ||
} | ||
else if (notebook.nbformat === 3 && notebook.nbformat_minor >= 0) { | ||
else if (v3.isNotebookV3(notebook)) { | ||
return v3.fromJS(notebook); | ||
@@ -63,3 +67,4 @@ } | ||
throw new TypeError("This notebook format is not supported"); | ||
}; | ||
} | ||
exports.fromJS = fromJS; | ||
/** | ||
@@ -73,3 +78,3 @@ * Converts an immutable representation of a notebook to a JSON representation of the | ||
*/ | ||
exports.toJS = (immnb) => { | ||
function toJS(immnb) { | ||
const minorVersion = immnb.get("nbformat_minor", null); | ||
@@ -82,3 +87,4 @@ if (immnb.get("nbformat") === 4 && | ||
throw new TypeError("Only notebook formats 3 and 4 are supported!"); | ||
}; | ||
} | ||
exports.toJS = toJS; | ||
/** | ||
@@ -91,2 +97,5 @@ * Converts a JSON representation of a notebook into a string representation. | ||
*/ | ||
exports.stringifyNotebook = (notebook) => JSON.stringify(notebook, null, 2); | ||
function stringifyNotebook(notebook) { | ||
return JSON.stringify(notebook, null, 2); | ||
} | ||
exports.stringifyNotebook = stringifyNotebook; |
/** | ||
* @module commutable | ||
*/ | ||
import { Map as ImmutableMap, List as ImmutableList, Record, RecordOf } from "immutable"; | ||
import { ExecutionCount, JSONObject, MultiLineString } from "./primitives"; | ||
export declare type ImmutableMimeBundle = ImmutableMap<string, any>; | ||
export declare type MimeBundle = { | ||
[key: string]: string | string[] | undefined; | ||
}; | ||
/** | ||
* Map over all the mimetypes, turning them into our in-memory format. | ||
* | ||
* ``` | ||
* { | ||
* "application/json": {"a": 3, "b": 2}, | ||
* "text/html": ["<p>\n", "Hey\n", "</p>"], | ||
* "text/plain": "Hey" | ||
* } | ||
* ``` | ||
* to | ||
* ``` | ||
* { | ||
* "application/json": {"a": 3, "b": 2}, | ||
* "text/html": "<p>\nHey\n</p>", | ||
* "text/plain": "Hey" | ||
* } | ||
* ``` | ||
* @param mimeBundle The mime | ||
* @param previous | ||
* @param key | ||
*/ | ||
export declare const cleanMimeAtKey: (mimeBundle: MimeBundle, previous: ImmutableMap<string, any>, key: string) => ImmutableMap<string, any>; | ||
/** | ||
* Cleans mimedata, primarily converts an array of strings into a single string | ||
* joined by newlines. | ||
* | ||
* @param key The key, usually a mime type, that is associated with the mime data. | ||
* @param data The mime data to clean. | ||
* | ||
* @returns The cleaned mime data. | ||
*/ | ||
export declare const cleanMimeData: (key: string, data: string | string[] | undefined) => string | string[] | undefined; | ||
export declare const createImmutableMimeBundle: (mimeBundle: MimeBundle) => ImmutableMap<string, any>; | ||
export declare const demultiline: (s: string | string[]) => string; | ||
/** | ||
* Split string into a list of strings delimited by newlines | ||
* | ||
* @param s The newline-delimited string that will be converted into an array of strings. | ||
* | ||
* @returns An array of strings. | ||
*/ | ||
export declare const remultiline: (s: string | string[]) => string[]; | ||
export declare const isJSONKey: (key: string) => boolean; | ||
import { List as ImmutableList, Record, RecordOf } from "immutable"; | ||
import { ExecutionCount, JSONObject, MediaBundle, MultiLineString, OnDiskMediaBundle } from "./primitives"; | ||
/** ExecuteResult Record Boilerplate */ | ||
declare type ExecuteResultParams = { | ||
export interface ExecuteResultParams { | ||
output_type: "execute_result"; | ||
execution_count: ExecutionCount; | ||
data: ImmutableMimeBundle; | ||
data: Readonly<MediaBundle>; | ||
metadata?: any; | ||
}; | ||
} | ||
export declare const makeExecuteResult: Record.Factory<ExecuteResultParams>; | ||
declare type ImmutableExecuteResult = RecordOf<ExecuteResultParams>; | ||
export declare type ImmutableExecuteResult = RecordOf<ExecuteResultParams>; | ||
/** DisplayData Record Boilerplate */ | ||
declare type DisplayDataParams = { | ||
export interface DisplayDataParams { | ||
data: Readonly<MediaBundle>; | ||
output_type: "display_data"; | ||
data: ImmutableMimeBundle; | ||
metadata?: any; | ||
}; | ||
} | ||
export declare const makeDisplayData: Record.Factory<DisplayDataParams>; | ||
declare type ImmutableDisplayData = RecordOf<DisplayDataParams>; | ||
export declare type ImmutableDisplayData = RecordOf<DisplayDataParams>; | ||
/** StreamOutput Record Boilerplate */ | ||
declare type StreamOutputParams = { | ||
export interface StreamOutputParams { | ||
output_type: "stream"; | ||
name: "stdout" | "stderr"; | ||
text: string; | ||
}; | ||
} | ||
export declare const makeStreamOutput: Record.Factory<StreamOutputParams>; | ||
declare type ImmutableStreamOutput = RecordOf<StreamOutputParams>; | ||
export declare type ImmutableStreamOutput = RecordOf<StreamOutputParams>; | ||
/** ErrorOutput Record Boilerplate */ | ||
declare type ErrorOutputParams = { | ||
export interface ErrorOutputParams { | ||
output_type: "error"; | ||
@@ -85,22 +37,19 @@ ename: string; | ||
traceback: ImmutableList<string>; | ||
}; | ||
} | ||
export declare const makeErrorOutput: Record.Factory<ErrorOutputParams>; | ||
declare type ImmutableErrorOutput = RecordOf<ErrorOutputParams>; | ||
export declare type ImmutableErrorOutput = RecordOf<ErrorOutputParams>; | ||
export declare type ImmutableOutput = ImmutableExecuteResult | ImmutableDisplayData | ImmutableStreamOutput | ImmutableErrorOutput; | ||
/** * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * | ||
* Output Types | ||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | ||
export interface ExecuteResult { | ||
export interface OnDiskExecuteResult { | ||
output_type: "execute_result"; | ||
execution_count: ExecutionCount; | ||
data: MimeBundle; | ||
data: OnDiskMediaBundle; | ||
metadata: JSONObject; | ||
} | ||
export interface DisplayData { | ||
export interface OnDiskDisplayData { | ||
output_type: "display_data"; | ||
data: MimeBundle; | ||
data: OnDiskMediaBundle; | ||
metadata: JSONObject; | ||
transient?: JSONObject; | ||
} | ||
export interface StreamOutput { | ||
export interface OnDiskStreamOutput { | ||
output_type: "stream"; | ||
@@ -110,4 +59,4 @@ name: "stdout" | "stderr"; | ||
} | ||
export interface ErrorOutput { | ||
output_type: "error" | "pyerr"; | ||
export interface OnDiskErrorOutput { | ||
output_type: "error"; | ||
ename: string; | ||
@@ -117,3 +66,3 @@ evalue: string; | ||
} | ||
export declare type Output = ExecuteResult | DisplayData | StreamOutput | ErrorOutput; | ||
export declare type OnDiskOutput = OnDiskExecuteResult | OnDiskDisplayData | OnDiskStreamOutput | OnDiskErrorOutput; | ||
/** | ||
@@ -126,3 +75,2 @@ * Converts a mutable representation of an output to an immutable representation. | ||
*/ | ||
export declare const createImmutableOutput: (output: Output) => ImmutableOutput; | ||
export {}; | ||
export declare function createImmutableOutput(output: OnDiskOutput): ImmutableOutput; |
@@ -7,76 +7,25 @@ "use strict"; | ||
const immutable_1 = require("immutable"); | ||
/** | ||
* Map over all the mimetypes, turning them into our in-memory format. | ||
* | ||
* ``` | ||
* { | ||
* "application/json": {"a": 3, "b": 2}, | ||
* "text/html": ["<p>\n", "Hey\n", "</p>"], | ||
* "text/plain": "Hey" | ||
* } | ||
* ``` | ||
* to | ||
* ``` | ||
* { | ||
* "application/json": {"a": 3, "b": 2}, | ||
* "text/html": "<p>\nHey\n</p>", | ||
* "text/plain": "Hey" | ||
* } | ||
* ``` | ||
* @param mimeBundle The mime | ||
* @param previous | ||
* @param key | ||
*/ | ||
exports.cleanMimeAtKey = (mimeBundle, previous, key) => previous.set(key, exports.cleanMimeData(key, mimeBundle[key])); | ||
/** | ||
* Cleans mimedata, primarily converts an array of strings into a single string | ||
* joined by newlines. | ||
* | ||
* @param key The key, usually a mime type, that is associated with the mime data. | ||
* @param data The mime data to clean. | ||
* | ||
* @returns The cleaned mime data. | ||
*/ | ||
exports.cleanMimeData = (key, data) => { | ||
// See https://github.com/jupyter/nbformat/blob/62d6eb8803616d198eaa2024604d1fe923f2a7b3/nbformat/v4/nbformat.v4.schema.json#L368 | ||
if (exports.isJSONKey(key)) { | ||
// Data stays as is for JSON types | ||
return data; | ||
} | ||
if (typeof data === "string" || Array.isArray(data)) { | ||
return exports.demultiline(data); | ||
} | ||
throw new TypeError(`Data for ${key} is expected to be a string or an Array of strings`); | ||
}; | ||
exports.createImmutableMimeBundle = (mimeBundle) => Object.keys(mimeBundle).reduce(exports.cleanMimeAtKey.bind(null, mimeBundle), immutable_1.Map()); | ||
exports.demultiline = (s) => Array.isArray(s) ? s.join("") : s; | ||
/** | ||
* Split string into a list of strings delimited by newlines | ||
* | ||
* @param s The newline-delimited string that will be converted into an array of strings. | ||
* | ||
* @returns An array of strings. | ||
*/ | ||
exports.remultiline = (s) => Array.isArray(s) ? s : s.split(/(.+?(?:\r\n|\n))/g).filter(x => x !== ""); | ||
exports.isJSONKey = (key) => /^application\/(.*\+)?json$/.test(key); | ||
const primitives_1 = require("./primitives"); | ||
// Used for initializing all output records | ||
const emptyMediaBundle = Object.freeze({}); | ||
exports.makeExecuteResult = immutable_1.Record({ | ||
output_type: "execute_result", | ||
data: emptyMediaBundle, | ||
execution_count: null, | ||
data: immutable_1.Map(), | ||
metadata: immutable_1.Map() | ||
metadata: immutable_1.Map(), | ||
output_type: "execute_result" | ||
}); | ||
exports.makeDisplayData = immutable_1.Record({ | ||
output_type: "display_data", | ||
data: immutable_1.Map(), | ||
metadata: immutable_1.Map() | ||
data: emptyMediaBundle, | ||
metadata: immutable_1.Map(), | ||
output_type: "display_data" | ||
}); | ||
exports.makeStreamOutput = immutable_1.Record({ | ||
name: "stdout", | ||
output_type: "stream", | ||
name: "stdout", | ||
text: "" | ||
}); | ||
exports.makeErrorOutput = immutable_1.Record({ | ||
output_type: "error", | ||
ename: "", | ||
evalue: "", | ||
output_type: "error", | ||
traceback: immutable_1.List() | ||
@@ -91,8 +40,8 @@ }); | ||
*/ | ||
exports.createImmutableOutput = (output) => { | ||
function createImmutableOutput(output) { | ||
switch (output.output_type) { | ||
case "execute_result": | ||
return exports.makeExecuteResult({ | ||
data: primitives_1.createFrozenMediaBundle(output.data), | ||
execution_count: output.execution_count, | ||
data: exports.createImmutableMimeBundle(output.data), | ||
metadata: immutable_1.fromJS(output.metadata) | ||
@@ -102,3 +51,3 @@ }); | ||
return exports.makeDisplayData({ | ||
data: exports.createImmutableMimeBundle(output.data), | ||
data: primitives_1.createFrozenMediaBundle(output.data), | ||
metadata: immutable_1.fromJS(output.metadata) | ||
@@ -109,9 +58,9 @@ }); | ||
name: output.name, | ||
text: exports.demultiline(output.text) | ||
text: primitives_1.demultiline(output.text) | ||
}); | ||
case "error": | ||
return exports.makeErrorOutput({ | ||
output_type: "error", | ||
ename: output.ename, | ||
evalue: output.evalue, | ||
output_type: "error", | ||
// Note: this is one of the cases where the Array of strings (for | ||
@@ -122,4 +71,11 @@ // traceback) is part of the format, not a multiline string | ||
default: | ||
throw new TypeError(`Output type ${output.output_type} not recognized`); | ||
// Since we're well typed, output is never. However we can still get new output types we don't handle | ||
// and need to fail hard instead of making indeterminate behavior | ||
const unknownOutput = output; | ||
if (unknownOutput.output_type) { | ||
throw new TypeError(`Output type ${output.output_type} not recognized`); | ||
} | ||
throw new TypeError(`Output structure not known: ${JSON.stringify(output)}`); | ||
} | ||
}; | ||
} | ||
exports.createImmutableOutput = createImmutableOutput; |
@@ -0,1 +1,4 @@ | ||
/** | ||
* @module commutable | ||
*/ | ||
import * as Immutable from "immutable"; | ||
@@ -11,3 +14,3 @@ export declare type ExecutionCount = number | null; | ||
export declare type CellId = string; | ||
export declare const createCellId: () => string; | ||
export declare function createCellId(): CellId; | ||
export declare type MultiLineString = string | string[]; | ||
@@ -17,1 +20,90 @@ export declare type ImmutableJSONType = PrimitiveImmutable | ImmutableJSONMap | ImmutableJSONList; | ||
export declare type ImmutableJSONList = Immutable.List<any>; | ||
/** | ||
* Media Bundles as they exist on disk from the notebook format | ||
* See https://nbformat.readthedocs.io/en/latest/format_description.html#display-data for docs | ||
* and https://github.com/jupyter/nbformat/blob/master/nbformat/v4/nbformat.v4.schema.json for the schema | ||
*/ | ||
export interface OnDiskMediaBundle { | ||
"text/plain"?: MultiLineString; | ||
"text/html"?: MultiLineString; | ||
"text/latex"?: MultiLineString; | ||
"text/markdown"?: MultiLineString; | ||
"application/javascript"?: MultiLineString; | ||
"image/png"?: MultiLineString; | ||
"image/jpeg"?: MultiLineString; | ||
"image/gif"?: MultiLineString; | ||
"image/svg+xml"?: MultiLineString; | ||
"application/json"?: string | string[] | {}; | ||
"application/vdom.v1+json"?: {}; | ||
"application/vnd.dataresource+json"?: {}; | ||
"text/vnd.plotly.v1+html"?: MultiLineString | {}; | ||
"application/vnd.plotly.v1+json"?: {}; | ||
"application/geo+json"?: {}; | ||
"application/x-nteract-model-debug+json"?: {}; | ||
"application/vnd.vega.v2+json"?: {}; | ||
"application/vnd.vega.v3+json"?: {}; | ||
"application/vnd.vegalite.v1+json"?: {}; | ||
"application/vnd.vegalite.v2+json"?: {}; | ||
[key: string]: string | string[] | {} | undefined; | ||
} | ||
export interface MediaBundle { | ||
"text/plain"?: string; | ||
"text/html"?: string; | ||
"text/latex"?: string; | ||
"text/markdown"?: string; | ||
"application/javascript"?: string; | ||
"image/png"?: string; | ||
"image/jpeg"?: string; | ||
"image/gif"?: string; | ||
"image/svg+xml"?: string; | ||
"application/json"?: { | ||
[key: string]: any; | ||
}; | ||
"application/vdom.v1+json"?: { | ||
[key: string]: any; | ||
}; | ||
"application/vnd.dataresource+json"?: { | ||
[key: string]: any; | ||
}; | ||
"text/vnd.plotly.v1+html"?: string | { | ||
[key: string]: any; | ||
}; | ||
"application/vnd.plotly.v1+json"?: { | ||
[key: string]: any; | ||
}; | ||
"application/geo+json"?: { | ||
[key: string]: any; | ||
}; | ||
"application/x-nteract-model-debug+json"?: { | ||
[key: string]: any; | ||
}; | ||
"application/vnd.vega.v2+json"?: { | ||
[key: string]: any; | ||
}; | ||
"application/vnd.vega.v3+json"?: { | ||
[key: string]: any; | ||
}; | ||
"application/vnd.vegalite.v1+json"?: { | ||
[key: string]: any; | ||
}; | ||
"application/vnd.vegalite.v2+json"?: { | ||
[key: string]: any; | ||
}; | ||
[key: string]: string | string[] | {} | undefined; | ||
} | ||
/** | ||
* Turn nbformat multiline strings (arrays of strings for simplifying diffs) into strings | ||
*/ | ||
export declare function demultiline(s: string | string[]): string; | ||
/** | ||
* Split string into a list of strings delimited by newlines; useful for on-disk git comparisons; | ||
* and is the expectation for jupyter notebooks on disk | ||
*/ | ||
export declare function remultiline(s: string | string[]): string[]; | ||
declare type DeepReadonly<T> = { | ||
readonly [P in keyof T]: DeepReadonly<T[P]>; | ||
}; | ||
export declare function deepFreeze<T>(object: T): DeepReadonly<T>; | ||
export declare function createFrozenMediaBundle(mediaBundle: OnDiskMediaBundle): Readonly<MediaBundle>; | ||
export declare function createOnDiskMediaBundle(mediaBundle: Readonly<MediaBundle>): OnDiskMediaBundle; | ||
export {}; |
@@ -6,6 +6,97 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const v4_1 = __importDefault(require("uuid/v4")); | ||
function createCellId() { | ||
return v4_1.default(); | ||
} | ||
exports.createCellId = createCellId; | ||
/** | ||
* @module commutable | ||
* Turn nbformat multiline strings (arrays of strings for simplifying diffs) into strings | ||
*/ | ||
const v4_1 = __importDefault(require("uuid/v4")); | ||
exports.createCellId = () => v4_1.default(); | ||
function demultiline(s) { | ||
if (Array.isArray(s)) { | ||
return s.join(""); | ||
} | ||
return s; | ||
} | ||
exports.demultiline = demultiline; | ||
/** | ||
* Split string into a list of strings delimited by newlines; useful for on-disk git comparisons; | ||
* and is the expectation for jupyter notebooks on disk | ||
*/ | ||
function remultiline(s) { | ||
if (Array.isArray(s)) { | ||
// Assume | ||
return s; | ||
} | ||
// Use positive lookahead regex to split on newline and retain newline char | ||
return s.split(/(.+?(?:\r\n|\n))/g).filter(x => x !== ""); | ||
} | ||
exports.remultiline = remultiline; | ||
function isJSONKey(key) { | ||
return /^application\/(.*\+)json$/.test(key); | ||
} | ||
// Taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze | ||
function deepFreeze(object) { | ||
// Retrieve the property names defined on object | ||
const propNames = Object.getOwnPropertyNames(object); | ||
// Freeze properties before freezing self | ||
for (const name of propNames) { | ||
// getOwnPropertyNames assures us we can index on name | ||
const value = object[name]; | ||
object[name] = | ||
value && typeof value === "object" ? deepFreeze(value) : value; | ||
} | ||
return Object.freeze(object); | ||
} | ||
exports.deepFreeze = deepFreeze; | ||
function createFrozenMediaBundle(mediaBundle) { | ||
// Map over all the mimetypes; turning them into our in-memory format | ||
// | ||
// { | ||
// "application/json": {"a": 3; "b": 2}; | ||
// "text/html": ["<p>\n"; "Hey\n"; "</p>"]; | ||
// "text/plain": "Hey" | ||
// } | ||
// | ||
// to | ||
// | ||
// { | ||
// "application/json": {"a": 3; "b": 2}; | ||
// "text/html": "<p>\nHey\n</p>"; | ||
// "text/plain": "Hey" | ||
// } | ||
// Since we have to convert from one type to another that has conflicting types; we need to hand convert it in a way that | ||
// flow is able to verify correctly. The way we do that is create a new object that we declare with the type we want; | ||
// set the keys and values we need; then seal the object with Object.freeze | ||
const bundle = {}; | ||
for (const key in mediaBundle) { | ||
if (!isJSONKey(key) && | ||
(typeof mediaBundle[key] === "string" || Array.isArray(mediaBundle[key]))) { | ||
// Because it's a string; we can't mutate it anyways (and don't have to Object.freeze it) | ||
bundle[key] = demultiline(mediaBundle[key]); | ||
} | ||
else { | ||
// we now know it's an Object of some kind | ||
bundle[key] = deepFreeze(mediaBundle[key]); | ||
} | ||
} | ||
return Object.freeze(bundle); | ||
} | ||
exports.createFrozenMediaBundle = createFrozenMediaBundle; | ||
function createOnDiskMediaBundle(mediaBundle) { | ||
// Technically we could return just the mediaBundle as is | ||
// return mediaBundle; | ||
// However for the sake of on-disk readability we write out remultilined versions of the array and string ones | ||
const freshBundle = {}; | ||
for (const key in mediaBundle) { | ||
if (!isJSONKey(key) && | ||
(typeof mediaBundle[key] === "string" || Array.isArray(mediaBundle[key]))) { | ||
freshBundle[key] = remultiline(mediaBundle[key]); | ||
} | ||
else { | ||
freshBundle[key] = mediaBundle[key]; | ||
} | ||
} | ||
return freshBundle; | ||
} | ||
exports.createOnDiskMediaBundle = createOnDiskMediaBundle; |
import { CellId } from "./primitives"; | ||
import { ImmutableNotebook } from "./notebook"; | ||
import { ImmutableCell } from "./cells"; | ||
import { Map as ImmutableMap, List as ImmutableList } from "immutable"; | ||
export declare const createCodeCell: import("immutable").Record.Factory<{ | ||
cell_type: "code"; | ||
metadata: ImmutableMap<string, any>; | ||
execution_count: number | null; | ||
source: string; | ||
outputs: ImmutableList<import("./outputs").ImmutableOutput>; | ||
}>; | ||
export declare const createMarkdownCell: import("immutable").Record.Factory<{ | ||
cell_type: "markdown"; | ||
source: string; | ||
metadata: ImmutableMap<string, any>; | ||
}>; | ||
export declare const emptyCodeCell: import("immutable").RecordOf<{ | ||
cell_type: "code"; | ||
metadata: ImmutableMap<string, any>; | ||
execution_count: number | null; | ||
source: string; | ||
outputs: ImmutableList<import("./outputs").ImmutableOutput>; | ||
}>; | ||
export declare const emptyMarkdownCell: import("immutable").RecordOf<{ | ||
cell_type: "markdown"; | ||
source: string; | ||
metadata: ImmutableMap<string, any>; | ||
}>; | ||
export declare const defaultNotebook: ImmutableNotebook; | ||
import { List as ImmutableList, Map as ImmutableMap } from "immutable"; | ||
export declare const createCodeCell: import("immutable").Record.Factory<import("./cells").CodeCellParams>; | ||
export declare const createMarkdownCell: import("immutable").Record.Factory<import("./cells").MarkdownCellParams>; | ||
export declare const emptyCodeCell: import("immutable").RecordOf<import("./cells").CodeCellParams>; | ||
export declare const emptyMarkdownCell: import("immutable").RecordOf<import("./cells").MarkdownCellParams>; | ||
export declare const defaultNotebook: import("immutable").Record<import("./notebook").NotebookRecordParams> & Readonly<import("./notebook").NotebookRecordParams>; | ||
export declare const createNotebook: import("immutable").Record.Factory<import("./notebook").NotebookRecordParams>; | ||
export declare const emptyNotebook: ImmutableNotebook; | ||
export declare type CellStructure = { | ||
export declare const emptyNotebook: import("immutable").Record<import("./notebook").NotebookRecordParams> & Readonly<import("./notebook").NotebookRecordParams>; | ||
export interface CellStructure { | ||
cellOrder: ImmutableList<CellId>; | ||
cellMap: ImmutableMap<CellId, ImmutableCell>; | ||
}; | ||
} | ||
/** | ||
@@ -45,3 +25,3 @@ * A function that appends a new cell to a CellStructure object. | ||
*/ | ||
export declare const appendCell: (cellStructure: CellStructure, immutableCell: ImmutableCell, id?: string) => CellStructure; | ||
export declare function appendCell(cellStructure: CellStructure, immutableCell: ImmutableCell, id?: CellId): CellStructure; | ||
/** | ||
@@ -55,3 +35,3 @@ * A function that appends a cell to an immutable notebook. | ||
*/ | ||
export declare const appendCellToNotebook: (immnb: ImmutableNotebook, immCell: ImmutableCell) => ImmutableNotebook; | ||
export declare function appendCellToNotebook(immnb: ImmutableNotebook, immCell: ImmutableCell): ImmutableNotebook; | ||
/** | ||
@@ -67,3 +47,3 @@ * Inserts a cell with cellID at a given index within the notebook. | ||
*/ | ||
export declare const insertCellAt: (notebook: ImmutableNotebook, cell: ImmutableCell, cellId: string, index: number) => ImmutableNotebook; | ||
export declare function insertCellAt(notebook: ImmutableNotebook, cell: ImmutableCell, cellId: string, index: number): ImmutableNotebook; | ||
/** | ||
@@ -78,7 +58,8 @@ * Inserts a new cell with cellID before an existing cell with priorCellID | ||
*/ | ||
export declare const insertCellAfter: (notebook: ImmutableNotebook, cell: ImmutableCell, cellId: string, priorCellId: string) => ImmutableNotebook; | ||
export declare function insertCellAfter(notebook: ImmutableNotebook, cell: ImmutableCell, cellId: string, priorCellId: string): ImmutableNotebook; | ||
/** | ||
* Delete a cell with CellID at a given location. Note that this function | ||
* is deprecated in favor of `deleteCell`. | ||
* Deprecated: Delete a cell with CellID at a given location. | ||
* | ||
* Note that this function is deprecated in favor of `deleteCell`. | ||
* | ||
* @param notebook The notebook containing the cell. | ||
@@ -91,3 +72,3 @@ * @param cellID The ID of the cell that will be deleted. | ||
*/ | ||
export declare const removeCell: (notebook: ImmutableNotebook, cellId: string) => ImmutableNotebook; | ||
export declare function removeCell(notebook: ImmutableNotebook, cellId: string): ImmutableNotebook; | ||
/** | ||
@@ -101,7 +82,7 @@ * Delete a cell with CellID at a given location. | ||
*/ | ||
export declare const deleteCell: (notebook: ImmutableNotebook, cellId: string) => ImmutableNotebook; | ||
export declare function deleteCell(notebook: ImmutableNotebook, cellId: string): ImmutableNotebook; | ||
/** | ||
* A new notebook with a single empty code cell. This function is useful | ||
* A new 'monocell' notebook with a single empty code cell. This function is useful | ||
* if you are looking to initialize a fresh, new notebook. | ||
*/ | ||
export declare const monocellNotebook: ImmutableNotebook; | ||
export declare const monocellNotebook: import("immutable").Record<import("./notebook").NotebookRecordParams> & Readonly<import("./notebook").NotebookRecordParams>; |
@@ -26,6 +26,9 @@ "use strict"; | ||
*/ | ||
exports.appendCell = (cellStructure, immutableCell, id = primitives_1.createCellId()) => ({ | ||
cellOrder: cellStructure.cellOrder.push(id), | ||
cellMap: cellStructure.cellMap.set(id, immutableCell) | ||
}); | ||
function appendCell(cellStructure, immutableCell, id = primitives_1.createCellId()) { | ||
return { | ||
cellOrder: cellStructure.cellOrder.push(id), | ||
cellMap: cellStructure.cellMap.set(id, immutableCell) | ||
}; | ||
} | ||
exports.appendCell = appendCell; | ||
/** | ||
@@ -39,10 +42,13 @@ * A function that appends a cell to an immutable notebook. | ||
*/ | ||
exports.appendCellToNotebook = (immnb, immCell) => immnb.withMutations(nb => { | ||
const cellStructure = { | ||
cellOrder: nb.get("cellOrder"), | ||
cellMap: nb.get("cellMap") | ||
}; | ||
const { cellOrder, cellMap } = exports.appendCell(cellStructure, immCell); | ||
return nb.set("cellOrder", cellOrder).set("cellMap", cellMap); | ||
}); | ||
function appendCellToNotebook(immnb, immCell) { | ||
return immnb.withMutations(nb => { | ||
const cellStructure = { | ||
cellOrder: nb.get("cellOrder"), | ||
cellMap: nb.get("cellMap") | ||
}; | ||
const { cellOrder, cellMap } = appendCell(cellStructure, immCell); | ||
return nb.set("cellOrder", cellOrder).set("cellMap", cellMap); | ||
}); | ||
} | ||
exports.appendCellToNotebook = appendCellToNotebook; | ||
/** | ||
@@ -58,5 +64,8 @@ * Inserts a cell with cellID at a given index within the notebook. | ||
*/ | ||
exports.insertCellAt = (notebook, cell, cellId, index) => notebook.withMutations(nb => nb | ||
.setIn(["cellMap", cellId], cell) | ||
.set("cellOrder", nb.get("cellOrder").insert(index, cellId))); | ||
function insertCellAt(notebook, cell, cellId, index) { | ||
return notebook.withMutations(nb => nb | ||
.setIn(["cellMap", cellId], cell) | ||
.set("cellOrder", nb.get("cellOrder").insert(index, cellId))); | ||
} | ||
exports.insertCellAt = insertCellAt; | ||
/** | ||
@@ -71,7 +80,11 @@ * Inserts a new cell with cellID before an existing cell with priorCellID | ||
*/ | ||
exports.insertCellAfter = (notebook, cell, cellId, priorCellId) => exports.insertCellAt(notebook, cell, cellId, notebook.get("cellOrder").indexOf(priorCellId) + 1); | ||
function insertCellAfter(notebook, cell, cellId, priorCellId) { | ||
return insertCellAt(notebook, cell, cellId, notebook.get("cellOrder").indexOf(priorCellId) + 1); | ||
} | ||
exports.insertCellAfter = insertCellAfter; | ||
/** | ||
* Delete a cell with CellID at a given location. Note that this function | ||
* is deprecated in favor of `deleteCell`. | ||
* Deprecated: Delete a cell with CellID at a given location. | ||
* | ||
* Note that this function is deprecated in favor of `deleteCell`. | ||
* | ||
* @param notebook The notebook containing the cell. | ||
@@ -84,6 +97,7 @@ * @param cellID The ID of the cell that will be deleted. | ||
*/ | ||
exports.removeCell = (notebook, cellId) => { | ||
function removeCell(notebook, cellId) { | ||
console.log("Deprecation Warning: removeCell() is being deprecated. Please use deleteCell() instead"); | ||
return exports.deleteCell(notebook, cellId); | ||
}; | ||
return deleteCell(notebook, cellId); | ||
} | ||
exports.removeCell = removeCell; | ||
/** | ||
@@ -97,9 +111,12 @@ * Delete a cell with CellID at a given location. | ||
*/ | ||
exports.deleteCell = (notebook, cellId) => notebook | ||
.removeIn(["cellMap", cellId]) | ||
.update("cellOrder", cellOrder => cellOrder.filterNot(id => id === cellId)); | ||
function deleteCell(notebook, cellId) { | ||
return notebook | ||
.removeIn(["cellMap", cellId]) | ||
.update("cellOrder", cellOrder => cellOrder.filterNot(id => id === cellId)); | ||
} | ||
exports.deleteCell = deleteCell; | ||
/** | ||
* A new notebook with a single empty code cell. This function is useful | ||
* A new 'monocell' notebook with a single empty code cell. This function is useful | ||
* if you are looking to initialize a fresh, new notebook. | ||
*/ | ||
exports.monocellNotebook = exports.appendCellToNotebook(exports.emptyNotebook, exports.emptyCodeCell); | ||
exports.monocellNotebook = appendCellToNotebook(exports.emptyNotebook, exports.emptyCodeCell); |
@@ -1,4 +0,3 @@ | ||
import { MultiLineString, JSONObject } from "./primitives"; | ||
import { ErrorOutput } from "./outputs"; | ||
import { RawCell, MarkdownCell } from "./v4"; | ||
import { JSONObject, MultiLineString } from "./primitives"; | ||
import { MarkdownCell, RawCell } from "./v4"; | ||
declare const VALID_MIMETYPES: { | ||
@@ -28,2 +27,8 @@ text: string; | ||
} | ||
export interface ErrorOutput { | ||
output_type: "error" | "pyerr"; | ||
ename: string; | ||
evalue: string; | ||
traceback: string[]; | ||
} | ||
export interface StreamOutput { | ||
@@ -48,3 +53,3 @@ output_type: "stream"; | ||
prompt_number: number; | ||
outputs: Array<Output>; | ||
outputs: Output[]; | ||
} | ||
@@ -56,3 +61,3 @@ export declare type Cell = RawCell | MarkdownCell | HeadingCell | CodeCell; | ||
} | ||
export declare type Notebook = { | ||
export interface NotebookV3 { | ||
worksheets: Worksheet[]; | ||
@@ -62,4 +67,5 @@ metadata: object; | ||
nbformat_minor: number; | ||
}; | ||
export declare const fromJS: (notebook: Notebook) => import("./notebook").ImmutableNotebook; | ||
} | ||
export declare function fromJS(notebook: NotebookV3): import("immutable").Record<import("./notebook").NotebookRecordParams> & Readonly<import("./notebook").NotebookRecordParams>; | ||
export declare function isNotebookV3(value: any): value is NotebookV3; | ||
export {}; |
108
lib/v3.js
@@ -7,2 +7,3 @@ "use strict"; | ||
const immutable_1 = require("immutable"); | ||
const primitives_1 = require("./primitives"); | ||
const notebook_1 = require("./notebook"); | ||
@@ -23,19 +24,24 @@ const cells_1 = require("./cells"); | ||
}; | ||
const createImmutableMarkdownCell = (cell) => cells_1.makeMarkdownCell({ | ||
cell_type: cell.cell_type, | ||
source: outputs_1.demultiline(cell.source), | ||
metadata: immutable_1.fromJS(cell.metadata) | ||
}); | ||
const createImmutableMimeBundle = (output) => { | ||
const mimeBundle = {}; | ||
function createImmutableMarkdownCell(cell) { | ||
return cells_1.makeMarkdownCell({ | ||
cell_type: cell.cell_type, | ||
source: primitives_1.demultiline(cell.source), | ||
metadata: immutable_1.fromJS(cell.metadata) | ||
}); | ||
} | ||
/** | ||
* Handle the old v3 version of the media | ||
*/ | ||
function createImmutableMediaBundle(output) { | ||
const mediaBundle = {}; | ||
for (const key of Object.keys(output)) { | ||
// v3 had non-media types for rich media | ||
if (key in VALID_MIMETYPES) { | ||
mimeBundle[VALID_MIMETYPES[key]] = | ||
mediaBundle[VALID_MIMETYPES[key]] = | ||
output[key]; | ||
} | ||
} | ||
return Object.keys(mimeBundle).reduce(outputs_1.cleanMimeAtKey.bind(null, mimeBundle), immutable_1.Map()); | ||
}; | ||
const createImmutableOutput = (output) => { | ||
return primitives_1.createFrozenMediaBundle(mediaBundle); | ||
} | ||
function createImmutableOutput(output) { | ||
switch (output.output_type) { | ||
@@ -46,3 +52,3 @@ case "pyout": | ||
// Note strangeness with v4 API | ||
data: createImmutableMimeBundle(output), | ||
data: createImmutableMediaBundle(output), | ||
metadata: immutable_1.fromJS(output.metadata) | ||
@@ -52,3 +58,3 @@ }); | ||
return outputs_1.makeDisplayData({ | ||
data: createImmutableMimeBundle(output), | ||
data: createImmutableMediaBundle(output), | ||
metadata: immutable_1.fromJS(output.metadata) | ||
@@ -61,3 +67,3 @@ }); | ||
name, | ||
text: outputs_1.demultiline(output.text) | ||
text: primitives_1.demultiline(output.text) | ||
}); | ||
@@ -73,28 +79,33 @@ case "pyerr": | ||
} | ||
}; | ||
const createImmutableCodeCell = (cell) => cells_1.makeCodeCell({ | ||
cell_type: cell.cell_type, | ||
source: outputs_1.demultiline(cell.input), | ||
outputs: immutable_1.List(cell.outputs.map(createImmutableOutput)), | ||
execution_count: cell.prompt_number, | ||
metadata: immutable_1.fromJS(cell.metadata) | ||
}); | ||
const createImmutableRawCell = (cell) => cells_1.makeRawCell({ | ||
cell_type: cell.cell_type, | ||
source: outputs_1.demultiline(cell.source), | ||
metadata: immutable_1.fromJS(cell.metadata) | ||
}); | ||
const createImmutableHeadingCell = (cell) => | ||
// v3 heading cells are just markdown cells in v4+ | ||
cells_1.makeMarkdownCell({ | ||
cell_type: "markdown", | ||
source: Array.isArray(cell.source) | ||
? outputs_1.demultiline(cell.source.map(line => Array(cell.level) | ||
.join("#") | ||
.concat(" ") | ||
.concat(line))) | ||
: cell.source, | ||
metadata: immutable_1.fromJS(cell.metadata) | ||
}); | ||
const createImmutableCell = (cell) => { | ||
} | ||
function createImmutableCodeCell(cell) { | ||
return cells_1.makeCodeCell({ | ||
cell_type: cell.cell_type, | ||
source: primitives_1.demultiline(cell.input), | ||
outputs: immutable_1.List(cell.outputs.map(createImmutableOutput)), | ||
execution_count: cell.prompt_number, | ||
metadata: immutable_1.fromJS(cell.metadata) | ||
}); | ||
} | ||
function createImmutableRawCell(cell) { | ||
return cells_1.makeRawCell({ | ||
cell_type: cell.cell_type, | ||
source: primitives_1.demultiline(cell.source), | ||
metadata: immutable_1.fromJS(cell.metadata) | ||
}); | ||
} | ||
function createImmutableHeadingCell(cell) { | ||
// v3 heading cells are just markdown cells in v4+ | ||
return cells_1.makeMarkdownCell({ | ||
cell_type: "markdown", | ||
source: Array.isArray(cell.source) | ||
? primitives_1.demultiline(cell.source.map(line => Array(cell.level) | ||
.join("#") | ||
.concat(" ") | ||
.concat(line))) | ||
: cell.source, | ||
metadata: immutable_1.fromJS(cell.metadata) | ||
}); | ||
} | ||
function createImmutableCell(cell) { | ||
switch (cell.cell_type) { | ||
@@ -112,5 +123,6 @@ case "markdown": | ||
} | ||
}; | ||
exports.fromJS = (notebook) => { | ||
if (notebook.nbformat !== 3 || notebook.nbformat_minor < 0) { | ||
} | ||
function fromJS(notebook) { | ||
if (!isNotebookV3(notebook)) { | ||
notebook = notebook; | ||
throw new TypeError(`Notebook is not a valid v3 notebook. v3 notebooks must be of form 3.x | ||
@@ -131,2 +143,10 @@ It lists nbformat v${notebook.nbformat}.${notebook.nbformat_minor}`); | ||
}); | ||
}; | ||
} | ||
exports.fromJS = fromJS; | ||
function isNotebookV3(value) { | ||
return (value && | ||
typeof value === "object" && | ||
value.nbformat === 3 && | ||
value.nbformat_minor >= 0); | ||
} | ||
exports.isNotebookV3 = isNotebookV3; |
@@ -5,7 +5,4 @@ /** | ||
import { ImmutableNotebook } from "./notebook"; | ||
import { JSONObject, MultiLineString, ExecutionCount } from "./primitives"; | ||
import { Output } from "./outputs"; | ||
/** * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * | ||
* Cell Types | ||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | ||
import { ExecutionCount, JSONObject, MultiLineString } from "./primitives"; | ||
import { OnDiskOutput } from "./outputs"; | ||
export interface CodeCell { | ||
@@ -16,3 +13,3 @@ cell_type: "code"; | ||
source: MultiLineString; | ||
outputs: Output[]; | ||
outputs: OnDiskOutput[]; | ||
} | ||
@@ -30,9 +27,9 @@ export interface MarkdownCell { | ||
export declare type Cell = CodeCell | MarkdownCell | RawCell; | ||
export declare type Notebook = { | ||
cells: Array<Cell>; | ||
metadata: Object; | ||
export interface NotebookV4 { | ||
cells: Cell[]; | ||
metadata: JSONObject; | ||
nbformat: 4; | ||
nbformat_minor: number; | ||
}; | ||
export declare const fromJS: (notebook: Notebook) => ImmutableNotebook; | ||
} | ||
export declare function fromJS(notebook: NotebookV4): import("immutable").Record<import("./notebook").NotebookRecordParams> & Readonly<import("./notebook").NotebookRecordParams>; | ||
/** | ||
@@ -45,2 +42,3 @@ * Converts an immutable representation of a notebook to a JSON representation. | ||
*/ | ||
export declare const toJS: (immnb: ImmutableNotebook) => Notebook; | ||
export declare function toJS(immnb: ImmutableNotebook): NotebookV4; | ||
export declare function isNotebookV4(value: any): value is NotebookV4; |
151
lib/v4.js
"use strict"; | ||
// Due to the on-disk format needing to be written out in an explicit order, we disable ordering for this file | ||
// tslint:disable:object-literal-sort-keys | ||
/** | ||
@@ -20,2 +22,3 @@ * @module commutable | ||
const notebook_1 = require("./notebook"); | ||
const primitives_1 = require("./primitives"); | ||
const cells_1 = require("./cells"); | ||
@@ -31,29 +34,37 @@ const outputs_1 = require("./outputs"); | ||
*/ | ||
const createImmutableMetadata = (metadata) => immutable_1.Map(metadata).map((v, k) => { | ||
if (k !== "tags") { | ||
return v; | ||
} | ||
if (Array.isArray(v)) { | ||
return immutable_1.Set(v); | ||
} | ||
// The notebook spec requires that this field is an Array of strings | ||
return immutable_1.Set(); | ||
}); | ||
const createImmutableRawCell = (cell) => cells_1.makeRawCell({ | ||
cell_type: cell.cell_type, | ||
source: outputs_1.demultiline(cell.source), | ||
metadata: createImmutableMetadata(cell.metadata) | ||
}); | ||
const createImmutableMarkdownCell = (cell) => cells_1.makeMarkdownCell({ | ||
cell_type: cell.cell_type, | ||
source: outputs_1.demultiline(cell.source), | ||
metadata: createImmutableMetadata(cell.metadata) | ||
}); | ||
const createImmutableCodeCell = (cell) => cells_1.makeCodeCell({ | ||
cell_type: cell.cell_type, | ||
source: outputs_1.demultiline(cell.source), | ||
outputs: immutable_1.List(cell.outputs.map(outputs_1.createImmutableOutput)), | ||
execution_count: cell.execution_count, | ||
metadata: createImmutableMetadata(cell.metadata) | ||
}); | ||
function createImmutableMetadata(metadata) { | ||
return immutable_1.Map(metadata).map((v, k) => { | ||
if (k !== "tags") { | ||
return v; | ||
} | ||
if (Array.isArray(v)) { | ||
return immutable_1.Set(v); | ||
} | ||
// The notebook spec requires that this field is an Array of strings | ||
return immutable_1.Set(); | ||
}); | ||
} | ||
function createImmutableRawCell(cell) { | ||
return cells_1.makeRawCell({ | ||
cell_type: cell.cell_type, | ||
source: primitives_1.demultiline(cell.source), | ||
metadata: createImmutableMetadata(cell.metadata) | ||
}); | ||
} | ||
function createImmutableMarkdownCell(cell) { | ||
return cells_1.makeMarkdownCell({ | ||
cell_type: cell.cell_type, | ||
source: primitives_1.demultiline(cell.source), | ||
metadata: createImmutableMetadata(cell.metadata) | ||
}); | ||
} | ||
function createImmutableCodeCell(cell) { | ||
return cells_1.makeCodeCell({ | ||
cell_type: cell.cell_type, | ||
source: primitives_1.demultiline(cell.source), | ||
outputs: immutable_1.List(cell.outputs.map(outputs_1.createImmutableOutput)), | ||
execution_count: cell.execution_count, | ||
metadata: createImmutableMetadata(cell.metadata) | ||
}); | ||
} | ||
/** | ||
@@ -67,3 +78,3 @@ * Converts a JSON representation of a cell of any type to the correct | ||
*/ | ||
const createImmutableCell = (cell) => { | ||
function createImmutableCell(cell) { | ||
switch (cell.cell_type) { | ||
@@ -79,5 +90,6 @@ case "markdown": | ||
} | ||
}; | ||
exports.fromJS = (notebook) => { | ||
if (notebook.nbformat !== 4 || notebook.nbformat_minor < 0) { | ||
} | ||
function fromJS(notebook) { | ||
if (!isNotebookV4(notebook)) { | ||
notebook = notebook; | ||
throw new TypeError(`Notebook is not a valid v4 notebook. v4 notebooks must be of form 4.x | ||
@@ -100,23 +112,8 @@ It lists nbformat v${notebook.nbformat}.${notebook.nbformat_minor}`); | ||
}); | ||
}; | ||
const metadataToJS = (immMetadata) => immMetadata.toJS(); | ||
const mimeBundleToJS = (immMimeBundle) => { | ||
const bundle = immMimeBundle.toObject(); | ||
Object.keys(bundle).map(key => { | ||
if (outputs_1.isJSONKey(key)) { | ||
if (immutable_1.Map.isMap(bundle[key])) { | ||
bundle[key] = bundle[key].toJS(); | ||
} | ||
return bundle; | ||
} | ||
const data = bundle[key]; | ||
if (typeof data === "string" || Array.isArray(data)) { | ||
bundle[key] = outputs_1.remultiline(data); | ||
return bundle; | ||
} | ||
throw new TypeError(`Data for ${key} is expected to be a string or an Array of strings`); | ||
}); | ||
return bundle; | ||
}; | ||
const outputToJS = (output) => { | ||
} | ||
exports.fromJS = fromJS; | ||
function metadataToJS(immMetadata) { | ||
return immMetadata.toJS(); | ||
} | ||
function outputToJS(output) { | ||
switch (output.output_type) { | ||
@@ -127,3 +124,3 @@ case "execute_result": | ||
execution_count: output.execution_count, | ||
data: mimeBundleToJS(output.data), | ||
data: primitives_1.createOnDiskMediaBundle(output.data), | ||
metadata: output.metadata.toJS() | ||
@@ -134,3 +131,3 @@ }; | ||
output_type: output.output_type, | ||
data: mimeBundleToJS(output.data), | ||
data: primitives_1.createOnDiskMediaBundle(output.data), | ||
metadata: output.metadata.toJS() | ||
@@ -142,3 +139,3 @@ }; | ||
name: output.name, | ||
text: outputs_1.remultiline(output.text) | ||
text: primitives_1.remultiline(output.text) | ||
}; | ||
@@ -155,8 +152,10 @@ case "error": | ||
} | ||
}; | ||
const markdownCellToJS = (immCell) => ({ | ||
cell_type: "markdown", | ||
source: outputs_1.remultiline(immCell.source), | ||
metadata: metadataToJS(immCell.metadata) | ||
}); | ||
} | ||
function markdownCellToJS(immCell) { | ||
return { | ||
cell_type: "markdown", | ||
source: primitives_1.remultiline(immCell.source), | ||
metadata: metadataToJS(immCell.metadata) | ||
}; | ||
} | ||
/** | ||
@@ -169,6 +168,6 @@ * Converts an immutable representation of a code cell to a JSON representation. | ||
*/ | ||
const codeCellToJS = (immCell) => { | ||
function codeCellToJS(immCell) { | ||
return { | ||
cell_type: "code", | ||
source: outputs_1.remultiline(immCell.source), | ||
source: primitives_1.remultiline(immCell.source), | ||
outputs: immCell.outputs.map(outputToJS).toArray(), | ||
@@ -178,3 +177,3 @@ execution_count: immCell.execution_count, | ||
}; | ||
}; | ||
} | ||
/** | ||
@@ -187,9 +186,9 @@ * Converts an immutable representation of a raw cell to a JSON representation. | ||
*/ | ||
const rawCellToJS = (immCell) => { | ||
function rawCellToJS(immCell) { | ||
return { | ||
cell_type: "raw", | ||
source: outputs_1.remultiline(immCell.source), | ||
source: primitives_1.remultiline(immCell.source), | ||
metadata: metadataToJS(immCell.get("metadata", immutable_1.Map())) | ||
}; | ||
}; | ||
} | ||
/** | ||
@@ -202,3 +201,3 @@ * Converts an immutable cell to a JSON cell. | ||
*/ | ||
const cellToJS = (immCell) => { | ||
function cellToJS(immCell) { | ||
switch (immCell.cell_type) { | ||
@@ -212,5 +211,5 @@ case "markdown": | ||
default: | ||
throw new TypeError(`Cell type unknown at runtime`); | ||
throw new TypeError("Cell type unknown at runtime"); | ||
} | ||
}; | ||
} | ||
/** | ||
@@ -223,3 +222,3 @@ * Converts an immutable representation of a notebook to a JSON representation. | ||
*/ | ||
exports.toJS = (immnb) => { | ||
function toJS(immnb) { | ||
const plainNotebook = immnb.toObject(); | ||
@@ -235,2 +234,10 @@ const plainCellOrder = plainNotebook.cellOrder.toArray(); | ||
}; | ||
}; | ||
} | ||
exports.toJS = toJS; | ||
function isNotebookV4(value) { | ||
return (value && | ||
typeof value === "object" && | ||
value.nbformat === 4 && | ||
value.nbformat_minor >= 0); | ||
} | ||
exports.isNotebookV4 = isNotebookV4; |
{ | ||
"name": "@nteract/commutable", | ||
"version": "6.0.4-alpha.0", | ||
"version": "7.0.0", | ||
"description": "library for immutable notebook operations", | ||
@@ -27,3 +27,3 @@ "main": "lib/index.js", | ||
}, | ||
"gitHead": "683bdfa1a29472a8947f60457e3ddbdfdfbb328b" | ||
"gitHead": "8125bd8f3d7db2ca479d1b078379b5061625cd8f" | ||
} |
# @nteract/commutable | ||
This is a package for Jupyter Notebook operations, helping to enable history stored as a series of immutable notebooks. | ||
Commutable is a package to represent a Jupyter notebook document, as well as | ||
operations on the notebook, as a series of immutable notebooks, each one with | ||
its own state at a point in time. | ||
This package follows the principles below, based on [Tom MacWright](http://www.macwright.org/2015/05/18/practical-undo.html)'s outline for practical undo. | ||
This package follows the principles below, based on | ||
[Tom MacWright](http://www.macwright.org/2015/05/18/practical-undo.html)'s | ||
outline for practical undo. | ||
- **A notebook document is immutable**. It is never mutated in-place. | ||
- Changes to a notebook document are encapsulated into **operations** that take a previous version and return a new one. | ||
- History is represented as a **list of states**, with past on one end, the present on the other, and an index that can back up into 'undo states'. | ||
- **A notebook document is immutable**. The notebook document's representation | ||
is never mutated in-place. | ||
- Changes to a notebook document are encapsulated into **operations** that | ||
take a previous version and return a new version of the notebook without | ||
modifying the old version. | ||
- History is represented as a **list of states**, with _the past_ on one end, _the | ||
present_ on the other, and _an index_ that can back up into 'undo states'. | ||
- Modifying a notebook document causes any **future states to be thrown away**. | ||
@@ -24,3 +32,6 @@ | ||
The example below shows how we can use the `emptyMarkdownCell` immutable object exported from this package to quickly create an empty Markdown cell in our nteract application. | ||
The example below shows how we can create an empty Markdown cell in our | ||
nteract notebook application. We use the `emptyMarkdownCell` immutable object | ||
exported from this package to represent a new empty Markdown cell in a | ||
notebook document. | ||
@@ -41,7 +52,10 @@ ```javascript | ||
You can view the reference documentation for `@nteract/commutable` in the [package docs](https://packages.nteract.io/modules/commutable). | ||
You can view the reference documentation for `@nteract/commutable` in the | ||
[package docs](https://packages.nteract.io/modules/commutable). | ||
## Support | ||
If you experience an issue while using this package or have a feature request, please file an issue on the [issue board](https://github.com/nteract/nteract/issues/new/choose) and add the `pkg:commutable` label. | ||
If you experience an issue while using this package or have a feature request, | ||
please file an issue on the [issue board](https://github.com/nteract/nteract/issues/new/choose) | ||
and, if possible, add the `pkg:commutable` label. | ||
@@ -48,0 +62,0 @@ ## License |
@@ -9,4 +9,4 @@ /** | ||
import { | ||
List as ImmutableList, | ||
Map as ImmutableMap, | ||
List as ImmutableList, | ||
Record, | ||
@@ -18,3 +18,3 @@ RecordOf | ||
type CodeCellParams = { | ||
export interface CodeCellParams { | ||
cell_type: "code"; | ||
@@ -26,3 +26,4 @@ // Sadly untyped and widely unspecced | ||
outputs: ImmutableList<ImmutableOutput>; | ||
}; | ||
} | ||
export const makeCodeCell = Record<CodeCellParams>({ | ||
@@ -44,7 +45,7 @@ cell_type: "code", | ||
type MarkdownCellParams = { | ||
export interface MarkdownCellParams { | ||
cell_type: "markdown"; | ||
source: string; | ||
metadata: ImmutableMap<string, any>; | ||
}; | ||
} | ||
@@ -61,7 +62,7 @@ export const makeMarkdownCell = Record<MarkdownCellParams>({ | ||
type RawCellParams = { | ||
export interface RawCellParams { | ||
cell_type: "raw"; | ||
source: string; | ||
metadata: ImmutableMap<string, any>; | ||
}; | ||
} | ||
@@ -68,0 +69,0 @@ export const makeRawCell = Record<RawCellParams>({ |
/** | ||
* @module commutable | ||
*/ | ||
// ..................................... | ||
// API Exports | ||
// | ||
@@ -13,50 +11,1 @@ export * from "./primitives"; | ||
export * from "./notebook"; | ||
/* | ||
// from structures | ||
export { | ||
emptyCodeCell, | ||
emptyMarkdownCell, | ||
emptyNotebook, | ||
monocellNotebook, | ||
createCodeCell, | ||
insertCellAt, | ||
insertCellAfter, | ||
deleteCell, | ||
appendCellToNotebook | ||
} from "./structures"; | ||
// v4 | ||
export { StreamOutput, Output, createImmutableOutput } from "./v4"; | ||
export { | ||
createImmutableMimeBundle, | ||
makeDisplayData, | ||
makeErrorOutput, | ||
makeStreamOutput, | ||
makeExecuteResult, | ||
MimeBundle | ||
} from "./outputs"; | ||
export { | ||
makeRawCell, | ||
makeCodeCell, | ||
makeMarkdownCell, | ||
ImmutableCodeCell, | ||
ImmutableMarkdownCell, | ||
ImmutableRawCell, | ||
ImmutableCell, | ||
CellType | ||
} from "./cells"; | ||
export { | ||
toJS, | ||
stringifyNotebook, | ||
fromJS, | ||
parseNotebook, | ||
makeNotebookRecord, | ||
Notebook, | ||
ImmutableNotebook | ||
} from "./notebook"; | ||
*/ |
@@ -10,11 +10,11 @@ /** | ||
*/ | ||
import * as v3 from "./v3"; | ||
import * as v4 from "./v4"; | ||
import * as v3 from "./v3"; | ||
import { Map as ImmutableMap, List as ImmutableList, Record } from "immutable"; | ||
import { List as ImmutableList, Map as ImmutableMap, Record } from "immutable"; | ||
import { ImmutableCell } from "./cells"; | ||
import { JSONType, CellId } from "./primitives"; | ||
import { CellId, JSONType } from "./primitives"; | ||
export type NotebookRecordParams = { | ||
export interface NotebookRecordParams { | ||
cellOrder: ImmutableList<CellId>; | ||
@@ -25,3 +25,3 @@ cellMap: ImmutableMap<CellId, ImmutableCell>; | ||
metadata: ImmutableMap<string, any>; | ||
}; | ||
} | ||
@@ -39,20 +39,20 @@ export const makeNotebookRecord = Record<NotebookRecordParams>({ | ||
const freezeReviver = <T extends JSONType>(_k: string, v: T) => | ||
Object.freeze(v) as T; | ||
function freezeReviver<T extends JSONType>(_k: string, v: T) { | ||
return Object.freeze(v); | ||
} | ||
export type Notebook = v4.Notebook | v3.Notebook; | ||
export type Notebook = v4.NotebookV4 | v3.NotebookV3; | ||
/** | ||
* Converts a string representation of a notebook into a JSON representation. | ||
* | ||
* | ||
* @param notebookString A string representation of a notebook. | ||
* | ||
* | ||
* @returns A JSON representation of the same notebook. | ||
*/ | ||
export const parseNotebook = (notebookString: string): Notebook => | ||
JSON.parse(notebookString, freezeReviver); | ||
export function parseNotebook(notebookString: string): Notebook { | ||
return JSON.parse(notebookString, freezeReviver); | ||
} | ||
export const fromJS = ( | ||
notebook: v4.Notebook | v3.Notebook | ImmutableNotebook | ||
) => { | ||
export function fromJS(notebook: Notebook | ImmutableNotebook) { | ||
if (Record.isRecord(notebook)) { | ||
@@ -63,17 +63,15 @@ if (notebook.has("cellOrder") && notebook.has("cellMap")) { | ||
throw new TypeError( | ||
`commutable was passed an Immutable.Record structure that is not a notebook` | ||
"commutable was passed an Immutable.Record structure that is not a notebook" | ||
); | ||
} | ||
if (notebook.nbformat === 4 && notebook.nbformat_minor >= 0) { | ||
var v4Notebook = notebook as v4.Notebook; | ||
if (v4.isNotebookV4(notebook)) { | ||
if ( | ||
Array.isArray(v4Notebook.cells) && | ||
Array.isArray(notebook.cells) && | ||
typeof notebook.metadata === "object" | ||
) { | ||
return v4.fromJS(v4Notebook); | ||
return v4.fromJS(notebook); | ||
} | ||
} else if (notebook.nbformat === 3 && notebook.nbformat_minor >= 0) { | ||
return v3.fromJS(notebook as v3.Notebook); | ||
} else if (v3.isNotebookV3(notebook)) { | ||
return v3.fromJS(notebook); | ||
} | ||
@@ -88,13 +86,14 @@ | ||
throw new TypeError("This notebook format is not supported"); | ||
}; | ||
} | ||
/** | ||
* Converts an immutable representation of a notebook to a JSON representation of the | ||
* notebook using the v4 of the nbformat specification. | ||
* | ||
* | ||
* @param immnb The immutable representation of a notebook. | ||
* | ||
* | ||
* @returns The JSON representation of a notebook. | ||
*/ | ||
export const toJS = (immnb: ImmutableNotebook): v4.Notebook => { | ||
const minorVersion: null | number = immnb.get("nbformat_minor", null); | ||
export function toJS(immnb: ImmutableNotebook): v4.NotebookV4 { | ||
const minorVersion: number | null = immnb.get("nbformat_minor", null); | ||
@@ -109,12 +108,13 @@ if ( | ||
throw new TypeError("Only notebook formats 3 and 4 are supported!"); | ||
}; | ||
} | ||
/** | ||
* Converts a JSON representation of a notebook into a string representation. | ||
* | ||
* | ||
* @param notebook The JSON representation of a notebook. | ||
* | ||
* | ||
* @returns A string containing the notebook data. | ||
*/ | ||
export const stringifyNotebook = (notebook: v4.Notebook) => | ||
JSON.stringify(notebook, null, 2); | ||
export function stringifyNotebook(notebook: v4.NotebookV4) { | ||
return JSON.stringify(notebook, null, 2); | ||
} |
@@ -5,157 +5,75 @@ /** | ||
import { | ||
fromJS as immutableFromJS, | ||
List as ImmutableList, | ||
Map as ImmutableMap, | ||
List as ImmutableList, | ||
Record, | ||
RecordOf, | ||
fromJS as immutableFromJS | ||
RecordOf | ||
} from "immutable"; | ||
import { ExecutionCount, JSONObject, MultiLineString } from "./primitives"; | ||
import { | ||
createFrozenMediaBundle, | ||
demultiline, | ||
ExecutionCount, | ||
JSONObject, | ||
MediaBundle, | ||
MultiLineString, | ||
OnDiskMediaBundle, | ||
remultiline | ||
} from "./primitives"; | ||
export type ImmutableMimeBundle = ImmutableMap<string, any>; | ||
// | ||
// MimeBundle example (disk format) | ||
// | ||
// { | ||
// "application/json": {"a": 3, "b": 2}, | ||
// "text/html": ["<p>\n", "Hey\n", "</p>"], | ||
// "text/plain": "Hey" | ||
// } | ||
// | ||
export type MimeBundle = { [key: string]: string | string[] | undefined }; | ||
/** | ||
* Map over all the mimetypes, turning them into our in-memory format. | ||
* | ||
* ``` | ||
* { | ||
* "application/json": {"a": 3, "b": 2}, | ||
* "text/html": ["<p>\n", "Hey\n", "</p>"], | ||
* "text/plain": "Hey" | ||
* } | ||
* ``` | ||
* to | ||
* ``` | ||
* { | ||
* "application/json": {"a": 3, "b": 2}, | ||
* "text/html": "<p>\nHey\n</p>", | ||
* "text/plain": "Hey" | ||
* } | ||
* ``` | ||
* @param mimeBundle The mime | ||
* @param previous | ||
* @param key | ||
*/ | ||
export const cleanMimeAtKey = ( | ||
mimeBundle: MimeBundle, | ||
previous: ImmutableMimeBundle, | ||
key: string | ||
): ImmutableMimeBundle => | ||
previous.set(key, cleanMimeData(key, mimeBundle[key])); | ||
/** | ||
* Cleans mimedata, primarily converts an array of strings into a single string | ||
* joined by newlines. | ||
* | ||
* @param key The key, usually a mime type, that is associated with the mime data. | ||
* @param data The mime data to clean. | ||
* | ||
* @returns The cleaned mime data. | ||
*/ | ||
export const cleanMimeData = ( | ||
key: string, | ||
data: string | string[] | undefined | ||
) => { | ||
// See https://github.com/jupyter/nbformat/blob/62d6eb8803616d198eaa2024604d1fe923f2a7b3/nbformat/v4/nbformat.v4.schema.json#L368 | ||
if (isJSONKey(key)) { | ||
// Data stays as is for JSON types | ||
return data; | ||
} | ||
if (typeof data === "string" || Array.isArray(data)) { | ||
return demultiline(data); | ||
} | ||
throw new TypeError( | ||
`Data for ${key} is expected to be a string or an Array of strings` | ||
); | ||
}; | ||
export const createImmutableMimeBundle = ( | ||
mimeBundle: MimeBundle | ||
): ImmutableMimeBundle => | ||
Object.keys(mimeBundle).reduce( | ||
cleanMimeAtKey.bind(null, mimeBundle), | ||
ImmutableMap() | ||
); | ||
export const demultiline = (s: string | string[]): string => | ||
Array.isArray(s) ? s.join("") : s; | ||
/** | ||
* Split string into a list of strings delimited by newlines | ||
* | ||
* @param s The newline-delimited string that will be converted into an array of strings. | ||
* | ||
* @returns An array of strings. | ||
*/ | ||
export const remultiline = (s: string | string[]): string[] => | ||
Array.isArray(s) ? s : s.split(/(.+?(?:\r\n|\n))/g).filter(x => x !== ""); | ||
export const isJSONKey = (key: string) => | ||
/^application\/(.*\+)?json$/.test(key); | ||
/** ExecuteResult Record Boilerplate */ | ||
type ExecuteResultParams = { | ||
export interface ExecuteResultParams { | ||
output_type: "execute_result"; | ||
execution_count: ExecutionCount; | ||
data: ImmutableMimeBundle; | ||
data: Readonly<MediaBundle>; | ||
metadata?: any; | ||
}; | ||
} | ||
// Used for initializing all output records | ||
const emptyMediaBundle = Object.freeze({}); | ||
export const makeExecuteResult = Record<ExecuteResultParams>({ | ||
output_type: "execute_result", | ||
data: emptyMediaBundle, | ||
execution_count: null, | ||
data: ImmutableMap(), | ||
metadata: ImmutableMap() | ||
metadata: ImmutableMap(), | ||
output_type: "execute_result" | ||
}); | ||
type ImmutableExecuteResult = RecordOf<ExecuteResultParams>; | ||
export type ImmutableExecuteResult = RecordOf<ExecuteResultParams>; | ||
/** DisplayData Record Boilerplate */ | ||
type DisplayDataParams = { | ||
export interface DisplayDataParams { | ||
data: Readonly<MediaBundle>; | ||
output_type: "display_data"; | ||
data: ImmutableMimeBundle; | ||
metadata?: any; | ||
}; | ||
} | ||
export const makeDisplayData = Record<DisplayDataParams>({ | ||
output_type: "display_data", | ||
data: ImmutableMap(), | ||
metadata: ImmutableMap() | ||
data: emptyMediaBundle, | ||
metadata: ImmutableMap(), | ||
output_type: "display_data" | ||
}); | ||
type ImmutableDisplayData = RecordOf<DisplayDataParams>; | ||
export type ImmutableDisplayData = RecordOf<DisplayDataParams>; | ||
/** StreamOutput Record Boilerplate */ | ||
type StreamOutputParams = { | ||
export interface StreamOutputParams { | ||
output_type: "stream"; | ||
name: "stdout" | "stderr"; | ||
text: string; | ||
}; | ||
} | ||
export const makeStreamOutput = Record<StreamOutputParams>({ | ||
name: "stdout", | ||
output_type: "stream", | ||
name: "stdout", | ||
text: "" | ||
}); | ||
type ImmutableStreamOutput = RecordOf<StreamOutputParams>; | ||
export type ImmutableStreamOutput = RecordOf<StreamOutputParams>; | ||
/** ErrorOutput Record Boilerplate */ | ||
type ErrorOutputParams = { | ||
export interface ErrorOutputParams { | ||
output_type: "error"; | ||
@@ -165,12 +83,12 @@ ename: string; | ||
traceback: ImmutableList<string>; | ||
}; | ||
} | ||
export const makeErrorOutput = Record<ErrorOutputParams>({ | ||
output_type: "error", | ||
ename: "", | ||
evalue: "", | ||
output_type: "error", | ||
traceback: ImmutableList() | ||
}); | ||
type ImmutableErrorOutput = RecordOf<ErrorOutputParams>; | ||
export type ImmutableErrorOutput = RecordOf<ErrorOutputParams>; | ||
@@ -187,16 +105,16 @@ ////////////// | ||
/** * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * | ||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * | ||
* Output Types | ||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | ||
export interface ExecuteResult { | ||
export interface OnDiskExecuteResult { | ||
output_type: "execute_result"; | ||
execution_count: ExecutionCount; | ||
data: MimeBundle; | ||
data: OnDiskMediaBundle; | ||
metadata: JSONObject; | ||
} | ||
export interface DisplayData { | ||
export interface OnDiskDisplayData { | ||
output_type: "display_data"; | ||
data: MimeBundle; | ||
data: OnDiskMediaBundle; | ||
metadata: JSONObject; | ||
@@ -206,3 +124,3 @@ transient?: JSONObject; | ||
export interface StreamOutput { | ||
export interface OnDiskStreamOutput { | ||
output_type: "stream"; | ||
@@ -213,4 +131,4 @@ name: "stdout" | "stderr"; | ||
export interface ErrorOutput { | ||
output_type: "error" | "pyerr"; | ||
export interface OnDiskErrorOutput { | ||
output_type: "error"; | ||
ename: string; | ||
@@ -221,3 +139,7 @@ evalue: string; | ||
export type Output = ExecuteResult | DisplayData | StreamOutput | ErrorOutput; | ||
export type OnDiskOutput = | ||
| OnDiskExecuteResult | ||
| OnDiskDisplayData | ||
| OnDiskStreamOutput | ||
| OnDiskErrorOutput; | ||
@@ -231,8 +153,8 @@ /** | ||
*/ | ||
export const createImmutableOutput = (output: Output): ImmutableOutput => { | ||
export function createImmutableOutput(output: OnDiskOutput): ImmutableOutput { | ||
switch (output.output_type) { | ||
case "execute_result": | ||
return makeExecuteResult({ | ||
data: createFrozenMediaBundle(output.data), | ||
execution_count: output.execution_count, | ||
data: createImmutableMimeBundle(output.data), | ||
metadata: immutableFromJS(output.metadata) | ||
@@ -242,3 +164,3 @@ }); | ||
return makeDisplayData({ | ||
data: createImmutableMimeBundle(output.data), | ||
data: createFrozenMediaBundle(output.data), | ||
metadata: immutableFromJS(output.metadata) | ||
@@ -253,5 +175,5 @@ }); | ||
return makeErrorOutput({ | ||
output_type: "error", | ||
ename: output.ename, | ||
evalue: output.evalue, | ||
output_type: "error", | ||
// Note: this is one of the cases where the Array of strings (for | ||
@@ -262,4 +184,14 @@ // traceback) is part of the format, not a multiline string | ||
default: | ||
throw new TypeError(`Output type ${output.output_type} not recognized`); | ||
// Since we're well typed, output is never. However we can still get new output types we don't handle | ||
// and need to fail hard instead of making indeterminate behavior | ||
const unknownOutput = output as any; | ||
if (unknownOutput.output_type) { | ||
throw new TypeError( | ||
`Output type ${(output as any).output_type} not recognized` | ||
); | ||
} | ||
throw new TypeError( | ||
`Output structure not known: ${JSON.stringify(output)}` | ||
); | ||
} | ||
}; | ||
} |
/** | ||
* @module commutable | ||
*/ | ||
import * as Immutable from "immutable"; | ||
import uuid from "uuid/v4"; | ||
import * as Immutable from "immutable"; | ||
@@ -18,3 +18,5 @@ export type ExecutionCount = number | null; | ||
export type CellId = string; | ||
export const createCellId = (): CellId => uuid(); | ||
export function createCellId(): CellId { | ||
return uuid(); | ||
} | ||
@@ -33,1 +35,172 @@ // On disk multi-line strings are used to accomodate line-by-line diffs in tools | ||
export type ImmutableJSONList = Immutable.List<any>; | ||
/** | ||
* Media Bundles as they exist on disk from the notebook format | ||
* See https://nbformat.readthedocs.io/en/latest/format_description.html#display-data for docs | ||
* and https://github.com/jupyter/nbformat/blob/master/nbformat/v4/nbformat.v4.schema.json for the schema | ||
*/ | ||
export interface OnDiskMediaBundle { | ||
"text/plain"?: MultiLineString; | ||
"text/html"?: MultiLineString; | ||
"text/latex"?: MultiLineString; | ||
"text/markdown"?: MultiLineString; | ||
"application/javascript"?: MultiLineString; | ||
"image/png"?: MultiLineString; | ||
"image/jpeg"?: MultiLineString; | ||
"image/gif"?: MultiLineString; | ||
"image/svg+xml"?: MultiLineString; | ||
// The JSON mimetype has some corner cases because of the protocol / format assuming the values | ||
// in a media bundle are either: | ||
// | ||
// * A string; which would be deserialized | ||
// * An array; which would have to be assumed to be a multiline string | ||
// | ||
"application/json"?: string | string[] | {}; | ||
"application/vdom.v1+json"?: {}; | ||
"application/vnd.dataresource+json"?: {}; | ||
"text/vnd.plotly.v1+html"?: MultiLineString | {}; | ||
"application/vnd.plotly.v1+json"?: {}; | ||
"application/geo+json"?: {}; | ||
"application/x-nteract-model-debug+json"?: {}; | ||
"application/vnd.vega.v2+json"?: {}; | ||
"application/vnd.vega.v3+json"?: {}; | ||
"application/vnd.vegalite.v1+json"?: {}; | ||
"application/vnd.vegalite.v2+json"?: {}; | ||
[key: string]: string | string[] | {} | undefined; | ||
} | ||
// Enumerating over all the media types we currently accept | ||
export interface MediaBundle { | ||
"text/plain"?: string; | ||
"text/html"?: string; | ||
"text/latex"?: string; | ||
"text/markdown"?: string; | ||
"application/javascript"?: string; | ||
"image/png"?: string; | ||
"image/jpeg"?: string; | ||
"image/gif"?: string; | ||
"image/svg+xml"?: string; | ||
// All our JSON types can only be JSON Objects | ||
"application/json"?: { [key: string]: any }; | ||
"application/vdom.v1+json"?: { [key: string]: any }; | ||
"application/vnd.dataresource+json"?: { [key: string]: any }; | ||
"text/vnd.plotly.v1+html"?: string | { [key: string]: any }; | ||
"application/vnd.plotly.v1+json"?: { [key: string]: any }; | ||
"application/geo+json"?: { [key: string]: any }; | ||
"application/x-nteract-model-debug+json"?: { [key: string]: any }; | ||
"application/vnd.vega.v2+json"?: { [key: string]: any }; | ||
"application/vnd.vega.v3+json"?: { [key: string]: any }; | ||
"application/vnd.vegalite.v1+json"?: { [key: string]: any }; | ||
"application/vnd.vegalite.v2+json"?: { [key: string]: any }; | ||
// Other media types can also come in that we don't recognize | ||
[key: string]: string | string[] | {} | undefined; | ||
} | ||
/** | ||
* Turn nbformat multiline strings (arrays of strings for simplifying diffs) into strings | ||
*/ | ||
export function demultiline(s: string | string[]): string { | ||
if (Array.isArray(s)) { | ||
return s.join(""); | ||
} | ||
return s; | ||
} | ||
/** | ||
* Split string into a list of strings delimited by newlines; useful for on-disk git comparisons; | ||
* and is the expectation for jupyter notebooks on disk | ||
*/ | ||
export function remultiline(s: string | string[]): string[] { | ||
if (Array.isArray(s)) { | ||
// Assume | ||
return s; | ||
} | ||
// Use positive lookahead regex to split on newline and retain newline char | ||
return s.split(/(.+?(?:\r\n|\n))/g).filter(x => x !== ""); | ||
} | ||
function isJSONKey(key: string) { | ||
return /^application\/(.*\+)json$/.test(key); | ||
} | ||
// A type with all ownPropertyNames also readonly; works for all JSON types | ||
type DeepReadonly<T> = { readonly [P in keyof T]: DeepReadonly<T[P]> }; | ||
// Taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze | ||
export function deepFreeze<T>(object: T): DeepReadonly<T> { | ||
// Retrieve the property names defined on object | ||
const propNames = Object.getOwnPropertyNames(object); | ||
// Freeze properties before freezing self | ||
for (const name of propNames) { | ||
// getOwnPropertyNames assures us we can index on name | ||
const value = (object as any)[name]; | ||
(object as any)[name] = | ||
value && typeof value === "object" ? deepFreeze(value) : value; | ||
} | ||
return (Object.freeze(object) as unknown) as DeepReadonly<T>; | ||
} | ||
export function createFrozenMediaBundle( | ||
mediaBundle: OnDiskMediaBundle | ||
): Readonly<MediaBundle> { | ||
// Map over all the mimetypes; turning them into our in-memory format | ||
// | ||
// { | ||
// "application/json": {"a": 3; "b": 2}; | ||
// "text/html": ["<p>\n"; "Hey\n"; "</p>"]; | ||
// "text/plain": "Hey" | ||
// } | ||
// | ||
// to | ||
// | ||
// { | ||
// "application/json": {"a": 3; "b": 2}; | ||
// "text/html": "<p>\nHey\n</p>"; | ||
// "text/plain": "Hey" | ||
// } | ||
// Since we have to convert from one type to another that has conflicting types; we need to hand convert it in a way that | ||
// flow is able to verify correctly. The way we do that is create a new object that we declare with the type we want; | ||
// set the keys and values we need; then seal the object with Object.freeze | ||
const bundle: MediaBundle = {}; | ||
for (const key in mediaBundle) { | ||
if ( | ||
!isJSONKey(key) && | ||
(typeof mediaBundle[key] === "string" || Array.isArray(mediaBundle[key])) | ||
) { | ||
// Because it's a string; we can't mutate it anyways (and don't have to Object.freeze it) | ||
bundle[key] = demultiline(mediaBundle[key] as MultiLineString); | ||
} else { | ||
// we now know it's an Object of some kind | ||
bundle[key] = deepFreeze(mediaBundle[key]!); | ||
} | ||
} | ||
return Object.freeze(bundle); | ||
} | ||
export function createOnDiskMediaBundle( | ||
mediaBundle: Readonly<MediaBundle> | ||
): OnDiskMediaBundle { | ||
// Technically we could return just the mediaBundle as is | ||
// return mediaBundle; | ||
// However for the sake of on-disk readability we write out remultilined versions of the array and string ones | ||
const freshBundle: OnDiskMediaBundle = {}; | ||
for (const key in mediaBundle) { | ||
if ( | ||
!isJSONKey(key) && | ||
(typeof mediaBundle[key] === "string" || Array.isArray(mediaBundle[key])) | ||
) { | ||
freshBundle[key] = remultiline(mediaBundle[key] as MultiLineString); | ||
} else { | ||
freshBundle[key] = mediaBundle[key]; | ||
} | ||
} | ||
return freshBundle; | ||
} |
@@ -8,7 +8,7 @@ /** | ||
import { makeNotebookRecord, ImmutableNotebook } from "./notebook"; | ||
import { ImmutableNotebook, makeNotebookRecord } from "./notebook"; | ||
import { makeCodeCell, makeMarkdownCell, ImmutableCell } from "./cells"; | ||
import { ImmutableCell, makeCodeCell, makeMarkdownCell } from "./cells"; | ||
import { Map as ImmutableMap, List as ImmutableList } from "immutable"; | ||
import { List as ImmutableList, Map as ImmutableMap } from "immutable"; | ||
@@ -29,38 +29,40 @@ // The cell creators here are a bit duplicative | ||
export type CellStructure = { | ||
export interface CellStructure { | ||
cellOrder: ImmutableList<CellId>; | ||
cellMap: ImmutableMap<CellId, ImmutableCell>; | ||
}; | ||
} | ||
/** | ||
* A function that appends a new cell to a CellStructure object. | ||
* | ||
* | ||
* @param cellStructure The cellOrder and cellMap of the current notebook | ||
* @param immutableCell The cell that will be inserted into the cellStructure | ||
* @param id The id of the new cell, defaults to a new UUID | ||
* | ||
* | ||
* @returns Cell structure with the new cell appended at the end | ||
*/ | ||
export const appendCell = ( | ||
export function appendCell( | ||
cellStructure: CellStructure, | ||
immutableCell: ImmutableCell, | ||
id: CellId = createCellId() | ||
): CellStructure => ({ | ||
cellOrder: cellStructure.cellOrder.push(id), | ||
cellMap: cellStructure.cellMap.set(id, immutableCell) | ||
}); | ||
): CellStructure { | ||
return { | ||
cellOrder: cellStructure.cellOrder.push(id), | ||
cellMap: cellStructure.cellMap.set(id, immutableCell) | ||
}; | ||
} | ||
/** | ||
* A function that appends a cell to an immutable notebook. | ||
* | ||
* | ||
* @param immnb An immutable data structure representing the notebook that will be modified | ||
* @param immCell The new cell that will be inserted into the notebook | ||
* | ||
* | ||
* @returns The modified notebook | ||
*/ | ||
export const appendCellToNotebook = ( | ||
export function appendCellToNotebook( | ||
immnb: ImmutableNotebook, | ||
immCell: ImmutableCell | ||
): ImmutableNotebook => | ||
immnb.withMutations(nb => { | ||
): ImmutableNotebook { | ||
return immnb.withMutations(nb => { | ||
const cellStructure: CellStructure = { | ||
@@ -73,14 +75,15 @@ cellOrder: nb.get("cellOrder"), | ||
}); | ||
} | ||
/** | ||
* Inserts a cell with cellID at a given index within the notebook. | ||
* | ||
* @param notebook The notebook the cell will be inserted into. | ||
* @param cell The cell that will be inserted | ||
* @param cellID The ID of the cell. | ||
* @param index The position we would like to insert the cell at | ||
* | ||
* @returns The modified notebook. | ||
*/ | ||
export const insertCellAt = ( | ||
/** | ||
* Inserts a cell with cellID at a given index within the notebook. | ||
* | ||
* @param notebook The notebook the cell will be inserted into. | ||
* @param cell The cell that will be inserted | ||
* @param cellID The ID of the cell. | ||
* @param index The position we would like to insert the cell at | ||
* | ||
* @returns The modified notebook. | ||
*/ | ||
export function insertCellAt( | ||
notebook: ImmutableNotebook, | ||
@@ -90,4 +93,4 @@ cell: ImmutableCell, | ||
index: number | ||
): ImmutableNotebook => | ||
notebook.withMutations(nb => | ||
): ImmutableNotebook { | ||
return notebook.withMutations(nb => | ||
nb | ||
@@ -97,2 +100,3 @@ .setIn(["cellMap", cellId], cell) | ||
); | ||
} | ||
@@ -102,3 +106,3 @@ /** | ||
* in the notebook. | ||
* | ||
* | ||
* @param notebook The notebook the cell will be inserted into. | ||
@@ -109,3 +113,3 @@ * @param cell The cell that will be inserted | ||
*/ | ||
export const insertCellAfter = ( | ||
export function insertCellAfter( | ||
notebook: ImmutableNotebook, | ||
@@ -115,4 +119,4 @@ cell: ImmutableCell, | ||
priorCellId: string | ||
): ImmutableNotebook => | ||
insertCellAt( | ||
): ImmutableNotebook { | ||
return insertCellAt( | ||
notebook, | ||
@@ -123,18 +127,20 @@ cell, | ||
); | ||
} | ||
/** | ||
* Delete a cell with CellID at a given location. Note that this function | ||
* is deprecated in favor of `deleteCell`. | ||
* | ||
* Deprecated: Delete a cell with CellID at a given location. | ||
* | ||
* Note that this function is deprecated in favor of `deleteCell`. | ||
* | ||
* @param notebook The notebook containing the cell. | ||
* @param cellID The ID of the cell that will be deleted. | ||
* | ||
* | ||
* @returns The modified notebook | ||
* | ||
* | ||
* @deprecated use `deleteCell()` instead | ||
*/ | ||
export const removeCell = ( | ||
export function removeCell( | ||
notebook: ImmutableNotebook, | ||
cellId: string | ||
): ImmutableNotebook => { | ||
): ImmutableNotebook { | ||
console.log( | ||
@@ -145,22 +151,23 @@ "Deprecation Warning: removeCell() is being deprecated. Please use deleteCell() instead" | ||
return deleteCell(notebook, cellId); | ||
}; | ||
} | ||
/** | ||
* Delete a cell with CellID at a given location. | ||
* | ||
* | ||
* @param notebook The notebook containing the cell. | ||
* @param cellID The ID of the cell that will be deleted. | ||
* | ||
* | ||
* @returns The modified notebook | ||
*/ | ||
export const deleteCell = ( | ||
export function deleteCell( | ||
notebook: ImmutableNotebook, | ||
cellId: string | ||
): ImmutableNotebook => | ||
notebook | ||
): ImmutableNotebook { | ||
return notebook | ||
.removeIn(["cellMap", cellId]) | ||
.update("cellOrder", cellOrder => cellOrder.filterNot(id => id === cellId)); | ||
} | ||
/** | ||
* A new notebook with a single empty code cell. This function is useful | ||
* A new 'monocell' notebook with a single empty code cell. This function is useful | ||
* if you are looking to initialize a fresh, new notebook. | ||
@@ -167,0 +174,0 @@ */ |
117
src/v3.ts
@@ -5,8 +5,15 @@ /** | ||
import { | ||
Map as ImmutableMap, | ||
fromJS as immutableFromJS, | ||
List as ImmutableList | ||
List as ImmutableList, | ||
Map as ImmutableMap | ||
} from "immutable"; | ||
import { MultiLineString, JSONObject } from "./primitives"; | ||
import { | ||
CellId, | ||
createFrozenMediaBundle, | ||
demultiline, | ||
JSONObject, | ||
MediaBundle, | ||
MultiLineString | ||
} from "./primitives"; | ||
@@ -16,2 +23,3 @@ import { makeNotebookRecord } from "./notebook"; | ||
import { | ||
ImmutableCell, | ||
ImmutableCodeCell, | ||
@@ -21,4 +29,4 @@ ImmutableMarkdownCell, | ||
makeCodeCell, | ||
makeRawCell, | ||
makeMarkdownCell | ||
makeMarkdownCell, | ||
makeRawCell | ||
} from "./cells"; | ||
@@ -28,14 +36,11 @@ | ||
ImmutableOutput, | ||
ImmutableMimeBundle, | ||
makeDisplayData, | ||
makeErrorOutput, | ||
makeExecuteResult, | ||
makeDisplayData, | ||
makeStreamOutput, | ||
makeErrorOutput, | ||
demultiline, | ||
cleanMimeAtKey, | ||
ErrorOutput | ||
OnDiskErrorOutput | ||
} from "./outputs"; | ||
import { CellStructure, appendCell } from "./structures"; | ||
import { RawCell, MarkdownCell } from "./v4"; | ||
import { appendCell, CellStructure } from "./structures"; | ||
import { MarkdownCell, RawCell } from "./v4"; | ||
@@ -64,2 +69,8 @@ const VALID_MIMETYPES = { | ||
export interface DisplayData extends MimeOutput<"display_data"> {} | ||
export interface ErrorOutput { | ||
output_type: "error" | "pyerr"; | ||
ename: string; | ||
evalue: string; | ||
traceback: string[]; | ||
} | ||
@@ -88,3 +99,3 @@ export interface StreamOutput { | ||
prompt_number: number; | ||
outputs: Array<Output>; | ||
outputs: Output[]; | ||
} | ||
@@ -99,3 +110,3 @@ | ||
export type Notebook = { | ||
export interface NotebookV3 { | ||
worksheets: Worksheet[]; | ||
@@ -105,8 +116,8 @@ metadata: object; | ||
nbformat_minor: number; | ||
}; | ||
} | ||
const createImmutableMarkdownCell = ( | ||
function createImmutableMarkdownCell( | ||
cell: MarkdownCell | ||
): ImmutableMarkdownCell => | ||
makeMarkdownCell({ | ||
): ImmutableMarkdownCell { | ||
return makeMarkdownCell({ | ||
cell_type: cell.cell_type, | ||
@@ -116,19 +127,20 @@ source: demultiline(cell.source), | ||
}); | ||
} | ||
const createImmutableMimeBundle = (output: MimeOutput): ImmutableMimeBundle => { | ||
const mimeBundle: { [key: string]: MultiLineString | undefined } = {}; | ||
/** | ||
* Handle the old v3 version of the media | ||
*/ | ||
function createImmutableMediaBundle(output: MimeOutput): Readonly<MediaBundle> { | ||
const mediaBundle: { [key: string]: MultiLineString | undefined } = {}; | ||
for (const key of Object.keys(output)) { | ||
// v3 had non-media types for rich media | ||
if (key in VALID_MIMETYPES) { | ||
mimeBundle[VALID_MIMETYPES[key as MimeTypeKey]] = | ||
mediaBundle[VALID_MIMETYPES[key as MimeTypeKey]] = | ||
output[key as keyof MimePayload]; | ||
} | ||
} | ||
return Object.keys(mimeBundle).reduce( | ||
cleanMimeAtKey.bind(null, mimeBundle), | ||
ImmutableMap() | ||
); | ||
}; | ||
return createFrozenMediaBundle(mediaBundle); | ||
} | ||
const createImmutableOutput = (output: Output): ImmutableOutput => { | ||
function createImmutableOutput(output: Output): ImmutableOutput { | ||
switch (output.output_type) { | ||
@@ -139,3 +151,3 @@ case "pyout": | ||
// Note strangeness with v4 API | ||
data: createImmutableMimeBundle(output), | ||
data: createImmutableMediaBundle(output), | ||
metadata: immutableFromJS(output.metadata) | ||
@@ -145,3 +157,3 @@ }); | ||
return makeDisplayData({ | ||
data: createImmutableMimeBundle(output), | ||
data: createImmutableMediaBundle(output), | ||
metadata: immutableFromJS(output.metadata) | ||
@@ -166,6 +178,6 @@ }); | ||
} | ||
}; | ||
} | ||
const createImmutableCodeCell = (cell: CodeCell): ImmutableCodeCell => | ||
makeCodeCell({ | ||
function createImmutableCodeCell(cell: CodeCell): ImmutableCodeCell { | ||
return makeCodeCell({ | ||
cell_type: cell.cell_type, | ||
@@ -177,5 +189,6 @@ source: demultiline(cell.input), | ||
}); | ||
} | ||
const createImmutableRawCell = (cell: RawCell): ImmutableRawCell => | ||
makeRawCell({ | ||
function createImmutableRawCell(cell: RawCell): ImmutableRawCell { | ||
return makeRawCell({ | ||
cell_type: cell.cell_type, | ||
@@ -185,6 +198,7 @@ source: demultiline(cell.source), | ||
}); | ||
} | ||
const createImmutableHeadingCell = (cell: HeadingCell): ImmutableMarkdownCell => | ||
function createImmutableHeadingCell(cell: HeadingCell): ImmutableMarkdownCell { | ||
// v3 heading cells are just markdown cells in v4+ | ||
makeMarkdownCell({ | ||
return makeMarkdownCell({ | ||
cell_type: "markdown", | ||
@@ -203,4 +217,5 @@ source: Array.isArray(cell.source) | ||
}); | ||
} | ||
const createImmutableCell = (cell: Cell) => { | ||
function createImmutableCell(cell: Cell) { | ||
switch (cell.cell_type) { | ||
@@ -218,6 +233,7 @@ case "markdown": | ||
} | ||
}; | ||
} | ||
export const fromJS = (notebook: Notebook) => { | ||
if (notebook.nbformat !== 3 || notebook.nbformat_minor < 0) { | ||
export function fromJS(notebook: NotebookV3) { | ||
if (!isNotebookV3(notebook)) { | ||
notebook = notebook as any; | ||
throw new TypeError( | ||
@@ -229,5 +245,5 @@ `Notebook is not a valid v3 notebook. v3 notebooks must be of form 3.x | ||
const starterCellStructure = { | ||
cellOrder: ImmutableList().asMutable(), | ||
cellMap: ImmutableMap().asMutable() | ||
const starterCellStructure: CellStructure = { | ||
cellOrder: ImmutableList<CellId>().asMutable(), | ||
cellMap: ImmutableMap<CellId, ImmutableCell>().asMutable() | ||
}; | ||
@@ -240,3 +256,3 @@ | ||
(cellStruct, cell) => appendCell(cellStruct, createImmutableCell(cell)), | ||
starterCellStructure as CellStructure | ||
starterCellStructure | ||
) | ||
@@ -253,2 +269,11 @@ ) | ||
}); | ||
}; | ||
} | ||
export function isNotebookV3(value: any): value is NotebookV3 { | ||
return ( | ||
value && | ||
typeof value === "object" && | ||
value.nbformat === 3 && | ||
value.nbformat_minor >= 0 | ||
); | ||
} |
177
src/v4.ts
@@ -0,1 +1,3 @@ | ||
// Due to the on-disk format needing to be written out in an explicit order, we disable ordering for this file | ||
// tslint:disable:object-literal-sort-keys | ||
/** | ||
@@ -18,41 +20,36 @@ * @module commutable | ||
import { | ||
Map as ImmutableMap, | ||
fromJS as immutableFromJS, | ||
List as ImmutableList, | ||
Map as ImmutableMap, | ||
Set as ImmutableSet | ||
} from "immutable"; | ||
import { ImmutableNotebook, makeNotebookRecord } from "./notebook"; | ||
import { | ||
makeNotebookRecord, | ||
ImmutableNotebook, | ||
NotebookRecordParams | ||
} from "./notebook"; | ||
CellId, | ||
createFrozenMediaBundle, | ||
createOnDiskMediaBundle, | ||
demultiline, | ||
ExecutionCount, | ||
JSONObject, | ||
MultiLineString, | ||
OnDiskMediaBundle, | ||
remultiline | ||
} from "./primitives"; | ||
import { JSONObject, MultiLineString, ExecutionCount } from "./primitives"; | ||
import { | ||
ImmutableCell, | ||
ImmutableCodeCell, | ||
ImmutableMarkdownCell, | ||
ImmutableRawCell, | ||
ImmutableCell, | ||
makeCodeCell, | ||
makeRawCell, | ||
makeMarkdownCell | ||
makeMarkdownCell, | ||
makeRawCell | ||
} from "./cells"; | ||
import { | ||
createImmutableMimeBundle, | ||
createImmutableOutput, | ||
ImmutableOutput, | ||
makeExecuteResult, | ||
makeDisplayData, | ||
makeStreamOutput, | ||
makeErrorOutput, | ||
demultiline, | ||
remultiline, | ||
isJSONKey, | ||
MimeBundle, | ||
ImmutableMimeBundle, | ||
Output, | ||
StreamOutput, | ||
createImmutableOutput | ||
OnDiskOutput | ||
} from "./outputs"; | ||
@@ -62,3 +59,3 @@ | ||
/** * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * | ||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * | ||
* Cell Types | ||
@@ -72,3 +69,3 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ | ||
source: MultiLineString; | ||
outputs: Output[]; | ||
outputs: OnDiskOutput[]; | ||
} | ||
@@ -90,8 +87,8 @@ | ||
export type Notebook = { | ||
cells: Array<Cell>; | ||
metadata: Object; | ||
export interface NotebookV4 { | ||
cells: Cell[]; | ||
metadata: JSONObject; | ||
nbformat: 4; | ||
nbformat_minor: number; | ||
}; | ||
} | ||
@@ -105,4 +102,4 @@ /** | ||
*/ | ||
const createImmutableMetadata = (metadata: JSONObject) => | ||
ImmutableMap(metadata).map((v, k: string) => { | ||
function createImmutableMetadata(metadata: JSONObject) { | ||
return ImmutableMap(metadata).map((v, k: string) => { | ||
if (k !== "tags") { | ||
@@ -119,5 +116,6 @@ return v; | ||
}); | ||
} | ||
const createImmutableRawCell = (cell: RawCell): ImmutableRawCell => | ||
makeRawCell({ | ||
function createImmutableRawCell(cell: RawCell): ImmutableRawCell { | ||
return makeRawCell({ | ||
cell_type: cell.cell_type, | ||
@@ -127,7 +125,8 @@ source: demultiline(cell.source), | ||
}); | ||
} | ||
const createImmutableMarkdownCell = ( | ||
function createImmutableMarkdownCell( | ||
cell: MarkdownCell | ||
): ImmutableMarkdownCell => | ||
makeMarkdownCell({ | ||
): ImmutableMarkdownCell { | ||
return makeMarkdownCell({ | ||
cell_type: cell.cell_type, | ||
@@ -137,5 +136,6 @@ source: demultiline(cell.source), | ||
}); | ||
} | ||
const createImmutableCodeCell = (cell: CodeCell): ImmutableCodeCell => | ||
makeCodeCell({ | ||
function createImmutableCodeCell(cell: CodeCell): ImmutableCodeCell { | ||
return makeCodeCell({ | ||
cell_type: cell.cell_type, | ||
@@ -147,2 +147,3 @@ source: demultiline(cell.source), | ||
}); | ||
} | ||
@@ -157,3 +158,3 @@ /** | ||
*/ | ||
const createImmutableCell = (cell: Cell): ImmutableCell => { | ||
function createImmutableCell(cell: Cell): ImmutableCell { | ||
switch (cell.cell_type) { | ||
@@ -169,6 +170,7 @@ case "markdown": | ||
} | ||
}; | ||
} | ||
export const fromJS = (notebook: Notebook) => { | ||
if (notebook.nbformat !== 4 || notebook.nbformat_minor < 0) { | ||
export function fromJS(notebook: NotebookV4) { | ||
if (!isNotebookV4(notebook)) { | ||
notebook = notebook as any; | ||
throw new TypeError( | ||
@@ -182,5 +184,5 @@ `Notebook is not a valid v4 notebook. v4 notebooks must be of form 4.x | ||
// switch back after. | ||
const starterCellStructure = { | ||
cellOrder: ImmutableList().asMutable(), | ||
cellMap: ImmutableMap().asMutable() | ||
const starterCellStructure: CellStructure = { | ||
cellOrder: ImmutableList<CellId>().asMutable(), | ||
cellMap: ImmutableMap<CellId, ImmutableCell>().asMutable() | ||
}; | ||
@@ -190,3 +192,3 @@ | ||
(cellStruct, cell) => appendCell(cellStruct, createImmutableCell(cell)), | ||
starterCellStructure as CellStructure | ||
starterCellStructure | ||
); | ||
@@ -201,33 +203,9 @@ | ||
}); | ||
}; | ||
} | ||
const metadataToJS = (immMetadata: ImmutableMap<string, any>) => | ||
immMetadata.toJS() as JSONObject; | ||
function metadataToJS(immMetadata: ImmutableMap<string, any>) { | ||
return immMetadata.toJS() as JSONObject; | ||
} | ||
const mimeBundleToJS = (immMimeBundle: ImmutableMimeBundle): MimeBundle => { | ||
const bundle = immMimeBundle.toObject(); | ||
Object.keys(bundle).map(key => { | ||
if (isJSONKey(key)) { | ||
if (ImmutableMap.isMap(bundle[key])) { | ||
bundle[key] = bundle[key].toJS(); | ||
} | ||
return bundle; | ||
} | ||
const data = bundle[key]; | ||
if (typeof data === "string" || Array.isArray(data)) { | ||
bundle[key] = remultiline(data); | ||
return bundle; | ||
} | ||
throw new TypeError( | ||
`Data for ${key} is expected to be a string or an Array of strings` | ||
); | ||
}); | ||
return bundle; | ||
}; | ||
const outputToJS = (output: ImmutableOutput): Output => { | ||
function outputToJS(output: ImmutableOutput): OnDiskOutput { | ||
switch (output.output_type) { | ||
@@ -238,3 +216,3 @@ case "execute_result": | ||
execution_count: output.execution_count, | ||
data: mimeBundleToJS(output.data), | ||
data: createOnDiskMediaBundle(output.data), | ||
metadata: output.metadata.toJS() | ||
@@ -245,3 +223,3 @@ }; | ||
output_type: output.output_type, | ||
data: mimeBundleToJS(output.data), | ||
data: createOnDiskMediaBundle(output.data), | ||
metadata: output.metadata.toJS() | ||
@@ -265,9 +243,11 @@ }; | ||
} | ||
}; | ||
} | ||
const markdownCellToJS = (immCell: ImmutableMarkdownCell): MarkdownCell => ({ | ||
cell_type: "markdown", | ||
source: remultiline(immCell.source), | ||
metadata: metadataToJS(immCell.metadata) | ||
}); | ||
function markdownCellToJS(immCell: ImmutableMarkdownCell): MarkdownCell { | ||
return { | ||
cell_type: "markdown", | ||
source: remultiline(immCell.source), | ||
metadata: metadataToJS(immCell.metadata) | ||
}; | ||
} | ||
@@ -281,3 +261,3 @@ /** | ||
*/ | ||
const codeCellToJS = (immCell: ImmutableCodeCell): CodeCell => { | ||
function codeCellToJS(immCell: ImmutableCodeCell): CodeCell { | ||
return { | ||
@@ -290,3 +270,3 @@ cell_type: "code", | ||
}; | ||
}; | ||
} | ||
@@ -300,3 +280,3 @@ /** | ||
*/ | ||
const rawCellToJS = (immCell: ImmutableRawCell): RawCell => { | ||
function rawCellToJS(immCell: ImmutableRawCell): RawCell { | ||
return { | ||
@@ -307,3 +287,3 @@ cell_type: "raw", | ||
}; | ||
}; | ||
} | ||
@@ -317,3 +297,3 @@ /** | ||
*/ | ||
const cellToJS = (immCell: ImmutableCell): Cell => { | ||
function cellToJS(immCell: ImmutableCell): Cell { | ||
switch (immCell.cell_type) { | ||
@@ -327,5 +307,5 @@ case "markdown": | ||
default: | ||
throw new TypeError(`Cell type unknown at runtime`); | ||
throw new TypeError("Cell type unknown at runtime"); | ||
} | ||
}; | ||
} | ||
@@ -339,4 +319,4 @@ /** | ||
*/ | ||
export const toJS = (immnb: ImmutableNotebook): Notebook => { | ||
const plainNotebook = immnb.toObject() as NotebookRecordParams; | ||
export function toJS(immnb: ImmutableNotebook): NotebookV4 { | ||
const plainNotebook = immnb.toObject(); | ||
const plainCellOrder: string[] = plainNotebook.cellOrder.toArray(); | ||
@@ -353,6 +333,15 @@ const plainCellMap: { | ||
cells, | ||
metadata: plainNotebook.metadata.toJS(), | ||
metadata: plainNotebook.metadata.toJS() as JSONObject, | ||
nbformat: 4, | ||
nbformat_minor: plainNotebook.nbformat_minor | ||
}; | ||
}; | ||
} | ||
export function isNotebookV4(value: any): value is NotebookV4 { | ||
return ( | ||
value && | ||
typeof value === "object" && | ||
value.nbformat === 4 && | ||
value.nbformat_minor >= 0 | ||
); | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
86480
29
2450
1
63