summaryrefslogtreecommitdiff
path: root/io
diff options
context:
space:
mode:
authorAndy Hayden <andyhayden1@gmail.com>2019-01-12 13:50:04 -0800
committerRyan Dahl <ry@tinyclouds.org>2019-01-12 16:50:04 -0500
commitf626b04ebe320a96a220af29595c6ca84cf9a10a (patch)
treeea059a796fed0650060398a8d347ae001d6f621c /io
parent7d6a0f64f20004f89f5e2cbfcf7941f0a8dafd21 (diff)
Reorgnanize repos, examples and tests (denoland/deno_std#105)
Original: https://github.com/denoland/deno_std/commit/c5e6e015b5be19027f60c19ca86283d12f9258f3
Diffstat (limited to 'io')
-rw-r--r--io/bufio.ts464
-rw-r--r--io/bufio_test.ts341
-rw-r--r--io/iotest.ts61
-rw-r--r--io/ioutil.ts36
-rw-r--r--io/ioutil_test.ts62
-rw-r--r--io/util.ts29
6 files changed, 993 insertions, 0 deletions
diff --git a/io/bufio.ts b/io/bufio.ts
new file mode 100644
index 000000000..0dd2b94b4
--- /dev/null
+++ b/io/bufio.ts
@@ -0,0 +1,464 @@
+// Based on https://github.com/golang/go/blob/891682/src/bufio/bufio.go
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+import { Reader, ReadResult, Writer } from "deno";
+import { assert, charCode, copyBytes } from "./util.ts";
+
+const DEFAULT_BUF_SIZE = 4096;
+const MIN_BUF_SIZE = 16;
+const MAX_CONSECUTIVE_EMPTY_READS = 100;
+const CR = charCode("\r");
+const LF = charCode("\n");
+
+export type BufState =
+ | null
+ | "EOF"
+ | "BufferFull"
+ | "ShortWrite"
+ | "NoProgress"
+ | Error;
+
+/** BufReader implements buffering for a Reader object. */
+export class BufReader implements Reader {
+ private buf: Uint8Array;
+ private rd: Reader; // Reader provided by caller.
+ private r = 0; // buf read position.
+ private w = 0; // buf write position.
+ private lastByte: number;
+ private lastCharSize: number;
+ private err: BufState;
+
+ constructor(rd: Reader, size = DEFAULT_BUF_SIZE) {
+ if (size < MIN_BUF_SIZE) {
+ size = MIN_BUF_SIZE;
+ }
+ this._reset(new Uint8Array(size), rd);
+ }
+
+ /** Returns the size of the underlying buffer in bytes. */
+ size(): number {
+ return this.buf.byteLength;
+ }
+
+ buffered(): number {
+ return this.w - this.r;
+ }
+
+ private _readErr(): BufState {
+ const err = this.err;
+ this.err = null;
+ return err;
+ }
+
+ // Reads a new chunk into the buffer.
+ private async _fill(): Promise<void> {
+ // Slide existing data to beginning.
+ if (this.r > 0) {
+ this.buf.copyWithin(0, this.r, this.w);
+ this.w -= this.r;
+ this.r = 0;
+ }
+
+ if (this.w >= this.buf.byteLength) {
+ throw Error("bufio: tried to fill full buffer");
+ }
+
+ // Read new data: try a limited number of times.
+ for (let i = MAX_CONSECUTIVE_EMPTY_READS; i > 0; i--) {
+ let rr: ReadResult;
+ try {
+ rr = await this.rd.read(this.buf.subarray(this.w));
+ } catch (e) {
+ this.err = e;
+ return;
+ }
+ assert(rr.nread >= 0, "negative read");
+ this.w += rr.nread;
+ if (rr.eof) {
+ this.err = "EOF";
+ return;
+ }
+ if (rr.nread > 0) {
+ return;
+ }
+ }
+ this.err = "NoProgress";
+ }
+
+ /** Discards any buffered data, resets all state, and switches
+ * the buffered reader to read from r.
+ */
+ reset(r: Reader): void {
+ this._reset(this.buf, r);
+ }
+
+ private _reset(buf: Uint8Array, rd: Reader): void {
+ this.buf = buf;
+ this.rd = rd;
+ this.lastByte = -1;
+ // this.lastRuneSize = -1;
+ }
+
+ /** reads data into p.
+ * It returns the number of bytes read into p.
+ * The bytes are taken from at most one Read on the underlying Reader,
+ * hence n may be less than len(p).
+ * At EOF, the count will be zero and err will be io.EOF.
+ * To read exactly len(p) bytes, use io.ReadFull(b, p).
+ */
+ async read(p: Uint8Array): Promise<ReadResult> {
+ let rr: ReadResult = { nread: p.byteLength, eof: false };
+ if (rr.nread === 0) {
+ if (this.err) {
+ throw this._readErr();
+ }
+ return rr;
+ }
+
+ if (this.r === this.w) {
+ if (this.err) {
+ throw this._readErr();
+ }
+ if (p.byteLength >= this.buf.byteLength) {
+ // Large read, empty buffer.
+ // Read directly into p to avoid copy.
+ rr = await this.rd.read(p);
+ assert(rr.nread >= 0, "negative read");
+ if (rr.nread > 0) {
+ this.lastByte = p[rr.nread - 1];
+ // this.lastRuneSize = -1;
+ }
+ if (this.err) {
+ throw this._readErr();
+ }
+ return rr;
+ }
+ // One read.
+ // Do not use this.fill, which will loop.
+ this.r = 0;
+ this.w = 0;
+ try {
+ rr = await this.rd.read(this.buf);
+ } catch (e) {
+ this.err = e;
+ }
+ assert(rr.nread >= 0, "negative read");
+ if (rr.nread === 0) {
+ if (this.err) {
+ throw this._readErr();
+ }
+ return rr;
+ }
+ this.w += rr.nread;
+ }
+
+ // copy as much as we can
+ rr.nread = copyBytes(p as Uint8Array, this.buf.subarray(this.r, this.w), 0);
+ this.r += rr.nread;
+ this.lastByte = this.buf[this.r - 1];
+ // this.lastRuneSize = -1;
+ return rr;
+ }
+
+ /** reads exactly len(p) bytes into p.
+ * Ported from https://golang.org/pkg/io/#ReadFull
+ * It returns the number of bytes copied and an error if fewer bytes were read.
+ * The error is EOF only if no bytes were read.
+ * If an EOF happens after reading some but not all the bytes,
+ * readFull returns ErrUnexpectedEOF. ("EOF" for current impl)
+ * On return, n == len(p) if and only if err == nil.
+ * If r returns an error having read at least len(buf) bytes,
+ * the error is dropped.
+ */
+ async readFull(p: Uint8Array): Promise<[number, BufState]> {
+ let rr = await this.read(p);
+ let nread = rr.nread;
+ if (rr.eof) {
+ return [nread, nread < p.length ? "EOF" : null];
+ }
+ while (!rr.eof && nread < p.length) {
+ rr = await this.read(p.subarray(nread));
+ nread += rr.nread;
+ }
+ return [nread, nread < p.length ? "EOF" : null];
+ }
+
+ /** Returns the next byte [0, 255] or -1 if EOF. */
+ async readByte(): Promise<number> {
+ while (this.r === this.w) {
+ await this._fill(); // buffer is empty.
+ if (this.err == "EOF") {
+ return -1;
+ }
+ if (this.err != null) {
+ throw this._readErr();
+ }
+ }
+ const c = this.buf[this.r];
+ this.r++;
+ this.lastByte = c;
+ return c;
+ }
+
+ /** readString() reads until the first occurrence of delim in the input,
+ * returning a string containing the data up to and including the delimiter.
+ * If ReadString encounters an error before finding a delimiter,
+ * it returns the data read before the error and the error itself (often io.EOF).
+ * ReadString returns err != nil if and only if the returned data does not end in
+ * delim.
+ * For simple uses, a Scanner may be more convenient.
+ */
+ async readString(delim: string): Promise<string> {
+ throw new Error("Not implemented");
+ }
+
+ /** readLine() is a low-level line-reading primitive. Most callers should use
+ * readBytes('\n') or readString('\n') instead or use a Scanner.
+ *
+ * readLine tries to return a single line, not including the end-of-line bytes.
+ * If the line was too long for the buffer then isPrefix is set and the
+ * beginning of the line is returned. The rest of the line will be returned
+ * from future calls. isPrefix will be false when returning the last fragment
+ * of the line. The returned buffer is only valid until the next call to
+ * ReadLine. ReadLine either returns a non-nil line or it returns an error,
+ * never both.
+ *
+ * The text returned from ReadLine does not include the line end ("\r\n" or "\n").
+ * No indication or error is given if the input ends without a final line end.
+ * Calling UnreadByte after ReadLine will always unread the last byte read
+ * (possibly a character belonging to the line end) even if that byte is not
+ * part of the line returned by ReadLine.
+ */
+ async readLine(): Promise<[Uint8Array, boolean, BufState]> {
+ let [line, err] = await this.readSlice(LF);
+
+ if (err === "BufferFull") {
+ // Handle the case where "\r\n" straddles the buffer.
+ if (line.byteLength > 0 && line[line.byteLength - 1] === CR) {
+ // Put the '\r' back on buf and drop it from line.
+ // Let the next call to ReadLine check for "\r\n".
+ assert(this.r > 0, "bufio: tried to rewind past start of buffer");
+ this.r--;
+ line = line.subarray(0, line.byteLength - 1);
+ }
+ return [line, true, null];
+ }
+
+ if (line.byteLength === 0) {
+ return [line, false, err];
+ }
+ err = null;
+
+ if (line[line.byteLength - 1] == LF) {
+ let drop = 1;
+ if (line.byteLength > 1 && line[line.byteLength - 2] === CR) {
+ drop = 2;
+ }
+ line = line.subarray(0, line.byteLength - drop);
+ }
+ return [line, false, err];
+ }
+
+ /** readSlice() reads until the first occurrence of delim in the input,
+ * returning a slice pointing at the bytes in the buffer. The bytes stop
+ * being valid at the next read. If readSlice() encounters an error before
+ * finding a delimiter, it returns all the data in the buffer and the error
+ * itself (often io.EOF). readSlice() fails with error ErrBufferFull if the
+ * buffer fills without a delim. Because the data returned from readSlice()
+ * will be overwritten by the next I/O operation, most clients should use
+ * readBytes() or readString() instead. readSlice() returns err != nil if and
+ * only if line does not end in delim.
+ */
+ async readSlice(delim: number): Promise<[Uint8Array, BufState]> {
+ let s = 0; // search start index
+ let line: Uint8Array;
+ let err: BufState;
+ while (true) {
+ // Search buffer.
+ let i = this.buf.subarray(this.r + s, this.w).indexOf(delim);
+ if (i >= 0) {
+ i += s;
+ line = this.buf.subarray(this.r, this.r + i + 1);
+ this.r += i + 1;
+ break;
+ }
+
+ // Pending error?
+ if (this.err) {
+ line = this.buf.subarray(this.r, this.w);
+ this.r = this.w;
+ err = this._readErr();
+ break;
+ }
+
+ // Buffer full?
+ if (this.buffered() >= this.buf.byteLength) {
+ this.r = this.w;
+ line = this.buf;
+ err = "BufferFull";
+ break;
+ }
+
+ s = this.w - this.r; // do not rescan area we scanned before
+
+ await this._fill(); // buffer is not full
+ }
+
+ // Handle last byte, if any.
+ let i = line.byteLength - 1;
+ if (i >= 0) {
+ this.lastByte = line[i];
+ // this.lastRuneSize = -1
+ }
+
+ return [line, err];
+ }
+
+ /** Peek returns the next n bytes without advancing the reader. The bytes stop
+ * being valid at the next read call. If Peek returns fewer than n bytes, it
+ * also returns an error explaining why the read is short. The error is
+ * ErrBufferFull if n is larger than b's buffer size.
+ */
+ async peek(n: number): Promise<[Uint8Array, BufState]> {
+ if (n < 0) {
+ throw Error("negative count");
+ }
+
+ while (
+ this.w - this.r < n &&
+ this.w - this.r < this.buf.byteLength &&
+ this.err == null
+ ) {
+ await this._fill(); // this.w - this.r < len(this.buf) => buffer is not full
+ }
+
+ if (n > this.buf.byteLength) {
+ return [this.buf.subarray(this.r, this.w), "BufferFull"];
+ }
+
+ // 0 <= n <= len(this.buf)
+ let err: BufState;
+ let avail = this.w - this.r;
+ if (avail < n) {
+ // not enough data in buffer
+ n = avail;
+ err = this._readErr();
+ if (!err) {
+ err = "BufferFull";
+ }
+ }
+ return [this.buf.subarray(this.r, this.r + n), 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.
+ * After all data has been written, the client should call the
+ * flush() method to guarantee all data has been forwarded to
+ * the underlying deno.Writer.
+ */
+export class BufWriter implements Writer {
+ buf: Uint8Array;
+ n: number = 0;
+ err: null | BufState = null;
+
+ constructor(private wr: Writer, size = DEFAULT_BUF_SIZE) {
+ 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.
+ */
+ reset(w: Writer): void {
+ this.err = null;
+ this.n = 0;
+ this.wr = w;
+ }
+
+ /** Flush writes any buffered data to the underlying io.Writer. */
+ async flush(): Promise<BufState> {
+ if (this.err != null) {
+ return this.err;
+ }
+ if (this.n == 0) {
+ return null;
+ }
+
+ let n: number;
+ let err: BufState = null;
+ try {
+ n = await this.wr.write(this.buf.subarray(0, this.n));
+ } catch (e) {
+ err = e;
+ }
+
+ if (n < this.n && err == null) {
+ err = "ShortWrite";
+ }
+
+ if (err != null) {
+ if (n > 0 && n < this.n) {
+ this.buf.copyWithin(0, n, this.n);
+ }
+ this.n -= n;
+ this.err = err;
+ return err;
+ }
+ this.n = 0;
+ }
+
+ /** Returns how many bytes are unused in the buffer. */
+ available(): number {
+ return this.buf.byteLength - this.n;
+ }
+
+ /** buffered returns the number of bytes that have been written into the
+ * current buffer.
+ */
+ buffered(): number {
+ return this.n;
+ }
+
+ /** Writes the contents of p into the buffer.
+ * Returns the number of bytes written.
+ */
+ async write(p: Uint8Array): Promise<number> {
+ let nn = 0;
+ let n: number;
+ while (p.byteLength > this.available() && !this.err) {
+ if (this.buffered() == 0) {
+ // Large write, empty buffer.
+ // Write directly from p to avoid copy.
+ try {
+ n = await this.wr.write(p);
+ } catch (e) {
+ this.err = e;
+ }
+ } else {
+ n = copyBytes(this.buf, p, this.n);
+ this.n += n;
+ await this.flush();
+ }
+ nn += n;
+ p = p.subarray(n);
+ }
+ if (this.err) {
+ throw this.err;
+ }
+ n = copyBytes(this.buf, p, this.n);
+ this.n += n;
+ nn += n;
+ return nn;
+ }
+}
diff --git a/io/bufio_test.ts b/io/bufio_test.ts
new file mode 100644
index 000000000..fa8f4b73b
--- /dev/null
+++ b/io/bufio_test.ts
@@ -0,0 +1,341 @@
+// Based on https://github.com/golang/go/blob/891682/src/bufio/bufio_test.go
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+import { Buffer, Reader, ReadResult } from "deno";
+import { test, assert, assertEqual } from "../testing/mod.ts";
+import { BufReader, BufState, BufWriter } from "./bufio.ts";
+import * as iotest from "./iotest.ts";
+import { charCode, copyBytes, stringsReader } from "./util.ts";
+
+const encoder = new TextEncoder();
+
+async function readBytes(buf: BufReader): Promise<string> {
+ const b = new Uint8Array(1000);
+ let nb = 0;
+ while (true) {
+ let c = await buf.readByte();
+ if (c < 0) {
+ break; // EOF
+ }
+ b[nb] = c;
+ nb++;
+ }
+ const decoder = new TextDecoder();
+ return decoder.decode(b.subarray(0, nb));
+}
+
+test(async function bufioReaderSimple() {
+ const data = "hello world";
+ const b = new BufReader(stringsReader(data));
+ const s = await readBytes(b);
+ assertEqual(s, data);
+});
+
+type ReadMaker = { name: string; fn: (r: Reader) => Reader };
+
+const readMakers: ReadMaker[] = [
+ { name: "full", fn: r => r },
+ { name: "byte", fn: r => new iotest.OneByteReader(r) },
+ { name: "half", fn: r => new iotest.HalfReader(r) }
+ // TODO { name: "data+err", r => new iotest.DataErrReader(r) },
+ // { name: "timeout", fn: r => new iotest.TimeoutReader(r) },
+];
+
+function readLines(b: BufReader): string {
+ let s = "";
+ while (true) {
+ let s1 = b.readString("\n");
+ if (s1 == null) {
+ break; // EOF
+ }
+ s += s1;
+ }
+ return s;
+}
+
+// Call read to accumulate the text of a file
+async function reads(buf: BufReader, m: number): Promise<string> {
+ const b = new Uint8Array(1000);
+ let nb = 0;
+ while (true) {
+ const { nread, eof } = await buf.read(b.subarray(nb, nb + m));
+ nb += nread;
+ if (eof) {
+ break;
+ }
+ }
+ const decoder = new TextDecoder();
+ return decoder.decode(b.subarray(0, nb));
+}
+
+type NamedBufReader = { name: string; fn: (r: BufReader) => Promise<string> };
+
+const bufreaders: NamedBufReader[] = [
+ { name: "1", fn: (b: BufReader) => reads(b, 1) },
+ { name: "2", fn: (b: BufReader) => reads(b, 2) },
+ { name: "3", fn: (b: BufReader) => reads(b, 3) },
+ { name: "4", fn: (b: BufReader) => reads(b, 4) },
+ { name: "5", fn: (b: BufReader) => reads(b, 5) },
+ { name: "7", fn: (b: BufReader) => reads(b, 7) },
+ { name: "bytes", fn: readBytes }
+ // { name: "lines", fn: readLines },
+];
+
+const MIN_READ_BUFFER_SIZE = 16;
+const bufsizes: number[] = [
+ 0,
+ MIN_READ_BUFFER_SIZE,
+ 23,
+ 32,
+ 46,
+ 64,
+ 93,
+ 128,
+ 1024,
+ 4096
+];
+
+test(async function bufioBufReader() {
+ const texts = new Array<string>(31);
+ let str = "";
+ let all = "";
+ for (let i = 0; i < texts.length - 1; i++) {
+ texts[i] = str + "\n";
+ all += texts[i];
+ str += String.fromCharCode((i % 26) + 97);
+ }
+ texts[texts.length - 1] = all;
+
+ for (let text of texts) {
+ for (let readmaker of readMakers) {
+ for (let bufreader of bufreaders) {
+ for (let bufsize of bufsizes) {
+ const read = readmaker.fn(stringsReader(text));
+ const buf = new BufReader(read, bufsize);
+ const s = await bufreader.fn(buf);
+ const debugStr =
+ `reader=${readmaker.name} ` +
+ `fn=${bufreader.name} bufsize=${bufsize} want=${text} got=${s}`;
+ assertEqual(s, text, debugStr);
+ }
+ }
+ }
+ }
+});
+
+test(async function bufioBufferFull() {
+ const longString =
+ "And now, hello, world! It is the time for all good men to come to the aid of their party";
+ const buf = new BufReader(stringsReader(longString), MIN_READ_BUFFER_SIZE);
+ let [line, err] = await buf.readSlice(charCode("!"));
+
+ const decoder = new TextDecoder();
+ let actual = decoder.decode(line);
+ assertEqual(err, "BufferFull");
+ assertEqual(actual, "And now, hello, ");
+
+ [line, err] = await buf.readSlice(charCode("!"));
+ actual = decoder.decode(line);
+ assertEqual(actual, "world!");
+ assert(err == null);
+});
+
+const testInput = encoder.encode(
+ "012\n345\n678\n9ab\ncde\nfgh\nijk\nlmn\nopq\nrst\nuvw\nxy"
+);
+const testInputrn = encoder.encode(
+ "012\r\n345\r\n678\r\n9ab\r\ncde\r\nfgh\r\nijk\r\nlmn\r\nopq\r\nrst\r\nuvw\r\nxy\r\n\n\r\n"
+);
+const testOutput = encoder.encode("0123456789abcdefghijklmnopqrstuvwxy");
+
+// TestReader wraps a Uint8Array and returns reads of a specific length.
+class TestReader implements Reader {
+ constructor(private data: Uint8Array, private stride: number) {}
+
+ async read(buf: Uint8Array): Promise<ReadResult> {
+ let nread = this.stride;
+ if (nread > this.data.byteLength) {
+ nread = this.data.byteLength;
+ }
+ if (nread > buf.byteLength) {
+ nread = buf.byteLength;
+ }
+ copyBytes(buf as Uint8Array, this.data);
+ this.data = this.data.subarray(nread);
+ let eof = false;
+ if (this.data.byteLength == 0) {
+ eof = true;
+ }
+ return { nread, eof };
+ }
+}
+
+async function testReadLine(input: Uint8Array): Promise<void> {
+ for (let stride = 1; stride < 2; stride++) {
+ let done = 0;
+ let reader = new TestReader(input, stride);
+ let l = new BufReader(reader, input.byteLength + 1);
+ while (true) {
+ let [line, isPrefix, err] = await l.readLine();
+ if (line.byteLength > 0 && err != null) {
+ throw Error("readLine returned both data and error");
+ }
+ assertEqual(isPrefix, false);
+ if (err == "EOF") {
+ break;
+ }
+ let want = testOutput.subarray(done, done + line.byteLength);
+ assertEqual(
+ line,
+ want,
+ `Bad line at stride ${stride}: want: ${want} got: ${line}`
+ );
+ done += line.byteLength;
+ }
+ assertEqual(
+ done,
+ testOutput.byteLength,
+ `readLine didn't return everything: got: ${done}, ` +
+ `want: ${testOutput} (stride: ${stride})`
+ );
+ }
+}
+
+test(async function bufioReadLine() {
+ await testReadLine(testInput);
+ await testReadLine(testInputrn);
+});
+
+test(async function bufioPeek() {
+ const decoder = new TextDecoder();
+ let p = new Uint8Array(10);
+ // string is 16 (minReadBufferSize) long.
+ let buf = new BufReader(
+ stringsReader("abcdefghijklmnop"),
+ MIN_READ_BUFFER_SIZE
+ );
+
+ let [actual, err] = await buf.peek(1);
+ assertEqual(decoder.decode(actual), "a");
+ assert(err == null);
+
+ [actual, err] = await buf.peek(4);
+ assertEqual(decoder.decode(actual), "abcd");
+ assert(err == null);
+
+ [actual, err] = await buf.peek(32);
+ assertEqual(decoder.decode(actual), "abcdefghijklmnop");
+ assertEqual(err, "BufferFull");
+
+ await buf.read(p.subarray(0, 3));
+ assertEqual(decoder.decode(p.subarray(0, 3)), "abc");
+
+ [actual, err] = await buf.peek(1);
+ assertEqual(decoder.decode(actual), "d");
+ assert(err == null);
+
+ [actual, err] = await buf.peek(1);
+ assertEqual(decoder.decode(actual), "d");
+ assert(err == null);
+
+ [actual, err] = await buf.peek(1);
+ assertEqual(decoder.decode(actual), "d");
+ assert(err == null);
+
+ [actual, err] = await buf.peek(2);
+ assertEqual(decoder.decode(actual), "de");
+ assert(err == null);
+
+ let { eof } = await buf.read(p.subarray(0, 3));
+ assertEqual(decoder.decode(p.subarray(0, 3)), "def");
+ assert(!eof);
+ assert(err == null);
+
+ [actual, err] = await buf.peek(4);
+ assertEqual(decoder.decode(actual), "ghij");
+ assert(err == null);
+
+ await buf.read(p);
+ assertEqual(decoder.decode(p), "ghijklmnop");
+
+ [actual, err] = await buf.peek(0);
+ assertEqual(decoder.decode(actual), "");
+ assert(err == null);
+
+ [actual, err] = await buf.peek(1);
+ assertEqual(decoder.decode(actual), "");
+ assert(err == "EOF");
+ /* TODO
+ // Test for issue 3022, not exposing a reader's error on a successful Peek.
+ buf = NewReaderSize(dataAndEOFReader("abcd"), 32)
+ if s, err := buf.Peek(2); string(s) != "ab" || err != nil {
+ t.Errorf(`Peek(2) on "abcd", EOF = %q, %v; want "ab", nil`, string(s), err)
+ }
+ if s, err := buf.Peek(4); string(s) != "abcd" || err != nil {
+ t.Errorf(`Peek(4) on "abcd", EOF = %q, %v; want "abcd", nil`, string(s), err)
+ }
+ if n, err := buf.Read(p[0:5]); string(p[0:n]) != "abcd" || err != nil {
+ t.Fatalf("Read after peek = %q, %v; want abcd, EOF", p[0:n], err)
+ }
+ if n, err := buf.Read(p[0:1]); string(p[0:n]) != "" || err != io.EOF {
+ t.Fatalf(`second Read after peek = %q, %v; want "", EOF`, p[0:n], err)
+ }
+ */
+});
+
+test(async function bufioWriter() {
+ const data = new Uint8Array(8192);
+
+ for (let i = 0; i < data.byteLength; i++) {
+ data[i] = charCode(" ") + (i % (charCode("~") - charCode(" ")));
+ }
+
+ const w = new Buffer();
+ for (let nwrite of bufsizes) {
+ for (let 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 BufWriter(w, bs);
+
+ const context = `nwrite=${nwrite} bufsize=${bs}`;
+ const n = await buf.write(data.subarray(0, nwrite));
+ assertEqual(n, nwrite, context);
+
+ await buf.flush();
+
+ const written = w.bytes();
+ assertEqual(written.byteLength, nwrite);
+
+ for (let l = 0; l < written.byteLength; l++) {
+ assertEqual(written[l], data[l]);
+ }
+ }
+ }
+});
+
+test(async function bufReaderReadFull() {
+ const enc = new TextEncoder();
+ const dec = new TextDecoder();
+ const text = "Hello World";
+ const data = new Buffer(enc.encode(text));
+ const bufr = new BufReader(data, 3);
+ {
+ const buf = new Uint8Array(6);
+ const [nread, err] = await bufr.readFull(buf);
+ assertEqual(nread, 6);
+ assert(!err);
+ assertEqual(dec.decode(buf), "Hello ");
+ }
+ {
+ const buf = new Uint8Array(6);
+ const [nread, err] = await bufr.readFull(buf);
+ assertEqual(nread, 5);
+ assertEqual(err, "EOF");
+ assertEqual(dec.decode(buf.subarray(0, 5)), "World");
+ }
+});
diff --git a/io/iotest.ts b/io/iotest.ts
new file mode 100644
index 000000000..e3a42f58a
--- /dev/null
+++ b/io/iotest.ts
@@ -0,0 +1,61 @@
+// Ported to Deno from
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+import { Reader, ReadResult } from "deno";
+
+/** OneByteReader returns a Reader that implements
+ * each non-empty Read by reading one byte from r.
+ */
+export class OneByteReader implements Reader {
+ constructor(readonly r: Reader) {}
+
+ async read(p: Uint8Array): Promise<ReadResult> {
+ if (p.byteLength === 0) {
+ return { nread: 0, eof: false };
+ }
+ if (!(p instanceof Uint8Array)) {
+ throw Error("expected Uint8Array");
+ }
+ return this.r.read(p.subarray(0, 1));
+ }
+}
+
+/** HalfReader returns a Reader that implements Read
+ * by reading half as many requested bytes from r.
+ */
+export class HalfReader implements Reader {
+ constructor(readonly r: Reader) {}
+
+ async read(p: Uint8Array): Promise<ReadResult> {
+ if (!(p instanceof Uint8Array)) {
+ throw Error("expected Uint8Array");
+ }
+ const half = Math.floor((p.byteLength + 1) / 2);
+ return this.r.read(p.subarray(0, half));
+ }
+}
+
+export class ErrTimeout extends Error {
+ constructor() {
+ super("timeout");
+ this.name = "ErrTimeout";
+ }
+}
+
+/** TimeoutReader returns ErrTimeout on the second read
+ * with no data. Subsequent calls to read succeed.
+ */
+export class TimeoutReader implements Reader {
+ count = 0;
+ constructor(readonly r: Reader) {}
+
+ async read(p: Uint8Array): Promise<ReadResult> {
+ this.count++;
+ if (this.count === 2) {
+ throw new ErrTimeout();
+ }
+ return this.r.read(p);
+ }
+}
diff --git a/io/ioutil.ts b/io/ioutil.ts
new file mode 100644
index 000000000..68d6e5190
--- /dev/null
+++ b/io/ioutil.ts
@@ -0,0 +1,36 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+import { BufReader } from "./bufio.ts";
+
+/* Read big endian 16bit short from BufReader */
+export async function readShort(buf: BufReader): Promise<number> {
+ const [high, low] = [await buf.readByte(), await buf.readByte()];
+ return (high << 8) | low;
+}
+
+/* Read big endian 32bit integer from BufReader */
+export async function readInt(buf: BufReader): Promise<number> {
+ const [high, low] = [await readShort(buf), await readShort(buf)];
+ return (high << 16) | low;
+}
+
+const BIT32 = 0xffffffff;
+/* Read big endian 64bit long from BufReader */
+export async function readLong(buf: BufReader): Promise<number> {
+ const [high, low] = [await readInt(buf), await readInt(buf)];
+ // ECMAScript doesn't support 64bit bit ops.
+ return high ? high * (BIT32 + 1) + low : low;
+}
+
+/* Slice number into 64bit big endian byte array */
+export function sliceLongToBytes(d: number, dest = new Array(8)): number[] {
+ let mask = 0xff;
+ let low = (d << 32) >>> 32;
+ let high = (d - low) / (BIT32 + 1);
+ let shift = 24;
+ for (let i = 0; i < 4; i++) {
+ dest[i] = (high >>> shift) & mask;
+ dest[i + 4] = (low >>> shift) & mask;
+ shift -= 8;
+ }
+ return dest;
+}
diff --git a/io/ioutil_test.ts b/io/ioutil_test.ts
new file mode 100644
index 000000000..422901e4a
--- /dev/null
+++ b/io/ioutil_test.ts
@@ -0,0 +1,62 @@
+import { Reader, ReadResult } from "deno";
+import { assertEqual, test } from "../testing/mod.ts";
+import { readInt, readLong, readShort, sliceLongToBytes } from "./ioutil.ts";
+import { BufReader } from "./bufio.ts";
+
+class BinaryReader implements Reader {
+ index = 0;
+
+ constructor(private bytes: Uint8Array = new Uint8Array(0)) {}
+
+ async read(p: Uint8Array): Promise<ReadResult> {
+ p.set(this.bytes.subarray(this.index, p.byteLength));
+ this.index += p.byteLength;
+ return { nread: p.byteLength, eof: false };
+ }
+}
+
+test(async function testReadShort() {
+ const r = new BinaryReader(new Uint8Array([0x12, 0x34]));
+ const short = await readShort(new BufReader(r));
+ assertEqual(short, 0x1234);
+});
+
+test(async function testReadInt() {
+ const r = new BinaryReader(new Uint8Array([0x12, 0x34, 0x56, 0x78]));
+ const int = await readInt(new BufReader(r));
+ assertEqual(int, 0x12345678);
+});
+
+test(async function testReadLong() {
+ const r = new BinaryReader(
+ new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78])
+ );
+ const long = await readLong(new BufReader(r));
+ assertEqual(long, 0x1234567812345678);
+});
+
+test(async function testReadLong2() {
+ const r = new BinaryReader(
+ new Uint8Array([0, 0, 0, 0, 0x12, 0x34, 0x56, 0x78])
+ );
+ const long = await readLong(new BufReader(r));
+ assertEqual(long, 0x12345678);
+});
+
+test(async function testSliceLongToBytes() {
+ const arr = sliceLongToBytes(0x1234567890abcdef);
+ const actual = readLong(new BufReader(new BinaryReader(new Uint8Array(arr))));
+ const expected = readLong(
+ new BufReader(
+ new BinaryReader(
+ new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef])
+ )
+ )
+ );
+ assertEqual(actual, expected);
+});
+
+test(async function testSliceLongToBytes2() {
+ const arr = sliceLongToBytes(0x12345678);
+ assertEqual(arr, [0, 0, 0, 0, 0x12, 0x34, 0x56, 0x78]);
+});
diff --git a/io/util.ts b/io/util.ts
new file mode 100644
index 000000000..811940b4d
--- /dev/null
+++ b/io/util.ts
@@ -0,0 +1,29 @@
+import { Buffer, Reader } from "deno";
+
+export function assert(cond: boolean, msg = "assert") {
+ if (!cond) {
+ throw Error(msg);
+ }
+}
+
+// `off` is the offset into `dst` where it will at which to begin writing values
+// from `src`.
+// Returns the number of bytes copied.
+export function copyBytes(dst: Uint8Array, src: Uint8Array, off = 0): number {
+ const r = dst.byteLength - off;
+ if (src.byteLength > r) {
+ src = src.subarray(0, r);
+ }
+ dst.set(src, off);
+ return src.byteLength;
+}
+
+export function charCode(s: string): number {
+ return s.charCodeAt(0);
+}
+
+const encoder = new TextEncoder();
+export function stringsReader(s: string): Reader {
+ const ui8 = encoder.encode(s);
+ return new Buffer(ui8.buffer as ArrayBuffer);
+}