Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/tools/render-all-formats.ts
3544 views
1
#!/usr/bin/env -S deno run --unstable
2
3
import * as fs from 'stdlib/fs';
4
import * as yaml from 'stdlib/yaml';
5
import * as path from 'stdlib/path';
6
7
const formatKeep: Record<string, string> = {
8
'pdf': 'tex',
9
'typst': 'typ',
10
};
11
12
const formatOutput: Record<string, string> = {
13
'pdf': 'pdf',
14
'html': 'html',
15
'typst': 'pdf',
16
'dashboard': 'html',
17
'docx': 'docx',
18
'pptx': 'pptx',
19
'docusaurus-md': 'mdx',
20
'revealjs': 'html',
21
};
22
23
async function extractMetadataFromFile(file: string): Promise<any> {
24
const fileContents = await Deno.readTextFile(file);
25
const lines = fileContents.split('\n');
26
let start: number | null = null;
27
let end: number | null = null;
28
29
for (let i = 0; i < lines.length; i++) {
30
const line = lines[i].trim();
31
if (line === '---') {
32
if (start === null) {
33
start = i;
34
} else {
35
end = i;
36
const metadata = yaml.parse(lines.slice(start + 1, end).join('\n'));
37
return metadata;
38
}
39
}
40
}
41
throw new Error(`No metadata found in file ${file}`);
42
}
43
44
async function renderAndMoveArtifacts(dryRun: boolean, outputRoot: string, qmdFile : string) {
45
if (!qmdFile.endsWith('.qmd')) {
46
console.log('expecting only .qmd files, skipping', qmdFile);
47
return 0;
48
}
49
50
console.log(qmdFile);
51
const qmdbase = path.basename(qmdFile).slice(0, -4);
52
let qmddir = path.dirname(qmdFile);
53
if(qmddir.includes('..') || qmdFile.startsWith('/')) {
54
console.warn("Warning: currently unable to replicate absolute or .. paths. Try running from the common root directory of the files if you get AlreadyExists errors.")
55
qmddir = '.'
56
}
57
const meta = await extractMetadataFromFile(qmdFile);
58
59
const mdformat = meta['format'];
60
if (!mdformat) {
61
console.log(`does not contain format, skipping`, qmdFile);
62
return 0;
63
}
64
const formats = typeof mdformat === 'string' ? [mdformat] : Object.keys(mdformat);
65
66
let nprocessed = 0;
67
for (const format of formats) {
68
const outext = formatOutput[format];
69
if (!outext) {
70
console.log(`unsupported format ${format}, skipping`, qmdFile);
71
continue;
72
}
73
const outdir = path.join(outputRoot, qmddir, qmdbase, format);
74
console.log(`mkdir -p ${outdir}`);
75
if (!dryRun && !await fs.exists(outdir)) {
76
await Deno.mkdir(outdir, { recursive: true });
77
}
78
const metadata: string[] = [];
79
const keepext = formatKeep[format];
80
if (keepext) {
81
metadata.push('-M', `keep-${keepext}:true`);
82
}
83
const qcmd = [
84
'render',
85
qmdFile,
86
'-t',
87
format,
88
'-M',
89
'keep-md:true',
90
...metadata
91
];
92
console.log('quarto', ...qcmd);
93
if (!dryRun) {
94
const cmd = new Deno.Command('quarto', {
95
args: qcmd
96
});
97
const output = await cmd.output();
98
if (!output.success) {
99
console.log(new TextDecoder().decode(output.stderr));
100
Deno.exit(1);
101
}
102
}
103
const movefiles = [
104
`${qmdbase}.${outext}`,
105
`${qmdbase}.${format}.md`
106
];
107
if (keepext) {
108
movefiles.push(`${qmdbase}.${keepext}`);
109
}
110
movefiles.push(`${qmdbase}_files`);
111
for (const movefile of movefiles) {
112
const src = path.join(qmddir, movefile);
113
const dest = path.join(outdir, movefile);
114
if(dryRun) {
115
console.log(`mv ${src} ${dest}`);
116
} else {
117
try {
118
await fs.move(src, dest);
119
console.log(`√ mv ${src} ${dest}`);
120
} catch (error) {
121
if(error instanceof Deno.errors.NotFound) {
122
console.log(`x mv ${src} ${dest}`);
123
}
124
else {
125
console.error(error);
126
Deno.exit(1);
127
}
128
}
129
}
130
}
131
nprocessed++;
132
}
133
return nprocessed;
134
}
135
136
if (import.meta.main) {
137
const startTime = performance.now();
138
const args = Deno.args;
139
if (args.includes('--help') || args.includes('-h') || args.filter(arg => !arg.startsWith('-')).length < 2) {
140
console.log('usage: render-all-formats.ts [--dryrun] output-root doc.qmd ...');
141
console.log(' creates output-root/doc/format/...');
142
console.log(' output-root should be empty');
143
Deno.exit(1);
144
}
145
146
let dryRun = false;
147
let argc = 0;
148
if (args[argc] === '--dryrun') {
149
dryRun = true;
150
argc += 1;
151
} else if (args[argc].startsWith('--')) {
152
console.log('unsupported option', args[argc]);
153
Deno.exit(1);
154
}
155
156
const outputRoot = args[argc];
157
const qmdFiles = args.slice(argc + 1);
158
159
const promises = qmdFiles.map(renderAndMoveArtifacts.bind(null, dryRun, outputRoot));
160
const counts = await Promise.all(promises);
161
const count = counts.reduce((a, b) => a+b, 0)
162
const endTime = performance.now();
163
const elapsed = endTime - startTime;
164
console.log(`Rendered ${count} documents in ${(elapsed/1000.).toFixed(2)} seconds`);
165
}
166
167