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