const fs = require('fs');
const {StringDecoder} = require('string_decoder');
const {Transform} = require('stream');
// Usage:
// fs.createReadStream(sourcePath)
// .pipe(new LineNumber())
// .pipe(fs.createWriteStream(destPath));
class Line extends Transform {
constructor() {
Object.assign(super(), {
decoder: new StringDecoder(),
buffer: [],
});
}
flush() {
const line = this.buffer.join('');
this.buffer = [];
this.push(line);
}
_transform(chunk, encoding, callback) {
let previous;
for (const c of this.decoder.write(chunk)) {
if (c === '\n') {
this.buffer.push(c);
this.flush();
} else {
if (previous === '\r') {
this.flush();
}
this.buffer.push(c);
}
previous = c;
}
callback();
}
_flush(callback) {
this.flush();
callback();
}
}
class LineNumber extends Line {
constructor({length = 4, fill = '0'} = {}) {
Object.assign(super(), {
length,
fill,
count: 0,
});
}
push(data) {
if (data || data === 'string') {
const prefix = `${++this.count}`.padStart(this.length, this.fill);
data = `${prefix}: ${data}`;
}
super.push(data);
}
}
// TEST
const assert = require('assert');
const {Writable, PassThrough} = require('stream');
const writeByteByByte = (stream, buffer) => {
for (let i = 0; i < buffer.length; i++) {
stream.write(Buffer.from([buffer[i]]));
}
};
async function test(source, options, expected) {
const actual = await new Promise((resolve, reject) => {
const sourceStream = new PassThrough();
const chunks = [];
sourceStream
.pipe(new LineNumber(options))
.pipe(new Writable({
write(chunk, encoding, callback) {
chunks.push(chunk);
callback();
},
final(callback) {
resolve(Buffer.concat(chunks).toString());
},
}));
writeByteByByte(sourceStream, Buffer.from(source));
sourceStream.end();
});
assert.equal(actual, expected);
console.log(actual);
}
Promise.all(
[
[
'ABC\nDEF\n',
undefined,
'0001: ABC\n0002: DEF\n0003: ',
],
[
'😀\r\n😁\n😂🤣\r\n😆',
{length: 2, fill: ' '},
' 1: 😀\r\n 2: 😁\n 3: 😂🤣\r\n 4: 😆',
],
]
.map(([source, options, expected]) => test(source, options, expected))
)
.catch((error) => {
console.error(error);
process.exit(1);
});