Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/src/core/console.ts
3562 views
1
/*
2
* console.ts
3
*
4
* Copyright (C) 2020-2022 Posit Software, PBC
5
*/
6
7
import { ansi } from "cliffy/ansi/mod.ts";
8
import { writeAllSync } from "io/write-all";
9
import { readAllSync } from "io/read-all";
10
import { info } from "../deno_ral/log.ts";
11
import { runningInCI } from "./ci-info.ts";
12
import { SpinnerOptions } from "./console-types.ts";
13
import { isWindows } from "../deno_ral/platform.ts";
14
15
// The spinner and progress characters
16
const kSpinnerChars = ["|", "/", "-", "\\"];
17
const kSpinerContainerChars = ["(", ")"];
18
const kSpinerCompleteContainerChars = ["[", "]"];
19
const kSpinnerCompleteChar = !isWindows ? "✓" : ">";
20
const kProgressIncrementChar = "#";
21
const kProgressContainerChars = ["[", "]"];
22
const kProgressBarWidth = 35;
23
24
// A progressBar display for the console
25
// Includes optional prefix message as well as status text and a final state
26
export function progressBar(total: number, prefixMessage?: string): {
27
update: (progress: number, status?: string) => void;
28
complete: (finalMsg?: string | boolean) => void;
29
} {
30
const isCi = runningInCI();
31
if (isCi && prefixMessage) {
32
info(prefixMessage);
33
}
34
35
// Core function to display the progressBar bar
36
const updateProgress = (progress: number, status?: string) => {
37
if (!isCi) {
38
const progressBar = `${
39
asciiProgressBar((progress / total) * 100, kProgressBarWidth)
40
}`;
41
const progressText = `\r${
42
prefixMessage ? prefixMessage + " " : ""
43
}${progressBar}${status ? " " + status : ""}`;
44
45
clearLine();
46
info(progressText, { newline: false });
47
}
48
};
49
50
// Return control functions for progressBar
51
return {
52
update: updateProgress,
53
complete: (finalMsg?: string | boolean) => {
54
// Clear the line and display an optional final message
55
if (!isCi) {
56
clearLine();
57
}
58
59
if (typeof finalMsg === "string") {
60
if (isCi) {
61
info(finalMsg);
62
} else {
63
updateProgress(total, finalMsg);
64
}
65
} else {
66
if (finalMsg !== false && prefixMessage) {
67
completeMessage(prefixMessage);
68
} else if (finalMsg !== false) {
69
updateProgress(total);
70
}
71
}
72
},
73
};
74
}
75
76
export async function withSpinner<T>(
77
options: SpinnerOptions,
78
op: () => Promise<T>,
79
) {
80
const cancel = spinner(options.message);
81
try {
82
return await op();
83
} finally {
84
cancel(options.doneMessage);
85
}
86
}
87
88
// A spinner in the console. Displays a message with a spinner
89
// and when canceled can disappear or display a completed message.
90
export function spinner(
91
status: string | (() => string),
92
timeInterval = 100,
93
): (finalMsg?: string | boolean) => void {
94
// Used to spin the spinner
95
let spin = 0;
96
97
// status fn
98
const statusFn = typeof status === "string"
99
? () => {
100
return status;
101
}
102
: () => {
103
if (!runningInCI()) {
104
clearLine();
105
}
106
return status();
107
};
108
109
// Increment the spinner every timeInterval
110
const id = setInterval(() => {
111
// Display the message
112
const char = kSpinnerChars[spin % kSpinnerChars.length];
113
const msg = `${spinContainer(char)} ${statusFn()}`;
114
115
// when running in CI only show the first tick
116
if (!runningInCI() || spin === 0) {
117
info(`\r${msg}`, {
118
newline: false,
119
});
120
}
121
122
// Increment the spin counter
123
spin = spin + 1;
124
}, timeInterval);
125
126
// Use returned function to cancel the spinner
127
return (finalMsg?: string | boolean) => {
128
// Clear the spin interval
129
clearInterval(id);
130
131
// Clear the line and display an optional final message
132
clearLine();
133
if (typeof finalMsg === "string") {
134
completeMessage(finalMsg);
135
} else {
136
if (finalMsg !== false) {
137
completeMessage(statusFn());
138
}
139
}
140
};
141
}
142
143
function spinContainer(body: string) {
144
return `${kSpinerContainerChars[0]}${body}${kSpinerContainerChars[1]}`;
145
}
146
147
export function completeMessage(msg: string) {
148
info(
149
`\r${kSpinerCompleteContainerChars[0]}${kSpinnerCompleteChar}${
150
kSpinerCompleteContainerChars[1]
151
} ${msg}`,
152
{
153
newline: true,
154
},
155
);
156
}
157
158
export function formatLine(values: string[], lengths: number[]) {
159
const line: string[] = [];
160
values.forEach((value, i) => {
161
const len = lengths[i];
162
if (value.length === len) {
163
line.push(value);
164
} else if (value.length > len) {
165
line.push(value.substr(0, len));
166
} else {
167
line.push(value.padEnd(len, " "));
168
}
169
});
170
return line.join("");
171
}
172
173
export function writeFileToStdout(file: string) {
174
const df = Deno.openSync(file, { read: true });
175
const contents = readAllSync(df);
176
writeAllSync(Deno.stdout, contents);
177
df.close();
178
}
179
180
export function clearLine() {
181
info(ansi.eraseLine.cursorLeft(), { newline: false });
182
}
183
184
// Creates an ascii progressBar bar of a specified width, displaying a percentage complete
185
function asciiProgressBar(percent: number, width = 25): string {
186
const segsComplete = Math.floor(percent / (100 / width));
187
let progressBar = kProgressContainerChars[0];
188
for (let i = 0; i < width; i++) {
189
progressBar = progressBar +
190
(i < segsComplete ? kProgressIncrementChar : " ");
191
}
192
progressBar = progressBar + kProgressContainerChars[1];
193
return progressBar;
194
}
195
196