diff --git a/BUILD.gn b/BUILD.gn
index 78d3507a74..839e904bec 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -47,6 +47,7 @@ main_extern = [
"$rust_build:rand",
"$rust_build:tokio",
"$rust_build:url",
+ "$rust_build:remove_dir_all",
"//build_extra/flatbuffers/rust:flatbuffers",
":msg_rs",
]
@@ -198,6 +199,7 @@ run_node("gen_declarations") {
"js/mkdir.ts",
"js/os.ts",
"js/read_file.ts",
+ "js/remove.ts",
"js/stat.ts",
"js/text_encoding.ts",
"js/timers.ts",
@@ -205,6 +207,7 @@ run_node("gen_declarations") {
"js/types.ts",
"js/util.ts",
"js/v8_source_maps.ts",
+ "js/write_file.ts",
]
outputs = [
"$out_dir/types/globals.d.ts",
@@ -239,12 +242,14 @@ run_node("bundle") {
"js/os.ts",
"js/plugins.d.ts",
"js/read_file.ts",
+ "js/remove.ts",
"js/stat.ts",
"js/text_encoding.ts",
"js/timers.ts",
"js/types.ts",
"js/util.ts",
"js/v8_source_maps.ts",
+ "js/write_file.ts",
"rollup.config.js",
"src/msg.fbs",
"tsconfig.json",
diff --git a/js/deno.ts b/js/deno.ts
index a1eb639bb4..3d0352a809 100644
--- a/js/deno.ts
+++ b/js/deno.ts
@@ -1,13 +1,9 @@
// Copyright 2018 the Deno authors. All rights reserved. MIT license.
// Public deno module.
///
-export {
- env,
- exit,
- makeTempDirSync,
- renameSync,
-} from "./os";
+export { env, exit, makeTempDirSync, renameSync } from "./os";
export { mkdirSync, mkdir } from "./mkdir";
+export { removeSync, remove, removeAllSync, removeAll } from "./remove";
export { readFileSync, readFile } from "./read_file";
export { FileInfo, statSync, lstatSync, stat, lstat } from "./stat";
export { writeFileSync, writeFile } from "./write_file";
diff --git a/js/remove.ts b/js/remove.ts
new file mode 100644
index 0000000000..9211107895
--- /dev/null
+++ b/js/remove.ts
@@ -0,0 +1,63 @@
+// Copyright 2018 the Deno authors. All rights reserved. MIT license.
+import * as fbs from "gen/msg_generated";
+import { flatbuffers } from "flatbuffers";
+import * as dispatch from "./dispatch";
+
+/**
+ * Removes the named file or (empty) directory synchronously.
+ * Would throw error if permission denied, not found, or
+ * directory not empty.
+ *
+ * import { removeSync } from "deno";
+ * removeSync("/path/to/empty_dir/or/file");
+ */
+export function removeSync(path: string): void {
+ dispatch.sendSync(...req(path, false));
+}
+
+/**
+ * Removes the named file or (empty) directory.
+ * Would throw error if permission denied, not found, or
+ * directory not empty.
+ *
+ * import { remove } from "deno";
+ * await remove("/path/to/empty_dir/or/file");
+ */
+export async function remove(path: string): Promise {
+ await dispatch.sendAsync(...req(path, false));
+}
+
+/**
+ * Recursively removes the named file or directory synchronously.
+ * Would throw error if permission denied or not found
+ *
+ * import { removeAllSync } from "deno";
+ * removeAllSync("/path/to/dir/or/file");
+ */
+export function removeAllSync(path: string): void {
+ dispatch.sendSync(...req(path, true));
+}
+
+/**
+ * Recursively removes the named file or directory.
+ * Would throw error if permission denied or not found
+ *
+ * import { removeAll } from "deno";
+ * await removeAll("/path/to/dir/or/file");
+ */
+export async function removeAll(path: string): Promise {
+ await dispatch.sendAsync(...req(path, true));
+}
+
+function req(
+ path: string,
+ recursive: boolean
+): [flatbuffers.Builder, fbs.Any, flatbuffers.Offset] {
+ const builder = new flatbuffers.Builder();
+ const path_ = builder.createString(path);
+ fbs.Remove.startRemove(builder);
+ fbs.Remove.addPath(builder, path_);
+ fbs.Remove.addRecursive(builder, recursive);
+ const msg = fbs.Remove.endRemove(builder);
+ return [builder, fbs.Any.Remove, msg];
+}
diff --git a/js/remove_test.ts b/js/remove_test.ts
new file mode 100644
index 0000000000..eb31f1ba3e
--- /dev/null
+++ b/js/remove_test.ts
@@ -0,0 +1,336 @@
+// Copyright 2018 the Deno authors. All rights reserved. MIT license.
+import { testPerm, assert, assertEqual } from "./test_util.ts";
+import * as deno from "deno";
+
+// SYNC
+
+testPerm({ write: true }, function removeSyncDirSuccess() {
+ // REMOVE EMPTY DIRECTORY
+ const path = deno.makeTempDirSync() + "/dir/subdir";
+ deno.mkdirSync(path);
+ const pathInfo = deno.statSync(path);
+ assert(pathInfo.isDirectory()); // check exist first
+ deno.removeSync(path); // remove
+ // We then check again after remove
+ let err;
+ try {
+ deno.statSync(path);
+ } catch (e) {
+ err = e;
+ }
+ // Directory is gone
+ assertEqual(err.kind, deno.ErrorKind.NotFound);
+ assertEqual(err.name, "NotFound");
+});
+
+testPerm({ write: true }, function removeSyncFileSuccess() {
+ // REMOVE FILE
+ const enc = new TextEncoder();
+ const data = enc.encode("Hello");
+ const filename = deno.makeTempDirSync() + "/test.txt";
+ deno.writeFileSync(filename, data, 0o666);
+ const fileInfo = deno.statSync(filename);
+ assert(fileInfo.isFile()); // check exist first
+ deno.removeSync(filename); // remove
+ // We then check again after remove
+ let err;
+ try {
+ deno.statSync(filename);
+ } catch (e) {
+ err = e;
+ }
+ // File is gone
+ assertEqual(err.kind, deno.ErrorKind.NotFound);
+ assertEqual(err.name, "NotFound");
+});
+
+testPerm({ write: true }, function removeSyncFail() {
+ // NON-EMPTY DIRECTORY
+ const path = deno.makeTempDirSync() + "/dir/subdir";
+ const subPath = path + "/subsubdir";
+ deno.mkdirSync(path);
+ deno.mkdirSync(subPath);
+ const pathInfo = deno.statSync(path);
+ assert(pathInfo.isDirectory()); // check exist first
+ const subPathInfo = deno.statSync(subPath);
+ assert(subPathInfo.isDirectory()); // check exist first
+ let err;
+ try {
+ // Should not be able to recursively remove
+ deno.removeSync(path);
+ } catch (e) {
+ err = e;
+ }
+ // TODO(ry) Is Other really the error we should get here? What would Go do?
+ assertEqual(err.kind, deno.ErrorKind.Other);
+ assertEqual(err.name, "Other");
+ // NON-EXISTENT DIRECTORY/FILE
+ try {
+ // Non-existent
+ deno.removeSync("/baddir");
+ } catch (e) {
+ err = e;
+ }
+ assertEqual(err.kind, deno.ErrorKind.NotFound);
+ assertEqual(err.name, "NotFound");
+});
+
+testPerm({ write: false }, function removeSyncPerm() {
+ let err;
+ try {
+ deno.removeSync("/baddir");
+ } catch (e) {
+ err = e;
+ }
+ assertEqual(err.kind, deno.ErrorKind.PermissionDenied);
+ assertEqual(err.name, "PermissionDenied");
+});
+
+testPerm({ write: true }, function removeAllSyncDirSuccess() {
+ // REMOVE EMPTY DIRECTORY
+ let path = deno.makeTempDirSync() + "/dir/subdir";
+ deno.mkdirSync(path);
+ let pathInfo = deno.statSync(path);
+ assert(pathInfo.isDirectory()); // check exist first
+ deno.removeAllSync(path); // remove
+ // We then check again after remove
+ let err;
+ try {
+ deno.statSync(path);
+ } catch (e) {
+ err = e;
+ }
+ // Directory is gone
+ assertEqual(err.kind, deno.ErrorKind.NotFound);
+ assertEqual(err.name, "NotFound");
+ // REMOVE NON-EMPTY DIRECTORY
+ path = deno.makeTempDirSync() + "/dir/subdir";
+ const subPath = path + "/subsubdir";
+ deno.mkdirSync(path);
+ deno.mkdirSync(subPath);
+ pathInfo = deno.statSync(path);
+ assert(pathInfo.isDirectory()); // check exist first
+ const subPathInfo = deno.statSync(subPath);
+ assert(subPathInfo.isDirectory()); // check exist first
+ deno.removeAllSync(path); // remove
+ // We then check parent directory again after remove
+ try {
+ deno.statSync(path);
+ } catch (e) {
+ err = e;
+ }
+ // Directory is gone
+ assertEqual(err.kind, deno.ErrorKind.NotFound);
+ assertEqual(err.name, "NotFound");
+});
+
+testPerm({ write: true }, function removeAllSyncFileSuccess() {
+ // REMOVE FILE
+ const enc = new TextEncoder();
+ const data = enc.encode("Hello");
+ const filename = deno.makeTempDirSync() + "/test.txt";
+ deno.writeFileSync(filename, data, 0o666);
+ const fileInfo = deno.statSync(filename);
+ assert(fileInfo.isFile()); // check exist first
+ deno.removeAllSync(filename); // remove
+ // We then check again after remove
+ let err;
+ try {
+ deno.statSync(filename);
+ } catch (e) {
+ err = e;
+ }
+ // File is gone
+ assertEqual(err.kind, deno.ErrorKind.NotFound);
+ assertEqual(err.name, "NotFound");
+});
+
+testPerm({ write: true }, function removeAllSyncFail() {
+ // NON-EXISTENT DIRECTORY/FILE
+ let err;
+ try {
+ // Non-existent
+ deno.removeAllSync("/baddir");
+ } catch (e) {
+ err = e;
+ }
+ assertEqual(err.kind, deno.ErrorKind.NotFound);
+ assertEqual(err.name, "NotFound");
+});
+
+testPerm({ write: false }, function removeAllSyncPerm() {
+ let err;
+ try {
+ deno.removeAllSync("/baddir");
+ } catch (e) {
+ err = e;
+ }
+ assertEqual(err.kind, deno.ErrorKind.PermissionDenied);
+ assertEqual(err.name, "PermissionDenied");
+});
+
+// ASYNC
+
+testPerm({ write: true }, async function removeDirSuccess() {
+ // REMOVE EMPTY DIRECTORY
+ const path = deno.makeTempDirSync() + "/dir/subdir";
+ deno.mkdirSync(path);
+ const pathInfo = deno.statSync(path);
+ assert(pathInfo.isDirectory()); // check exist first
+ await deno.remove(path); // remove
+ // We then check again after remove
+ let err;
+ try {
+ deno.statSync(path);
+ } catch (e) {
+ err = e;
+ }
+ // Directory is gone
+ assertEqual(err.kind, deno.ErrorKind.NotFound);
+ assertEqual(err.name, "NotFound");
+});
+
+testPerm({ write: true }, async function removeFileSuccess() {
+ // REMOVE FILE
+ const enc = new TextEncoder();
+ const data = enc.encode("Hello");
+ const filename = deno.makeTempDirSync() + "/test.txt";
+ deno.writeFileSync(filename, data, 0o666);
+ const fileInfo = deno.statSync(filename);
+ assert(fileInfo.isFile()); // check exist first
+ await deno.remove(filename); // remove
+ // We then check again after remove
+ let err;
+ try {
+ deno.statSync(filename);
+ } catch (e) {
+ err = e;
+ }
+ // File is gone
+ assertEqual(err.kind, deno.ErrorKind.NotFound);
+ assertEqual(err.name, "NotFound");
+});
+
+testPerm({ write: true }, async function removeFail() {
+ // NON-EMPTY DIRECTORY
+ const path = deno.makeTempDirSync() + "/dir/subdir";
+ const subPath = path + "/subsubdir";
+ deno.mkdirSync(path);
+ deno.mkdirSync(subPath);
+ const pathInfo = deno.statSync(path);
+ assert(pathInfo.isDirectory()); // check exist first
+ const subPathInfo = deno.statSync(subPath);
+ assert(subPathInfo.isDirectory()); // check exist first
+ let err;
+ try {
+ // Should not be able to recursively remove
+ await deno.remove(path);
+ } catch (e) {
+ err = e;
+ }
+ assertEqual(err.kind, deno.ErrorKind.Other);
+ assertEqual(err.name, "Other");
+ // NON-EXISTENT DIRECTORY/FILE
+ try {
+ // Non-existent
+ await deno.remove("/baddir");
+ } catch (e) {
+ err = e;
+ }
+ assertEqual(err.kind, deno.ErrorKind.NotFound);
+ assertEqual(err.name, "NotFound");
+});
+
+testPerm({ write: false }, async function removePerm() {
+ let err;
+ try {
+ await deno.remove("/baddir");
+ } catch (e) {
+ err = e;
+ }
+ assertEqual(err.kind, deno.ErrorKind.PermissionDenied);
+ assertEqual(err.name, "PermissionDenied");
+});
+
+testPerm({ write: true }, async function removeAllDirSuccess() {
+ // REMOVE EMPTY DIRECTORY
+ let path = deno.makeTempDirSync() + "/dir/subdir";
+ deno.mkdirSync(path);
+ let pathInfo = deno.statSync(path);
+ assert(pathInfo.isDirectory()); // check exist first
+ await deno.removeAll(path); // remove
+ // We then check again after remove
+ let err;
+ try {
+ deno.statSync(path);
+ } catch (e) {
+ err = e;
+ }
+ // Directory is gone
+ assertEqual(err.kind, deno.ErrorKind.NotFound);
+ assertEqual(err.name, "NotFound");
+ // REMOVE NON-EMPTY DIRECTORY
+ path = deno.makeTempDirSync() + "/dir/subdir";
+ const subPath = path + "/subsubdir";
+ deno.mkdirSync(path);
+ deno.mkdirSync(subPath);
+ pathInfo = deno.statSync(path);
+ assert(pathInfo.isDirectory()); // check exist first
+ const subPathInfo = deno.statSync(subPath);
+ assert(subPathInfo.isDirectory()); // check exist first
+ await deno.removeAll(path); // remove
+ // We then check parent directory again after remove
+ try {
+ deno.statSync(path);
+ } catch (e) {
+ err = e;
+ }
+ // Directory is gone
+ assertEqual(err.kind, deno.ErrorKind.NotFound);
+ assertEqual(err.name, "NotFound");
+});
+
+testPerm({ write: true }, async function removeAllFileSuccess() {
+ // REMOVE FILE
+ const enc = new TextEncoder();
+ const data = enc.encode("Hello");
+ const filename = deno.makeTempDirSync() + "/test.txt";
+ deno.writeFileSync(filename, data, 0o666);
+ const fileInfo = deno.statSync(filename);
+ assert(fileInfo.isFile()); // check exist first
+ await deno.removeAll(filename); // remove
+ // We then check again after remove
+ let err;
+ try {
+ deno.statSync(filename);
+ } catch (e) {
+ err = e;
+ }
+ // File is gone
+ assertEqual(err.kind, deno.ErrorKind.NotFound);
+ assertEqual(err.name, "NotFound");
+});
+
+testPerm({ write: true }, async function removeAllFail() {
+ // NON-EXISTENT DIRECTORY/FILE
+ let err;
+ try {
+ // Non-existent
+ await deno.removeAll("/baddir");
+ } catch (e) {
+ err = e;
+ }
+ assertEqual(err.kind, deno.ErrorKind.NotFound);
+ assertEqual(err.name, "NotFound");
+});
+
+testPerm({ write: false }, async function removeAllPerm() {
+ let err;
+ try {
+ await deno.removeAll("/baddir");
+ } catch (e) {
+ err = e;
+ }
+ assertEqual(err.kind, deno.ErrorKind.PermissionDenied);
+ assertEqual(err.name, "PermissionDenied");
+});
diff --git a/js/stat_test.ts b/js/stat_test.ts
index c811b23ab4..424077dc5a 100644
--- a/js/stat_test.ts
+++ b/js/stat_test.ts
@@ -123,4 +123,3 @@ test(async function lstatNotFound() {
assert(caughtError);
assertEqual(badInfo, undefined);
});
-
diff --git a/src/handlers.rs b/src/handlers.rs
index 2ea00391e4..18152c682a 100644
--- a/src/handlers.rs
+++ b/src/handlers.rs
@@ -12,6 +12,7 @@ use hyper::Client;
use libdeno;
use libdeno::{deno_buf, DenoC};
use msg;
+use remove_dir_all::remove_dir_all;
use std;
use std::fs;
use std::path::Path;
@@ -50,6 +51,7 @@ pub extern "C" fn msg_from_js(d: *const DenoC, buf: deno_buf) {
msg::Any::TimerClear => handle_timer_clear,
msg::Any::MakeTempDir => handle_make_temp_dir,
msg::Any::Mkdir => handle_mkdir,
+ msg::Any::Remove => handle_remove,
msg::Any::ReadFile => handle_read_file,
msg::Any::RenameSync => handle_rename_sync,
msg::Any::SetEnv => handle_set_env,
@@ -435,6 +437,32 @@ fn handle_mkdir(d: *const DenoC, base: &msg::Base) -> Box {
}()))
}
+fn handle_remove(d: *const DenoC, base: &msg::Base) -> Box {
+ let msg = base.msg_as_remove().unwrap();
+ let path = msg.path().unwrap();
+ let recursive = msg.recursive();
+ let deno = from_c(d);
+ if !deno.flags.allow_write {
+ return odd_future(permission_denied());
+ }
+ // TODO Use tokio_threadpool.
+ Box::new(futures::future::result(|| -> OpResult {
+ debug!("handle_remove {}", path);
+ let path_ = Path::new(&path);
+ let metadata = fs::metadata(&path_)?;
+ if metadata.is_file() {
+ fs::remove_file(&path_)?;
+ } else {
+ if recursive {
+ remove_dir_all(&path_)?;
+ } else {
+ fs::remove_dir(&path_)?;
+ }
+ }
+ Ok(None)
+ }()))
+}
+
// Prototype https://github.com/denoland/deno/blob/golang/os.go#L171-L184
fn handle_read_file(_d: *const DenoC, base: &msg::Base) -> Box {
let msg = base.msg_as_read_file().unwrap();
diff --git a/src/main.rs b/src/main.rs
index 68e680c719..12179ac6e3 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -10,6 +10,7 @@ extern crate url;
#[macro_use]
extern crate log;
extern crate hyper_rustls;
+extern crate remove_dir_all;
extern crate ring;
mod deno_dir;
diff --git a/src/msg.fbs b/src/msg.fbs
index f988c9d0bb..4437ad1bca 100644
--- a/src/msg.fbs
+++ b/src/msg.fbs
@@ -15,6 +15,7 @@ union Any {
MakeTempDir,
MakeTempDirRes,
Mkdir,
+ Remove,
ReadFile,
ReadFileRes,
WriteFile,
@@ -173,6 +174,11 @@ table Mkdir {
// mode specified by https://godoc.org/os#FileMode
}
+table Remove {
+ path: string;
+ recursive: bool;
+}
+
table ReadFile {
filename: string;
}