Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/tests/utils.ts
3544 views
1
/*
2
* utils.ts
3
*
4
* Copyright (C) 2020-2022 Posit Software, PBC
5
*
6
*/
7
8
import { basename, dirname, extname, join, relative } from "../src/deno_ral/path.ts";
9
import { parseFormatString } from "../src/core/pandoc/pandoc-formats.ts";
10
import { kMetadataFormat, kOutputExt } from "../src/config/constants.ts";
11
import { pathWithForwardSlashes, safeExistsSync } from "../src/core/path.ts";
12
import { readYaml } from "../src/core/yaml.ts";
13
import { isWindows } from "../src/deno_ral/platform.ts";
14
15
// caller is responsible for cleanup!
16
export function inTempDirectory(fn: (dir: string) => unknown): unknown {
17
const dir = Deno.makeTempDirSync();
18
return fn(dir);
19
}
20
21
// Find a _quarto.yaml file in the directory hierarchy of the input file
22
export function findProjectDir(input: string, until?: RegExp | undefined): string | undefined {
23
let dir = dirname(input);
24
// This is used for smoke-all tests and should stop there
25
// to avoid side effect of _quarto.yml outside of Quarto tests folders
26
while (dir !== "" && dir !== "." && (until ? !until.test(pathWithForwardSlashes(dir)) : true)) {
27
const filename = ["_quarto.yml", "_quarto.yaml"].find((file) => {
28
const yamlPath = join(dir, file);
29
if (safeExistsSync(yamlPath)) {
30
return true;
31
}
32
});
33
if (filename) {
34
return dir;
35
}
36
37
const newDir = dirname(dir); // stops at the root for both Windows and Posix
38
if (newDir === dir) {
39
return;
40
}
41
dir = newDir;
42
}
43
}
44
45
export function findProjectOutputDir(projectdir: string | undefined) {
46
if (!projectdir) {
47
return;
48
}
49
const yaml = readYaml(join(projectdir, "_quarto.yml"));
50
let type = undefined;
51
try {
52
// deno-lint-ignore no-explicit-any
53
type = ((yaml as any).project as any).type;
54
} catch (error) {
55
throw new Error("Failed to read quarto project YAML" + String(error));
56
}
57
if (type === "book") {
58
return "_book";
59
}
60
if (type === "website") {
61
return (yaml as any)?.project?.["output-dir"] || "_site";
62
}
63
if (type === "manuscript") {
64
return (yaml as any)?.project?.["output-dir"] || "_manuscript";
65
}
66
// type default explicit or just unset
67
return (yaml as any)?.project?.["output-dir"] || "";
68
}
69
70
// Gets output that should be created for this input file and target format
71
export function outputForInput(
72
input: string,
73
to: string,
74
projectOutDir?: string,
75
projectRoot?: string,
76
// deno-lint-ignore no-explicit-any
77
metadata?: Record<string, any>,
78
) {
79
// TODO: Consider improving this (e.g. for cases like Beamer, or typst)
80
projectRoot = projectRoot ?? findProjectDir(input);
81
projectOutDir = projectOutDir ?? findProjectOutputDir(projectRoot);
82
const dir = projectRoot ? relative(projectRoot, dirname(input)) : dirname(input);
83
let stem = basename(input, extname(input));
84
let ext = metadata?.[kMetadataFormat]?.[to]?.[kOutputExt];
85
86
// TODO: there's a bug where output-ext keys from a custom format are
87
// not recognized (specifically this happens for confluence)
88
//
89
// we hack it here for the time being.
90
//
91
if (to === "confluence-publish") {
92
ext = "xml";
93
}
94
if (to === "docusaurus-md") {
95
ext = "mdx";
96
}
97
98
99
const formatDesc = parseFormatString(to);
100
const baseFormat = formatDesc.baseFormat;
101
if (formatDesc.baseFormat === "pdf") {
102
stem = `${stem}${formatDesc.variants.join("")}${
103
formatDesc.modifiers.join("")
104
}`;
105
}
106
107
let outputExt;
108
if (ext) {
109
outputExt = ext
110
} else {
111
outputExt = baseFormat || "html";
112
if (baseFormat === "latex" || baseFormat == "context") {
113
outputExt = "tex";
114
}
115
if (baseFormat === "beamer") {
116
outputExt = "pdf";
117
}
118
if (baseFormat === "revealjs") {
119
outputExt = "html";
120
}
121
if (["commonmark", "gfm", "markdown", "markdown_strict"].some((f) => f === baseFormat)) {
122
outputExt = "md";
123
}
124
if (baseFormat === "csljson") {
125
outputExt = "csl";
126
}
127
if (baseFormat === "bibtex" || baseFormat === "biblatex") {
128
outputExt = "bib";
129
}
130
if (baseFormat === "jats") {
131
outputExt = "xml";
132
}
133
if (baseFormat === "asciidoc") {
134
outputExt = "adoc";
135
}
136
if (baseFormat === "typst") {
137
outputExt = "pdf";
138
}
139
if (baseFormat === "dashboard") {
140
outputExt = "html";
141
}
142
if (baseFormat === "email") {
143
outputExt = "html";
144
}
145
}
146
147
const outputPath: string = projectRoot && projectOutDir !== undefined
148
? join(projectRoot, projectOutDir, dir, `${stem}.${outputExt}`)
149
: join(dir, `${stem}.${outputExt}`);
150
const supportPath: string = projectRoot && projectOutDir !== undefined
151
? join(projectRoot, projectOutDir, dir, `${stem}_files`)
152
: join(dir, `${stem}_files`);
153
154
return {
155
outputPath,
156
supportPath,
157
};
158
}
159
160
export function projectOutputForInput(input: string) {
161
const projectDir = findProjectDir(input);
162
const projectOutDir = findProjectOutputDir(projectDir);
163
if (!projectDir) {
164
throw new Error("No project directory found");
165
}
166
const dir = join(projectDir, projectOutDir, relative(projectDir, dirname(input)));
167
const stem = basename(input, extname(input));
168
169
const outputPath = join(dir, `${stem}.html`);
170
const supportPath = join(dir, `site_libs`);
171
172
return {
173
outputPath,
174
supportPath,
175
};
176
}
177
178
export function docs(path: string): string {
179
return join("docs", path);
180
}
181
182
export function fileLoader(...path: string[]) {
183
return (file: string, to: string) => {
184
const input = docs(join(...path, file));
185
const output = outputForInput(input, to);
186
return {
187
input,
188
output,
189
};
190
};
191
}
192
193
// On Windows, `quarto.cmd` needs to be explicit in `execProcess()`
194
export function quartoDevCmd(): string {
195
return isWindows ? "quarto.cmd" : "quarto";
196
}
197
198
199