-
Notifications
You must be signed in to change notification settings - Fork 331
/
percent.ts
133 lines (121 loc) · 4.02 KB
/
percent.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/*
* percent.ts
*
* Copyright (C) 2020-202 Posit Software, PBC
*/
import { extname } from "../../deno_ral/path.ts";
import { lines } from "../../core/text.ts";
import { trimEmptyLines } from "../../core/lib/text.ts";
import { Metadata } from "../../config/types.ts";
import { asYamlText } from "../../core/jupyter/jupyter-fixups.ts";
import { pandocAttrKeyvalueFromText } from "../../core/pandoc/pandoc-attr.ts";
import { kCellRawMimeType } from "../../config/constants.ts";
import { mdFormatOutput, mdRawOutput } from "../../core/jupyter/jupyter.ts";
export const kJupyterPercentScriptExtensions = [
".py",
".jl",
".r",
];
export function isJupyterPercentScript(file: string) {
const ext = extname(file).toLowerCase();
if (kJupyterPercentScriptExtensions.includes(ext)) {
const text = Deno.readTextFileSync(file);
return !!text.match(/^\s*#\s*%%+\s+\[markdown|raw\]/);
} else {
return false;
}
}
export function markdownFromJupyterPercentScript(file: string) {
// determine language/kernel
const ext = extname(file).toLowerCase();
const language = ext === ".jl" ? "julia" : ext === ".r" ? "r" : "python";
// break into cells
const cells: PercentCell[] = [];
const activeCell = () => cells[cells.length - 1];
for (const line of lines(Deno.readTextFileSync(file).trim())) {
const header = percentCellHeader(line);
if (header) {
cells.push({ header, lines: [] });
} else {
activeCell()?.lines.push(line);
}
}
// resolve markdown and raw cells
const isTripleQuote = (line: string) => !!line.match(/^"{3,}\s*$/);
const asCell = (lines: string[]) => lines.join("\n") + "\n\n";
const stripPrefix = (line: string) => line.replace(/^#\s?/, "");
const cellContent = (cellLines: string[]) => {
if (
cellLines.length > 2 && isTripleQuote(cellLines[0]) &&
isTripleQuote(cellLines[cellLines.length - 1])
) {
return asCell(cellLines.slice(1, cellLines.length - 1));
} else {
// commented
return asCell(cellLines.map(stripPrefix));
}
};
return cells.reduce((markdown, cell) => {
const cellLines = trimEmptyLines(cell.lines);
if (cell.header.type === "code") {
if (cell.header.metadata) {
const yamlText = asYamlText(cell.header.metadata);
cellLines.unshift(...lines(yamlText).map((line) => `#| ${line}`));
}
markdown += asCell(["```{" + language + "}", ...cellLines, "```"]);
} else if (cell.header.type === "markdown") {
markdown += cellContent(cellLines);
} else if (cell.header.type == "raw") {
let rawContent = cellContent(cellLines);
const format = cell.header?.metadata?.["format"];
const mimeType = cell.header.metadata?.[kCellRawMimeType];
if (typeof (mimeType) === "string") {
const rawBlock = mdRawOutput(mimeType, lines(rawContent));
rawContent = rawBlock || rawContent;
} else if (typeof (format) === "string") {
rawContent = mdFormatOutput(format, lines(rawContent));
}
markdown += rawContent;
}
return markdown;
}, "");
}
interface PercentCell {
header: PercentCellHeader;
lines: string[];
}
interface PercentCellHeader {
type: "code" | "raw" | "markdown";
metadata?: Metadata;
}
function percentCellHeader(line: string): PercentCellHeader | undefined {
const match = line.match(
/^\s*#\s*%%+\s*(?:\[(markdown|raw)\])?\s*(.*)?$/,
);
if (match) {
const type = match[1] || "code";
const attribs = match[2] || "";
if (["code", "raw", "markdown"].includes(type)) {
return {
type,
metadata: parsePercentAttribs(attribs),
} as PercentCellHeader;
} else {
throw new Error(`Invalid cell type: ${type}`);
}
}
}
function parsePercentAttribs(
attribs: string,
): Metadata | undefined {
// skip over title
const match = attribs.match(/[\w\-]+=.*$/);
if (match) {
const keyValue = pandocAttrKeyvalueFromText(match[0], " ");
const metadata: Metadata = {};
keyValue.forEach((value) => {
metadata[value[0]] = value[1];
});
return metadata;
}
}