Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/src/command/render/flags.ts
3584 views
1
/*
2
* flags.ts
3
*
4
* Copyright (C) 2020-2022 Posit Software, PBC
5
*/
6
import { existsSync } from "../../deno_ral/fs.ts";
7
8
import { readYaml, readYamlFromString } from "../../core/yaml.ts";
9
10
import { mergeConfigs } from "../../core/config.ts";
11
12
import {
13
kAuthor,
14
kDate,
15
kEmbedResources,
16
kListings,
17
kNumberOffset,
18
kNumberSections,
19
kReferenceLocation,
20
kSelfContained,
21
kShiftHeadingLevelBy,
22
kStandalone,
23
kTableOfContents,
24
kToc,
25
kTopLevelDivision,
26
} from "../../config/constants.ts";
27
import { isQuartoMetadata } from "../../config/metadata.ts";
28
import { RenderFlags, RenderOptions } from "./types.ts";
29
30
import { isAbsolute, SEP_PATTERN } from "../../deno_ral/path.ts";
31
import { normalizePath } from "../../core/path.ts";
32
import { removeFlags } from "../../core/flags.ts";
33
34
export const kStdOut = "-";
35
36
export async function parseRenderFlags(args: string[]) {
37
const flags: RenderFlags = {};
38
39
const argsStack = [...args];
40
let arg = argsStack.shift();
41
while (arg !== undefined) {
42
// we need to handle equals signs here,
43
// because of the way pandoc handles optional arguments
44
// see #7868 and #7908.
45
const equalSignIndex = arg.indexOf("=");
46
if (arg.startsWith("--") && equalSignIndex > 0) {
47
argsStack.unshift(arg.slice(equalSignIndex + 1));
48
arg = arg.slice(0, equalSignIndex);
49
}
50
switch (arg) {
51
case "-t":
52
case "--to":
53
arg = argsStack.shift();
54
if (arg && !arg.startsWith("-")) {
55
flags.to = arg;
56
}
57
break;
58
59
case "-o":
60
case "--output":
61
arg = argsStack.shift();
62
if (!arg || arg.startsWith("-")) {
63
flags.output = kStdOut;
64
} else {
65
// https://github.com/quarto-dev/quarto-cli/issues/2440
66
if (arg.match(SEP_PATTERN)) {
67
throw new Error(
68
"--output option cannot specify a relative or absolute path",
69
);
70
}
71
flags.output = arg;
72
}
73
break;
74
75
case "--output-dir":
76
arg = argsStack.shift();
77
flags.outputDir = arg;
78
break;
79
80
case "--site-url":
81
arg = argsStack.shift();
82
flags.siteUrl = arg;
83
break;
84
85
case "--standalone":
86
flags[kStandalone] = true;
87
arg = argsStack.shift();
88
break;
89
90
case "--self-contained":
91
flags[kSelfContained] = true;
92
arg = argsStack.shift();
93
break;
94
95
case "--embed-resources":
96
flags[kEmbedResources] = true;
97
arg = argsStack.shift();
98
break;
99
100
case "--pdf-engine":
101
arg = argsStack.shift();
102
flags.pdfEngine = arg;
103
break;
104
105
case "--pdf-engine-opt":
106
arg = argsStack.shift();
107
if (arg) {
108
flags.pdfEngineOpts = flags.pdfEngineOpts || [];
109
flags.pdfEngineOpts.push(arg);
110
}
111
break;
112
113
case "--latex-makeindex-opt":
114
arg = argsStack.shift();
115
if (arg) {
116
flags.makeIndexOpts = flags.makeIndexOpts || [];
117
flags.makeIndexOpts.push(arg);
118
}
119
break;
120
121
case "--latex-tlmgr-opt":
122
arg = argsStack.shift();
123
if (arg) {
124
flags.tlmgrOpts = flags.tlmgrOpts || [];
125
flags.tlmgrOpts.push(arg);
126
}
127
break;
128
129
case "--natbib":
130
arg = argsStack.shift();
131
flags.natbib = true;
132
break;
133
134
case "--biblatex":
135
arg = argsStack.shift();
136
flags.biblatex = true;
137
break;
138
139
case `--${kToc}`:
140
case `--${kTableOfContents}`:
141
arg = argsStack.shift();
142
flags.toc = true;
143
break;
144
145
case "--listings":
146
arg = argsStack.shift();
147
flags[kListings] = true;
148
break;
149
150
case "--number-sections":
151
arg = argsStack.shift();
152
flags[kNumberSections] = true;
153
break;
154
155
case "--number-offset":
156
arg = argsStack.shift();
157
flags[kNumberSections] = true;
158
flags[kNumberOffset] = parseNumbers("--number-offset", arg);
159
break;
160
161
case "--top-level-division":
162
arg = argsStack.shift();
163
flags[kTopLevelDivision] = arg;
164
break;
165
166
case "--shift-heading-level-by":
167
arg = argsStack.shift();
168
flags[kShiftHeadingLevelBy] = arg;
169
break;
170
171
case "--include-in-header":
172
case "--include-before-body":
173
case "--include-after-body": {
174
const include = arg.replace("^--", "");
175
const includeFlags = flags as { [key: string]: string[] };
176
includeFlags[include] = includeFlags[include] || [];
177
arg = argsStack.shift() as string;
178
includeFlags[include].push(arg);
179
break;
180
}
181
182
case "--mathjax":
183
flags.mathjax = true;
184
arg = argsStack.shift();
185
break;
186
187
case "--katex":
188
flags.katex = true;
189
arg = argsStack.shift();
190
break;
191
192
case "--mathml":
193
flags.mathml = true;
194
arg = argsStack.shift();
195
break;
196
197
case "--gladtex":
198
flags.gladtex = true;
199
arg = argsStack.shift();
200
break;
201
202
case "--webtex":
203
flags.webtex = true;
204
arg = argsStack.shift();
205
break;
206
207
case "--execute":
208
flags.execute = true;
209
arg = argsStack.shift();
210
break;
211
212
case "--no-execute":
213
flags.execute = false;
214
arg = argsStack.shift();
215
break;
216
217
case "--execute-params":
218
arg = argsStack.shift();
219
flags.paramsFile = arg;
220
break;
221
222
case "--execute-dir":
223
arg = argsStack.shift();
224
if (arg) {
225
if (isAbsolute(arg)) {
226
flags.executeDir = arg;
227
} else {
228
flags.executeDir = normalizePath(arg);
229
}
230
}
231
232
break;
233
234
case "--execute-daemon":
235
arg = argsStack.shift();
236
flags.executeDaemon = parseInt(arg!, 10);
237
if (isNaN(flags.executeDaemon)) {
238
delete flags.executeDaemon;
239
}
240
break;
241
242
case "--no-execute-daemon":
243
arg = argsStack.shift();
244
flags.executeDaemon = 0;
245
break;
246
247
case "--execute-daemon-restart":
248
arg = argsStack.shift();
249
flags.executeDaemonRestart = true;
250
break;
251
252
case "--execute-debug":
253
arg = argsStack.shift();
254
flags.executeDebug = true;
255
break;
256
257
case "--use-freezer":
258
arg = argsStack.shift();
259
flags.useFreezer = true;
260
break;
261
262
case "--cache":
263
arg = argsStack.shift();
264
flags.executeCache = true;
265
break;
266
267
case "--no-cache":
268
arg = argsStack.shift();
269
flags.executeCache = false;
270
break;
271
272
case "--cache-refresh":
273
arg = argsStack.shift();
274
flags.executeCache = "refresh";
275
break;
276
277
case "--clean":
278
arg = argsStack.shift();
279
flags.clean = true;
280
break;
281
282
case "--no-clean":
283
arg = argsStack.shift();
284
flags.clean = false;
285
break;
286
287
case "--debug":
288
flags.debug = true;
289
arg = argsStack.shift();
290
break;
291
292
case "-P":
293
case "--execute-param":
294
arg = argsStack.shift();
295
if (arg) {
296
const param = parseMetadataFlagValue(arg);
297
if (param) {
298
if (param.value !== undefined) {
299
flags.params = flags.params || {};
300
flags.params[param.name] = param.value;
301
}
302
}
303
}
304
break;
305
306
case "-M":
307
case "--metadata":
308
arg = argsStack.shift();
309
if (arg) {
310
const metadata = parseMetadataFlagValue(arg);
311
if (metadata) {
312
if (metadata.value !== undefined) {
313
if (isQuartoMetadata(metadata.name)) {
314
flags.metadata = flags.metadata || {};
315
flags.metadata[metadata.name] = metadata.value;
316
} else {
317
flags.pandocMetadata = flags.pandocMetadata || {};
318
flags.pandocMetadata[metadata.name] = metadata.value;
319
}
320
}
321
}
322
}
323
break;
324
325
case "--metadata-file":
326
arg = argsStack.shift();
327
if (arg) {
328
if (existsSync(arg)) {
329
const metadata =
330
(await readYamlFromString(Deno.readTextFileSync(arg))) as Record<
331
string,
332
unknown
333
>;
334
flags.metadata = { ...flags.metadata, ...metadata };
335
// flags.metadata = mergeConfigs(flags.metadata, metadata); // { ...flags.metadata, ...metadata };
336
}
337
}
338
break;
339
340
case "--reference-location":
341
arg = argsStack.shift();
342
if (arg) {
343
flags[kReferenceLocation] = arg;
344
}
345
break;
346
347
default:
348
arg = argsStack.shift();
349
break;
350
}
351
}
352
353
// re-inject implicit true args (e.g. clean)
354
if (flags.clean === undefined) {
355
flags.clean = true;
356
}
357
358
return flags;
359
}
360
361
export function havePandocArg(pandocArgs: string[], arg: string) {
362
return pandocArgs.indexOf(arg) !== -1;
363
}
364
365
export function replacePandocArg(
366
pandocArgs: string[],
367
arg: string,
368
value: string,
369
) {
370
const newArgs = [...pandocArgs];
371
const argIndex = pandocArgs.indexOf(arg);
372
if (argIndex !== -1) {
373
newArgs[argIndex + 1] = value;
374
} else {
375
newArgs.push(arg);
376
newArgs.push(value);
377
}
378
return newArgs;
379
}
380
381
export function getPandocArg(
382
pandocArgs: string[],
383
arg: string,
384
) {
385
const argIndex = pandocArgs.indexOf(arg);
386
if (argIndex !== -1 && argIndex + 1 < pandocArgs.length) {
387
return pandocArgs[argIndex + 1];
388
} else {
389
return undefined;
390
}
391
}
392
393
export function replacePandocOutputArg(pandocArgs: string[], output: string) {
394
if (havePandocArg(pandocArgs, "--output")) {
395
return replacePandocArg(pandocArgs, "--output", output);
396
} else if (havePandocArg(pandocArgs, "-o")) {
397
return replacePandocArg(pandocArgs, "-o", output);
398
} else {
399
return pandocArgs;
400
}
401
}
402
403
// repair 'damage' done to pandoc args by cliffy (e.g. the - after --output is dropped)
404
export function fixupPandocArgs(pandocArgs: string[], flags: RenderFlags) {
405
// --output - gets eaten by cliffy, re-inject it if necessary
406
pandocArgs = pandocArgs.reduce((args, arg, index) => {
407
args.push(arg);
408
if (
409
flags.output === kStdOut &&
410
pandocArgs[index + 1] !== kStdOut &&
411
(arg === "-o" || arg === "--output")
412
) {
413
args.push(kStdOut);
414
}
415
return args;
416
}, new Array<string>());
417
418
// remove other args as needed
419
const removeArgs = new Map<string, boolean>();
420
removeArgs.set("--output-dir", true);
421
removeArgs.set("--site-url", true);
422
removeArgs.set("--execute", false);
423
removeArgs.set("--no-execute", false);
424
removeArgs.set("-P", true);
425
removeArgs.set("--execute-param", true);
426
removeArgs.set("--execute-params", true);
427
removeArgs.set("--execute-dir", true);
428
removeArgs.set("--execute-daemon", true);
429
removeArgs.set("--no-execute-daemon", false);
430
removeArgs.set("--execute-daemon-restart", false);
431
removeArgs.set("--execute-debug", false);
432
removeArgs.set("--use-freezer", false);
433
removeArgs.set("--cache", false);
434
removeArgs.set("--no-cache", false);
435
removeArgs.set("--cache-refresh", false);
436
removeArgs.set("--clean", false);
437
removeArgs.set("--no-clean", false);
438
removeArgs.set("--debug", false);
439
removeArgs.set("--metadata-file", true);
440
removeArgs.set("--latex-makeindex-opt", true);
441
removeArgs.set("--latex-tlmgr-opt", true);
442
removeArgs.set("--log", true);
443
removeArgs.set("--l", true);
444
removeArgs.set("--log-level", true);
445
removeArgs.set("--ll", true);
446
removeArgs.set("--log-format", true);
447
removeArgs.set("--lf", true);
448
removeArgs.set("--quiet", false);
449
removeArgs.set("--q", false);
450
removeArgs.set("--profile", true);
451
452
// Remove un-needed pandoc args (including -M/--metadata as appropriate)
453
pandocArgs = removePandocArgs(pandocArgs, removeArgs);
454
return removeQuartoMetadataFlags(pandocArgs);
455
}
456
457
export function removePandocArgs(
458
pandocArgs: string[],
459
removeArgs: Map<string, boolean>,
460
) {
461
return removeFlags(pandocArgs, removeArgs);
462
}
463
464
export function removePandocToArg(args: string[]) {
465
const removeArgs = new Map<string, boolean>();
466
removeArgs.set("--to", true);
467
removeArgs.set("-t", true);
468
return removePandocArgs(args, removeArgs);
469
}
470
471
export function removePandocTo(renderOptions: RenderOptions) {
472
renderOptions = {
473
...renderOptions,
474
flags: {
475
...(renderOptions.flags || {}),
476
},
477
} as RenderOptions;
478
delete renderOptions.flags?.to;
479
if (renderOptions.pandocArgs) {
480
renderOptions.pandocArgs = removePandocToArg(renderOptions.pandocArgs);
481
}
482
return renderOptions;
483
}
484
485
function removeQuartoMetadataFlags(pandocArgs: string[]) {
486
const args: string[] = [];
487
for (let i = 0; i < pandocArgs.length; i++) {
488
const arg = pandocArgs[i];
489
if (arg === "--metadata" || arg === "-M") {
490
const flagValue = parseMetadataFlagValue(pandocArgs[i + 1] || "");
491
if (
492
flagValue !== undefined && (isQuartoMetadata(flagValue.name) ||
493
kQuartoForwardedMetadataFields.includes(flagValue.name))
494
) {
495
i++;
496
} else {
497
args.push(arg);
498
}
499
} else {
500
args.push(arg);
501
}
502
}
503
return args;
504
}
505
export const kQuartoForwardedMetadataFields = [kAuthor, kDate];
506
507
function parseMetadataFlagValue(
508
arg: string,
509
): { name: string; value: unknown } | undefined {
510
const match = arg.match(/^([^=:]+)[=:](.*)$/);
511
if (match) {
512
return { name: match[1], value: readYamlFromString(match[2]) };
513
}
514
return undefined;
515
}
516
517
// resolve parameters (if any)
518
export function resolveParams(
519
params?: { [key: string]: unknown },
520
paramsFile?: string,
521
) {
522
if (params || paramsFile) {
523
params = params || {};
524
if (paramsFile) {
525
params = mergeConfigs(
526
readYaml(paramsFile) as { [key: string]: unknown },
527
params,
528
);
529
}
530
return params;
531
} else {
532
return undefined;
533
}
534
}
535
536
function parseNumbers(flag: string, value?: string): number[] {
537
if (value) {
538
const numbers = value.split(/,/)
539
.map((number) => parseInt(number.trim(), 10))
540
.filter((number) => !isNaN(number));
541
if (numbers.length > 0) {
542
return numbers;
543
}
544
}
545
546
// didn't parse the numbers
547
throw new Error(
548
`Invalid value for ${flag} (should be a comma separated list of numbers)`,
549
);
550
}
551
552