CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/project/jupyter/convert/index.ts
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
/*
7
Node.js interface to nbconvert.
8
*/
9
10
import { executeCode } from "@cocalc/backend/execute-code";
11
import ipynbToHtml, { htmlPath } from "./ipynb-to-html";
12
import htmlToPDF from "./html-to-pdf";
13
import { parseSource, parseTo } from "./util";
14
import { join } from "path";
15
import { getLogger } from "@cocalc/project/logger";
16
import { sanitize_nbconvert_path } from "@cocalc/util/sanitize-nbconvert";
17
import type { NbconvertParams } from "@cocalc/jupyter/types/nbconvert";
18
19
const log = getLogger("jupyter-nbconvert");
20
21
export async function nbconvert(opts: NbconvertParams): Promise<void> {
22
log.debug("start", opts);
23
try {
24
if (!opts.timeout) {
25
opts.timeout = 60;
26
}
27
28
let { j, to } = parseTo(opts.args);
29
30
if (to == "cocalc-html" || to == "cocalc-pdf") {
31
// We use our own internal cocalc conversion, since I'm tired of weird subtle issues
32
// with upstream nbconvert, and we can also be much faster.
33
const ipynb = join(opts.directory ?? "", parseSource(opts.args)); // make relative to home directory
34
const html = await ipynbToHtml(ipynb);
35
if (to == "cocalc-html") {
36
return;
37
}
38
if (to == "cocalc-pdf") {
39
await htmlToPDF(html, opts.timeout);
40
return;
41
}
42
throw Error("impossible");
43
}
44
45
let convertToPDF = false;
46
const originalSource = parseSource(opts.args); // before any mangling for the benefit of nbconvert.
47
if (to == "lab-pdf") {
48
for (let i = 0; i < opts.args.length; i++) {
49
if (opts.args[i] == "lab-pdf") {
50
opts.args[i] = "html";
51
break;
52
}
53
}
54
to = "html";
55
convertToPDF = true;
56
} else if (to == "classic-pdf") {
57
for (let i = 0; i < opts.args.length; i++) {
58
if (opts.args[i] == "classic-pdf") {
59
opts.args[i] = "html";
60
break;
61
}
62
}
63
to = "html";
64
convertToPDF = true;
65
// Put --template argument at beginning -- path must be at the end.
66
opts.args = ["--template", "classic"].concat(opts.args);
67
}
68
69
let command: string;
70
let args: string[];
71
if (to === "sagews") {
72
// support sagews converter, which is its own script, not in nbconvert.
73
// NOTE that if to is set, then j must be set.
74
command = "smc-ipynb2sagews";
75
args = opts.args.slice(0, j).concat(opts.args.slice(j + 3)); // j+3 cuts out --to and --.
76
} else {
77
command = "jupyter";
78
args = ["nbconvert"].concat(opts.args);
79
// This is the **one and only case** where we sanitize the input filename. Doing so when not calling
80
// nbconvert would actually break everything.
81
args[args.length - 1] = sanitize_nbconvert_path(args[args.length - 1]);
82
}
83
84
log.debug("running ", { command, args });
85
// Note about bash/ulimit_timeout below. This is critical since nbconvert
86
// could launch things like pdflatex that might run forever and without
87
// ulimit they do not get killed properly; this has happened in production!
88
const output = await executeCode({
89
command,
90
args,
91
path: opts.directory,
92
err_on_exit: false,
93
timeout: opts.timeout, // in seconds
94
ulimit_timeout: true,
95
bash: true,
96
});
97
if (output.exit_code != 0) {
98
throw Error(output.stderr);
99
}
100
101
if (convertToPDF) {
102
// Important to use *unmangled* source here!
103
await htmlToPDF(htmlPath(join(opts.directory ?? "", originalSource)));
104
}
105
} finally {
106
log.debug("finished");
107
}
108
}
109
110