Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/src/command/create/artifacts/artifact-shared.ts
3589 views
1
/*
2
* artifact-shared.ts
3
*
4
* Copyright (C) 2020-2022 Posit Software, PBC
5
*/
6
7
import { capitalizeTitle } from "../../../core/text.ts";
8
import { quartoConfig } from "../../../core/quarto.ts";
9
import { execProcess } from "../../../core/process.ts";
10
import { gfmAutoIdentifier } from "../../../core/pandoc/pandoc-id.ts";
11
12
import { coerce } from "semver/mod.ts";
13
import { info } from "../../../deno_ral/log.ts";
14
import { basename, dirname, join, relative } from "../../../deno_ral/path.ts";
15
import { ensureDirSync, walkSync } from "../../../deno_ral/fs.ts";
16
import { renderEjs } from "../../../core/ejs.ts";
17
import { safeExistsSync } from "../../../core/path.ts";
18
import { CreateDirective, CreateDirectiveData } from "../cmd-types.ts";
19
20
// File paths that include this string will get fixed up
21
// and the value from the ejs data will be substituted
22
const keyRegExp = /(.*)qstart-(.*)-qend(.*)/;
23
24
export function renderAndCopyArtifacts(
25
target: string,
26
artifactSrcDir: string,
27
createDirective: CreateDirective,
28
data: CreateDirectiveData,
29
quiet?: boolean,
30
) {
31
// Ensure that the target directory exists and
32
// copy the files
33
ensureDirSync(target);
34
35
// Walk the artifact directory, copying to the target
36
// directoy and rendering as we go
37
const copiedFiles: string[] = [];
38
for (const artifact of walkSync(artifactSrcDir)) {
39
if (artifact.isFile) {
40
keyRegExp.lastIndex = 0;
41
let match = keyRegExp.exec(artifact.path);
42
let resolvedPath = artifact.path;
43
while (match) {
44
const prefix = match[1];
45
const key = match[2];
46
const suffix = match[3];
47
48
if (data[key]) {
49
resolvedPath = `${prefix}${data[key]}${suffix}`;
50
} else {
51
resolvedPath = `${prefix}${key}${suffix}`;
52
}
53
match = keyRegExp.exec(resolvedPath);
54
}
55
keyRegExp.lastIndex = 0;
56
// Compute target paths
57
const targetRelativePath = relative(artifactSrcDir, resolvedPath);
58
const targetAbsolutePath = join(
59
createDirective.directory,
60
targetRelativePath,
61
);
62
63
// Render the EJS file rather than copying this file
64
copiedFiles.push(renderArtifact(
65
artifact.path,
66
targetAbsolutePath,
67
data,
68
));
69
}
70
}
71
72
// Provide status - wait until the end
73
// so that all files, renames, and so on will be completed
74
// (since some paths will be variables that are resolved at the very end)
75
if (!quiet) {
76
info(`Creating ${createDirective.displayType} at `, { newline: false });
77
info(`${createDirective.directory}`, { bold: true, newline: false });
78
info(":");
79
80
for (const copiedFile of copiedFiles) {
81
const relPath = relative(createDirective.directory, copiedFile);
82
info(
83
` - Created ${relPath}`,
84
);
85
}
86
}
87
88
return copiedFiles;
89
}
90
91
// Render an ejs file to the output directory
92
const renderArtifact = (
93
src: string,
94
target: string,
95
data: CreateDirectiveData,
96
) => {
97
const srcFileName = basename(src);
98
if (srcFileName.includes(".ejs.")) {
99
// The target file name
100
const renderTarget = target.replace(/\.ejs\./, ".");
101
102
if (safeExistsSync(renderTarget)) {
103
throw new Error(`The file ${renderTarget} already exists.`);
104
}
105
106
// Render the EJS
107
const rendered = renderEjs(src, data, false);
108
109
// Write the rendered EJS to the output file
110
ensureDirSync(dirname(renderTarget));
111
Deno.writeTextFileSync(renderTarget, rendered);
112
return renderTarget;
113
} else {
114
if (safeExistsSync(target)) {
115
throw new Error(`The file ${target} already exists.`);
116
}
117
ensureDirSync(dirname(target));
118
Deno.copyFileSync(src, target);
119
return target;
120
}
121
};
122
123
export async function ejsData(
124
createDirective: CreateDirective,
125
): Promise<CreateDirectiveData> {
126
// Name variants
127
const title = capitalizeTitle(createDirective.name);
128
129
const classname = title.replaceAll(/[^\w]/gm, "");
130
const filesafename = gfmAutoIdentifier(createDirective.name, true);
131
132
// Other metadata
133
const version = "1.0.0";
134
const author = await gitAuthor() || "First Last";
135
136
// Limit the quarto version to the major and minor version
137
const qVer = coerce(quartoConfig.version());
138
const quartoversion = `${qVer?.major}.${qVer?.minor}.0`;
139
140
return {
141
name: createDirective.name,
142
filesafename,
143
title,
144
classname,
145
author: author.trim(),
146
version,
147
quartoversion,
148
};
149
}
150
151
async function gitAuthor() {
152
const result = await execProcess({
153
cmd: "git",
154
args: ["config", "--global", "user.name"],
155
stdout: "piped",
156
stderr: "piped",
157
});
158
if (result.success) {
159
return result.stdout;
160
} else {
161
return undefined;
162
}
163
}
164
165