UNPKG

@jupyterlab/rendermime

Version:
359 lines (327 loc) 9.1 kB
/* ----------------------------------------------------------------------------- | Copyright (c) Jupyter Development Team. | Distributed under the terms of the Modified BSD License. |----------------------------------------------------------------------------*/ import * as nbformat from '@jupyterlab/nbformat'; import { IObservableJSON, IObservableString, ObservableJSON, ObservableString } from '@jupyterlab/observables'; import { IRenderMime } from '@jupyterlab/rendermime-interfaces'; import { JSONExt, JSONObject, JSONValue, PartialJSONObject, PartialJSONValue, ReadonlyPartialJSONObject } from '@lumino/coreutils'; import { ISignal, Signal } from '@lumino/signaling'; import { MimeModel } from './mimemodel'; /** * The interface for an output model. */ export interface IOutputModel extends IRenderMime.IMimeModel { /** * A signal emitted when the output model changes. */ readonly changed: ISignal<this, void>; /** * The output type. */ readonly type: string; /** * The execution count of the model. */ readonly executionCount: nbformat.ExecutionCount; /** * The observable text, present when the output `type` is set to `"stream"`. */ readonly streamText?: IObservableString; /** * Whether the output is trusted. */ trusted: boolean; /** * Dispose of the resources used by the output model. */ dispose(): void; /** * Serialize the model to JSON. */ toJSON(): nbformat.IOutput; } /** * The namespace for IOutputModel sub-interfaces. */ export namespace IOutputModel { /** * The options used to create a notebook output model. */ export interface IOptions { /** * The raw output value. */ value: nbformat.IOutput; /** * Whether the output is trusted. The default is false. */ trusted?: boolean; } } /** * The default implementation of a notebook output model. */ export class OutputModel implements IOutputModel { /** * Construct a new output model. */ constructor(options: IOutputModel.IOptions) { const { data, metadata, trusted } = Private.getBundleOptions(options); this._rawData = data; if (options.value !== undefined && nbformat.isStream(options.value)) { this._text = new ObservableString( typeof options.value.text === 'string' ? options.value.text : options.value.text.join('') ); } this._metadata = new ObservableJSON({ values: metadata as JSONObject }); this._rawMetadata = metadata; this.trusted = trusted; // Make a copy of the data. const value = options.value; for (const key in value) { // Ignore data and metadata that were stripped. switch (key) { case 'data': case 'metadata': break; default: this._raw[key] = Private.extract(value, key); } } this.type = value.output_type; if (nbformat.isExecuteResult(value)) { this.executionCount = value.execution_count; } else { this.executionCount = null; } } /** * A signal emitted when the output model changes. */ get changed(): ISignal<this, void> { return this._changed; } /** * The output type. */ readonly type: string; /** * The execution count. */ readonly executionCount: nbformat.ExecutionCount; /** * Whether the model is trusted. */ readonly trusted: boolean; /** * Dispose of the resources used by the output model. */ dispose(): void { this._text?.dispose(); this._metadata.dispose(); Signal.clearData(this); } /** * The data associated with the model. */ get data(): ReadonlyPartialJSONObject { return Private.getData(this.toJSON()); } get streamText(): IObservableString | undefined { return this._text; } /** * The metadata associated with the model. */ get metadata(): ReadonlyPartialJSONObject { return this._rawMetadata; } /** * Set the data associated with the model. * * #### Notes * Depending on the implementation of the mime model, * this call may or may not have deferred effects, */ setData(options: IRenderMime.IMimeModel.ISetDataOptions): void { if (options.data) { this._rawData = options.data; } if (options.metadata) { this._updateObservable(this._metadata, options.metadata!); this._rawMetadata = options.metadata; } this._changed.emit(); } /** * Serialize the model to JSON. */ toJSON(): nbformat.IOutput { const output: PartialJSONValue = {}; for (const key in this._raw) { output[key] = Private.extract(this._raw, key); } if (this._text !== undefined) { output['text'] = this._text.text; } switch (this.type) { case 'display_data': case 'execute_result': case 'update_display_data': output['data'] = this._rawData as PartialJSONObject; output['metadata'] = this.metadata as PartialJSONObject; break; default: break; } // Remove transient data. delete output['transient']; return output as nbformat.IOutput; } /** * Update an observable JSON object using a readonly JSON object. */ private _updateObservable( observable: IObservableJSON, data: ReadonlyPartialJSONObject ) { const oldKeys = observable.keys(); const newKeys = Object.keys(data); // Handle removed keys. for (const key of oldKeys) { if (newKeys.indexOf(key) === -1) { observable.delete(key); } } // Handle changed data. for (const key of newKeys) { const oldValue = observable.get(key); const newValue = data[key]; if (oldValue !== newValue) { observable.set(key, newValue as JSONValue); } } } private _changed = new Signal<this, void>(this); private _raw: PartialJSONObject = {}; private _rawMetadata: ReadonlyPartialJSONObject; private _rawData: ReadonlyPartialJSONObject; private _text?: IObservableString = undefined; private _metadata: IObservableJSON; } /** * The namespace for OutputModel statics. */ export namespace OutputModel { /** * Get the data for an output. * * @param output - A kernel output message payload. * * @returns - The data for the payload. */ export function getData(output: nbformat.IOutput): PartialJSONObject { return Private.getData(output); } /** * Get the metadata from an output message. * * @param output - A kernel output message payload. * * @returns - The metadata for the payload. */ export function getMetadata(output: nbformat.IOutput): PartialJSONObject { return Private.getMetadata(output); } } /** * The namespace for module private data. */ namespace Private { /** * Get the data from a notebook output. */ export function getData(output: nbformat.IOutput): PartialJSONObject { let bundle: nbformat.IMimeBundle = {}; if ( nbformat.isExecuteResult(output) || nbformat.isDisplayData(output) || nbformat.isDisplayUpdate(output) ) { bundle = (output as nbformat.IExecuteResult).data; } else if (nbformat.isStream(output)) { if (output.name === 'stderr') { bundle['application/vnd.jupyter.stderr'] = output.text; } else { bundle['application/vnd.jupyter.stdout'] = output.text; } } else if (nbformat.isError(output)) { bundle['application/vnd.jupyter.error'] = output; const traceback = output.traceback.join('\n'); bundle['application/vnd.jupyter.stderr'] = traceback || `${output.ename}: ${output.evalue}`; } return convertBundle(bundle); } /** * Get the metadata from an output message. */ export function getMetadata(output: nbformat.IOutput): PartialJSONObject { const value: PartialJSONObject = Object.create(null); if (nbformat.isExecuteResult(output) || nbformat.isDisplayData(output)) { for (const key in output.metadata) { value[key] = extract(output.metadata, key); } } return value; } /** * Get the bundle options given output model options. */ export function getBundleOptions( options: IOutputModel.IOptions ): Required<Omit<MimeModel.IOptions, 'callback'>> { const data = getData(options.value); const metadata = getMetadata(options.value); const trusted = !!options.trusted; return { data, metadata, trusted }; } /** * Extract a value from a JSONObject. */ export function extract( value: ReadonlyPartialJSONObject, key: string ): PartialJSONValue | undefined { const item = value[key]; if (item === undefined || JSONExt.isPrimitive(item)) { return item; } return JSON.parse(JSON.stringify(item)); } /** * Convert a mime bundle to mime data. */ function convertBundle(bundle: nbformat.IMimeBundle): PartialJSONObject { const map: PartialJSONObject = Object.create(null); for (const mimeType in bundle) { map[mimeType] = extract(bundle, mimeType); } return map; } }