Added v3 Notebook format support (#3697)

* Added v3 format support
This commit is contained in:
Kevin Cunnane
2019-01-09 17:00:56 -08:00
committed by GitHub
parent 42afcf9322
commit 8bd6691331

View File

@@ -13,14 +13,14 @@ import * as json from 'vs/base/common/json';
import * as pfs from 'vs/base/node/pfs'; import * as pfs from 'vs/base/node/pfs';
import URI from 'vs/base/common/uri'; import URI from 'vs/base/common/uri';
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
import { JSONObject } from 'sql/parts/notebook/models/jsonext';
import ContentManager = nb.ContentManager; import { JSONObject } from 'sql/parts/notebook/models/jsonext';
import { OutputTypes } from 'sql/parts/notebook/models/contracts'; import { OutputTypes } from 'sql/parts/notebook/models/contracts';
import { nbversion } from 'sql/parts/notebook/notebookConstants';
type MimeBundle = { [key: string]: string | string[] | undefined }; type MimeBundle = { [key: string]: string | string[] | undefined };
export class LocalContentManager implements ContentManager { export class LocalContentManager implements nb.ContentManager {
public async getNotebookContents(notebookUri: URI): Promise<nb.INotebookContents> { public async getNotebookContents(notebookUri: URI): Promise<nb.INotebookContents> {
if (!notebookUri) { if (!notebookUri) {
return undefined; return undefined;
@@ -71,6 +71,7 @@ namespace v4 {
return notebook; return notebook;
} }
function readCell(cell: nb.ICellContents): nb.ICellContents { function readCell(cell: nb.ICellContents): nb.ICellContents {
switch (cell.cell_type) { switch (cell.cell_type) {
case 'markdown': case 'markdown':
@@ -83,13 +84,14 @@ namespace v4 {
} }
} }
function createDefaultCell(cell: nb.ICellContents): nb.ICellContents { export function createDefaultCell(cell: nb.ICellContents): nb.ICellContents {
return { return {
cell_type: cell.cell_type, cell_type: cell.cell_type,
source: demultiline(cell.source), source: demultiline(cell.source),
metadata: cell.metadata metadata: cell.metadata
}; };
} }
function createCodeCell(cell: nb.ICellContents): nb.ICellContents { function createCodeCell(cell: nb.ICellContents): nb.ICellContents {
return { return {
cell_type: cell.cell_type, cell_type: cell.cell_type,
@@ -158,7 +160,7 @@ namespace v4 {
* *
* @returns The cleaned mime data. * @returns The cleaned mime data.
*/ */
function cleanMimeData(key: string, data: string | string[] | undefined) { export function cleanMimeData(key: string, data: string | string[] | undefined) {
// See https://github.com/jupyter/nbformat/blob/62d6eb8803616d198eaa2024604d1fe923f2a7b3/nbformat/v4/nbformat.v4.schema.json#L368 // See https://github.com/jupyter/nbformat/blob/62d6eb8803616d198eaa2024604d1fe923f2a7b3/nbformat/v4/nbformat.v4.schema.json#L368
if (isJSONKey(key)) { if (isJSONKey(key)) {
// Data stays as is for JSON types // Data stays as is for JSON types
@@ -172,7 +174,7 @@ namespace v4 {
throw new TypeError(localize('invalidMimeData', 'Data for {0} is expected to be a string or an Array of strings', key)); throw new TypeError(localize('invalidMimeData', 'Data for {0} is expected to be a string or an Array of strings', key));
} }
function demultiline(value: nb.MultilineString): string { export function demultiline(value: nb.MultilineString): string {
return Array.isArray(value) ? value.join('') : value; return Array.isArray(value) ? value.join('') : value;
} }
@@ -183,8 +185,182 @@ namespace v4 {
namespace v3 { namespace v3 {
export function readNotebook(contents: nb.INotebookContents): nb.INotebookContents { export function readNotebook(contents: Notebook): nb.INotebookContents {
// TODO will add v3 support in future update let notebook: nb.INotebookContents = {
throw new TypeError(localize('nbNotSupported', 'This notebook format is not supported')); cells: [],
metadata: contents.metadata,
// Note: upgrading to v4 as we're converting to our codebase
nbformat: 4,
nbformat_minor: nbversion.MINOR_VERSION
};
if (contents.worksheets) {
for (let worksheet of contents.worksheets) {
if (worksheet.cells) {
notebook.cells.push(...worksheet.cells.map(cell => createCell(cell)));
}
}
}
return notebook;
}
function createCell(cell: Cell): nb.ICellContents {
switch (cell.cell_type) {
case 'markdown':
case 'raw':
return v4.createDefaultCell(cell);
case 'code':
return createCodeCell(cell as CodeCell);
case 'heading':
return createHeadingCell(cell);
default:
throw new TypeError(`Cell type ${(cell as any).cell_type} unknown`);
}
}
function createMimeBundle(oldMimeBundle: MimeOutput): MimeBundle {
let mimeBundle: MimeBundle = {};
for (let key of Object.keys(oldMimeBundle)) {
// v3 had non-media types for rich media
if (key in VALID_MIMETYPES) {
let newKey = VALID_MIMETYPES[key as MimeTypeKey];
mimeBundle[newKey] = v4.cleanMimeData(newKey, oldMimeBundle[key]);
}
}
return mimeBundle;
}
const createOutput = (output: Output): nb.ICellOutput => {
switch (output.output_type) {
case 'pyout':
return <nb.IExecuteResult> {
output_type: OutputTypes.ExecuteResult,
execution_count: output.prompt_number,
data: createMimeBundle(output),
metadata: output.metadata
};
case 'display_data':
return <nb.IDisplayData> {
output_type: OutputTypes.DisplayData,
data: createMimeBundle(output),
metadata: output.metadata
};
case 'stream':
// Default to stdout in all cases unless it's stderr
const name = output.stream === 'stderr' ? 'stderr' : 'stdout';
return <nb.IStreamResult> {
output_type: OutputTypes.Stream,
name: name,
text: v4.demultiline(output.text)
};
case 'pyerr':
return <nb.IErrorResult> {
output_type: OutputTypes.Error,
ename: output.ename,
evalue: output.evalue,
traceback: output.traceback
};
default:
throw new TypeError(localize('unrecognizedOutputType', 'Output type {0} not recognized', output.output_type));
}
};
function createCodeCell(cell: CodeCell): nb.ICellContents {
return <nb.ICellContents> {
cell_type: cell.cell_type,
source: v4.demultiline(cell.input),
outputs: cell.outputs.map(createOutput),
execution_count: cell.prompt_number,
metadata: cell.metadata
};
}
function createHeadingCell(cell: HeadingCell): nb.ICellContents {
// v3 heading cells are just markdown cells in v4+
return <nb.ICellContents> {
cell_type: 'markdown',
source: Array.isArray(cell.source)
? v4.demultiline(
cell.source.map(line =>
Array(cell.level)
.join('#')
.concat(' ')
.concat(line)
)
)
: cell.source,
metadata: cell.metadata
};
}
const VALID_MIMETYPES = {
text: 'text/plain',
latex: 'text/latex',
png: 'image/png',
jpeg: 'image/jpeg',
svg: 'image/svg+xml',
html: 'text/html',
javascript: 'application/x-javascript',
json: 'application/javascript',
pdf: 'application/pdf'
};
type MimeTypeKey = keyof typeof VALID_MIMETYPES;
type MimePayload = { [P in MimeTypeKey]?: nb.MultilineString };
interface MimeOutput<T extends string = string> extends MimePayload {
output_type: T;
prompt_number?: number;
metadata: object;
}
export interface ExecuteResult extends MimeOutput<'pyout'> { }
export interface DisplayData extends MimeOutput<'display_data'> { }
export interface StreamOutput {
output_type: 'stream';
stream: string;
text: nb.MultilineString;
}
export interface ErrorOutput {
output_type: 'error' | 'pyerr';
ename: string;
evalue: string;
traceback: string[];
}
export type Output = ExecuteResult | DisplayData | StreamOutput | ErrorOutput;
export interface HeadingCell {
cell_type: 'heading';
metadata: JSONObject;
source: nb.MultilineString;
level: number;
}
export interface CodeCell {
cell_type: 'code';
language: string;
collapsed: boolean;
metadata: JSONObject;
input: nb.MultilineString;
prompt_number: number;
outputs: Array<Output>;
}
export type Cell = nb.ICellContents | HeadingCell | CodeCell;
export interface Worksheet {
cells: Cell[];
metadata: object;
}
export interface Notebook {
worksheets: Worksheet[];
metadata: nb.INotebookMetadata;
nbformat: 3;
nbformat_minor: number;
} }
} }