UNPKG

update-file-content

Version:

Utility for executing RegEx replacement on files, powered by stream.

131 lines (106 loc) 3.58 kB
"use strict"; //CREDIT: https://github.com/signicode/rw-stream const { strictEqual } = require("assert"); const { promises: fsp } = require("fs"); const { Readable, Writable } = require("stream"); const { open } = fsp; module.exports = (async (file, { readStart, writeStart } = {}) => { let readIndex = Number(readStart) || 0; // NaN -> 0 let writeIndex = Number(writeStart) || 0; // NaN -> 0 /** * verbose type check */ if(typeof readIndex !== "number" || typeof writeIndex !== "number") throw new TypeError("Read index or write index is NOT A NUMBER."); if (readStart < writeStart) throw new RangeError("Read index MUST come before write index."); if (readStart < 0 || writeStart < 0) throw new RangeError("Negative value is passed as a file operation start index."); const fd = await open(file, "r+"); let nextReadingDone = { promise: null, _emit: () => void 0 }; function advanceReadPosition(bytesRead) { const emitDone = nextReadingDone._emit; if (bytesRead > 0) { readIndex += bytesRead; nextReadingDone.promise = new Promise ( emit => nextReadingDone._emit = emit ); } else { // EOF readIndex = Infinity; } return emitDone(bytesRead); } const readableStream = new Readable({ async read(size) { try { const buffer = Buffer.alloc(size); const { bytesRead } = await fd.read(buffer, 0, size, readIndex); advanceReadPosition(bytesRead); /** * the end-of-file is reached when the number of bytes read is zero */ if (bytesRead === 0) return this.push(null); this.push(buffer.slice(0, bytesRead)); } catch (err) { this.destroy(err); } } }).once("error", fd.close); const writableStream = new Writable({ async write(chunk, encoding, callback) { try { /** * Switch an existing stream into object mode is possible, though not safe. */ const toWrite = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, encoding); if(toWrite.length <= 0) return callback(); let totalBytesWritten = 0; while (totalBytesWritten < toWrite.length) { const writeLength = Math.min ( /** * Left available space. * readIndex will become Infinity once EOF is reached */ readIndex - (writeIndex + totalBytesWritten), /** * length of bytes not written yet */ toWrite.length - totalBytesWritten ); /** * A rare case where readIndex - (writeIndex + totalBytesWritten) * equals 0. This hardly happen as the read speed is much faster * than the write speed. */ if (writeLength === 0) { strictEqual(toWrite.length !== totalBytesWritten, true); await nextReadingDone.promise; continue; } const { bytesWritten } = await fd.write(toWrite, totalBytesWritten, writeLength, writeIndex + totalBytesWritten); totalBytesWritten += bytesWritten; } writeIndex += toWrite.length; return callback(); } catch (err) { return callback(err); } }, final(callback) { fd.truncate(writeIndex) .then(fd.close) .then(() => callback(), callback) } }).once("error", fd.close); return { fd, readableStream, writableStream }; });