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:
parent
6f0b70eb1e
commit
1b6fc87b71
4 changed files with 397 additions and 0 deletions
193
std/node/_fs/_fs_appendFile.ts
Normal file
193
std/node/_fs/_fs_appendFile.ts
Normal 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;
|
||||
}
|
166
std/node/_fs/_fs_appendFile_test.ts
Normal file
166
std/node/_fs/_fs_appendFile_test.ts
Normal 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);
|
||||
}
|
||||
});
|
18
std/node/_fs/_fs_common.ts
Normal file
18
std/node/_fs/_fs_common.ts
Normal 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
|
||||
);
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue