stream/index.js

const io = require('io');

const decoder = new TextDecoder();

/**
 * @exports stream
 */
const stream = {};

/**
 * Create an stream from a file descriptor.
 *
 * Streams are an efficient way to scan file descriptors for certain types of
 * formats (f.e.: a line of text), without needing to read one byte at a time.
 *
 * Note that you should NOT create more than one stream for the same file
 * descriptor or you may get into trouble because reads may look incoherent.
 *
 * @param {number} fd A valid file descriptor
 * @returns {object} An opaque stream descriptor
 */
stream.create = function (fd) {
	return {
		_buffer: new Uint8Array(256),
		_fd: fd,
		_pushback: [],
	};
};

/**
 * Read a whole line (all available characters until '\n' or EOF).
 *
 * @param {object} sd An opaque stream descriptor
 *
 * @returns {null|string}
 * A line of text (without the ending `\n`) or null if EOF was found before
 * the delimiter.
 */
stream.read_line = function (sd) {
	return stream.read_until(sd, '\n');
};

/**
 * Read available characters until a delimiter is found.
 *
 * Note that, for now, only character (as opposed to string) delimiters are
 * supported.
 *
 * @param {object} sd An opaque stream descriptor
 * @param {string} delim A one-char string containing the delimiter
 *
 * @returns {string}
 * A string without the delimiter or null if EOF was found before the delimiter.
 */
stream.read_until = function (sd, delim) {
	// TODO: support string delimiters
	if (delim.length !== 1) {
		throw new Error('Only one-char delimiters are supported');
	}

	delim = delim.charCodeAt(0);

	const bytes = [];

	while (true) {
		while (sd._pushback.length) {
			const b = sd._pushback.shift();

			if (b === delim) {
				return decoder.decode(new Uint8Array(bytes));
			}

			bytes.push(b);
		}

		if (!feed(sd)) {
			return null;
		}
	}
};

/**
 * Feed a stream's buffer with more bytes
 *
 * @param {object} sd An opaque stream descriptor
 * @returns {boolean} The value `true` if anything was read, `false` on EOF
 * @private
 */
function feed(sd) {
	const bread = io.read(sd._fd, sd._buffer, sd._buffer.length);

	if (bread === 0) {
		return false;
	}

	for (var i = 0; i < bread; i++) {
		sd._pushback.push(sd._buffer[i]);
	}

	return true;
}

return stream;