diff --git a/BUILD.gn b/BUILD.gn index 7c29e42583..259c5bdc44 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -72,6 +72,7 @@ ts_sources = [ "js/console.ts", "js/copy_file.ts", "js/deno.ts", + "js/dir.ts", "js/dispatch.ts", "js/dom_types.ts", "js/errors.ts", @@ -107,7 +108,6 @@ ts_sources = [ "js/util.ts", "js/v8_source_maps.ts", "js/write_file.ts", - "tsconfig.json", # Listing package.json and yarn.lock as sources ensures the bundle is rebuilt diff --git a/js/deno.ts b/js/deno.ts index 2125445411..565d4a7e6c 100644 --- a/js/deno.ts +++ b/js/deno.ts @@ -2,6 +2,7 @@ // Public deno module. /// export { env, exit } from "./os"; +export { chdir, cwd } from "./dir"; export { File, open, stdin, stdout, stderr, read, write, close } from "./files"; export { copy, diff --git a/js/dir.ts b/js/dir.ts new file mode 100644 index 0000000000..4bd3e6bd7c --- /dev/null +++ b/js/dir.ts @@ -0,0 +1,37 @@ +// Copyright 2018 the Deno authors. All rights reserved. MIT license. +import * as msg from "gen/msg_generated"; +import { assert } from "./util"; +import { flatbuffers } from "flatbuffers"; +import { sendSync } from "./dispatch"; + +/** + * cwd() Return a string representing the current working directory. + * If the current directory can be reached via multiple paths + * (due to symbolic links), cwd() may return + * any one of them. + * throws NotFound exception if directory not available + */ +export function cwd(): string { + const builder = new flatbuffers.Builder(0); + msg.Cwd.startCwd(builder); + const inner = msg.Cwd.endCwd(builder); + const baseRes = sendSync(builder, msg.Any.Cwd, inner); + assert(baseRes != null); + assert(msg.Any.CwdRes === baseRes!.innerType()); + const res = new msg.CwdRes(); + assert(baseRes!.inner(res) != null); + return res.cwd()!; +} + +/** + * chdir() Change the current working directory to path. + * throws NotFound exception if directory not available + */ +export function chdir(directory: string): void { + const builder = new flatbuffers.Builder(); + const directory_ = builder.createString(directory); + msg.Chdir.startChdir(builder); + msg.Chdir.addDirectory(builder, directory_); + const inner = msg.Chdir.endChdir(builder); + sendSync(builder, msg.Any.Chdir, inner); +} diff --git a/js/dir_test.ts b/js/dir_test.ts new file mode 100644 index 0000000000..ff55a0e696 --- /dev/null +++ b/js/dir_test.ts @@ -0,0 +1,54 @@ +import { test, testPerm, assert, assertEqual } from "./test_util.ts"; +import * as deno from "deno"; + +test(function dirCwdNotNull() { + assert(deno.cwd() != null); +}); + +testPerm({ write: true }, function dirCwdChdirSuccess() { + const initialdir = deno.cwd(); + const path = deno.makeTempDirSync(); + deno.chdir(path); + const current = deno.cwd(); + if (deno.platform.os === "mac") { + assertEqual(current, "/private" + path); + } else { + assertEqual(current, path); + } + deno.chdir(initialdir); +}); + +testPerm({ write: true }, function dirCwdError() { + // excluding windows since it throws resource busy, while removeSync + if (["linux", "mac"].includes(deno.platform.os)) { + const initialdir = deno.cwd(); + const path = deno.makeTempDirSync(); + deno.chdir(path); + deno.removeSync(path); + try { + deno.cwd(); + throw Error("current directory removed, should throw error"); + } catch (err) { + if (err instanceof deno.DenoError) { + console.log(err.name === "NotFound"); + } else { + throw Error("raised different exception"); + } + } + deno.chdir(initialdir); + } +}); + +testPerm({ write: true }, function dirChdirError() { + const path = deno.makeTempDirSync() + "test"; + try { + deno.chdir(path); + throw Error("directory not available, should throw error"); + } catch (err) { + if (err instanceof deno.DenoError) { + console.log(err.name === "NotFound"); + } else { + throw Error("raised different exception"); + } + } +}); diff --git a/js/unit_tests.ts b/js/unit_tests.ts index 24fdac8234..e09d0aba02 100644 --- a/js/unit_tests.ts +++ b/js/unit_tests.ts @@ -11,6 +11,7 @@ import "./read_dir_test.ts"; import "./write_file_test.ts"; import "./copy_file_test.ts"; import "./mkdir_test.ts"; +import "./dir_test"; import "./make_temp_dir_test.ts"; import "./stat_test.ts"; import "./rename_test.ts"; diff --git a/src/msg.fbs b/src/msg.fbs index 43fde4d688..5b60213ade 100644 --- a/src/msg.fbs +++ b/src/msg.fbs @@ -41,6 +41,9 @@ union Any { Accept, Dial, NewConn, + Chdir, + Cwd, + CwdRes, Metrics, MetricsRes, } @@ -93,6 +96,12 @@ enum ErrorKind: byte { HttpOther, } +table Cwd {} + +table CwdRes { + cwd: string; +} + table Base { cmd_id: uint32; sync: bool = true; // TODO(ry) Change default to false. @@ -135,6 +144,10 @@ table CodeCache { output_code: string; } +table Chdir { + directory: string; +} + table SetTimeout { timeout: double; } diff --git a/src/ops.rs b/src/ops.rs index a58532bafc..261ed67f5d 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -101,6 +101,8 @@ pub fn dispatch( msg::Any::Listen => op_listen, msg::Any::Accept => op_accept, msg::Any::Dial => op_dial, + msg::Any::Chdir => op_chdir, + msg::Any::Cwd => op_cwd, msg::Any::Metrics => op_metrics, _ => panic!(format!( "Unhandled message {}", @@ -110,8 +112,7 @@ pub fn dispatch( op_creator(isolate.state.clone(), &base, data) }; - let boxed_op = Box::new( - op.or_else(move |err: DenoError| -> DenoResult { + let boxed_op = Box::new(op.or_else(move |err: DenoError| -> DenoResult { debug!("op err {}", err); // No matter whether we got an Err or Ok, we want a serialized message to // send back. So transform the DenoError into a deno_buf. @@ -143,8 +144,7 @@ pub fn dispatch( ) }; Ok(buf) - }), - ); + })); debug!( "msg_from_js {} sync {}", @@ -283,6 +283,20 @@ fn op_code_cache( }())) } +fn op_chdir( + _state: Arc, + base: &msg::Base, + data: &'static mut [u8], +) -> Box { + assert_eq!(data.len(), 0); + let inner = base.inner_as_chdir().unwrap(); + let directory = inner.directory().unwrap(); + Box::new(futures::future::result(|| -> OpResult { + let _result = std::env::set_current_dir(&directory)?; + Ok(empty_buf()) + }())) +} + fn op_set_timeout( isolate: &mut Isolate, base: &msg::Base, @@ -811,6 +825,37 @@ fn get_mode(_perm: fs::Permissions) -> u32 { 0 } +fn op_cwd( + _state: Arc, + base: &msg::Base, + data: &'static mut [u8], +) -> Box { + assert_eq!(data.len(), 0); + let cmd_id = base.cmd_id(); + Box::new(futures::future::result(|| -> OpResult { + let path = std::env::current_dir()?; + let builder = &mut FlatBufferBuilder::new(); + let cwd = + builder.create_string(&path.into_os_string().into_string().unwrap()); + let inner = msg::CwdRes::create( + builder, + &msg::CwdResArgs { + cwd: Some(cwd), + ..Default::default() + }, + ); + Ok(serialize_response( + cmd_id, + builder, + msg::BaseArgs { + inner: Some(inner.as_union_value()), + inner_type: msg::Any::CwdRes, + ..Default::default() + }, + )) + }())) +} + fn op_stat( _config: Arc, base: &msg::Base,