diff options
Diffstat (limited to 'std/node/_fs')
-rw-r--r-- | std/node/_fs/_fs_appendFile.ts | 193 | ||||
-rw-r--r-- | std/node/_fs/_fs_appendFile_test.ts | 166 | ||||
-rw-r--r-- | std/node/_fs/_fs_common.ts | 18 |
3 files changed, 377 insertions, 0 deletions
diff --git a/std/node/_fs/_fs_appendFile.ts b/std/node/_fs/_fs_appendFile.ts new file mode 100644 index 000000000..962f44884 --- /dev/null +++ b/std/node/_fs/_fs_appendFile.ts @@ -0,0 +1,193 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { FileOptions, isFileOptions } from "./_fs_common.ts"; +import { notImplemented } from "../_utils.ts"; + +/** + * TODO: Also accept 'data' parameter as a Node polyfill Buffer type once this + * is implemented. See https://github.com/denoland/deno/issues/3403 + */ +export async function appendFile( + pathOrRid: string | number, + data: string, + optionsOrCallback: string | FileOptions | Function, + callback?: Function +): Promise<void> { + const callbackFn: Function | undefined = + optionsOrCallback instanceof Function ? optionsOrCallback : callback; + const options: string | FileOptions | undefined = + optionsOrCallback instanceof Function ? undefined : optionsOrCallback; + if (!callbackFn) { + throw new Error("No callback function supplied"); + } + + validateEncoding(options); + + let rid = -1; + try { + if (typeof pathOrRid === "number") { + rid = pathOrRid; + } else { + const mode: number | undefined = isFileOptions(options) + ? options.mode + : undefined; + const flag: string | undefined = isFileOptions(options) + ? options.flag + : undefined; + + if (mode) { + //TODO rework once https://github.com/denoland/deno/issues/4017 completes + notImplemented("Deno does not yet support setting mode on create"); + } + + const file = await Deno.open(pathOrRid, getOpenOptions(flag)); + rid = file.rid; + } + + const buffer: Uint8Array = new TextEncoder().encode(data); + + await Deno.write(rid, buffer); + callbackFn(); + } catch (err) { + callbackFn(err); + } finally { + if (typeof pathOrRid === "string" && rid != -1) { + //Only close if a path was supplied and a rid allocated + Deno.close(rid); + } + } +} + +/** + * TODO: Also accept 'data' parameter as a Node polyfill Buffer type once this + * is implemented. See https://github.com/denoland/deno/issues/3403 + */ +export function appendFileSync( + pathOrRid: string | number, + data: string, + options?: string | FileOptions +): void { + let rid = -1; + + validateEncoding(options); + + try { + if (typeof pathOrRid === "number") { + rid = pathOrRid; + } else { + const mode: number | undefined = isFileOptions(options) + ? options.mode + : undefined; + const flag: string | undefined = isFileOptions(options) + ? options.flag + : undefined; + + if (mode) { + // TODO rework once https://github.com/denoland/deno/issues/4017 completes + notImplemented("Deno does not yet support setting mode on create"); + } + + const file = Deno.openSync(pathOrRid, getOpenOptions(flag)); + rid = file.rid; + } + + const buffer: Uint8Array = new TextEncoder().encode(data); + + Deno.writeSync(rid, buffer); + } finally { + if (typeof pathOrRid === "string" && rid != -1) { + //Only close if a 'string' path was supplied and a rid allocated + Deno.close(rid); + } + } +} + +function validateEncoding( + encodingOption: string | FileOptions | undefined +): void { + if (!encodingOption) return; + + if (typeof encodingOption === "string") { + if (encodingOption !== "utf8") { + throw new Error("Only 'utf8' encoding is currently supported"); + } + } else if (encodingOption.encoding && encodingOption.encoding !== "utf8") { + throw new Error("Only 'utf8' encoding is currently supported"); + } +} + +function getOpenOptions(flag: string | undefined): Deno.OpenOptions { + if (!flag) { + return { create: true, append: true }; + } + + let openOptions: Deno.OpenOptions; + switch (flag) { + case "a": { + // 'a': Open file for appending. The file is created if it does not exist. + openOptions = { create: true, append: true }; + break; + } + case "ax": { + // 'ax': Like 'a' but fails if the path exists. + openOptions = { createNew: true, write: true, append: true }; + break; + } + case "a+": { + // 'a+': Open file for reading and appending. The file is created if it does not exist. + openOptions = { read: true, create: true, append: true }; + break; + } + case "ax+": { + // 'ax+': Like 'a+' but fails if the path exists. + openOptions = { read: true, createNew: true, append: true }; + break; + } + case "r": { + // 'r': Open file for reading. An exception occurs if the file does not exist. + openOptions = { read: true }; + break; + } + case "r+": { + // 'r+': Open file for reading and writing. An exception occurs if the file does not exist. + openOptions = { read: true, write: true }; + break; + } + case "w": { + // 'w': Open file for writing. The file is created (if it does not exist) or truncated (if it exists). + openOptions = { create: true, write: true, truncate: true }; + break; + } + case "wx": { + // 'wx': Like 'w' but fails if the path exists. + openOptions = { createNew: true, write: true }; + break; + } + case "w+": { + // 'w+': Open file for reading and writing. The file is created (if it does not exist) or truncated (if it exists). + openOptions = { create: true, write: true, truncate: true, read: true }; + break; + } + case "wx+": { + // 'wx+': Like 'w+' but fails if the path exists. + openOptions = { createNew: true, write: true, read: true }; + break; + } + case "as": { + // 'as': Open file for appending in synchronous mode. The file is created if it does not exist. + openOptions = { create: true, append: true }; + } + case "as+": { + // 'as+': Open file for reading and appending in synchronous mode. The file is created if it does not exist. + openOptions = { create: true, read: true, append: true }; + } + case "rs+": { + // 'rs+': Open file for reading and writing in synchronous mode. Instructs the operating system to bypass the local file system cache. + openOptions = { create: true, read: true, write: true }; + } + default: { + throw new Error(`Unrecognized file system flag: ${flag}`); + } + } + + return openOptions; +} diff --git a/std/node/_fs/_fs_appendFile_test.ts b/std/node/_fs/_fs_appendFile_test.ts new file mode 100644 index 000000000..dcea69783 --- /dev/null +++ b/std/node/_fs/_fs_appendFile_test.ts @@ -0,0 +1,166 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +const { test } = Deno; +import { + assertEquals, + assert, + assertThrows, + assertThrowsAsync +} from "../../testing/asserts.ts"; +import { appendFile, appendFileSync } from "./_fs_appendFile.ts"; + +const decoder = new TextDecoder("utf-8"); + +test({ + name: "No callback Fn results in Error", + async fn() { + await assertThrowsAsync( + async () => { + await appendFile("some/path", "some data", "utf8"); + }, + Error, + "No callback function supplied" + ); + } +}); + +test({ + name: "Unsupported encoding results in error()", + async fn() { + await assertThrowsAsync( + async () => { + await appendFile( + "some/path", + "some data", + "made-up-encoding", + () => {} + ); + }, + Error, + "Only 'utf8' encoding is currently supported" + ); + await assertThrowsAsync( + async () => { + await appendFile( + "some/path", + "some data", + { encoding: "made-up-encoding" }, + () => {} + ); + }, + Error, + "Only 'utf8' encoding is currently supported" + ); + assertThrows( + () => appendFileSync("some/path", "some data", "made-up-encoding"), + Error, + "Only 'utf8' encoding is currently supported" + ); + assertThrows( + () => + appendFileSync("some/path", "some data", { + encoding: "made-up-encoding" + }), + Error, + "Only 'utf8' encoding is currently supported" + ); + } +}); + +test({ + name: "Async: Data is written to passed in rid", + async fn() { + const tempFile: string = await Deno.makeTempFile(); + const file: Deno.File = await Deno.open(tempFile, { + create: true, + write: true, + read: true + }); + let calledBack = false; + await appendFile(file.rid, "hello world", () => { + calledBack = true; + }); + assert(calledBack); + Deno.close(file.rid); + const data = await Deno.readFile(tempFile); + assertEquals(decoder.decode(data), "hello world"); + await Deno.remove(tempFile); + } +}); + +test({ + name: "Async: Data is written to passed in file path", + async fn() { + let calledBack = false; + const openResourcesBeforeAppend: Deno.ResourceMap = Deno.resources(); + await appendFile("_fs_appendFile_test_file.txt", "hello world", () => { + calledBack = true; + }); + assert(calledBack); + assertEquals(Deno.resources(), openResourcesBeforeAppend); + const data = await Deno.readFile("_fs_appendFile_test_file.txt"); + assertEquals(decoder.decode(data), "hello world"); + await Deno.remove("_fs_appendFile_test_file.txt"); + } +}); + +test({ + name: + "Async: Callback is made with error if attempting to append data to an existing file with 'ax' flag", + async fn() { + let calledBack = false; + const openResourcesBeforeAppend: Deno.ResourceMap = Deno.resources(); + const tempFile: string = await Deno.makeTempFile(); + await appendFile(tempFile, "hello world", { flag: "ax" }, (err: Error) => { + calledBack = true; + assert(err); + }); + assert(calledBack); + assertEquals(Deno.resources(), openResourcesBeforeAppend); + await Deno.remove(tempFile); + } +}); + +test({ + name: "Sync: Data is written to passed in rid", + fn() { + const tempFile: string = Deno.makeTempFileSync(); + const file: Deno.File = Deno.openSync(tempFile, { + create: true, + write: true, + read: true + }); + appendFileSync(file.rid, "hello world"); + Deno.close(file.rid); + const data = Deno.readFileSync(tempFile); + assertEquals(decoder.decode(data), "hello world"); + Deno.removeSync(tempFile); + } +}); + +test({ + name: "Sync: Data is written to passed in file path", + fn() { + const openResourcesBeforeAppend: Deno.ResourceMap = Deno.resources(); + appendFileSync("_fs_appendFile_test_file_sync.txt", "hello world"); + assertEquals(Deno.resources(), openResourcesBeforeAppend); + const data = Deno.readFileSync("_fs_appendFile_test_file_sync.txt"); + assertEquals(decoder.decode(data), "hello world"); + Deno.removeSync("_fs_appendFile_test_file_sync.txt"); + } +}); + +test({ + name: + "Sync: error thrown if attempting to append data to an existing file with 'ax' flag", + fn() { + const openResourcesBeforeAppend: Deno.ResourceMap = Deno.resources(); + const tempFile: string = Deno.makeTempFileSync(); + assertThrows( + () => appendFileSync(tempFile, "hello world", { flag: "ax" }), + Deno.errors.AlreadyExists, + "" + ); + assertEquals(Deno.resources(), openResourcesBeforeAppend); + Deno.removeSync(tempFile); + } +}); diff --git a/std/node/_fs/_fs_common.ts b/std/node/_fs/_fs_common.ts new file mode 100644 index 000000000..4de0899ea --- /dev/null +++ b/std/node/_fs/_fs_common.ts @@ -0,0 +1,18 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +export interface FileOptions { + encoding?: string; + mode?: number; + flag?: string; +} + +export function isFileOptions( + fileOptions: string | FileOptions | undefined +): fileOptions is FileOptions { + if (!fileOptions) return false; + + return ( + (fileOptions as FileOptions).encoding != undefined || + (fileOptions as FileOptions).flag != undefined || + (fileOptions as FileOptions).mode != undefined + ); +} |