@jawis/stdio-filter
Version:
Filter the stdio from console applications.
157 lines (156 loc) • 6.15 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.makeIncludeLine = exports.makeMapLine = exports.makeStdioLinearizer_old = void 0;
const _jab_1 = require("^jab");
/**
*
* - onStdio callback is called with each line, making it easy to handle the stdio stream.
* - newline is not included in the line given to onStdio.
* - onStdio callback is called with undefined, when the line is fully filtered.
* - If a partial line is received, it might be emitted without filtering.
* - timeout tells when buffered data is emitted, if it hasn't been terminated by a newline.
* - If timeout is 0, flush will be performed sync. (useful for testing)
*
* impl
* - By ensuring we can handle re-entry for async cases, we ensure all async workloads are handled.
* It's a slightly stricter constraint, but it makes writing test cases easier, because they are sync,
* and any async sequence can be simulated that way.
* re-entry isn't that important to support. Async consistency is a must, however.
* That's why sync re-entry isn't implemented.
*
* states
* state 1: No buffered data and no active timeout handle.
* state 2: Buffered data and active timeout handle.
* state 3: Partial emitted and no active timeout handle.
*/
const makeStdioLinearizer_old = (onStdio, includeLine, timeout = -1) => {
let buffer = "";
let handle;
let partialEmitted = false;
return (data) => {
partialEmitted && (0, _jab_1.assert)(buffer === "");
//use buffer from previous data
const str = buffer + data.toString();
buffer = "";
const lines = str.split("\n");
//previous partial lines
if (partialEmitted) {
(0, _jab_1.assert)(handle === undefined);
if (!str.includes("\n")) {
//we just emit the new partial str. And remain in this state.
onStdio(str);
return;
}
else {
(0, _jab_1.assert)(lines.length > 1);
partialEmitted = false;
const justOneLine = str.endsWith("\n") && lines.length === 2;
if (justOneLine) {
onStdio(str);
return; //have to return. Below wouldn't expect this case.
}
else {
//emit line without checking if line should be ignored.
const first = lines.shift();
onStdio(first + "\n");
//we leave the rest to below. It's guaranteed there are lines left in the array.
}
}
}
//handle partial lines
if (!str.endsWith("\n")) {
//without newline, we can't determine if line should be ignored yet.
buffer = (0, _jab_1.def)(lines.pop());
//set timer, if not already set. And the timeout parameter tells us to.
if (timeout >= 0 && handle === undefined) {
handle = setTimeout(() => {
//must be done first, so a new timer is set, in case of re-entry.
handle = undefined;
//output
if (includeLine(buffer)) {
partialEmitted = true;
const tmp = buffer;
buffer = ""; //must be done before call to external, so the state is correct for possible re-entry.
onStdio(tmp);
}
else {
//we don't need to output a pseudo line, because the 'partial line' is ignored.
}
}, timeout);
}
}
else {
//cancel the timer. It's not needed anymore.
if (handle) {
clearTimeout(handle);
handle = undefined;
}
//the final newline gives a meaningless empty string, which must be ignored.
(0, _jab_1.def)(lines.pop());
}
//output
for (const line of lines) {
if (includeLine(line)) {
//This can also cause re-entry. But that will only cause the stdio lines to be out of order.
onStdio(line + "\n");
}
else {
onStdio();
}
}
};
};
exports.makeStdioLinearizer_old = makeStdioLinearizer_old;
const makeMapLine = (deps) => {
const actualIgnoreLiteralPrefixes = (deps.ignoreLiteralPrefixes || []).filter((prefix) => prefix !== "");
return (line) => {
//map before filter
if (deps.mapLineBeforeFilter) {
line = deps.mapLineBeforeFilter(line);
}
//remove prefixes
return actualIgnoreLiteralPrefixes.reduce((accLine, prefix) => {
const index = accLine.indexOf(prefix);
if (index !== 0) {
return accLine;
}
else {
return accLine.slice(prefix.length);
}
}, line);
};
};
exports.makeMapLine = makeMapLine;
const makeIncludeLine = (deps) => {
const acutalIgnoreLiteralLines = (deps.ignoreLiteralLines || [])
.map(replaceForCompare)
.filter((line) => line !== "");
const mapLine = (0, exports.makeMapLine)(deps);
return (rawLine) => {
//remove prefixes
const line = mapLine(rawLine);
//evaluated include
if (acutalIgnoreLiteralLines.includes(replaceForCompare(line))) {
return false;
}
if (deps.includeLine && !deps.includeLine(line)) {
return false;
}
if (deps.includeJson && line.match(/^\s*{/) !== null) {
try {
const obj = JSON.parse(line);
return deps.includeJson(obj);
}
catch (error) {
//just ignore
}
}
return true;
};
};
exports.makeIncludeLine = makeIncludeLine;
const replaceForCompare = (line) => line
.replace(/\d+/g, "")
.replace(/\r/g, "")
.replace(/^\s*/, "")
.replace(/\s*$/, "");