summaryrefslogtreecommitdiff
path: root/std/io
diff options
context:
space:
mode:
Diffstat (limited to 'std/io')
-rw-r--r--std/io/bufio.ts212
-rw-r--r--std/io/bufio_test.ts35
-rw-r--r--std/io/util.ts18
3 files changed, 207 insertions, 58 deletions
diff --git a/std/io/bufio.ts b/std/io/bufio.ts
index 87613e341..a4e729cfd 100644
--- a/std/io/bufio.ts
+++ b/std/io/bufio.ts
@@ -5,6 +5,7 @@
type Reader = Deno.Reader;
type Writer = Deno.Writer;
+type SyncWriter = Deno.SyncWriter;
import { charCode, copyBytes } from "./util.ts";
import { assert } from "../testing/asserts.ts";
@@ -400,6 +401,40 @@ export class BufReader implements Reader {
}
}
+abstract class AbstractBufBase {
+ buf!: Uint8Array;
+ usedBufferBytes = 0;
+ err: Error | null = null;
+
+ /** Size returns the size of the underlying buffer in bytes. */
+ size(): number {
+ return this.buf.byteLength;
+ }
+
+ /** Returns how many bytes are unused in the buffer. */
+ available(): number {
+ return this.buf.byteLength - this.usedBufferBytes;
+ }
+
+ /** buffered returns the number of bytes that have been written into the
+ * current buffer.
+ */
+ buffered(): number {
+ return this.usedBufferBytes;
+ }
+
+ checkBytesWritten(numBytesWritten: number): void {
+ if (numBytesWritten < this.usedBufferBytes) {
+ if (numBytesWritten > 0) {
+ this.buf.copyWithin(0, numBytesWritten, this.usedBufferBytes);
+ this.usedBufferBytes -= numBytesWritten;
+ }
+ this.err = new Error("Short write");
+ throw this.err;
+ }
+ }
+}
+
/** BufWriter implements buffering for an deno.Writer object.
* If an error occurs writing to a Writer, no more data will be
* accepted and all subsequent writes, and flush(), will return the error.
@@ -407,106 +442,179 @@ export class BufReader implements Reader {
* flush() method to guarantee all data has been forwarded to
* the underlying deno.Writer.
*/
-export class BufWriter implements Writer {
- buf: Uint8Array;
- n = 0;
- err: Error | null = null;
-
- /** return new BufWriter unless w is BufWriter */
- static create(w: Writer, size: number = DEFAULT_BUF_SIZE): BufWriter {
- return w instanceof BufWriter ? w : new BufWriter(w, size);
+export class BufWriter extends AbstractBufBase implements Writer {
+ /** return new BufWriter unless writer is BufWriter */
+ static create(writer: Writer, size: number = DEFAULT_BUF_SIZE): BufWriter {
+ return writer instanceof BufWriter ? writer : new BufWriter(writer, size);
}
- constructor(private wr: Writer, size: number = DEFAULT_BUF_SIZE) {
+ constructor(private writer: Writer, size: number = DEFAULT_BUF_SIZE) {
+ super();
if (size <= 0) {
size = DEFAULT_BUF_SIZE;
}
this.buf = new Uint8Array(size);
}
- /** Size returns the size of the underlying buffer in bytes. */
- size(): number {
- return this.buf.byteLength;
- }
-
/** Discards any unflushed buffered data, clears any error, and
- * resets b to write its output to w.
+ * resets buffer to write its output to w.
*/
reset(w: Writer): void {
this.err = null;
- this.n = 0;
- this.wr = w;
+ this.usedBufferBytes = 0;
+ this.writer = w;
}
/** Flush writes any buffered data to the underlying io.Writer. */
async flush(): Promise<void> {
if (this.err !== null) throw this.err;
- if (this.n === 0) return;
+ if (this.usedBufferBytes === 0) return;
- let n = 0;
+ let numBytesWritten = 0;
try {
- n = await this.wr.write(this.buf.subarray(0, this.n));
+ numBytesWritten = await this.writer.write(
+ this.buf.subarray(0, this.usedBufferBytes)
+ );
} catch (e) {
this.err = e;
throw e;
}
- if (n < this.n) {
- if (n > 0) {
- this.buf.copyWithin(0, n, this.n);
- this.n -= n;
+ this.checkBytesWritten(numBytesWritten);
+
+ this.usedBufferBytes = 0;
+ }
+
+ /** Writes the contents of `data` into the buffer. If the contents won't fully
+ * fit into the buffer, those bytes that can are copied into the buffer, the
+ * buffer is the flushed to the writer and the remaining bytes are copied into
+ * the now empty buffer.
+ *
+ * @return the number of bytes written to the buffer.
+ */
+ async write(data: Uint8Array): Promise<number> {
+ if (this.err !== null) throw this.err;
+ if (data.length === 0) return 0;
+
+ let totalBytesWritten = 0;
+ let numBytesWritten = 0;
+ while (data.byteLength > this.available()) {
+ if (this.buffered() === 0) {
+ // Large write, empty buffer.
+ // Write directly from data to avoid copy.
+ try {
+ numBytesWritten = await this.writer.write(data);
+ } catch (e) {
+ this.err = e;
+ throw e;
+ }
+ } else {
+ numBytesWritten = copyBytes(this.buf, data, this.usedBufferBytes);
+ this.usedBufferBytes += numBytesWritten;
+ await this.flush();
}
- this.err = new Error("Short write");
- throw this.err;
+ totalBytesWritten += numBytesWritten;
+ data = data.subarray(numBytesWritten);
}
- this.n = 0;
+ numBytesWritten = copyBytes(this.buf, data, this.usedBufferBytes);
+ this.usedBufferBytes += numBytesWritten;
+ totalBytesWritten += numBytesWritten;
+ return totalBytesWritten;
}
+}
- /** Returns how many bytes are unused in the buffer. */
- available(): number {
- return this.buf.byteLength - this.n;
+/** BufWriterSync implements buffering for a deno.SyncWriter object.
+ * If an error occurs writing to a SyncWriter, no more data will be
+ * accepted and all subsequent writes, and flush(), will return the error.
+ * After all data has been written, the client should call the
+ * flush() method to guarantee all data has been forwarded to
+ * the underlying deno.SyncWriter.
+ */
+export class BufWriterSync extends AbstractBufBase implements SyncWriter {
+ /** return new BufWriterSync unless writer is BufWriterSync */
+ static create(
+ writer: SyncWriter,
+ size: number = DEFAULT_BUF_SIZE
+ ): BufWriterSync {
+ return writer instanceof BufWriterSync
+ ? writer
+ : new BufWriterSync(writer, size);
}
- /** buffered returns the number of bytes that have been written into the
- * current buffer.
+ constructor(private writer: SyncWriter, size: number = DEFAULT_BUF_SIZE) {
+ super();
+ if (size <= 0) {
+ size = DEFAULT_BUF_SIZE;
+ }
+ this.buf = new Uint8Array(size);
+ }
+
+ /** Discards any unflushed buffered data, clears any error, and
+ * resets buffer to write its output to w.
*/
- buffered(): number {
- return this.n;
+ reset(w: SyncWriter): void {
+ this.err = null;
+ this.usedBufferBytes = 0;
+ this.writer = w;
+ }
+
+ /** Flush writes any buffered data to the underlying io.SyncWriter. */
+ flush(): void {
+ if (this.err !== null) throw this.err;
+ if (this.usedBufferBytes === 0) return;
+
+ let numBytesWritten = 0;
+ try {
+ numBytesWritten = this.writer.writeSync(
+ this.buf.subarray(0, this.usedBufferBytes)
+ );
+ } catch (e) {
+ this.err = e;
+ throw e;
+ }
+
+ this.checkBytesWritten(numBytesWritten);
+
+ this.usedBufferBytes = 0;
}
- /** Writes the contents of p into the buffer.
- * Returns the number of bytes written.
+ /** Writes the contents of `data` into the buffer. If the contents won't fully
+ * fit into the buffer, those bytes that can are copied into the buffer, the
+ * buffer is the flushed to the writer and the remaining bytes are copied into
+ * the now empty buffer.
+ *
+ * @return the number of bytes written to the buffer.
*/
- async write(p: Uint8Array): Promise<number> {
+ writeSync(data: Uint8Array): number {
if (this.err !== null) throw this.err;
- if (p.length === 0) return 0;
+ if (data.length === 0) return 0;
- let nn = 0;
- let n = 0;
- while (p.byteLength > this.available()) {
+ let totalBytesWritten = 0;
+ let numBytesWritten = 0;
+ while (data.byteLength > this.available()) {
if (this.buffered() === 0) {
// Large write, empty buffer.
- // Write directly from p to avoid copy.
+ // Write directly from data to avoid copy.
try {
- n = await this.wr.write(p);
+ numBytesWritten = this.writer.writeSync(data);
} catch (e) {
this.err = e;
throw e;
}
} else {
- n = copyBytes(this.buf, p, this.n);
- this.n += n;
- await this.flush();
+ numBytesWritten = copyBytes(this.buf, data, this.usedBufferBytes);
+ this.usedBufferBytes += numBytesWritten;
+ this.flush();
}
- nn += n;
- p = p.subarray(n);
+ totalBytesWritten += numBytesWritten;
+ data = data.subarray(numBytesWritten);
}
- n = copyBytes(this.buf, p, this.n);
- this.n += n;
- nn += n;
- return nn;
+ numBytesWritten = copyBytes(this.buf, data, this.usedBufferBytes);
+ this.usedBufferBytes += numBytesWritten;
+ totalBytesWritten += numBytesWritten;
+ return totalBytesWritten;
}
}
diff --git a/std/io/bufio_test.ts b/std/io/bufio_test.ts
index c1b1b856b..ef1fcc11e 100644
--- a/std/io/bufio_test.ts
+++ b/std/io/bufio_test.ts
@@ -14,6 +14,7 @@ import {
import {
BufReader,
BufWriter,
+ BufWriterSync,
BufferFullError,
PartialReadError,
readStringDelim,
@@ -353,6 +354,40 @@ Deno.test(async function bufioWriter(): Promise<void> {
}
});
+Deno.test(function bufioWriterSync(): void {
+ const data = new Uint8Array(8192);
+
+ for (let i = 0; i < data.byteLength; i++) {
+ // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
+ data[i] = charCode(" ") + (i % (charCode("~") - charCode(" ")));
+ }
+
+ const w = new Buffer();
+ for (const nwrite of bufsizes) {
+ for (const bs of bufsizes) {
+ // Write nwrite bytes using buffer size bs.
+ // Check that the right amount makes it out
+ // and that the data is correct.
+
+ w.reset();
+ const buf = new BufWriterSync(w, bs);
+
+ const context = `nwrite=${nwrite} bufsize=${bs}`;
+ const n = buf.writeSync(data.subarray(0, nwrite));
+ assertEquals(n, nwrite, context);
+
+ buf.flush();
+
+ const written = w.bytes();
+ assertEquals(written.byteLength, nwrite);
+
+ for (let l = 0; l < written.byteLength; l++) {
+ assertEquals(written[l], data[l]);
+ }
+ }
+ }
+});
+
Deno.test(async function bufReaderReadFull(): Promise<void> {
const enc = new TextEncoder();
const dec = new TextDecoder();
diff --git a/std/io/util.ts b/std/io/util.ts
index 4479e2c38..28688ae91 100644
--- a/std/io/util.ts
+++ b/std/io/util.ts
@@ -5,14 +5,20 @@ type Reader = Deno.Reader;
import * as path from "../path/mod.ts";
import { encode } from "../encoding/utf8.ts";
-// `off` is the offset into `dst` where it will at which to begin writing values
-// from `src`.
-// Returns the number of bytes copied.
+/**
+ * Copy bytes from one Uint8Array to another. Bytes from `src` which don't fit
+ * into `dst` will not be copied.
+ *
+ * @param dst Destination byte array
+ * @param src Source byte array
+ * @param off Offset into `dst` at which to begin writing values from `src`.
+ * @return number of bytes copied
+ */
export function copyBytes(dst: Uint8Array, src: Uint8Array, off = 0): number {
off = Math.max(0, Math.min(off, dst.byteLength));
- const r = dst.byteLength - off;
- if (src.byteLength > r) {
- src = src.subarray(0, r);
+ const dstBytesAvailable = dst.byteLength - off;
+ if (src.byteLength > dstBytesAvailable) {
+ src = src.subarray(0, dstBytesAvailable);
}
dst.set(src, off);
return src.byteLength;