diff options
Diffstat (limited to 'js')
-rw-r--r-- | js/deno.ts | 2 | ||||
-rw-r--r-- | js/files.ts | 89 | ||||
-rw-r--r-- | js/files_test.ts | 20 | ||||
-rw-r--r-- | js/io.ts | 114 | ||||
-rw-r--r-- | js/unit_tests.ts | 1 |
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"; |