Skip to content

Commit ffd3c7f

Browse files
committed
more convert work
1 parent 86bfd0c commit ffd3c7f

File tree

3 files changed

+95
-46
lines changed

3 files changed

+95
-46
lines changed

src/command/convert/cmd.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export const convertCommand = new Command()
2626
"Convert between markdown and notebook representations of documents.",
2727
)
2828
.option(
29-
"-t, --to <format:string>",
29+
"-t, --to [format:string]",
3030
"Format to convert to (markdown or notebook)",
3131
)
3232
.option(
@@ -65,8 +65,10 @@ export const convertCommand = new Command()
6565
: kMarkdownFormat;
6666

6767
// determine and validate target format
68-
const targetFormat = options.to ||
69-
(srcFormat === kNotebookFormat ? kMarkdownFormat : kNotebookFormat);
68+
const targetFormat = options.to;
69+
if (!targetFormat) {
70+
throw new Error("Target format (--to) not specified");
71+
}
7072
if (![kNotebookFormat, kMarkdownFormat].includes(targetFormat)) {
7173
throw new Error("Invalid target format: " + targetFormat);
7274
}

src/command/convert/convert.ts

Lines changed: 65 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,27 @@
77

88
import { stringify } from "encoding/yaml.ts";
99

10+
import { ld } from "lodash/mod.ts";
11+
1012
import {
1113
partitionYamlFrontMatter,
1214
readYamlFromMarkdown,
1315
} from "../../core/yaml.ts";
1416
import {
1517
jupyterAutoIdentifier,
18+
JupyterCell,
19+
JupyterCellOptions,
1620
jupyterCellOptionsAsComment,
17-
JupyterCellWithOptions,
18-
jupyterCellWithOptions,
1921
jupyterFromFile,
2022
kCellId,
2123
kCellLabel,
2224
mdEnsureTrailingNewline,
2325
mdFromContentCell,
2426
mdFromRawCell,
27+
partitionJupyterCellOptions,
2528
quartoMdToJupyter,
2629
} from "../../core/jupyter/jupyter.ts";
27-
import { cellLabelValidator } from "../../core/jupyter/labels.ts";
30+
import { Metadata } from "../../config/metadata.ts";
2831

2932
export async function convertMarkdownToNotebook(
3033
file: string,
@@ -42,19 +45,10 @@ export function convertNotebookToMarkdown(file: string, includeIds: boolean) {
4245
// generate markdown
4346
const md: string[] = [];
4447

45-
// validate unique cell labels as we go
46-
const validateCellLabel = cellLabelValidator();
47-
4848
for (let i = 0; i < notebook.cells.length; i++) {
4949
{
50-
// convert cell yaml to cell metadata
51-
const cell = jupyterCellWithOptions(
52-
kernelspec.language,
53-
notebook.cells[i],
54-
);
55-
56-
// validate unique cell labels
57-
validateCellLabel(cell);
50+
// alias cell
51+
const cell = notebook.cells[i];
5852

5953
// write markdown
6054
switch (cell.cell_type) {
@@ -94,7 +88,7 @@ export function convertNotebookToMarkdown(file: string, includeIds: boolean) {
9488

9589
function mdFromCodeCell(
9690
language: string,
97-
cell: JupyterCellWithOptions,
91+
cell: JupyterCell,
9892
includeIds: boolean,
9993
) {
10094
// redact if the cell has no source
@@ -105,26 +99,70 @@ function mdFromCodeCell(
10599
// begin code cell
106100
const md: string[] = ["```{" + language + "}\n"];
107101

108-
// remove the id if requested or if it matches what would be auto-generated from the label
109-
if (cell.options[kCellId]) {
102+
// partition
103+
const { yaml, source } = partitionJupyterCellOptions(language, cell.source);
104+
const options = yaml ? yaml as JupyterCellOptions : {};
105+
console.log(options);
106+
107+
// handle id
108+
if (cell.id) {
110109
if (!includeIds) {
111-
delete cell.options[kCellId];
112-
} else if (cell.options[kCellLabel]) {
113-
const label = String(cell.options[kCellLabel]);
114-
if (jupyterAutoIdentifier(label) === cell.options[kCellId]) {
115-
delete cell.options[kCellId];
110+
cell.id = undefined;
111+
} else if (options[kCellLabel]) {
112+
const label = String(options[kCellLabel]);
113+
if (jupyterAutoIdentifier(label) === cell.id) {
114+
cell.id = undefined;
115+
}
116+
}
117+
}
118+
119+
// prepare the options for writing
120+
let yamlOptions: Metadata = {};
121+
if (cell.id) {
122+
yamlOptions[kCellId] = cell.id;
123+
}
124+
yamlOptions = {
125+
...cell.metadata,
126+
...yaml,
127+
...yamlOptions,
128+
};
129+
130+
// cell id first
131+
if (yamlOptions[kCellId]) {
132+
md.push(
133+
...jupyterCellOptionsAsComment(language, { id: yamlOptions[kCellId] }),
134+
);
135+
delete yamlOptions[kCellId];
136+
}
137+
138+
// yaml
139+
if (yaml) {
140+
const yamlOutput: Metadata = {};
141+
for (const key in yaml) {
142+
const value = yamlOptions[key];
143+
if (value !== undefined) {
144+
yamlOutput[key] = value;
145+
delete yamlOptions[key];
116146
}
117147
}
148+
md.push(...jupyterCellOptionsAsComment(language, yamlOutput));
118149
}
119150

120-
// write cell options
121-
if (Object.keys(cell.options).length > 0) {
122-
const yamlOptions = jupyterCellOptionsAsComment(language, cell.options);
123-
md.push(...yamlOptions);
151+
// metadata
152+
const metadataOutput: Metadata = {};
153+
for (const key in cell.metadata) {
154+
const value = cell.metadata[key];
155+
if (value !== undefined) {
156+
metadataOutput[key] = value;
157+
delete yamlOptions[key];
158+
}
124159
}
160+
md.push(
161+
...jupyterCellOptionsAsComment(language, metadataOutput, { flowLevel: 1 }),
162+
);
125163

126164
// write cell code
127-
md.push(...mdEnsureTrailingNewline(cell.source));
165+
md.push(...mdEnsureTrailingNewline(source));
128166

129167
// end code cell
130168
md.push("```\n");

src/core/jupyter/jupyter.ts

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { ensureDirSync } from "fs/ensure_dir.ts";
1111
import { dirname, extname, join, relative } from "path/mod.ts";
1212
import { walkSync } from "fs/walk.ts";
1313
import { decode as base64decode } from "encoding/base64.ts";
14-
import { stringify } from "encoding/yaml.ts";
14+
import { stringify, StringifyOptions } from "encoding/yaml.ts";
1515

1616
import { ld } from "lodash/mod.ts";
1717

@@ -689,26 +689,32 @@ export function jupyterCellWithOptions(
689689
export function jupyterCellOptionsAsComment(
690690
language: string,
691691
options: Record<string, unknown>,
692+
stringifyOptions?: StringifyOptions,
692693
) {
693-
const cellYaml = stringify(options, {
694-
indent: 2,
695-
sortKeys: false,
696-
skipInvalid: true,
697-
});
698-
const commentChars = langCommentChars(language);
699-
const yamlOutput = lines(cellYaml).map((line) => {
700-
line = optionCommentPrefix(commentChars[0]) + line +
701-
optionCommentSuffix(commentChars[1]);
702-
return line + "\n";
703-
}).concat([""]);
704-
return yamlOutput;
694+
if (Object.keys(options).length > 0) {
695+
const cellYaml = stringify(options, {
696+
indent: 2,
697+
sortKeys: false,
698+
skipInvalid: true,
699+
...stringifyOptions,
700+
});
701+
const commentChars = langCommentChars(language);
702+
const yamlOutput = mdTrimEmptyLines(lines(cellYaml)).map((line) => {
703+
line = optionCommentPrefix(commentChars[0]) + line +
704+
optionCommentSuffix(commentChars[1]);
705+
return line + "\n";
706+
});
707+
return yamlOutput;
708+
} else {
709+
return [];
710+
}
705711
}
706712

707-
export function mdFromContentCell(cell: JupyterCellWithOptions) {
713+
export function mdFromContentCell(cell: JupyterCell) {
708714
return mdEnsureTrailingNewline(cell.source);
709715
}
710716

711-
export function mdFromRawCell(cell: JupyterCellWithOptions) {
717+
export function mdFromRawCell(cell: JupyterCell) {
712718
const mimeType = cell.metadata?.[kRawMimeType];
713719
if (mimeType) {
714720
switch (mimeType) {
@@ -738,7 +744,10 @@ export function mdEnsureTrailingNewline(source: string[]) {
738744
}
739745
}
740746

741-
function partitionJupyterCellOptions(language: string, source: string[]) {
747+
export function partitionJupyterCellOptions(
748+
language: string,
749+
source: string[],
750+
) {
742751
const commentChars = langCommentChars(language);
743752
const optionPrefix = optionCommentPrefix(commentChars[0]);
744753
const optionSuffix = commentChars[1] || "";

0 commit comments

Comments
 (0)