Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
ed9851c
feat:add outputs node
agoose77 Apr 1, 2025
00b8751
chore: add changeset
agoose77 Mar 7, 2025
f8c584f
fix: drop style change
agoose77 Mar 7, 2025
ee85d11
fix: only label first code and outputs
agoose77 Mar 7, 2025
2af0e14
fix: handle IDs
agoose77 Mar 7, 2025
aaf93af
fix: return type
agoose77 Mar 7, 2025
9b7d135
fix: selectAll → select
agoose77 Mar 7, 2025
910977a
refactor: move user expression lowering into function
agoose77 Apr 1, 2025
1772491
refactor: nest declaration
agoose77 Apr 1, 2025
d602880
fix: rename outputs
agoose77 Apr 1, 2025
827d287
refactor: simplify conditions
agoose77 Apr 1, 2025
102d918
test: fix tests
agoose77 Apr 1, 2025
15e0ead
fix: outputs for JATS
agoose77 Apr 2, 2025
5edaa47
fix: use outputs- for all prefixes
agoose77 Apr 2, 2025
7bd35c3
fix: outputs for JATS
agoose77 Apr 2, 2025
8ad105d
refactor: simplify test for code cells
agoose77 Apr 2, 2025
9dc505a
fix: early exit non-matplotlib
agoose77 Apr 2, 2025
df0a268
test: partially fix JATS test
agoose77 Apr 2, 2025
19ace17
fix: final JATS test
agoose77 Apr 2, 2025
ad0af31
test: fix myst-execute tests
agoose77 Apr 2, 2025
47cf042
test: fix test titles
agoose77 Apr 2, 2025
5664c04
test: fix missing children
agoose77 Apr 2, 2025
f8a97cb
test: more fixes
agoose77 Apr 2, 2025
db80c54
test: fix test titles
agoose77 Apr 2, 2025
88cc3e0
fix: handle visibility properly
agoose77 Apr 2, 2025
ad44fad
fix: update test cases
agoose77 Apr 2, 2025
421bd10
🪪 adding id to outputs enables in-browser compute for figures and embeds
stevejpurves Jun 25, 2025
901041c
feat: bump version
agoose77 Jul 30, 2025
7be7c0b
feat: add warning for unbounded migration
agoose77 Jul 30, 2025
9493999
test: bump version in test
agoose77 Jul 30, 2025
f1a7a44
test: bump version
agoose77 Jul 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/ten-bats-warn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"mystmd": minor
"myst-directives": patch
"myst-transforms": patch
"myst-spec-ext": patch
"myst-execute": patch
"myst-cli": patch
---

Add support for new Outputs node
44 changes: 24 additions & 20 deletions packages/myst-cli/src/process/notebook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,21 @@ export async function processNotebook(
return mdast;
}

/**
* Embed the Jupyter output data for a user expression into the AST
*/
function embedInlineExpressions(
userExpressions: IUserExpressionMetadata[] | undefined,
block: GenericNode,
) {
const inlineNodes = selectAll('inlineExpression', block) as InlineExpression[];
inlineNodes.forEach((inlineExpression) => {
const data = findExpression(userExpressions, inlineExpression.value);
if (!data) return;
inlineExpression.result = data.result as unknown as Record<string, unknown>;
});
}

export async function processNotebookFull(
session: ISession,
file: string,
Expand Down Expand Up @@ -136,17 +151,7 @@ export async function processNotebookFull(
return acc.concat(...cellMdast.children);
}
const block = blockParent(cell, cellMdast.children) as GenericNode;

// Embed expression results into expression
const userExpressions = block.data?.[metadataSection] as
| IUserExpressionMetadata[]
| undefined;
const inlineNodes = selectAll('inlineExpression', block) as InlineExpression[];
inlineNodes.forEach((inlineExpression) => {
const data = findExpression(userExpressions, inlineExpression.value);
if (!data) return;
inlineExpression.result = data.result as unknown as Record<string, unknown>;
});
embedInlineExpressions(block.data?.[metadataSection], block);
return acc.concat(block);
}
if (cell.cell_type === CELL_TYPES.raw) {
Expand All @@ -165,17 +170,16 @@ export async function processNotebookFull(
value: ensureString(cell.source),
};

// Embed outputs in an output block
const output: { type: 'output'; id: string; data: IOutput[] } = {
type: 'output',
const outputs = {
type: 'outputs',
id: nanoid(),
data: [],
children: (cell.outputs as IOutput[]).map((output) => ({
type: 'output',
jupyter_data: output,
children: [],
})),
};

if (cell.outputs && (cell.outputs as IOutput[]).length > 0) {
output.data = cell.outputs as IOutput[];
}
return acc.concat(blockParent(cell, [code, output]));
return acc.concat(blockParent(cell, [code, outputs]));
}
return acc;
},
Expand Down
2 changes: 1 addition & 1 deletion packages/myst-cli/src/spec-version.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const SPEC_VERSION = 2;
export const SPEC_VERSION = 3;
36 changes: 21 additions & 15 deletions packages/myst-cli/src/transforms/code.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,10 @@ function build_mdast(tags: string[], has_output: boolean) {
],
};
if (has_output) {
mdast.children[0].children.push({ type: 'output' });
mdast.children[0].children.push({
type: 'outputs',
children: [{ type: 'output', children: [] }],
});
}
return mdast;
}
Expand Down Expand Up @@ -261,7 +264,7 @@ describe('propagateBlockDataToCode', () => {
const mdast = build_mdast([tag], has_output);
propagateBlockDataToCode(new Session(), new VFile(), mdast);
let result = '';
const outputNode = mdast.children[0].children[1];
const outputsNode = mdast.children[0].children[1];
switch (target) {
case 'cell':
result = mdast.children[0].visibility;
Expand All @@ -270,12 +273,14 @@ describe('propagateBlockDataToCode', () => {
result = mdast.children[0].children[0].visibility;
break;
case 'output':
if (!has_output && target == 'output') {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

target is known to be output here.

expect(outputNode).toEqual(undefined);
if (!has_output) {
expect(outputsNode).toEqual(undefined);
continue;
}
result = outputNode.visibility;
result = outputsNode.visibility;
break;
default:
throw new Error();
}
expect(result).toEqual(action);
}
Expand All @@ -290,18 +295,18 @@ describe('propagateBlockDataToCode', () => {
propagateBlockDataToCode(new Session(), new VFile(), mdast);
const blockNode = mdast.children[0];
const codeNode = mdast.children[0].children[0];
const outputNode = mdast.children[0].children[1];
const outputsNode = mdast.children[0].children[1];
expect(blockNode.visibility).toEqual(action);
expect(codeNode.visibility).toEqual(action);
if (has_output) {
expect(outputNode.visibility).toEqual(action);
expect(outputsNode.visibility).toEqual(action);
} else {
expect(outputNode).toEqual(undefined);
expect(outputsNode).toEqual(undefined);
}
}
}
});
it('placeholder creates image node child of output', async () => {
it('placeholder creates image node child of outputs', async () => {
const mdast: any = {
type: 'root',
children: [
Expand All @@ -313,7 +318,8 @@ describe('propagateBlockDataToCode', () => {
executable: true,
},
{
type: 'output',
type: 'outputs',
children: [],
},
],
data: {
Expand All @@ -323,12 +329,12 @@ describe('propagateBlockDataToCode', () => {
],
};
propagateBlockDataToCode(new Session(), new VFile(), mdast);
const outputNode = mdast.children[0].children[1];
expect(outputNode.children?.length).toEqual(1);
expect(outputNode.children[0].type).toEqual('image');
expect(outputNode.children[0].placeholder).toBeTruthy();
const outputsNode = mdast.children[0].children[1];
expect(outputsNode.children?.length).toEqual(1);
expect(outputsNode.children[0].type).toEqual('image');
expect(outputsNode.children[0].placeholder).toBeTruthy();
});
it('placeholder passes with no output', async () => {
it('placeholder passes with no outputs', async () => {
const mdast: any = {
type: 'root',
children: [
Expand Down
17 changes: 8 additions & 9 deletions packages/myst-cli/src/transforms/code.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { GenericNode, GenericParent } from 'myst-common';
import { NotebookCellTags, RuleId, fileError, fileWarn } from 'myst-common';
import type { Image, Output } from 'myst-spec-ext';
import type { Image, Outputs } from 'myst-spec-ext';
import { select, selectAll } from 'unist-util-select';
import yaml from 'js-yaml';
import type { VFile } from 'vfile';
Expand Down Expand Up @@ -156,10 +156,9 @@ export function propagateBlockDataToCode(session: ISession, vfile: VFile, mdast:
const blocks = selectAll('block', mdast) as GenericNode[];
blocks.forEach((block) => {
if (!block.data) return;
const outputNode = select('output', block) as Output | null;
if (block.data.placeholder && outputNode) {
if (!outputNode.children) outputNode.children = [];
outputNode.children.push({
const outputsNode = select('outputs', block) as Outputs | null;
if (block.data.placeholder && outputsNode) {
outputsNode.children.push({
type: 'image',
placeholder: true,
url: block.data.placeholder as string,
Expand Down Expand Up @@ -195,18 +194,18 @@ export function propagateBlockDataToCode(session: ISession, vfile: VFile, mdast:
if (codeNode) codeNode.visibility = 'remove';
break;
case NotebookCellTags.hideOutput:
if (outputNode) outputNode.visibility = 'hide';
if (outputsNode) outputsNode.visibility = 'hide';
break;
case NotebookCellTags.removeOutput:
if (outputNode) outputNode.visibility = 'remove';
if (outputsNode) outputsNode.visibility = 'remove';
break;
default:
session.log.debug(`tag '${tag}' is not valid in code-cell tags'`);
}
});
if (!block.visibility) block.visibility = 'show';
if (codeNode && !codeNode.visibility) codeNode.visibility = 'show';
if (outputNode && !outputNode.visibility) outputNode.visibility = 'show';
if (outputsNode && !outputsNode.visibility) outputsNode.visibility = 'show';
});
}

Expand All @@ -233,7 +232,7 @@ export function transformLiftCodeBlocksInJupytext(mdast: GenericParent) {
child.type === 'block' &&
child.children?.length === 2 &&
child.children?.[0].type === 'code' &&
child.children?.[1].type === 'output'
child.children?.[1].type === 'outputs'
) {
newBlocks.push(child as GenericParent);
newBlocks.push({ type: 'block', children: [] });
Expand Down
110 changes: 86 additions & 24 deletions packages/myst-cli/src/transforms/outputs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,40 @@ describe('reduceOutputs', () => {
],
},
{
type: 'output',
id: 'abc123',
data: [],
type: 'outputs',
children: [
{
type: 'output',
id: 'abc123',
jupyter_data: null,
children: [],
},
],
},
],
},
],
};
expect(mdast.children[0].children.length).toEqual(2);
reduceOutputs(new Session(), mdast, 'notebook.ipynb', '/my/folder');
expect(mdast.children[0].children.length).toEqual(1);
expect(mdast).toEqual({
type: 'root',
children: [
{
type: 'block',
children: [
{
type: 'paragraph',
children: [
{
type: 'text',
value: 'hi',
},
],
},
],
},
],
});
});
it('output with complex data is removed', async () => {
const mdast = {
Expand All @@ -49,18 +72,22 @@ describe('reduceOutputs', () => {
],
},
{
type: 'output',
type: 'outputs',
id: 'abc123',
data: [
children: [
{
output_type: 'display_data',
execution_count: 3,
metadata: {},
data: {
'application/octet-stream': {
content_type: 'application/octet-stream',
hash: 'def456',
path: '/my/path/def456.png',
type: 'output',
children: [],
jupyter_data: {
output_type: 'display_data',
execution_count: 3,
metadata: {},
data: {
'application/octet-stream': {
content_type: 'application/octet-stream',
hash: 'def456',
path: '/my/path/def456.png',
},
},
},
},
Expand All @@ -72,9 +99,27 @@ describe('reduceOutputs', () => {
};
expect(mdast.children[0].children.length).toEqual(2);
reduceOutputs(new Session(), mdast, 'notebook.ipynb', '/my/folder');
expect(mdast.children[0].children.length).toEqual(1);
expect(mdast).toEqual({
type: 'root',
children: [
{
type: 'block',
children: [
{
type: 'paragraph',
children: [
{
type: 'text',
value: 'hi',
},
],
},
],
},
],
});
});
it('output is replaced with placeholder image', async () => {
it('outputs is replaced with placeholder image', async () => {
const mdast = {
type: 'root',
children: [
Expand All @@ -91,9 +136,8 @@ describe('reduceOutputs', () => {
],
},
{
type: 'output',
type: 'outputs',
id: 'abc123',
data: [],
children: [
{
type: 'image',
Expand All @@ -108,11 +152,29 @@ describe('reduceOutputs', () => {
};
expect(mdast.children[0].children.length).toEqual(2);
reduceOutputs(new Session(), mdast, 'notebook.ipynb', '/my/folder');
expect(mdast.children[0].children.length).toEqual(2);
expect(mdast.children[0].children[1]).toEqual({
type: 'image',
placeholder: true,
url: 'placeholder.png',
expect(mdast).toEqual({
type: 'root',
children: [
{
type: 'block',
children: [
{
type: 'paragraph',
children: [
{
type: 'text',
value: 'hi',
},
],
},
{
type: 'image',
placeholder: true,
url: 'placeholder.png',
},
],
},
],
});
});
// // These tests now require file IO...
Expand Down
Loading
Loading