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 URI from 'vs/base/common/uri';
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 { nbversion } from 'sql/parts/notebook/notebookConstants';
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> {
if (!notebookUri) {
return undefined;
@@ -71,6 +71,7 @@ namespace v4 {
return notebook;
}
function readCell(cell: nb.ICellContents): nb.ICellContents {
switch (cell.cell_type) {
case 'markdown':
@@ -79,17 +80,18 @@ namespace v4 {
case 'code':
return createCodeCell(cell);
default:
throw new TypeError(localize('unknownCellType', 'Cell type {0} unknown', cell.cell_type));
}
throw new TypeError(localize('unknownCellType', 'Cell type {0} unknown', cell.cell_type));
}
}
function createDefaultCell(cell: nb.ICellContents): nb.ICellContents {
export function createDefaultCell(cell: nb.ICellContents): nb.ICellContents {
return {
cell_type: cell.cell_type,
source: demultiline(cell.source),
metadata: cell.metadata
};
}
function createCodeCell(cell: nb.ICellContents): nb.ICellContents {
return {
cell_type: cell.cell_type,
@@ -107,7 +109,7 @@ namespace v4 {
function createOutput(output: nb.Output): nb.ICellOutput {
switch (output.output_type) {
case OutputTypes.ExecuteResult:
return <nb.IExecuteResult> {
return <nb.IExecuteResult>{
output_type: output.output_type,
execution_count: output.execution_count,
data: createMimeBundle(output.data),
@@ -115,19 +117,19 @@ namespace v4 {
};
case OutputTypes.DisplayData:
case OutputTypes.UpdateDisplayData:
return <nb.IDisplayResult> {
return <nb.IDisplayResult>{
output_type: output.output_type,
data: createMimeBundle(output.data),
metadata: output.metadata
};
case 'stream':
return <nb.IStreamResult> {
return <nb.IStreamResult>{
output_type: output.output_type,
name: output.name,
text: demultiline(output.text)
};
case 'error':
return <nb.IErrorResult> {
return <nb.IErrorResult>{
output_type: 'error',
ename: output.ename,
evalue: output.evalue,
@@ -138,7 +140,7 @@ namespace v4 {
default:
// Should never get here
throw new TypeError(localize('unrecognizedOutput', 'Output type {0} not recognized', (<any>output).output_type));
}
}
}
function createMimeBundle(oldMimeBundle: MimeBundle): MimeBundle {
@@ -158,7 +160,7 @@ namespace v4 {
*
* @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
if (isJSONKey(key)) {
// 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));
}
function demultiline(value: nb.MultilineString): string {
export function demultiline(value: nb.MultilineString): string {
return Array.isArray(value) ? value.join('') : value;
}
@@ -183,8 +185,182 @@ namespace v4 {
namespace v3 {
export function readNotebook(contents: nb.INotebookContents): nb.INotebookContents {
// TODO will add v3 support in future update
throw new TypeError(localize('nbNotSupported', 'This notebook format is not supported'));
export function readNotebook(contents: Notebook): nb.INotebookContents {
let notebook: nb.INotebookContents = {
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;
}
}