Skip to content

Commit 6be4a09

Browse files
author
nickpape-msft
committed
PR 28135: Merge nickpape/pristine-rush to master
- Move v-next stream moderator to subfolder and reintroduce old interleaver - Add missing API - don't rename package - Use the old stream-moderator - Force everyone back onto a working version of rush
1 parent 86002e0 commit 6be4a09

File tree

10 files changed

+407
-8
lines changed

10 files changed

+407
-8
lines changed

libraries/stream-collator/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@ms/stream-moderator",
3-
"version": "0.3.1",
3+
"version": "1.0.0",
44
"description": "Manage interleaving of multiple streams into a single stream...",
55
"repository": {
66
"type": "git",
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/**
2+
* @file Interleaver.ts
3+
* @Copyright (c) Microsoft Corporation. All rights reserved.
4+
*
5+
* A factory which creates streams designed for processes running in parallel to write their output to.
6+
*/
7+
8+
import * as colors from 'colors';
9+
import * as os from 'os';
10+
11+
/**
12+
* An writable interface for managing output of simultaneous processes.
13+
* @todo #168347: should we export a WritableStream or Buffer or similar?
14+
*/
15+
export interface ITaskWriter {
16+
write(data: string): void; // Writes a string to the buffer
17+
writeLine(data: string): void; // Writes a string with a newline character at the end
18+
writeError(data: string): void; // Writes an error to the stderr stream
19+
getStdOutput(): string; // Returns standard output buffer as a string
20+
getStdError(): string; // Returns standard error buffer as a string
21+
close(): void; // Closes the stream and marks the simultaneous process as completed
22+
}
23+
24+
enum TaskWriterState {
25+
Open = 1,
26+
ClosedUnwritten = 2,
27+
Written = 3
28+
}
29+
30+
interface ITaskWriterInfo {
31+
state: TaskWriterState;
32+
quietMode: boolean;
33+
stdout: string[];
34+
stderr: string[];
35+
}
36+
37+
enum ITaskOutputStream {
38+
stdout = 1,
39+
stderr = 2,
40+
warning = 3
41+
}
42+
43+
/**
44+
* A static class which manages the output of multiple threads.
45+
* @todo #168348: make this class not be static
46+
* @todo #168349: add ability to inject stdout WritableStream
47+
* @todo #168350: add unit testing
48+
*/
49+
export default class Interleaver {
50+
private static _tasks: Map<string, ITaskWriterInfo> = new Map<string, ITaskWriterInfo>();
51+
private static _activeTask: string = undefined;
52+
private static _stdout: { write: (text: string) => void } = process.stdout;
53+
54+
/**
55+
* Resets the default output stream
56+
*/
57+
public static setStdOut(stdout: { write: (text: string) => void }): void {
58+
this._stdout = stdout;
59+
}
60+
61+
/**
62+
* Registers a task into the list of active buffers and returns a ITaskWriter for the
63+
* calling process to use to manage output.
64+
*/
65+
public static registerTask(taskName: string, quietMode: boolean = false): ITaskWriter {
66+
if (this._tasks.has(taskName)) {
67+
throw new Error('A task with that name has already been registered');
68+
}
69+
70+
this._tasks.set(taskName, {
71+
quietMode: quietMode,
72+
state: TaskWriterState.Open,
73+
stderr: [],
74+
stdout: []
75+
});
76+
77+
if (this._activeTask === undefined) {
78+
this._activeTask = taskName;
79+
}
80+
81+
return {
82+
close: (): void => this._completeTask(taskName),
83+
getStdError: (): string => this._getTaskOutput(taskName, ITaskOutputStream.stderr),
84+
getStdOutput: (): string => this._getTaskOutput(taskName),
85+
write: (data: string): void => this._writeTaskOutput(taskName, data),
86+
writeError: (data: string): void => {
87+
const stream: ITaskOutputStream = (data.indexOf('Warning - ') === 0) ?
88+
ITaskOutputStream.warning : // Warning written to stderr
89+
ITaskOutputStream.stderr;
90+
91+
this._writeTaskOutput(taskName, data, stream);
92+
},
93+
writeLine: (data: string): void => this._writeTaskOutput(taskName, data + os.EOL)
94+
};
95+
}
96+
97+
/**
98+
* Removes information about all running tasks
99+
*/
100+
public static reset(): void {
101+
this._activeTask = undefined;
102+
this._tasks = new Map<string, ITaskWriterInfo>();
103+
}
104+
105+
/**
106+
* Adds the text to the tasks's buffer, and writes it to the console if it is the active task
107+
*/
108+
private static _writeTaskOutput(taskName: string, data: string,
109+
stream: ITaskOutputStream = ITaskOutputStream.stdout): void {
110+
111+
const taskInfo: ITaskWriterInfo = this._tasks.get(taskName);
112+
if (!taskInfo || taskInfo.state !== TaskWriterState.Open) {
113+
throw new Error('The task is not registered or has been completed and written.');
114+
}
115+
const outputBuffer: string[] = (stream === ITaskOutputStream.stderr ? taskInfo.stderr : taskInfo.stdout);
116+
117+
if (!this._activeTask) {
118+
this._activeTask = taskName;
119+
this._writeTask(taskName, taskInfo);
120+
taskInfo.state = TaskWriterState.Open;
121+
}
122+
123+
outputBuffer.push(data);
124+
if (this._activeTask === taskName) {
125+
if (stream === ITaskOutputStream.stdout && !taskInfo.quietMode) {
126+
this._stdout.write(data);
127+
} else if (stream === ITaskOutputStream.warning && !taskInfo.quietMode) {
128+
this._stdout.write(colors.yellow(data));
129+
} else if (stream === ITaskOutputStream.stderr) {
130+
this._stdout.write(colors.red(data));
131+
}
132+
}
133+
}
134+
135+
/**
136+
* Returns the current value of the task's buffer
137+
*/
138+
private static _getTaskOutput(taskName: string, stream: ITaskOutputStream = ITaskOutputStream.stdout): string {
139+
const taskInfo: ITaskWriterInfo = this._tasks.get(taskName);
140+
return (stream === ITaskOutputStream.stdout ? taskInfo.stdout : taskInfo.stderr).join('');
141+
}
142+
143+
/**
144+
* Marks a task as completed. There are 3 cases:
145+
* - If the task was the active task, also write out all completed, unwritten tasks
146+
* - If there is no active task, write the output to the screen
147+
* - If there is an active task, mark the task as completed and wait for active task to complete
148+
*/
149+
private static _completeTask(taskName: string): void {
150+
const taskInfo: ITaskWriterInfo = this._tasks.get(taskName);
151+
if (!taskInfo || taskInfo.state !== TaskWriterState.Open) {
152+
throw new Error('The task is not registered or has been completed and written.');
153+
}
154+
155+
if (this._activeTask === undefined) {
156+
this._writeTask(taskName, taskInfo);
157+
} else if (taskName === this._activeTask) {
158+
this._activeTask = undefined;
159+
taskInfo.state = TaskWriterState.Written;
160+
this._writeAllCompletedTasks();
161+
} else {
162+
taskInfo.state = TaskWriterState.ClosedUnwritten;
163+
}
164+
}
165+
166+
/**
167+
* Helper function which writes all completed tasks
168+
*/
169+
private static _writeAllCompletedTasks(): void {
170+
this._tasks.forEach((task: ITaskWriterInfo, taskName: string) => {
171+
if (task && task.state === TaskWriterState.ClosedUnwritten) {
172+
this._writeTask(taskName, task);
173+
}
174+
});
175+
}
176+
177+
/**
178+
* Write and delete task
179+
*/
180+
private static _writeTask(taskName: string, taskInfo: ITaskWriterInfo): void {
181+
taskInfo.state = TaskWriterState.Written;
182+
if (!taskInfo.quietMode) {
183+
this._stdout.write(taskInfo.stdout.join(''));
184+
}
185+
this._stdout.write(colors.red(taskInfo.stderr.join('')));
186+
}
187+
188+
/**
189+
* A constructor which throws an exception if used
190+
*/
191+
constructor() {
192+
throw Error('do not use constructor directly, only static functions');
193+
}
194+
}
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1-
export { default as default } from './StreamModerator';
2-
export { default as DualTaskStream } from './DualTaskStream';
3-
export { default as PersistentStream } from './PersistentStream';
1+
export { default as default } from './Interleaver';
2+
export { ITaskWriter as ITaskWriter } from './Interleaver';
3+
export { default as StreamModerator } from './next/StreamModerator';
4+
export { default as DualTaskStream } from './next/DualTaskStream';
5+
export { default as PersistentStream } from './next/PersistentStream';
File renamed without changes.
File renamed without changes.
File renamed without changes.

libraries/stream-collator/src/test/DualTaskStream.test.ts renamed to libraries/stream-collator/src/next/test/DualTaskStream.test.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
/// <reference path="../../typings/tsd.d.ts" />
2-
31
import * as chai from 'chai';
42
import * as colors from 'colors';
53

libraries/stream-collator/src/test/PersistentStream.test.ts renamed to libraries/stream-collator/src/next/test/PersistentStream.test.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
/// <reference path="../../typings/tsd.d.ts" />
2-
31
import * as chai from 'chai';
42

53
import PersistentStream from '../PersistentStream';

libraries/stream-collator/src/test/StreamModerator.test.ts renamed to libraries/stream-collator/src/next/test/StreamModerator.test.ts

File renamed without changes.

0 commit comments

Comments
 (0)