forked from angular/web-codegen-scorer
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdynamic-progress-logger.ts
More file actions
157 lines (138 loc) · 4.48 KB
/
dynamic-progress-logger.ts
File metadata and controls
157 lines (138 loc) · 4.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
import { MultiBar, SingleBar, Presets } from 'cli-progress';
import chalk from 'chalk';
import { RootPromptDefinition } from '../shared-interfaces.js';
import {
ProgressLogger,
ProgressType,
progressTypeToIcon,
} from './progress-logger.js';
import { redX } from '../reporting/format.js';
const PREFIX_WIDTH = 20;
/** A progress logger that logs the progression with a dynamic way. */
export class DynamicProgressLogger implements ProgressLogger {
private wrapper: MultiBar | undefined;
private totalBar: SingleBar | undefined;
private pendingBars = new Map<RootPromptDefinition, SingleBar>();
private spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
private currentSpinnerFrame = 0;
private spinnerInterval: ReturnType<typeof setInterval> | undefined;
private errors: {
prompt: RootPromptDefinition;
message: string;
details?: string;
}[] = [];
initialize(total: number): void {
this.finalize();
// Create a multi-bar as a container.
this.wrapper = new MultiBar(
{
clearOnComplete: true,
hideCursor: true,
// We render one "bar" for each current eval. We use this mostly to show text since
// there isn't an exact progress number for each eval.
format: '{name} {message}',
gracefulExit: true,
// The implementation has some logic that won't redraw the progress bar unless the actual
// value changed. We bypass this, because the progress value for the individual prompts
// does change, only the status does. Without this option, we can occasionally get
// duplicated prompts in the list.
forceRedraw: true,
},
{
...Presets.rect,
// Use a character so the bar is visible while it's empty.
barIncompleteChar: '_',
}
);
// Bar that tracks how many prompts are completed in total.
this.totalBar = this.wrapper.create(total, 0, undefined, {
format: '{bar} {spinner} {value}/{total} prompts completed',
barsize: PREFIX_WIDTH,
});
// Interval to update the spinner.
this.spinnerInterval = setInterval(() => {
if (!this.totalBar) {
clearInterval(this.spinnerInterval);
return;
}
this.currentSpinnerFrame =
this.currentSpinnerFrame >= this.spinnerFrames.length - 1
? 0
: this.currentSpinnerFrame + 1;
this.totalBar.update({
spinner: this.spinnerFrames[this.currentSpinnerFrame],
});
}, 80);
}
finalize(): void {
clearInterval(this.spinnerInterval);
this.wrapper?.stop();
this.pendingBars.clear();
this.wrapper = this.totalBar = this.spinnerInterval = undefined;
for (const error of this.errors) {
let message = `${redX()} [${error.prompt.name}] ${error.message}`;
if (error.details) {
message += `\n ${error.details}`;
}
console.error(message);
}
}
log(
prompt: RootPromptDefinition,
type: ProgressType,
message: string,
details?: string
): void {
if (!this.wrapper || !this.totalBar) {
return;
}
let bar = this.pendingBars.get(prompt);
// Drop the bar from the screen if it's complete.
if (type === 'done') {
this.pendingBars.delete(prompt);
if (bar) {
this.totalBar.increment();
this.wrapper.remove(bar);
}
return;
}
// Capture errors for static printing once the dynamic progress is hidden.
if (type === 'error') {
this.errors.push({ prompt, message, details });
}
// Pad/trim the name so they're all the same length.
const name = this.trimString(
prompt.name.padEnd(PREFIX_WIDTH, ' '),
PREFIX_WIDTH
);
const payload = {
name: `${this.getColorFunction(type)(name)}`,
message: `${progressTypeToIcon(type)} ${this.trimString(message, 100)}`,
};
if (bar) {
bar.update(0, payload);
} else {
const bar = this.wrapper.create(1, 0, payload);
this.pendingBars.set(prompt, bar);
}
}
private getColorFunction(type: ProgressType): (value: string) => string {
switch (type) {
case 'done':
case 'success':
case 'build':
return chalk.green;
case 'error':
return chalk.red;
case 'codegen':
return chalk.cyan;
case 'eval':
return chalk.blueBright;
}
}
private trimString(value: string, maxLength: number): string {
return value.length > maxLength
? value.slice(0, maxLength - 1) + '…'
: value;
}
}