summaryrefslogtreecommitdiff
path: root/js
diff options
context:
space:
mode:
authorRyan Dahl <ry@tinyclouds.org>2018-09-27 00:56:39 -0400
committerRyan Dahl <ry@tinyclouds.org>2018-09-28 20:53:33 -0400
commitbcbbee7399d41d813e78abe63126e2a01edb5848 (patch)
tree0c1d044bf8c441cec322d5e792ca915126cc856d /js
parentd653808c9f4a7d09acd5f251ffc510d470d687b0 (diff)
Adds basic File I/O and FD table.
Adds deno.stdin, deno.stdout, deno.stderr, deno.open(), deno.write(), deno.read(), deno.Reader, deno.Writer, deno.copy(). Fixes #721. tests/cat.ts works.
Diffstat (limited to 'js')
-rw-r--r--js/deno.ts2
-rw-r--r--js/files.ts89
-rw-r--r--js/files_test.ts20
-rw-r--r--js/io.ts114
-rw-r--r--js/unit_tests.ts1
5 files changed, 226 insertions, 0 deletions
diff --git a/js/deno.ts b/js/deno.ts
index 92ba5301d..44fba6164 100644
--- a/js/deno.ts
+++ b/js/deno.ts
@@ -2,6 +2,8 @@
// Public deno module.
/// <amd-module name="deno"/>
export { env, exit } from "./os";
+export { File, open, stdin, stdout, stderr, read, write, close } from "./files";
+export { copy, Reader, Writer } from "./io";
export { mkdirSync, mkdir } from "./mkdir";
export { makeTempDirSync, makeTempDir } from "./make_temp_dir";
export { removeSync, remove, removeAllSync, removeAll } from "./remove";
diff --git a/js/files.ts b/js/files.ts
new file mode 100644
index 000000000..d22f1b390
--- /dev/null
+++ b/js/files.ts
@@ -0,0 +1,89 @@
+// Copyright 2018 the Deno authors. All rights reserved. MIT license.
+
+import { Reader, Writer, Closer, ReadResult } from "./io";
+import * as dispatch from "./dispatch";
+import * as fbs from "gen/msg_generated";
+import { assert } from "./util";
+import { flatbuffers } from "flatbuffers";
+
+export class File implements Reader, Writer, Closer {
+ constructor(readonly fd: number) {}
+
+ write(p: ArrayBufferView): Promise<number> {
+ return write(this.fd, p);
+ }
+
+ read(p: ArrayBufferView): Promise<ReadResult> {
+ return read(this.fd, p);
+ }
+
+ close(): void {
+ return close(this.fd);
+ }
+}
+
+export const stdin = new File(0);
+export const stdout = new File(1);
+export const stderr = new File(2);
+
+// TODO This is just a placeholder - not final API.
+export type OpenMode = "r" | "w" | "w+" | "x";
+
+export function create(filename: string): Promise<File> {
+ return open(filename, "x");
+}
+
+export async function open(
+ filename: string,
+ mode: OpenMode = "r"
+): Promise<File> {
+ const builder = new flatbuffers.Builder();
+ const filename_ = builder.createString(filename);
+ fbs.Open.startOpen(builder);
+ fbs.Open.addFilename(builder, filename_);
+ const msg = fbs.Open.endOpen(builder);
+ const baseRes = await dispatch.sendAsync(builder, fbs.Any.Open, msg);
+ assert(baseRes != null);
+ assert(fbs.Any.OpenRes === baseRes!.msgType());
+ const res = new fbs.OpenRes();
+ assert(baseRes!.msg(res) != null);
+ const fd = res.fd();
+ return new File(fd);
+}
+
+export async function read(
+ fd: number,
+ p: ArrayBufferView
+): Promise<ReadResult> {
+ const builder = new flatbuffers.Builder();
+ fbs.Read.startRead(builder);
+ fbs.Read.addFd(builder, fd);
+ const msg = fbs.Read.endRead(builder);
+ const baseRes = await dispatch.sendAsync(builder, fbs.Any.Read, msg, p);
+ assert(baseRes != null);
+ assert(fbs.Any.ReadRes === baseRes!.msgType());
+ const res = new fbs.ReadRes();
+ assert(baseRes!.msg(res) != null);
+ return { nread: res.nread(), eof: res.eof() };
+}
+
+export async function write(fd: number, p: ArrayBufferView): Promise<number> {
+ const builder = new flatbuffers.Builder();
+ fbs.Write.startWrite(builder);
+ fbs.Write.addFd(builder, fd);
+ const msg = fbs.Write.endWrite(builder);
+ const baseRes = await dispatch.sendAsync(builder, fbs.Any.Write, msg, p);
+ assert(baseRes != null);
+ assert(fbs.Any.WriteRes === baseRes!.msgType());
+ const res = new fbs.WriteRes();
+ assert(baseRes!.msg(res) != null);
+ return res.nbyte();
+}
+
+export function close(fd: number): void {
+ const builder = new flatbuffers.Builder();
+ fbs.Close.startClose(builder);
+ fbs.Close.addFd(builder, fd);
+ const msg = fbs.Close.endClose(builder);
+ dispatch.sendSync(builder, fbs.Any.Close, msg);
+}
diff --git a/js/files_test.ts b/js/files_test.ts
new file mode 100644
index 000000000..82af10aa2
--- /dev/null
+++ b/js/files_test.ts
@@ -0,0 +1,20 @@
+// Copyright 2018 the Deno authors. All rights reserved. MIT license.
+
+import * as deno from "deno";
+import { test, assert, assertEqual } from "./test_util.ts";
+
+test(function filesStdioFileDescriptors() {
+ assertEqual(deno.stdin.fd, 0);
+ assertEqual(deno.stdout.fd, 1);
+ assertEqual(deno.stderr.fd, 2);
+});
+
+test(async function filesCopyToStdout() {
+ const filename = "package.json";
+ const file = await deno.open(filename);
+ assert(file.fd > 2);
+ const bytesWritten = await deno.copy(deno.stdout, file);
+ const fileSize = deno.statSync(filename).len;
+ assertEqual(bytesWritten, fileSize);
+ console.log("bytes written", bytesWritten);
+});
diff --git a/js/io.ts b/js/io.ts
new file mode 100644
index 000000000..710722f42
--- /dev/null
+++ b/js/io.ts
@@ -0,0 +1,114 @@
+// Interfaces 100% copied from Go.
+// Documentation liberally lifted from them too.
+// Thank you! We love Go!
+
+// The bytes read during an I/O call and a boolean indicating EOF.
+export interface ReadResult {
+ nread: number;
+ eof: boolean;
+}
+
+// Reader is the interface that wraps the basic read() method.
+// https://golang.org/pkg/io/#Reader
+export interface Reader {
+ // read() reads up to p.byteLength bytes into p. It returns the number of
+ // bytes read (0 <= n <= p.byteLength) and any error encountered. Even if
+ // read() returns n < p.byteLength, it may use all of p as scratch space
+ // during the call. If some data is available but not p.byteLength bytes,
+ // read() conventionally returns what is available instead of waiting for
+ // more.
+ //
+ // When read() encounters an error or end-of-file condition after successfully
+ // reading n > 0 bytes, it returns the number of bytes read. It may return the
+ // (non-nil) error from the same call or return the error (and n == 0) from a
+ // subsequent call. An instance of this general case is that a Reader
+ // returning a non-zero number of bytes at the end of the input stream may
+ // return either err == EOF or err == nil. The next read() should return 0,
+ // EOF.
+ //
+ // Callers should always process the n > 0 bytes returned before considering
+ // the EOF. Doing so correctly handles I/O errors that happen after reading
+ // some bytes and also both of the allowed EOF behaviors.
+ //
+ // Implementations of read() are discouraged from returning a zero byte count
+ // with a nil error, except when p.byteLength == 0. Callers should treat a
+ // return of 0 and nil as indicating that nothing happened; in particular it
+ // does not indicate EOF.
+ //
+ // Implementations must not retain p.
+ read(p: ArrayBufferView): Promise<ReadResult>;
+}
+
+// Writer is the interface that wraps the basic write() method.
+// https://golang.org/pkg/io/#Writer
+export interface Writer {
+ // write() writes p.byteLength bytes from p to the underlying data stream. It
+ // returns the number of bytes written from p (0 <= n <= p.byteLength) and any
+ // error encountered that caused the write to stop early. write() must return
+ // a non-nil error if it returns n < p.byteLength. write() must not modify the
+ // slice data, even temporarily.
+ //
+ // Implementations must not retain p.
+ write(p: ArrayBufferView): Promise<number>;
+}
+
+// https://golang.org/pkg/io/#Closer
+export interface Closer {
+ // The behavior of Close after the first call is undefined. Specific
+ // implementations may document their own behavior.
+ close(): void;
+}
+
+// https://golang.org/pkg/io/#Seeker
+export interface Seeker {
+ // Seek sets the offset for the next read() or write() to offset, interpreted
+ // according to whence: SeekStart means relative to the start of the file,
+ // SeekCurrent means relative to the current offset, and SeekEnd means
+ // relative to the end. Seek returns the new offset relative to the start of
+ // the file and an error, if any.
+ //
+ // Seeking to an offset before the start of the file is an error. Seeking to
+ // any positive offset is legal, but the behavior of subsequent I/O operations
+ // on the underlying object is implementation-dependent.
+ seek(offset: number, whence: number): Promise<void>;
+}
+
+// https://golang.org/pkg/io/#ReadCloser
+export interface ReaderCloser extends Reader, Closer {}
+
+// https://golang.org/pkg/io/#WriteCloser
+export interface WriteCloser extends Writer, Closer {}
+
+// https://golang.org/pkg/io/#ReadSeeker
+export interface ReadSeeker extends Reader, Seeker {}
+
+// https://golang.org/pkg/io/#WriteSeeker
+export interface WriteSeeker extends Writer, Seeker {}
+
+// https://golang.org/pkg/io/#ReadWriteCloser
+export interface ReadWriteCloser extends Reader, Writer, Closer {}
+
+// https://golang.org/pkg/io/#ReadWriteSeeker
+export interface ReadWriteSeeker extends Reader, Writer, Seeker {}
+
+// copy() copies from src to dst until either EOF is reached on src or an error
+// occurs. It returns the number of bytes copied and the first error encountered
+// while copying, if any.
+//
+// Because copy() is defined to read from src until EOF, it does not treat an
+// EOF from read() as an error to be reported.
+//
+// https://golang.org/pkg/io/#Copy
+export async function copy(dst: Writer, src: Reader): Promise<number> {
+ let n = 0;
+ const b = new Uint8Array(1024);
+ let gotEOF = false;
+ while (gotEOF === false) {
+ const result = await src.read(b);
+ if (result.eof) {
+ gotEOF = true;
+ }
+ n += await dst.write(b.subarray(0, result.nread));
+ }
+ return n;
+}
diff --git a/js/unit_tests.ts b/js/unit_tests.ts
index 578a65c6d..3a1fdd9d1 100644
--- a/js/unit_tests.ts
+++ b/js/unit_tests.ts
@@ -5,6 +5,7 @@ import "./compiler_test.ts";
import "./console_test.ts";
import "./fetch_test.ts";
import "./os_test.ts";
+import "./files_test.ts";
import "./read_file_test.ts";
import "./write_file_test.ts";
import "./mkdir_test.ts";