-
Notifications
You must be signed in to change notification settings - Fork 618
/
Copy pathTextRewriterTransform.ts
147 lines (128 loc) · 4.82 KB
/
TextRewriterTransform.ts
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
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import type { NewlineKind } from '@rushstack/node-core-library';
import { type ITerminalChunk, TerminalChunkKind } from './ITerminalChunk';
import { TerminalTransform, type ITerminalTransformOptions } from './TerminalTransform';
import type { TextRewriter, TextRewriterState } from './TextRewriter';
import { RemoveColorsTextRewriter } from './RemoveColorsTextRewriter';
import { NormalizeNewlinesTextRewriter } from './NormalizeNewlinesTextRewriter';
/**
* Constructor options for {@link TextRewriterTransform}.
*
* @public
*/
export interface ITextRewriterTransformOptions extends ITerminalTransformOptions {
/**
* A list of rewriters to be applied. More items may be appended to the list, for example
* if {@link ITextRewriterTransformOptions.removeColors} is specified.
*
* @remarks
* The final list must contain at least one item.
*/
textRewriters?: TextRewriter[];
/**
* If specified, a {@link RemoveColorsTextRewriter} will be appended to the list of rewriters.
*/
removeColors?: boolean;
/**
* If `normalizeNewlines` or `ensureNewlineAtEnd` is specified, a {@link NormalizeNewlinesTextRewriter}
* will be appended to the list of rewriters with the specified settings.
*
* @remarks
* See {@link INormalizeNewlinesTextRewriterOptions} for details.
*/
normalizeNewlines?: NewlineKind;
/**
* If `normalizeNewlines` or `ensureNewlineAtEnd` is specified, a {@link NormalizeNewlinesTextRewriter}
* will be appended to the list of rewriters with the specified settings.
*
* @remarks
* See {@link INormalizeNewlinesTextRewriterOptions} for details.
*/
ensureNewlineAtEnd?: boolean;
}
/**
* A {@link TerminalTransform} subclass that performs one or more {@link TextRewriter} operations.
* The most common operations are {@link NormalizeNewlinesTextRewriter} and {@link RemoveColorsTextRewriter}.
*
* @remarks
* The `TextRewriter` operations are applied separately to the `stderr` and `stdout` streams.
* If multiple {@link ITextRewriterTransformOptions.textRewriters} are configured, they are applied
* in the order that they appear in the array.
*
* @public
*/
export class TextRewriterTransform extends TerminalTransform {
private readonly _stderrStates: TextRewriterState[];
private readonly _stdoutStates: TextRewriterState[];
public readonly textRewriters: ReadonlyArray<TextRewriter>;
public constructor(options: ITextRewriterTransformOptions) {
super(options);
const textRewriters: TextRewriter[] = options.textRewriters || [];
if (options.removeColors) {
textRewriters.push(new RemoveColorsTextRewriter());
}
if (options.normalizeNewlines) {
textRewriters.push(
new NormalizeNewlinesTextRewriter({
newlineKind: options.normalizeNewlines,
ensureNewlineAtEnd: options.ensureNewlineAtEnd
})
);
}
if (textRewriters.length === 0) {
throw new Error('TextRewriterTransform requires at least one matcher');
}
this.textRewriters = textRewriters;
this._stderrStates = this.textRewriters.map((x) => x.initialize());
this._stdoutStates = this.textRewriters.map((x) => x.initialize());
}
protected onWriteChunk(chunk: ITerminalChunk): void {
if (chunk.kind === TerminalChunkKind.Stderr) {
this._processText(chunk, this._stderrStates);
} else if (chunk.kind === TerminalChunkKind.Stdout) {
this._processText(chunk, this._stdoutStates);
} else {
this.destination.writeChunk(chunk);
}
}
private _processText(chunk: ITerminalChunk, states: TextRewriterState[]): void {
let text: string = chunk.text;
for (let i: number = 0; i < states.length; ++i) {
if (text.length > 0) {
text = this.textRewriters[i].process(states[i], text);
}
}
if (text.length > 0) {
// If possible, avoid allocating a new chunk
if (text === chunk.text) {
this.destination.writeChunk(chunk);
} else {
this.destination.writeChunk({
text: text,
kind: chunk.kind
});
}
}
}
private _closeRewriters(states: TextRewriterState[], chunkKind: TerminalChunkKind): void {
let text: string = '';
for (let i: number = 0; i < states.length; ++i) {
if (text.length > 0) {
text = this.textRewriters[i].process(states[i], text);
}
text += this.textRewriters[i].close(states[i]);
}
if (text.length > 0) {
this.destination.writeChunk({
text: text,
kind: chunkKind
});
}
}
protected onClose(): void {
this._closeRewriters(this._stderrStates, TerminalChunkKind.Stderr);
this._closeRewriters(this._stdoutStates, TerminalChunkKind.Stdout);
this.autocloseDestination();
}
}