Skip to content

Commit 909449e

Browse files
committed
Improved jupyter assets cleanup
1 parent 3ec1d8e commit 909449e

File tree

4 files changed

+110
-78
lines changed

4 files changed

+110
-78
lines changed

src/command/render/render.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
import { inputFilesDir } from "../../core/render.ts";
2121
import { pathWithForwardSlashes } from "../../core/path.ts";
2222

23-
import { Format, FormatPandoc } from "../../config/types.ts";
23+
import { FormatPandoc } from "../../config/types.ts";
2424
import {
2525
executionEngine,
2626
executionEngineKeepMd,
@@ -123,10 +123,10 @@ export async function renderPandoc(
123123

124124
// Map notebook includes to pandoc includes
125125
const pandocIncludes: PandocIncludes = {
126-
[kIncludeAfterBody]: notebookResult && notebookResult.includes?.afterBody
126+
[kIncludeAfterBody]: notebookResult.includes?.afterBody
127127
? [notebookResult.includes?.afterBody]
128128
: undefined,
129-
[kIncludeInHeader]: notebookResult && notebookResult.includes?.inHeader
129+
[kIncludeInHeader]: notebookResult.includes?.inHeader
130130
? [notebookResult.includes?.inHeader]
131131
: undefined,
132132
};
@@ -139,7 +139,9 @@ export async function renderPandoc(
139139

140140
// pandoc options
141141
const pandocOptions: PandocOptions = {
142-
markdown: notebookResult ? notebookResult.markdown : executeResult.markdown,
142+
markdown: notebookResult.markdown
143+
? notebookResult.markdown
144+
: executeResult.markdown,
143145
source: context.target.source,
144146
output: recipe.output,
145147
keepYaml: recipe.keepYaml,
@@ -282,6 +284,11 @@ export async function renderPandoc(
282284
supporting.push(...htmlPostProcessResult.supporting);
283285
}
284286

287+
if (notebookResult.supporting) {
288+
supporting = supporting || [];
289+
supporting.push(...notebookResult.supporting);
290+
}
291+
285292
withTiming("render-cleanup", () =>
286293
renderCleanup(
287294
context.target.input,

src/core/jupyter/jupyter-embed.ts

Lines changed: 72 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { resourcePath } from "../resources.ts";
99
import { getNamedLifetime, ObjectWithLifetime } from "../lifetimes.ts";
1010

1111
import {
12+
cleanEmptyJupyterAssets,
1213
jupyterAssets,
1314
jupyterFromFile,
1415
jupyterToMarkdown,
@@ -44,7 +45,7 @@ import {
4445
import { globalTempContext } from "../temp.ts";
4546
import { isAbsolute } from "path/mod.ts";
4647
import { partitionMarkdown } from "../pandoc/pandoc-partition.ts";
47-
import { safeExistsSync } from "../path.ts";
48+
import { removeIfEmptyDir, safeExistsSync } from "../path.ts";
4849
import { basename } from "path/mod.ts";
4950

5051
export interface JupyterNotebookAddress {
@@ -148,82 +149,82 @@ export async function replaceNotebookPlaceholders(
148149
flags: RenderFlags,
149150
markdown: string,
150151
) {
152+
// Assets
153+
const assets = jupyterAssets(
154+
input,
155+
to,
156+
);
151157
let match = kPlaceholderRegex.exec(markdown);
152-
if (match) {
153-
// Assets
154-
const assets = jupyterAssets(
155-
input,
156-
to,
157-
);
158-
159-
let includes;
160-
while (match) {
161-
// Parse the address and if this is a notebook
162-
// then proceed with the replacement
163-
const nbAddressStr = match[1];
164-
const nbAddress = parseNotebookAddress(nbAddressStr);
165-
if (nbAddress) {
166-
// If a list of outputs are provided, resolve that range
167-
const outputsStr = match[2];
168-
const nbOutputs = outputsStr ? resolveRange(outputsStr) : undefined;
169-
170-
// If cell options are provided, resolve those
171-
const placeholderStr = match[3];
172-
const nbOptions = placeholderStr
173-
? placeholderToOptions(placeholderStr)
174-
: {};
175-
176-
// Compute appropriate includes based upon the note
177-
// dependendencies
178-
const notebookIncludes = () => {
179-
const nbPath = resolveNbPath(input, nbAddress.path);
180-
if (safeExistsSync(nbPath)) {
181-
const notebook = jupyterFromFile(nbPath);
182-
const dependencies = isHtmlOutput(context.format.pandoc)
183-
? extractJupyterWidgetDependencies(notebook)
184-
: undefined;
185-
if (dependencies) {
186-
const tempDir = globalTempContext().createDir();
187-
return includesForJupyterWidgetDependencies(
188-
[dependencies],
189-
tempDir,
190-
);
191-
} else {
192-
return undefined;
193-
}
194-
} else {
195-
const notebookName = basename(nbPath);
196-
throw new Error(
197-
`Unable to embed content from notebook '${notebookName}'\nThe file ${nbPath} doesn't exist or cannot be read.`,
158+
let includes;
159+
while (match) {
160+
// Parse the address and if this is a notebook
161+
// then proceed with the replacement
162+
const nbAddressStr = match[1];
163+
const nbAddress = parseNotebookAddress(nbAddressStr);
164+
if (nbAddress) {
165+
// If a list of outputs are provided, resolve that range
166+
const outputsStr = match[2];
167+
const nbOutputs = outputsStr ? resolveRange(outputsStr) : undefined;
168+
169+
// If cell options are provided, resolve those
170+
const placeholderStr = match[3];
171+
const nbOptions = placeholderStr
172+
? placeholderToOptions(placeholderStr)
173+
: {};
174+
175+
// Compute appropriate includes based upon the note
176+
// dependendencies
177+
const notebookIncludes = () => {
178+
const nbPath = resolveNbPath(input, nbAddress.path);
179+
if (safeExistsSync(nbPath)) {
180+
const notebook = jupyterFromFile(nbPath);
181+
const dependencies = isHtmlOutput(context.format.pandoc)
182+
? extractJupyterWidgetDependencies(notebook)
183+
: undefined;
184+
if (dependencies) {
185+
const tempDir = globalTempContext().createDir();
186+
return includesForJupyterWidgetDependencies(
187+
[dependencies],
188+
tempDir,
198189
);
190+
} else {
191+
return undefined;
199192
}
200-
};
201-
includes = notebookIncludes();
202-
203-
// Render the notebook markdown
204-
const nbMarkdown = await notebookMarkdown(
205-
nbAddress,
206-
assets,
207-
context,
208-
flags,
209-
nbOptions,
210-
nbOutputs,
211-
);
193+
} else {
194+
const notebookName = basename(nbPath);
195+
throw new Error(
196+
`Unable to embed content from notebook '${notebookName}'\nThe file ${nbPath} doesn't exist or cannot be read.`,
197+
);
198+
}
199+
};
200+
includes = notebookIncludes();
212201

213-
// Replace the placeholders with the rendered markdown
214-
markdown = markdown.replaceAll(match[0], nbMarkdown);
215-
}
216-
match = kPlaceholderRegex.exec(markdown);
202+
// Render the notebook markdown
203+
const nbMarkdown = await notebookMarkdown(
204+
nbAddress,
205+
assets,
206+
context,
207+
flags,
208+
nbOptions,
209+
nbOutputs,
210+
);
211+
212+
// Replace the placeholders with the rendered markdown
213+
markdown = markdown.replaceAll(match[0], nbMarkdown);
217214
}
218-
kPlaceholderRegex.lastIndex = 0;
219-
return {
220-
includes,
221-
markdown,
222-
supporting: [join(assets.base_dir, assets.supporting_dir)],
223-
};
224-
} else {
225-
return undefined;
215+
match = kPlaceholderRegex.exec(markdown);
226216
}
217+
kPlaceholderRegex.lastIndex = 0;
218+
const cleaned = cleanEmptyJupyterAssets(assets);
219+
const supporting = cleaned
220+
? []
221+
: [join(assets.base_dir, assets.supporting_dir)];
222+
223+
return {
224+
includes,
225+
markdown,
226+
supporting: supporting,
227+
};
227228
}
228229

229230
function resolveNbPath(input: string, path: string) {

src/core/jupyter/jupyter.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ import { figuresDir, inputFilesDir } from "../render.ts";
145145
import { lines } from "../text.ts";
146146
import { readYamlFromMarkdown } from "../yaml.ts";
147147
import { languagesInMarkdown } from "../../execute/engine-shared.ts";
148-
import { pathWithForwardSlashes } from "../path.ts";
148+
import { pathWithForwardSlashes, removeIfEmptyDir } from "../path.ts";
149149
import { convertToHtmlSpans, hasAnsiEscapeCodes } from "../ansi-colors.ts";
150150
import { ProjectContext } from "../../project/types.ts";
151151
import { mergeConfigs } from "../config.ts";
@@ -572,7 +572,17 @@ export function jupyterAutoIdentifier(label: string) {
572572
}
573573
}
574574

575-
export function jupyterAssets(input: string, to?: string) {
575+
export interface JupyterNotebookAssetPaths {
576+
base_dir: string;
577+
files_dir: string;
578+
figures_dir: string;
579+
supporting_dir: string;
580+
}
581+
582+
export function jupyterAssets(
583+
input: string,
584+
to?: string,
585+
): JupyterNotebookAssetPaths {
576586
// calculate and create directories
577587
input = Deno.realPathSync(input);
578588
const files_dir = join(dirname(input), inputFilesDir(input));
@@ -602,6 +612,16 @@ export function jupyterAssets(input: string, to?: string) {
602612
};
603613
}
604614

615+
export function cleanEmptyJupyterAssets(assets: JupyterNotebookAssetPaths) {
616+
const figuresRemoved = removeIfEmptyDir(
617+
join(assets.base_dir, assets.figures_dir),
618+
);
619+
const filesRemoved = removeIfEmptyDir(
620+
join(assets.base_dir, assets.files_dir),
621+
);
622+
return figuresRemoved && filesRemoved;
623+
}
624+
605625
// Attach fully rendered notebook to render services
606626
// Render notebook only once per document
607627
// Return cells with markdown instead of complete markdown

src/core/path.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export function safeRemoveIfExists(file: string) {
4040
}
4141
}
4242

43-
export function removeIfEmptyDir(dir: string) {
43+
export function removeIfEmptyDir(dir: string): boolean {
4444
if (existsSync(dir)) {
4545
let empty = true;
4646
for (const _entry of Deno.readDirSync(dir)) {
@@ -49,7 +49,11 @@ export function removeIfEmptyDir(dir: string) {
4949
}
5050
if (empty) {
5151
Deno.removeSync(dir, { recursive: true });
52+
return true;
5253
}
54+
return false;
55+
} else {
56+
return false;
5357
}
5458
}
5559

0 commit comments

Comments
 (0)