0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-03-03 09:31:22 -05:00

feat(std/node) add appendFile and appendFileSync (#4294)

This commit is contained in:
Chris Knight 2020-03-08 23:14:53 +00:00 committed by GitHub
parent 6f0b70eb1e
commit 1b6fc87b71
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 397 additions and 0 deletions

View file

@ -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;
}

View file

@ -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);
}
});

View file

@ -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
);
}

View file

@ -1,8 +1,13 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import {
notImplemented,
intoCallbackAPIWithIntercept,
MaybeEmpty
} from "./_utils.ts";
import { appendFile, appendFileSync } from "./_fs/_fs_appendFile.ts";
export { appendFile, appendFileSync };
const {
readFile: denoReadFile,
readFileSync: denoReadFileSync,
@ -128,3 +133,18 @@ export function readlinkSync(
): string | Uint8Array {
return maybeEncode(denoReadlinkSync(path), getEncoding(opt));
}
/** Revist once https://github.com/denoland/deno/issues/4017 lands */
export function access(
path: string, // eslint-disable-line @typescript-eslint/no-unused-vars
modeOrCallback: number | Function, // eslint-disable-line @typescript-eslint/no-unused-vars
callback?: Function // eslint-disable-line @typescript-eslint/no-unused-vars
): void {
notImplemented("Not yet available");
}
/** Revist once https://github.com/denoland/deno/issues/4017 lands */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function accessSync(path: string, mode?: number): undefined {
notImplemented("Not yet available");
}