-
Notifications
You must be signed in to change notification settings - Fork 618
/
Copy pathTerminalWritable.ts
156 lines (144 loc) · 5.57 KB
/
TerminalWritable.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
148
149
150
151
152
153
154
155
156
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import type { ITerminalChunk } from './ITerminalChunk';
/**
* Constructor options for {@link TerminalWritable}
*
* @public
*/
export interface ITerminalWritableOptions {
/**
* When this object is the {@link TerminalTransform.destination} for a transform,
* the transform will automatically close this object. Set `preventAutoclose` to `true`
* to prevent that behavior.
*
* @remarks
* When a transform is closed, normally it will automatically close its destination
* `TerminalWritable` object. There are two ways to prevent that: either by setting
* `preventDestinationAutoclose` to `true` for the transform, or by setting
* {@link TerminalWritable.preventAutoclose} to `true` for the `destination` object.
*/
preventAutoclose?: boolean;
}
/**
* The abstract base class for objects that can present, route, or process text output for
* a console application. This output is typically prepared using
* the {@link Terminal} API.
*
* @remarks
*
* The design is based loosely on the `WritableStream` and `TransformStream` classes from
* the system {@link https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Concepts
* | Streams API}, except that instead of asynchronous byte streams, the `TerminalWritable`
* system synchronously transmits human readable messages intended to be rendered on a text
* console or log file.
*
* Consider a console application whose output may need to be processed in different ways
* before finally being output. The conceptual block diagram might look like this:
*
* ```
* [Terminal API]
* |
* V
* [normalize newlines]
* |
* V
* +----[splitter]-------+
* | |
* V V
* [shell console] [remove ANSI colors]
* |
* V
* [write to build.log]
* ```
*
* The application uses the `Terminal` API to print `stdout` and `stderr` messages, for example with standardized
* formatting for errors and warnings, and ANSI escapes to make nice colors. Maybe it also includes text
* received from external processes, whose newlines may be inconsistent. Ultimately we want to write the
* output to the shell console and a `build.log` file, but we don't want to put ANSI colors in the build log.
*
* For the above example, `[shell console]` and `[write to build.log]` would be modeled as subclasses of
* `TerminalWritable`. The `[normalize newlines]` and `[remove ANSI colors]` steps are modeled as subclasses
* of {@link TerminalTransform}, because they output to a "destination" object. The `[splitter]` would be
* implemented using {@link SplitterTransform}.
*
* The stream of messages are {@link ITerminalChunk} objects, which can represent both `stdout` and `stderr`
* channels. The pipeline operates synchronously on each chunk, but by processing one chunk at a time,
* it avoids storing the entire output in memory. This means that operations like `[remove ANSI colors]`
* cannot be simple regular expressions -- they must be implemented as state machines ({@link TextRewriter}
* subclasses) capable of matching substrings that span multiple chunks.
*
* @public
*/
export abstract class TerminalWritable {
private _isOpen: boolean;
public readonly preventAutoclose: boolean;
public constructor(options?: ITerminalWritableOptions) {
this._isOpen = true;
if (!options) {
options = {};
}
this.preventAutoclose = !!options.preventAutoclose;
}
/**
* This property is initially `true` when the object is constructed, and becomes `false`
* when `close()` is called.
* @sealed
*/
public get isOpen(): boolean {
return this._isOpen;
}
/**
* Upstream objects call this method to provide inputs to this object.
*
* @remarks
* The subclass provides its implementation via the the {@link TerminalWritable.onWriteChunk}
* method, which is called by `writeChunk()`.
*
* The object that calls `writeChunk()` must call `close()` when it is finished;
* failing to do so may introduce a resource leak, or may prevent some buffered data from
* being written.
*
* @sealed
*/
public writeChunk(chunk: ITerminalChunk): void {
if (!this._isOpen) {
throw new Error('Writer was already closed');
}
this.onWriteChunk(chunk);
}
/**
* Subclasses should implement this `abstract` method to process the chunk.
*/
protected abstract onWriteChunk(chunk: ITerminalChunk): void;
/**
* Calling this method flushes any remaining outputs and permanently transitions the
* `TerminalWritable` to a "closed" state, where no further chunks can be written.
*
* @remarks
* The subclass provides its implementation via the the {@link TerminalWritable.onClose}
* method, which is called by `close()`.
*
* If this method is called more than once, the additional calls are ignored;
* `TerminalWritable.onClose` will be called at most once.
*
* @sealed
*/
public close(): void {
if (this._isOpen) {
this.onClose();
this._isOpen = false;
}
}
/**
* Subclasses can override this empty method to perform additional operations
* such as closing a file handle.
*
* @remarks
* It is guaranteed that this method will be called at most once during the lifetime
* of a `TerminalWritable` object.
*
* @virtual
*/
protected onClose(): void {}
}