diff --git a/cli/js/deno.ts b/cli/js/deno.ts index 355000ac55..2efb6920ad 100644 --- a/cli/js/deno.ts +++ b/cli/js/deno.ts @@ -99,6 +99,7 @@ export { kill } from "./ops/process.ts"; export { run, RunOptions, Process, ProcessStatus } from "./process.ts"; export { DirEntry, readdirSync, readdir } from "./ops/fs/read_dir.ts"; export { readFileSync, readFile } from "./read_file.ts"; +export { readTextFileSync, readTextFile } from "./read_text_file.ts"; export { readlinkSync, readlink } from "./ops/fs/read_link.ts"; export { realpathSync, realpath } from "./ops/fs/realpath.ts"; export { removeSync, remove, RemoveOptions } from "./ops/fs/remove.ts"; @@ -114,6 +115,7 @@ export { umask } from "./ops/fs/umask.ts"; export { utimeSync, utime } from "./ops/fs/utime.ts"; export { version } from "./version.ts"; export { writeFileSync, writeFile, WriteFileOptions } from "./write_file.ts"; +export { writeTextFileSync, writeTextFile } from "./write_text_file.ts"; export const args: string[] = []; export { TestDefinition, test } from "./testing.ts"; diff --git a/cli/js/lib.deno.ns.d.ts b/cli/js/lib.deno.ns.d.ts index d33fde8449..6d354d0342 100644 --- a/cli/js/lib.deno.ns.d.ts +++ b/cli/js/lib.deno.ns.d.ts @@ -1276,6 +1276,25 @@ declare namespace Deno { * Requires `allow-read` and `allow-write` permission. */ export function rename(oldpath: string, newpath: string): Promise; + /** Synchronously reads and returns the entire contents of a file as utf8 encoded string + * encoded string. Reading a directory returns an empty string. + * + * const data = Deno.readTextFileSync("hello.txt"); + * console.log(data); + * + * Requires `allow-read` permission. */ + export function readTextFileSync(path: string): string; + + /** Asynchronously reads and returns the entire contents of a file as a utf8 + * encoded string. Reading a directory returns an empty data array. + * + * const decoder = new TextDecoder("utf-8"); + * const data = Deno.readFileSync("hello.txt"); + * console.log(decoder.decode(data)); + * + * Requires `allow-read` permission. */ + export function readTextFile(path: string): Promise; + /** Synchronously reads and returns the entire contents of a file as an array * of bytes. `TextDecoder` can be used to transform the bytes to string if * required. Reading a directory returns an empty data array. @@ -1598,6 +1617,24 @@ declare namespace Deno { options?: WriteFileOptions ): Promise; + /** Synchronously write string `data` to the given `path`, by default creating a new file if needed, + * else overwriting. + * + * await Deno.writeTextFileSync("hello1.txt", "Hello world\n"); // overwrite "hello1.txt" or create it + * + * Requires `allow-write` permission, and `allow-read` if `options.create` is `false`. + */ + export function writeTextFileSync(path: string, data: string): void; + + /** Asynchronously write string `data` to the given `path`, by default creating a new file if needed, + * else overwriting. + * + * await Deno.writeTextFile("hello1.txt", "Hello world\n"); // overwrite "hello1.txt" or create it + * + * Requires `allow-write` permission, and `allow-read` if `options.create` is `false`. + */ + export function writeTextFile(path: string, data: string): Promise; + /** **UNSTABLE**: Should not have same name as `window.location` type. */ interface Location { /** The full url for the module, e.g. `file://some/file.ts` or diff --git a/cli/js/read_text_file.ts b/cli/js/read_text_file.ts new file mode 100644 index 0000000000..3423b26b88 --- /dev/null +++ b/cli/js/read_text_file.ts @@ -0,0 +1,18 @@ +import { open, openSync } from "./files.ts"; +import { readAll, readAllSync } from "./buffer.ts"; + +export function readTextFileSync(path: string): string { + const decoder = new TextDecoder(); + const file = openSync(path); + const content = readAllSync(file); + file.close(); + return decoder.decode(content); +} + +export async function readTextFile(path: string): Promise { + const decoder = new TextDecoder(); + const file = await open(path); + const content = await readAll(file); + file.close(); + return decoder.decode(content); +} diff --git a/cli/js/tests/read_text_file_test.ts b/cli/js/tests/read_text_file_test.ts new file mode 100644 index 0000000000..3e7493e4a2 --- /dev/null +++ b/cli/js/tests/read_text_file_test.ts @@ -0,0 +1,61 @@ +import { unitTest, assert, assertEquals } from "./test_util.ts"; + +unitTest({ perms: { read: true } }, function readTextFileSyncSuccess(): void { + const data = Deno.readTextFileSync("cli/tests/fixture.json"); + assert(data.length > 0); + const pkg = JSON.parse(data); + assertEquals(pkg.name, "deno"); +}); + +unitTest({ perms: { read: false } }, function readTextFileSyncPerm(): void { + let caughtError = false; + try { + Deno.readTextFileSync("cli/tests/fixture.json"); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.PermissionDenied); + } + assert(caughtError); +}); + +unitTest({ perms: { read: true } }, function readTextFileSyncNotFound(): void { + let caughtError = false; + let data; + try { + data = Deno.readTextFileSync("bad_filename"); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.NotFound); + } + assert(caughtError); + assert(data === undefined); +}); + +unitTest( + { perms: { read: true } }, + async function readTextFileSuccess(): Promise { + const data = await Deno.readTextFile("cli/tests/fixture.json"); + assert(data.length > 0); + const pkg = JSON.parse(data); + assertEquals(pkg.name, "deno"); + } +); + +unitTest({ perms: { read: false } }, async function readTextFilePerm(): Promise< + void +> { + let caughtError = false; + try { + await Deno.readTextFile("cli/tests/fixture.json"); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.PermissionDenied); + } + assert(caughtError); +}); + +unitTest({ perms: { read: true } }, function readTextFileSyncLoop(): void { + for (let i = 0; i < 256; i++) { + Deno.readTextFileSync("cli/tests/fixture.json"); + } +}); diff --git a/cli/js/tests/unit_tests.ts b/cli/js/tests/unit_tests.ts index 627832ca9d..dd4abf1164 100644 --- a/cli/js/tests/unit_tests.ts +++ b/cli/js/tests/unit_tests.ts @@ -44,6 +44,7 @@ import "./permissions_test.ts"; import "./process_test.ts"; import "./realpath_test.ts"; import "./read_dir_test.ts"; +import "./read_text_file_test.ts"; import "./read_file_test.ts"; import "./read_link_test.ts"; import "./remove_test.ts"; @@ -64,5 +65,6 @@ import "./url_test.ts"; import "./url_search_params_test.ts"; import "./utime_test.ts"; import "./write_file_test.ts"; +import "./write_text_file_test.ts"; import "./performance_test.ts"; import "./version_test.ts"; diff --git a/cli/js/tests/write_text_file_test.ts b/cli/js/tests/write_text_file_test.ts new file mode 100644 index 0000000000..321189b0e2 --- /dev/null +++ b/cli/js/tests/write_text_file_test.ts @@ -0,0 +1,79 @@ +import { unitTest, assert, assertEquals } from "./test_util.ts"; + +unitTest( + { perms: { read: true, write: true } }, + function writeTextFileSyncSuccess(): void { + const filename = Deno.makeTempDirSync() + "/test.txt"; + Deno.writeTextFileSync(filename, "Hello"); + const dataRead = Deno.readTextFileSync(filename); + assertEquals("Hello", dataRead); + } +); + +unitTest({ perms: { write: true } }, function writeTextFileSyncFail(): void { + const filename = "/baddir/test.txt"; + // The following should fail because /baddir doesn't exist (hopefully). + let caughtError = false; + try { + Deno.writeTextFileSync(filename, "hello"); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.NotFound); + } + assert(caughtError); +}); + +unitTest({ perms: { write: false } }, function writeTextFileSyncPerm(): void { + const filename = "/baddir/test.txt"; + // The following should fail due to no write permission + let caughtError = false; + try { + Deno.writeTextFileSync(filename, "Hello"); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.PermissionDenied); + } + assert(caughtError); +}); + +unitTest( + { perms: { read: true, write: true } }, + async function writeTextFileSuccess(): Promise { + const filename = Deno.makeTempDirSync() + "/test.txt"; + await Deno.writeTextFile(filename, "Hello"); + const dataRead = Deno.readTextFileSync(filename); + assertEquals("Hello", dataRead); + } +); + +unitTest( + { perms: { read: true, write: true } }, + async function writeTextFileNotFound(): Promise { + const filename = "/baddir/test.txt"; + // The following should fail because /baddir doesn't exist (hopefully). + let caughtError = false; + try { + await Deno.writeTextFile(filename, "Hello"); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.NotFound); + } + assert(caughtError); + } +); + +unitTest( + { perms: { write: false } }, + async function writeTextFilePerm(): Promise { + const filename = "/baddir/test.txt"; + // The following should fail due to no write permission + let caughtError = false; + try { + await Deno.writeTextFile(filename, "Hello"); + } catch (e) { + caughtError = true; + assert(e instanceof Deno.errors.PermissionDenied); + } + assert(caughtError); + } +); diff --git a/cli/js/write_text_file.ts b/cli/js/write_text_file.ts new file mode 100644 index 0000000000..d2a6575d7f --- /dev/null +++ b/cli/js/write_text_file.ts @@ -0,0 +1,18 @@ +import { open, openSync } from "./files.ts"; +import { writeAll, writeAllSync } from "./buffer.ts"; + +export function writeTextFileSync(path: string, data: string): void { + const file = openSync(path, { write: true, create: true, truncate: true }); + const enc = new TextEncoder(); + const contents = enc.encode(data); + writeAllSync(file, contents); + file.close(); +} + +export async function writeTextFile(path: string, data: string): Promise { + const file = await open(path, { write: true, create: true, truncate: true }); + const enc = new TextEncoder(); + const contents = enc.encode(data); + await writeAll(file, contents); + file.close(); +}