mirror of
https://github.com/denoland/deno.git
synced 2025-01-11 16:42:21 -05:00
feat(std/fs/node): adding some functions (#7921)
This commit is contained in:
parent
f75bd89aff
commit
5bed06fb94
17 changed files with 1403 additions and 0 deletions
59
std/node/_fs/_fs_lstat.ts
Normal file
59
std/node/_fs/_fs_lstat.ts
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import {
|
||||||
|
BigIntStats,
|
||||||
|
CFISBIS,
|
||||||
|
statCallback,
|
||||||
|
statCallbackBigInt,
|
||||||
|
statOptions,
|
||||||
|
Stats,
|
||||||
|
} from "./_fs_stat.ts";
|
||||||
|
|
||||||
|
export function lstat(path: string | URL, callback: statCallback): void;
|
||||||
|
export function lstat(
|
||||||
|
path: string | URL,
|
||||||
|
options: { bigint: false },
|
||||||
|
callback: statCallback,
|
||||||
|
): void;
|
||||||
|
export function lstat(
|
||||||
|
path: string | URL,
|
||||||
|
options: { bigint: true },
|
||||||
|
callback: statCallbackBigInt,
|
||||||
|
): void;
|
||||||
|
export function lstat(
|
||||||
|
path: string | URL,
|
||||||
|
optionsOrCallback: statCallback | statCallbackBigInt | statOptions,
|
||||||
|
maybeCallback?: statCallback | statCallbackBigInt,
|
||||||
|
) {
|
||||||
|
const callback =
|
||||||
|
(typeof optionsOrCallback === "function"
|
||||||
|
? optionsOrCallback
|
||||||
|
: maybeCallback) as (
|
||||||
|
err: Error | undefined,
|
||||||
|
stat: BigIntStats | Stats,
|
||||||
|
) => void;
|
||||||
|
const options = typeof optionsOrCallback === "object"
|
||||||
|
? optionsOrCallback
|
||||||
|
: { bigint: false };
|
||||||
|
|
||||||
|
if (!callback) throw new Error("No callback function supplied");
|
||||||
|
|
||||||
|
Deno.lstat(path)
|
||||||
|
.then((stat) => callback(undefined, CFISBIS(stat, options.bigint)))
|
||||||
|
.catch((err) => callback(err, err));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function lstatSync(path: string | URL): Stats;
|
||||||
|
export function lstatSync(
|
||||||
|
path: string | URL,
|
||||||
|
options: { bigint: false },
|
||||||
|
): Stats;
|
||||||
|
export function lstatSync(
|
||||||
|
path: string | URL,
|
||||||
|
options: { bigint: true },
|
||||||
|
): BigIntStats;
|
||||||
|
export function lstatSync(
|
||||||
|
path: string | URL,
|
||||||
|
options?: statOptions,
|
||||||
|
): Stats | BigIntStats {
|
||||||
|
const origin = Deno.lstatSync(path);
|
||||||
|
return CFISBIS(origin, options?.bigint || false);
|
||||||
|
}
|
56
std/node/_fs/_fs_lstat_test.ts
Normal file
56
std/node/_fs/_fs_lstat_test.ts
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import { lstat, lstatSync } from "./_fs_lstat.ts";
|
||||||
|
import { fail } from "../../testing/asserts.ts";
|
||||||
|
import { assertStats, assertStatsBigInt } from "./_fs_stat_test.ts";
|
||||||
|
import type { BigIntStats, Stats } from "./_fs_stat.ts";
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "ASYNC: get a file Stats (lstat)",
|
||||||
|
async fn() {
|
||||||
|
const file = Deno.makeTempFileSync();
|
||||||
|
await new Promise<Stats>((resolve, reject) => {
|
||||||
|
lstat(file, (err, stat) => {
|
||||||
|
if (err) reject(err);
|
||||||
|
resolve(stat);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then((stat) => {
|
||||||
|
assertStats(stat, Deno.lstatSync(file));
|
||||||
|
})
|
||||||
|
.catch(() => fail())
|
||||||
|
.finally(() => {
|
||||||
|
Deno.removeSync(file);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "SYNC: get a file Stats (lstat)",
|
||||||
|
fn() {
|
||||||
|
const file = Deno.makeTempFileSync();
|
||||||
|
assertStats(lstatSync(file), Deno.lstatSync(file));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "ASYNC: get a file BigInt Stats (lstat)",
|
||||||
|
async fn() {
|
||||||
|
const file = Deno.makeTempFileSync();
|
||||||
|
await new Promise<BigIntStats>((resolve, reject) => {
|
||||||
|
lstat(file, { bigint: true }, (err, stat) => {
|
||||||
|
if (err) reject(err);
|
||||||
|
resolve(stat);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then((stat) => assertStatsBigInt(stat, Deno.lstatSync(file)))
|
||||||
|
.catch(() => fail())
|
||||||
|
.finally(() => Deno.removeSync(file));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "SYNC: BigInt Stats (lstat)",
|
||||||
|
fn() {
|
||||||
|
const file = Deno.makeTempFileSync();
|
||||||
|
assertStatsBigInt(lstatSync(file, { bigint: true }), Deno.lstatSync(file));
|
||||||
|
},
|
||||||
|
});
|
103
std/node/_fs/_fs_open.ts
Normal file
103
std/node/_fs/_fs_open.ts
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
import { existsSync } from "../../fs/mod.ts";
|
||||||
|
import { fromFileUrl } from "../path.ts";
|
||||||
|
import { getOpenOptions } from "./_fs_common.ts";
|
||||||
|
|
||||||
|
type openFlags =
|
||||||
|
| "a"
|
||||||
|
| "ax"
|
||||||
|
| "a+"
|
||||||
|
| "ax+"
|
||||||
|
| "as"
|
||||||
|
| "as+"
|
||||||
|
| "r"
|
||||||
|
| "r+"
|
||||||
|
| "rs+"
|
||||||
|
| "w"
|
||||||
|
| "wx"
|
||||||
|
| "w+"
|
||||||
|
| "wx+";
|
||||||
|
|
||||||
|
type openCallback = (err: Error | undefined, fd: number) => void;
|
||||||
|
|
||||||
|
function convertFlagAndModeToOptions(
|
||||||
|
flag?: openFlags,
|
||||||
|
mode?: number,
|
||||||
|
): Deno.OpenOptions | undefined {
|
||||||
|
if (!flag && !mode) return undefined;
|
||||||
|
if (!flag && mode) return { mode };
|
||||||
|
return { ...getOpenOptions(flag), mode };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function open(path: string | URL, callback: openCallback): void;
|
||||||
|
export function open(
|
||||||
|
path: string | URL,
|
||||||
|
flags: openFlags,
|
||||||
|
callback: openCallback,
|
||||||
|
): void;
|
||||||
|
export function open(
|
||||||
|
path: string | URL,
|
||||||
|
flags: openFlags,
|
||||||
|
mode: number,
|
||||||
|
callback: openCallback,
|
||||||
|
): void;
|
||||||
|
export function open(
|
||||||
|
path: string | URL,
|
||||||
|
flagsOrCallback: openCallback | openFlags,
|
||||||
|
callbackOrMode?: openCallback | number,
|
||||||
|
maybeCallback?: openCallback,
|
||||||
|
) {
|
||||||
|
const flags = typeof flagsOrCallback === "string"
|
||||||
|
? flagsOrCallback
|
||||||
|
: undefined;
|
||||||
|
const callback = typeof flagsOrCallback === "function"
|
||||||
|
? flagsOrCallback
|
||||||
|
: typeof callbackOrMode === "function"
|
||||||
|
? callbackOrMode
|
||||||
|
: maybeCallback;
|
||||||
|
const mode = typeof callbackOrMode === "number" ? callbackOrMode : undefined;
|
||||||
|
path = path instanceof URL ? fromFileUrl(path) : path;
|
||||||
|
|
||||||
|
if (!callback) throw new Error("No callback function supplied");
|
||||||
|
|
||||||
|
if (["ax", "ax+", "wx", "wx+"].includes(flags || "") && existsSync(path)) {
|
||||||
|
const err = new Error(`EEXIST: file already exists, open '${path}'`);
|
||||||
|
callback(err, 0);
|
||||||
|
} else {
|
||||||
|
if (flags === "as" || flags === "as+") {
|
||||||
|
try {
|
||||||
|
const res = openSync(path, flags, mode);
|
||||||
|
callback(undefined, res);
|
||||||
|
} catch (error) {
|
||||||
|
callback(error, error);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Deno.open(path, convertFlagAndModeToOptions(flags, mode))
|
||||||
|
.then((file) => callback(undefined, file.rid))
|
||||||
|
.catch((err) => callback(err, err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function openSync(path: string | URL): number;
|
||||||
|
export function openSync(path: string | URL, flags?: openFlags): number;
|
||||||
|
export function openSync(path: string | URL, mode?: number): number;
|
||||||
|
export function openSync(
|
||||||
|
path: string | URL,
|
||||||
|
flags?: openFlags,
|
||||||
|
mode?: number,
|
||||||
|
): number;
|
||||||
|
export function openSync(
|
||||||
|
path: string | URL,
|
||||||
|
flagsOrMode?: openFlags | number,
|
||||||
|
maybeMode?: number,
|
||||||
|
) {
|
||||||
|
const flags = typeof flagsOrMode === "string" ? flagsOrMode : undefined;
|
||||||
|
const mode = typeof flagsOrMode === "number" ? flagsOrMode : maybeMode;
|
||||||
|
path = path instanceof URL ? fromFileUrl(path) : path;
|
||||||
|
|
||||||
|
if (["ax", "ax+", "wx", "wx+"].includes(flags || "") && existsSync(path)) {
|
||||||
|
throw new Error(`EEXIST: file already exists, open '${path}'`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Deno.openSync(path, convertFlagAndModeToOptions(flags, mode)).rid;
|
||||||
|
}
|
209
std/node/_fs/_fs_open_test.ts
Normal file
209
std/node/_fs/_fs_open_test.ts
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
import {
|
||||||
|
assert,
|
||||||
|
assertEquals,
|
||||||
|
assertThrows,
|
||||||
|
fail,
|
||||||
|
} from "../../testing/asserts.ts";
|
||||||
|
import { open, openSync } from "./_fs_open.ts";
|
||||||
|
import { join, parse } from "../../path/mod.ts";
|
||||||
|
import { existsSync } from "../../fs/mod.ts";
|
||||||
|
import { closeSync } from "./_fs_close.ts";
|
||||||
|
|
||||||
|
const temp_dir = parse(Deno.makeTempFileSync()).dir;
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "ASYNC: open file",
|
||||||
|
async fn() {
|
||||||
|
const file = Deno.makeTempFileSync();
|
||||||
|
let fd1: number;
|
||||||
|
await new Promise<number>((resolve, reject) => {
|
||||||
|
open(file, (err, fd) => {
|
||||||
|
if (err) reject(err);
|
||||||
|
resolve(fd);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then((fd) => {
|
||||||
|
fd1 = fd;
|
||||||
|
assert(Deno.resources()[fd], `${fd}`);
|
||||||
|
})
|
||||||
|
.catch(() => fail())
|
||||||
|
.finally(() => closeSync(fd1));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "SYNC: open file",
|
||||||
|
fn() {
|
||||||
|
const file = Deno.makeTempFileSync();
|
||||||
|
const fd = openSync(file);
|
||||||
|
assert(Deno.resources()[fd]);
|
||||||
|
closeSync(fd);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "open with flag 'a'",
|
||||||
|
fn() {
|
||||||
|
const file = join(temp_dir, "some_random_file");
|
||||||
|
const fd = openSync(file, "a");
|
||||||
|
assertEquals(typeof fd, "number");
|
||||||
|
assertEquals(existsSync(file), true);
|
||||||
|
assert(Deno.resources()[fd]);
|
||||||
|
closeSync(fd);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "open with flag 'ax'",
|
||||||
|
fn() {
|
||||||
|
const file = Deno.makeTempFileSync();
|
||||||
|
assertThrows(
|
||||||
|
() => {
|
||||||
|
openSync(file, "ax");
|
||||||
|
},
|
||||||
|
Error,
|
||||||
|
`EEXIST: file already exists, open '${file}'`,
|
||||||
|
);
|
||||||
|
Deno.removeSync(file);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "open with flag 'a+'",
|
||||||
|
fn() {
|
||||||
|
const file = join(temp_dir, "some_random_file2");
|
||||||
|
const fd = openSync(file, "a+");
|
||||||
|
assertEquals(typeof fd, "number");
|
||||||
|
assertEquals(existsSync(file), true);
|
||||||
|
closeSync(fd);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "open with flag 'ax+'",
|
||||||
|
fn() {
|
||||||
|
const file = Deno.makeTempFileSync();
|
||||||
|
assertThrows(
|
||||||
|
() => {
|
||||||
|
openSync(file, "ax+");
|
||||||
|
},
|
||||||
|
Error,
|
||||||
|
`EEXIST: file already exists, open '${file}'`,
|
||||||
|
);
|
||||||
|
Deno.removeSync(file);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "open with flag 'as'",
|
||||||
|
fn() {
|
||||||
|
const file = join(temp_dir, "some_random_file10");
|
||||||
|
const fd = openSync(file, "as");
|
||||||
|
assertEquals(existsSync(file), true);
|
||||||
|
assertEquals(typeof fd, "number");
|
||||||
|
closeSync(fd);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "open with flag 'as+'",
|
||||||
|
fn() {
|
||||||
|
const file = join(temp_dir, "some_random_file10");
|
||||||
|
const fd = openSync(file, "as+");
|
||||||
|
assertEquals(existsSync(file), true);
|
||||||
|
assertEquals(typeof fd, "number");
|
||||||
|
closeSync(fd);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "open with flag 'r'",
|
||||||
|
fn() {
|
||||||
|
const file = join(temp_dir, "some_random_file3");
|
||||||
|
assertThrows(() => {
|
||||||
|
openSync(file, "r");
|
||||||
|
}, Error);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "open with flag 'r+'",
|
||||||
|
fn() {
|
||||||
|
const file = join(temp_dir, "some_random_file4");
|
||||||
|
assertThrows(() => {
|
||||||
|
openSync(file, "r+");
|
||||||
|
}, Error);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "open with flag 'w'",
|
||||||
|
fn() {
|
||||||
|
const file = Deno.makeTempFileSync();
|
||||||
|
Deno.writeTextFileSync(file, "hi there");
|
||||||
|
const fd = openSync(file, "w");
|
||||||
|
assertEquals(typeof fd, "number");
|
||||||
|
assertEquals(Deno.readTextFileSync(file), "");
|
||||||
|
closeSync(fd);
|
||||||
|
|
||||||
|
const file2 = join(temp_dir, "some_random_file5");
|
||||||
|
const fd2 = openSync(file2, "w");
|
||||||
|
assertEquals(typeof fd2, "number");
|
||||||
|
assertEquals(existsSync(file2), true);
|
||||||
|
closeSync(fd2);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "open with flag 'wx'",
|
||||||
|
fn() {
|
||||||
|
const file = Deno.makeTempFileSync();
|
||||||
|
Deno.writeTextFileSync(file, "hi there");
|
||||||
|
const fd = openSync(file, "w");
|
||||||
|
assertEquals(typeof fd, "number");
|
||||||
|
assertEquals(Deno.readTextFileSync(file), "");
|
||||||
|
closeSync(fd);
|
||||||
|
|
||||||
|
const file2 = Deno.makeTempFileSync();
|
||||||
|
assertThrows(
|
||||||
|
() => {
|
||||||
|
openSync(file2, "wx");
|
||||||
|
},
|
||||||
|
Error,
|
||||||
|
`EEXIST: file already exists, open '${file2}'`,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "open with flag 'w+'",
|
||||||
|
fn() {
|
||||||
|
const file = Deno.makeTempFileSync();
|
||||||
|
Deno.writeTextFileSync(file, "hi there");
|
||||||
|
const fd = openSync(file, "w+");
|
||||||
|
assertEquals(typeof fd, "number");
|
||||||
|
assertEquals(Deno.readTextFileSync(file), "");
|
||||||
|
closeSync(fd);
|
||||||
|
|
||||||
|
const file2 = join(temp_dir, "some_random_file6");
|
||||||
|
const fd2 = openSync(file2, "w+");
|
||||||
|
assertEquals(typeof fd2, "number");
|
||||||
|
assertEquals(existsSync(file2), true);
|
||||||
|
closeSync(fd2);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "open with flag 'wx+'",
|
||||||
|
fn() {
|
||||||
|
const file = Deno.makeTempFileSync();
|
||||||
|
assertThrows(
|
||||||
|
() => {
|
||||||
|
openSync(file, "wx+");
|
||||||
|
},
|
||||||
|
Error,
|
||||||
|
`EEXIST: file already exists, open '${file}'`,
|
||||||
|
);
|
||||||
|
Deno.removeSync(file);
|
||||||
|
},
|
||||||
|
});
|
117
std/node/_fs/_fs_readdir.ts
Normal file
117
std/node/_fs/_fs_readdir.ts
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
import { asyncIterableToCallback } from "./_fs_watch.ts";
|
||||||
|
import Dirent from "./_fs_dirent.ts";
|
||||||
|
import { fromFileUrl } from "../path.ts";
|
||||||
|
|
||||||
|
function toDirent(val: Deno.DirEntry): Dirent {
|
||||||
|
return new Dirent(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
type readDirOptions = {
|
||||||
|
encoding?: string;
|
||||||
|
withFileTypes?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type readDirCallback = (err: Error | undefined, files: string[]) => void;
|
||||||
|
|
||||||
|
type readDirCallbackDirent = (err: Error | undefined, files: Dirent[]) => void;
|
||||||
|
|
||||||
|
type readDirBoth = (
|
||||||
|
err: Error | undefined,
|
||||||
|
files: string[] | Dirent[] | Array<string | Dirent>,
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
export function readdir(
|
||||||
|
path: string | URL,
|
||||||
|
options: { withFileTypes?: false; encoding?: string },
|
||||||
|
callback: readDirCallback,
|
||||||
|
): void;
|
||||||
|
export function readdir(
|
||||||
|
path: string | URL,
|
||||||
|
options: { withFileTypes: true; encoding?: string },
|
||||||
|
callback: readDirCallbackDirent,
|
||||||
|
): void;
|
||||||
|
export function readdir(path: string | URL, callback: readDirCallback): void;
|
||||||
|
export function readdir(
|
||||||
|
path: string | URL,
|
||||||
|
optionsOrCallback: readDirOptions | readDirCallback | readDirCallbackDirent,
|
||||||
|
maybeCallback?: readDirCallback | readDirCallbackDirent,
|
||||||
|
) {
|
||||||
|
const callback =
|
||||||
|
(typeof optionsOrCallback === "function"
|
||||||
|
? optionsOrCallback
|
||||||
|
: maybeCallback) as readDirBoth | undefined;
|
||||||
|
const options = typeof optionsOrCallback === "object"
|
||||||
|
? optionsOrCallback
|
||||||
|
: null;
|
||||||
|
const result: Array<string | Dirent> = [];
|
||||||
|
path = path instanceof URL ? fromFileUrl(path) : path;
|
||||||
|
|
||||||
|
if (!callback) throw new Error("No callback function supplied");
|
||||||
|
|
||||||
|
if (options?.encoding) {
|
||||||
|
try {
|
||||||
|
new TextDecoder(options.encoding);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(
|
||||||
|
`TypeError [ERR_INVALID_OPT_VALUE_ENCODING]: The value "${options.encoding}" is invalid for option "encoding"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
asyncIterableToCallback(Deno.readDir(path), (val, done) => {
|
||||||
|
if (typeof path !== "string") return;
|
||||||
|
if (done) {
|
||||||
|
callback(undefined, result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (options?.withFileTypes) {
|
||||||
|
result.push(toDirent(val));
|
||||||
|
} else result.push(decode(val.name));
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
callback(error, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function decode(str: string, encoding?: string): string {
|
||||||
|
if (!encoding) return str;
|
||||||
|
else {
|
||||||
|
const decoder = new TextDecoder(encoding);
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
return decoder.decode(encoder.encode(str));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readdirSync(
|
||||||
|
path: string | URL,
|
||||||
|
options: { withFileTypes: true; encoding?: string },
|
||||||
|
): Dirent[];
|
||||||
|
export function readdirSync(
|
||||||
|
path: string | URL,
|
||||||
|
options?: { withFileTypes?: false; encoding?: string },
|
||||||
|
): string[];
|
||||||
|
export function readdirSync(
|
||||||
|
path: string | URL,
|
||||||
|
options?: readDirOptions,
|
||||||
|
): Array<string | Dirent> {
|
||||||
|
const result = [];
|
||||||
|
path = path instanceof URL ? fromFileUrl(path) : path;
|
||||||
|
|
||||||
|
if (options?.encoding) {
|
||||||
|
try {
|
||||||
|
new TextDecoder(options.encoding);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(
|
||||||
|
`TypeError [ERR_INVALID_OPT_VALUE_ENCODING]: The value "${options.encoding}" is invalid for option "encoding"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const file of Deno.readDirSync(path)) {
|
||||||
|
if (options?.withFileTypes) {
|
||||||
|
result.push(toDirent(file));
|
||||||
|
} else result.push(decode(file.name));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
71
std/node/_fs/_fs_readdir_test.ts
Normal file
71
std/node/_fs/_fs_readdir_test.ts
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
import { assertEquals, assertNotEquals, fail } from "../../testing/asserts.ts";
|
||||||
|
import { readdir, readdirSync } from "./_fs_readdir.ts";
|
||||||
|
import { join } from "../../path/mod.ts";
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "ASYNC: reading empty directory",
|
||||||
|
async fn() {
|
||||||
|
const dir = Deno.makeTempDirSync();
|
||||||
|
await new Promise<string[]>((resolve, reject) => {
|
||||||
|
readdir(dir, (err, files) => {
|
||||||
|
if (err) reject(err);
|
||||||
|
resolve(files);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then((files) => assertEquals(files, []))
|
||||||
|
.catch(() => fail())
|
||||||
|
.finally(() => Deno.removeSync(dir));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function assertEqualsArrayAnyOrder<T>(actual: T[], expected: T[]) {
|
||||||
|
assertEquals(actual.length, expected.length);
|
||||||
|
for (const item of expected) {
|
||||||
|
const index = actual.indexOf(item);
|
||||||
|
assertNotEquals(index, -1);
|
||||||
|
expected = expected.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "ASYNC: reading non-empty directory",
|
||||||
|
async fn() {
|
||||||
|
const dir = Deno.makeTempDirSync();
|
||||||
|
Deno.writeTextFileSync(join(dir, "file1.txt"), "hi");
|
||||||
|
Deno.writeTextFileSync(join(dir, "file2.txt"), "hi");
|
||||||
|
Deno.mkdirSync(join(dir, "some_dir"));
|
||||||
|
await new Promise<string[]>((resolve, reject) => {
|
||||||
|
readdir(dir, (err, files) => {
|
||||||
|
if (err) reject(err);
|
||||||
|
resolve(files);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then((files) =>
|
||||||
|
assertEqualsArrayAnyOrder(files, ["file1.txt", "some_dir", "file2.txt"])
|
||||||
|
)
|
||||||
|
.catch(() => fail())
|
||||||
|
.finally(() => Deno.removeSync(dir, { recursive: true }));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "SYNC: reading empty the directory",
|
||||||
|
fn() {
|
||||||
|
const dir = Deno.makeTempDirSync();
|
||||||
|
assertEquals(readdirSync(dir), []);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "SYNC: reading non-empty directory",
|
||||||
|
fn() {
|
||||||
|
const dir = Deno.makeTempDirSync();
|
||||||
|
Deno.writeTextFileSync(join(dir, "file1.txt"), "hi");
|
||||||
|
Deno.writeTextFileSync(join(dir, "file2.txt"), "hi");
|
||||||
|
Deno.mkdirSync(join(dir, "some_dir"));
|
||||||
|
assertEqualsArrayAnyOrder(
|
||||||
|
readdirSync(dir),
|
||||||
|
["file1.txt", "some_dir", "file2.txt"],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
23
std/node/_fs/_fs_rename.ts
Normal file
23
std/node/_fs/_fs_rename.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { fromFileUrl } from "../path.ts";
|
||||||
|
|
||||||
|
export function rename(
|
||||||
|
oldPath: string | URL,
|
||||||
|
newPath: string | URL,
|
||||||
|
callback: (err?: Error) => void,
|
||||||
|
) {
|
||||||
|
oldPath = oldPath instanceof URL ? fromFileUrl(oldPath) : oldPath;
|
||||||
|
newPath = newPath instanceof URL ? fromFileUrl(newPath) : newPath;
|
||||||
|
|
||||||
|
if (!callback) throw new Error("No callback function supplied");
|
||||||
|
|
||||||
|
Deno.rename(oldPath, newPath)
|
||||||
|
.then((_) => callback())
|
||||||
|
.catch(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function renameSync(oldPath: string | URL, newPath: string | URL) {
|
||||||
|
oldPath = oldPath instanceof URL ? fromFileUrl(oldPath) : oldPath;
|
||||||
|
newPath = newPath instanceof URL ? fromFileUrl(newPath) : newPath;
|
||||||
|
|
||||||
|
Deno.renameSync(oldPath, newPath);
|
||||||
|
}
|
38
std/node/_fs/_fs_rename_test.ts
Normal file
38
std/node/_fs/_fs_rename_test.ts
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import { assertEquals, fail } from "../../testing/asserts.ts";
|
||||||
|
import { rename, renameSync } from "./_fs_rename.ts";
|
||||||
|
import { existsSync } from "../../fs/mod.ts";
|
||||||
|
import { join, parse } from "../../path/mod.ts";
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "ASYNC: renaming a file",
|
||||||
|
async fn() {
|
||||||
|
const file = Deno.makeTempFileSync();
|
||||||
|
const newPath = join(parse(file).dir, `${parse(file).base}_renamed`);
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
rename(file, newPath, (err) => {
|
||||||
|
if (err) reject(err);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
assertEquals(existsSync(newPath), true);
|
||||||
|
assertEquals(existsSync(file), false);
|
||||||
|
})
|
||||||
|
.catch(() => fail())
|
||||||
|
.finally(() => {
|
||||||
|
if (existsSync(file)) Deno.removeSync(file);
|
||||||
|
if (existsSync(newPath)) Deno.removeSync(newPath);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "SYNC: renaming a file",
|
||||||
|
fn() {
|
||||||
|
const file = Deno.makeTempFileSync();
|
||||||
|
const newPath = join(parse(file).dir, `${parse(file).base}_renamed`);
|
||||||
|
renameSync(file, newPath);
|
||||||
|
assertEquals(existsSync(newPath), true);
|
||||||
|
assertEquals(existsSync(file), false);
|
||||||
|
},
|
||||||
|
});
|
36
std/node/_fs/_fs_rmdir.ts
Normal file
36
std/node/_fs/_fs_rmdir.ts
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
type rmdirOptions = {
|
||||||
|
maxRetries?: number;
|
||||||
|
recursive?: boolean;
|
||||||
|
retryDelay?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type rmdirCallback = (err?: Error) => void;
|
||||||
|
|
||||||
|
export function rmdir(path: string | URL, callback: rmdirCallback): void;
|
||||||
|
export function rmdir(
|
||||||
|
path: string | URL,
|
||||||
|
options: rmdirOptions,
|
||||||
|
callback: rmdirCallback,
|
||||||
|
): void;
|
||||||
|
export function rmdir(
|
||||||
|
path: string | URL,
|
||||||
|
optionsOrCallback: rmdirOptions | rmdirCallback,
|
||||||
|
maybeCallback?: rmdirCallback,
|
||||||
|
) {
|
||||||
|
const callback = typeof optionsOrCallback === "function"
|
||||||
|
? optionsOrCallback
|
||||||
|
: maybeCallback;
|
||||||
|
const options = typeof optionsOrCallback === "object"
|
||||||
|
? optionsOrCallback
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (!callback) throw new Error("No callback function supplied");
|
||||||
|
|
||||||
|
Deno.remove(path, { recursive: options?.recursive })
|
||||||
|
.then((_) => callback())
|
||||||
|
.catch(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rmdirSync(path: string | URL, options?: rmdirOptions) {
|
||||||
|
Deno.removeSync(path, { recursive: options?.recursive });
|
||||||
|
}
|
88
std/node/_fs/_fs_rmdir_test.ts
Normal file
88
std/node/_fs/_fs_rmdir_test.ts
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
import { assertEquals, fail } from "../../testing/asserts.ts";
|
||||||
|
import { rmdir, rmdirSync } from "./_fs_rmdir.ts";
|
||||||
|
import { closeSync } from "./_fs_close.ts";
|
||||||
|
import { existsSync } from "../../fs/mod.ts";
|
||||||
|
import { join } from "../../path/mod.ts";
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "ASYNC: removing empty folder",
|
||||||
|
async fn() {
|
||||||
|
const dir = Deno.makeTempDirSync();
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
rmdir(dir, (err) => {
|
||||||
|
if (err) reject(err);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(() => assertEquals(existsSync(dir), false))
|
||||||
|
.catch(() => fail())
|
||||||
|
.finally(() => {
|
||||||
|
if (existsSync(dir)) Deno.removeSync(dir);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "SYNC: removing empty folder",
|
||||||
|
fn() {
|
||||||
|
const dir = Deno.makeTempDirSync();
|
||||||
|
rmdirSync(dir);
|
||||||
|
assertEquals(existsSync(dir), false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function closeRes(before: Deno.ResourceMap, after: Deno.ResourceMap) {
|
||||||
|
for (const key in after) {
|
||||||
|
if (!before[key]) {
|
||||||
|
try {
|
||||||
|
closeSync(Number(key));
|
||||||
|
} catch (error) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "ASYNC: removing non-empty folder",
|
||||||
|
async fn() {
|
||||||
|
const rBefore = Deno.resources();
|
||||||
|
const dir = Deno.makeTempDirSync();
|
||||||
|
Deno.createSync(join(dir, "file1.txt"));
|
||||||
|
Deno.createSync(join(dir, "file2.txt"));
|
||||||
|
Deno.mkdirSync(join(dir, "some_dir"));
|
||||||
|
Deno.createSync(join(dir, "some_dir", "file.txt"));
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
rmdir(dir, { recursive: true }, (err) => {
|
||||||
|
if (err) reject(err);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(() => assertEquals(existsSync(dir), false))
|
||||||
|
.catch(() => fail())
|
||||||
|
.finally(() => {
|
||||||
|
if (existsSync(dir)) Deno.removeSync(dir, { recursive: true });
|
||||||
|
const rAfter = Deno.resources();
|
||||||
|
closeRes(rBefore, rAfter);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
ignore: Deno.build.os === "windows",
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "SYNC: removing non-empty folder",
|
||||||
|
fn() {
|
||||||
|
const rBefore = Deno.resources();
|
||||||
|
const dir = Deno.makeTempDirSync();
|
||||||
|
Deno.createSync(join(dir, "file1.txt"));
|
||||||
|
Deno.createSync(join(dir, "file2.txt"));
|
||||||
|
Deno.mkdirSync(join(dir, "some_dir"));
|
||||||
|
Deno.createSync(join(dir, "some_dir", "file.txt"));
|
||||||
|
rmdirSync(dir, { recursive: true });
|
||||||
|
assertEquals(existsSync(dir), false);
|
||||||
|
// closing resources
|
||||||
|
const rAfter = Deno.resources();
|
||||||
|
closeRes(rBefore, rAfter);
|
||||||
|
},
|
||||||
|
ignore: Deno.build.os === "windows",
|
||||||
|
});
|
289
std/node/_fs/_fs_stat.ts
Normal file
289
std/node/_fs/_fs_stat.ts
Normal file
|
@ -0,0 +1,289 @@
|
||||||
|
export type statOptions = {
|
||||||
|
bigint: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Stats = {
|
||||||
|
/** ID of the device containing the file.
|
||||||
|
*
|
||||||
|
* _Linux/Mac OS only._ */
|
||||||
|
dev: number | null;
|
||||||
|
/** Inode number.
|
||||||
|
*
|
||||||
|
* _Linux/Mac OS only._ */
|
||||||
|
ino: number | null;
|
||||||
|
/** **UNSTABLE**: Match behavior with Go on Windows for `mode`.
|
||||||
|
*
|
||||||
|
* The underlying raw `st_mode` bits that contain the standard Unix
|
||||||
|
* permissions for this file/directory. */
|
||||||
|
mode: number | null;
|
||||||
|
/** Number of hard links pointing to this file.
|
||||||
|
*
|
||||||
|
* _Linux/Mac OS only._ */
|
||||||
|
nlink: number | null;
|
||||||
|
/** User ID of the owner of this file.
|
||||||
|
*
|
||||||
|
* _Linux/Mac OS only._ */
|
||||||
|
uid: number | null;
|
||||||
|
/** Group ID of the owner of this file.
|
||||||
|
*
|
||||||
|
* _Linux/Mac OS only._ */
|
||||||
|
gid: number | null;
|
||||||
|
/** Device ID of this file.
|
||||||
|
*
|
||||||
|
* _Linux/Mac OS only._ */
|
||||||
|
rdev: number | null;
|
||||||
|
/** The size of the file, in bytes. */
|
||||||
|
size: number;
|
||||||
|
/** Blocksize for filesystem I/O.
|
||||||
|
*
|
||||||
|
* _Linux/Mac OS only._ */
|
||||||
|
blksize: number | null;
|
||||||
|
/** Number of blocks allocated to the file, in 512-byte units.
|
||||||
|
*
|
||||||
|
* _Linux/Mac OS only._ */
|
||||||
|
blocks: number | null;
|
||||||
|
/** The last modification time of the file. This corresponds to the `mtime`
|
||||||
|
* field from `stat` on Linux/Mac OS and `ftLastWriteTime` on Windows. This
|
||||||
|
* may not be available on all platforms. */
|
||||||
|
mtime: Date | null;
|
||||||
|
/** The last access time of the file. This corresponds to the `atime`
|
||||||
|
* field from `stat` on Unix and `ftLastAccessTime` on Windows. This may not
|
||||||
|
* be available on all platforms. */
|
||||||
|
atime: Date | null;
|
||||||
|
/** The creation time of the file. This corresponds to the `birthtime`
|
||||||
|
* field from `stat` on Mac/BSD and `ftCreationTime` on Windows. This may
|
||||||
|
* not be available on all platforms. */
|
||||||
|
birthtime: Date | null;
|
||||||
|
/** change time */
|
||||||
|
ctime: Date | null;
|
||||||
|
/** atime in milliseconds */
|
||||||
|
atimeMs: number | null;
|
||||||
|
/** atime in milliseconds */
|
||||||
|
mtimeMs: number | null;
|
||||||
|
/** atime in milliseconds */
|
||||||
|
ctimeMs: number | null;
|
||||||
|
/** atime in milliseconds */
|
||||||
|
birthtimeMs: number | null;
|
||||||
|
isBlockDevice: () => boolean;
|
||||||
|
isCharacterDevice: () => boolean;
|
||||||
|
isDirectory: () => boolean;
|
||||||
|
isFIFO: () => boolean;
|
||||||
|
isFile: () => boolean;
|
||||||
|
isSocket: () => boolean;
|
||||||
|
isSymbolicLink: () => boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BigIntStats = {
|
||||||
|
/** ID of the device containing the file.
|
||||||
|
*
|
||||||
|
* _Linux/Mac OS only._ */
|
||||||
|
dev: BigInt | null;
|
||||||
|
/** Inode number.
|
||||||
|
*
|
||||||
|
* _Linux/Mac OS only._ */
|
||||||
|
ino: BigInt | null;
|
||||||
|
/** **UNSTABLE**: Match behavior with Go on Windows for `mode`.
|
||||||
|
*
|
||||||
|
* The underlying raw `st_mode` bits that contain the standard Unix
|
||||||
|
* permissions for this file/directory. */
|
||||||
|
mode: BigInt | null;
|
||||||
|
/** Number of hard links pointing to this file.
|
||||||
|
*
|
||||||
|
* _Linux/Mac OS only._ */
|
||||||
|
nlink: BigInt | null;
|
||||||
|
/** User ID of the owner of this file.
|
||||||
|
*
|
||||||
|
* _Linux/Mac OS only._ */
|
||||||
|
uid: BigInt | null;
|
||||||
|
/** Group ID of the owner of this file.
|
||||||
|
*
|
||||||
|
* _Linux/Mac OS only._ */
|
||||||
|
gid: BigInt | null;
|
||||||
|
/** Device ID of this file.
|
||||||
|
*
|
||||||
|
* _Linux/Mac OS only._ */
|
||||||
|
rdev: BigInt | null;
|
||||||
|
/** The size of the file, in bytes. */
|
||||||
|
size: BigInt;
|
||||||
|
/** Blocksize for filesystem I/O.
|
||||||
|
*
|
||||||
|
* _Linux/Mac OS only._ */
|
||||||
|
blksize: BigInt | null;
|
||||||
|
/** Number of blocks allocated to the file, in 512-byte units.
|
||||||
|
*
|
||||||
|
* _Linux/Mac OS only._ */
|
||||||
|
blocks: BigInt | null;
|
||||||
|
/** The last modification time of the file. This corresponds to the `mtime`
|
||||||
|
* field from `stat` on Linux/Mac OS and `ftLastWriteTime` on Windows. This
|
||||||
|
* may not be available on all platforms. */
|
||||||
|
mtime: Date | null;
|
||||||
|
/** The last access time of the file. This corresponds to the `atime`
|
||||||
|
* field from `stat` on Unix and `ftLastAccessTime` on Windows. This may not
|
||||||
|
* be available on all platforms. */
|
||||||
|
atime: Date | null;
|
||||||
|
/** The creation time of the file. This corresponds to the `birthtime`
|
||||||
|
* field from `stat` on Mac/BSD and `ftCreationTime` on Windows. This may
|
||||||
|
* not be available on all platforms. */
|
||||||
|
birthtime: Date | null;
|
||||||
|
/** change time */
|
||||||
|
ctime: Date | null;
|
||||||
|
/** atime in milliseconds */
|
||||||
|
atimeMs: BigInt | null;
|
||||||
|
/** atime in milliseconds */
|
||||||
|
mtimeMs: BigInt | null;
|
||||||
|
/** atime in milliseconds */
|
||||||
|
ctimeMs: BigInt | null;
|
||||||
|
/** atime in nanoseconds */
|
||||||
|
birthtimeMs: BigInt | null;
|
||||||
|
/** atime in nanoseconds */
|
||||||
|
atimeNs: BigInt | null;
|
||||||
|
/** atime in nanoseconds */
|
||||||
|
mtimeNs: BigInt | null;
|
||||||
|
/** atime in nanoseconds */
|
||||||
|
ctimeNs: BigInt | null;
|
||||||
|
/** atime in nanoseconds */
|
||||||
|
birthtimeNs: BigInt | null;
|
||||||
|
isBlockDevice: () => boolean;
|
||||||
|
isCharacterDevice: () => boolean;
|
||||||
|
isDirectory: () => boolean;
|
||||||
|
isFIFO: () => boolean;
|
||||||
|
isFile: () => boolean;
|
||||||
|
isSocket: () => boolean;
|
||||||
|
isSymbolicLink: () => boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function convertFileInfoToStats(origin: Deno.FileInfo): Stats {
|
||||||
|
return {
|
||||||
|
dev: origin.dev,
|
||||||
|
ino: origin.ino,
|
||||||
|
mode: origin.mode,
|
||||||
|
nlink: origin.nlink,
|
||||||
|
uid: origin.uid,
|
||||||
|
gid: origin.gid,
|
||||||
|
rdev: origin.rdev,
|
||||||
|
size: origin.size,
|
||||||
|
blksize: origin.blksize,
|
||||||
|
blocks: origin.blocks,
|
||||||
|
mtime: origin.mtime,
|
||||||
|
atime: origin.atime,
|
||||||
|
birthtime: origin.birthtime,
|
||||||
|
mtimeMs: origin.mtime?.getTime() || null,
|
||||||
|
atimeMs: origin.atime?.getTime() || null,
|
||||||
|
birthtimeMs: origin.birthtime?.getTime() || null,
|
||||||
|
isFile: () => origin.isFile,
|
||||||
|
isDirectory: () => origin.isDirectory,
|
||||||
|
isSymbolicLink: () => origin.isSymlink,
|
||||||
|
// not sure about those
|
||||||
|
isBlockDevice: () => false,
|
||||||
|
isFIFO: () => false,
|
||||||
|
isCharacterDevice: () => false,
|
||||||
|
isSocket: () => false,
|
||||||
|
ctime: origin.mtime,
|
||||||
|
ctimeMs: origin.mtime?.getTime() || null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function to_BigInt(number?: number | null) {
|
||||||
|
if (number === null || number === undefined) return null;
|
||||||
|
return BigInt(number);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function convertFileInfoToBigIntStats(
|
||||||
|
origin: Deno.FileInfo,
|
||||||
|
): BigIntStats {
|
||||||
|
return {
|
||||||
|
dev: to_BigInt(origin.dev),
|
||||||
|
ino: to_BigInt(origin.ino),
|
||||||
|
mode: to_BigInt(origin.mode),
|
||||||
|
nlink: to_BigInt(origin.nlink),
|
||||||
|
uid: to_BigInt(origin.uid),
|
||||||
|
gid: to_BigInt(origin.gid),
|
||||||
|
rdev: to_BigInt(origin.rdev),
|
||||||
|
size: to_BigInt(origin.size) || 0n,
|
||||||
|
blksize: to_BigInt(origin.blksize),
|
||||||
|
blocks: to_BigInt(origin.blocks),
|
||||||
|
mtime: origin.mtime,
|
||||||
|
atime: origin.atime,
|
||||||
|
birthtime: origin.birthtime,
|
||||||
|
mtimeMs: origin.mtime ? BigInt(origin.mtime.getTime()) : null,
|
||||||
|
atimeMs: origin.atime ? BigInt(origin.atime.getTime()) : null,
|
||||||
|
birthtimeMs: origin.birthtime ? BigInt(origin.birthtime.getTime()) : null,
|
||||||
|
mtimeNs: origin.mtime ? BigInt(origin.mtime.getTime()) * 1000000n : null,
|
||||||
|
atimeNs: origin.atime ? BigInt(origin.atime.getTime()) * 1000000n : null,
|
||||||
|
birthtimeNs: origin.birthtime
|
||||||
|
? BigInt(origin.birthtime.getTime()) * 1000000n
|
||||||
|
: null,
|
||||||
|
isFile: () => origin.isFile,
|
||||||
|
isDirectory: () => origin.isDirectory,
|
||||||
|
isSymbolicLink: () => origin.isSymlink,
|
||||||
|
// not sure about those
|
||||||
|
isBlockDevice: () => false,
|
||||||
|
isFIFO: () => false,
|
||||||
|
isCharacterDevice: () => false,
|
||||||
|
isSocket: () => false,
|
||||||
|
ctime: origin.mtime,
|
||||||
|
ctimeMs: origin.mtime ? BigInt(origin.mtime.getTime()) : null,
|
||||||
|
ctimeNs: origin.mtime ? BigInt(origin.mtime.getTime()) * 1000000n : null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// shortcut for Convert File Info to Stats or BigIntStats
|
||||||
|
export function CFISBIS(fileInfo: Deno.FileInfo, bigInt: boolean) {
|
||||||
|
if (bigInt) return convertFileInfoToBigIntStats(fileInfo);
|
||||||
|
return convertFileInfoToStats(fileInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
export type statCallbackBigInt = (
|
||||||
|
err: Error | undefined,
|
||||||
|
stat: BigIntStats,
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
export type statCallback = (err: Error | undefined, stat: Stats) => void;
|
||||||
|
|
||||||
|
export function stat(path: string | URL, callback: statCallback): void;
|
||||||
|
export function stat(
|
||||||
|
path: string | URL,
|
||||||
|
options: { bigint: false },
|
||||||
|
callback: statCallback,
|
||||||
|
): void;
|
||||||
|
export function stat(
|
||||||
|
path: string | URL,
|
||||||
|
options: { bigint: true },
|
||||||
|
callback: statCallbackBigInt,
|
||||||
|
): void;
|
||||||
|
export function stat(
|
||||||
|
path: string | URL,
|
||||||
|
optionsOrCallback: statCallback | statCallbackBigInt | statOptions,
|
||||||
|
maybeCallback?: statCallback | statCallbackBigInt,
|
||||||
|
) {
|
||||||
|
const callback =
|
||||||
|
(typeof optionsOrCallback === "function"
|
||||||
|
? optionsOrCallback
|
||||||
|
: maybeCallback) as (
|
||||||
|
err: Error | undefined,
|
||||||
|
stat: BigIntStats | Stats,
|
||||||
|
) => void;
|
||||||
|
const options = typeof optionsOrCallback === "object"
|
||||||
|
? optionsOrCallback
|
||||||
|
: { bigint: false };
|
||||||
|
|
||||||
|
if (!callback) throw new Error("No callback function supplied");
|
||||||
|
|
||||||
|
Deno.stat(path)
|
||||||
|
.then((stat) => callback(undefined, CFISBIS(stat, options.bigint)))
|
||||||
|
.catch((err) => callback(err, err));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function statSync(path: string | URL): Stats;
|
||||||
|
export function statSync(path: string | URL, options: { bigint: false }): Stats;
|
||||||
|
export function statSync(
|
||||||
|
path: string | URL,
|
||||||
|
options: { bigint: true },
|
||||||
|
): BigIntStats;
|
||||||
|
export function statSync(
|
||||||
|
path: string | URL,
|
||||||
|
options: statOptions = { bigint: false },
|
||||||
|
): Stats | BigIntStats {
|
||||||
|
const origin = Deno.statSync(path);
|
||||||
|
return CFISBIS(origin, options.bigint);
|
||||||
|
}
|
107
std/node/_fs/_fs_stat_test.ts
Normal file
107
std/node/_fs/_fs_stat_test.ts
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
import { BigIntStats, stat, Stats, statSync } from "./_fs_stat.ts";
|
||||||
|
import { assertEquals, fail } from "../../testing/asserts.ts";
|
||||||
|
|
||||||
|
export function assertStats(actual: Stats, expected: Deno.FileInfo) {
|
||||||
|
assertEquals(actual.dev, expected.dev);
|
||||||
|
assertEquals(actual.gid, expected.gid);
|
||||||
|
assertEquals(actual.size, expected.size);
|
||||||
|
assertEquals(actual.blksize, expected.blksize);
|
||||||
|
assertEquals(actual.blocks, expected.blocks);
|
||||||
|
assertEquals(actual.ino, expected.ino);
|
||||||
|
assertEquals(actual.gid, expected.gid);
|
||||||
|
assertEquals(actual.mode, expected.mode);
|
||||||
|
assertEquals(actual.nlink, expected.nlink);
|
||||||
|
assertEquals(actual.rdev, expected.rdev);
|
||||||
|
assertEquals(actual.uid, expected.uid);
|
||||||
|
assertEquals(actual.atime?.getTime(), expected.atime?.getTime());
|
||||||
|
assertEquals(actual.mtime?.getTime(), expected.mtime?.getTime());
|
||||||
|
assertEquals(actual.birthtime?.getTime(), expected.birthtime?.getTime());
|
||||||
|
assertEquals(actual.atimeMs, expected.atime?.getTime());
|
||||||
|
assertEquals(actual.mtimeMs, expected.mtime?.getTime());
|
||||||
|
assertEquals(actual.birthtimeMs, expected.birthtime?.getTime());
|
||||||
|
assertEquals(actual.isFile(), expected.isFile);
|
||||||
|
assertEquals(actual.isDirectory(), expected.isDirectory);
|
||||||
|
assertEquals(actual.isSymbolicLink(), expected.isSymlink);
|
||||||
|
}
|
||||||
|
|
||||||
|
function to_BigInt(num?: number | null) {
|
||||||
|
if (num === undefined || num === null) return null;
|
||||||
|
return BigInt(num);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function assertStatsBigInt(
|
||||||
|
actual: BigIntStats,
|
||||||
|
expected: Deno.FileInfo,
|
||||||
|
) {
|
||||||
|
assertEquals(actual.dev, to_BigInt(expected.dev));
|
||||||
|
assertEquals(actual.gid, to_BigInt(expected.gid));
|
||||||
|
assertEquals(actual.size, to_BigInt(expected.size));
|
||||||
|
assertEquals(actual.blksize, to_BigInt(expected.blksize));
|
||||||
|
assertEquals(actual.blocks, to_BigInt(expected.blocks));
|
||||||
|
assertEquals(actual.ino, to_BigInt(expected.ino));
|
||||||
|
assertEquals(actual.gid, to_BigInt(expected.gid));
|
||||||
|
assertEquals(actual.mode, to_BigInt(expected.mode));
|
||||||
|
assertEquals(actual.nlink, to_BigInt(expected.nlink));
|
||||||
|
assertEquals(actual.rdev, to_BigInt(expected.rdev));
|
||||||
|
assertEquals(actual.uid, to_BigInt(expected.uid));
|
||||||
|
assertEquals(actual.atime?.getTime(), expected.atime?.getTime());
|
||||||
|
assertEquals(actual.mtime?.getTime(), expected.mtime?.getTime());
|
||||||
|
assertEquals(actual.birthtime?.getTime(), expected.birthtime?.getTime());
|
||||||
|
assertEquals(Number(actual.atimeMs), expected.atime?.getTime());
|
||||||
|
assertEquals(Number(actual.mtimeMs), expected.mtime?.getTime());
|
||||||
|
assertEquals(Number(actual.birthtimeMs), expected.birthtime?.getTime());
|
||||||
|
assertEquals(actual.atimeNs === null, actual.atime === null);
|
||||||
|
assertEquals(actual.mtimeNs === null, actual.mtime === null);
|
||||||
|
assertEquals(actual.birthtimeNs === null, actual.birthtime === null);
|
||||||
|
assertEquals(actual.isFile(), expected.isFile);
|
||||||
|
assertEquals(actual.isDirectory(), expected.isDirectory);
|
||||||
|
assertEquals(actual.isSymbolicLink(), expected.isSymlink);
|
||||||
|
}
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "ASYNC: get a file Stats",
|
||||||
|
async fn() {
|
||||||
|
const file = Deno.makeTempFileSync();
|
||||||
|
await new Promise<Stats>((resolve, reject) => {
|
||||||
|
stat(file, (err, stat) => {
|
||||||
|
if (err) reject(err);
|
||||||
|
resolve(stat);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then((stat) => assertStats(stat, Deno.statSync(file)))
|
||||||
|
.catch(() => fail())
|
||||||
|
.finally(() => Deno.removeSync(file));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "SYNC: get a file Stats",
|
||||||
|
fn() {
|
||||||
|
const file = Deno.makeTempFileSync();
|
||||||
|
assertStats(statSync(file), Deno.statSync(file));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "ASYNC: get a file BigInt Stats",
|
||||||
|
async fn() {
|
||||||
|
const file = Deno.makeTempFileSync();
|
||||||
|
await new Promise<BigIntStats>((resolve, reject) => {
|
||||||
|
stat(file, { bigint: true }, (err, stat) => {
|
||||||
|
if (err) reject(err);
|
||||||
|
resolve(stat);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then((stat) => assertStatsBigInt(stat, Deno.statSync(file)))
|
||||||
|
.catch(() => fail())
|
||||||
|
.finally(() => Deno.removeSync(file));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "SYNC: get a file BigInt Stats",
|
||||||
|
fn() {
|
||||||
|
const file = Deno.makeTempFileSync();
|
||||||
|
assertStatsBigInt(statSync(file, { bigint: true }), Deno.statSync(file));
|
||||||
|
},
|
||||||
|
});
|
10
std/node/_fs/_fs_unlink.ts
Normal file
10
std/node/_fs/_fs_unlink.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
export function unlink(path: string | URL, callback: (err?: Error) => void) {
|
||||||
|
if (!callback) throw new Error("No callback function supplied");
|
||||||
|
Deno.remove(path)
|
||||||
|
.then((_) => callback())
|
||||||
|
.catch(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unlinkSync(path: string | URL) {
|
||||||
|
Deno.removeSync(path);
|
||||||
|
}
|
30
std/node/_fs/_fs_unlink_test.ts
Normal file
30
std/node/_fs/_fs_unlink_test.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import { assertEquals, fail } from "../../testing/asserts.ts";
|
||||||
|
import { existsSync } from "../../fs/mod.ts";
|
||||||
|
import { unlink, unlinkSync } from "./_fs_unlink.ts";
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "ASYNC: deleting a file",
|
||||||
|
async fn() {
|
||||||
|
const file = Deno.makeTempFileSync();
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
unlink(file, (err) => {
|
||||||
|
if (err) reject(err);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(() => assertEquals(existsSync(file), false))
|
||||||
|
.catch(() => fail())
|
||||||
|
.finally(() => {
|
||||||
|
if (existsSync(file)) Deno.removeSync(file);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "SYNC: Test deleting a file",
|
||||||
|
fn() {
|
||||||
|
const file = Deno.makeTempFileSync();
|
||||||
|
unlinkSync(file);
|
||||||
|
assertEquals(existsSync(file), false);
|
||||||
|
},
|
||||||
|
});
|
111
std/node/_fs/_fs_watch.ts
Normal file
111
std/node/_fs/_fs_watch.ts
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
import { fromFileUrl } from "../path.ts";
|
||||||
|
import { EventEmitter } from "../events.ts";
|
||||||
|
import { notImplemented } from "../_utils.ts";
|
||||||
|
|
||||||
|
export function asyncIterableIteratorToCallback<T>(
|
||||||
|
iterator: AsyncIterableIterator<T>,
|
||||||
|
callback: (val: T, done?: boolean) => void,
|
||||||
|
) {
|
||||||
|
function next() {
|
||||||
|
iterator.next().then((obj) => {
|
||||||
|
if (obj.done) {
|
||||||
|
callback(obj.value, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback(obj.value);
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function asyncIterableToCallback<T>(
|
||||||
|
iter: AsyncIterable<T>,
|
||||||
|
callback: (val: T, done?: boolean) => void,
|
||||||
|
) {
|
||||||
|
const iterator = iter[Symbol.asyncIterator]();
|
||||||
|
function next() {
|
||||||
|
iterator.next().then((obj) => {
|
||||||
|
if (obj.done) {
|
||||||
|
callback(obj.value, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback(obj.value);
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
type watchOptions = {
|
||||||
|
persistent?: boolean;
|
||||||
|
recursive?: boolean;
|
||||||
|
encoding?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type watchListener = (eventType: string, filename: string) => void;
|
||||||
|
|
||||||
|
export function watch(
|
||||||
|
filename: string | URL,
|
||||||
|
options: watchOptions,
|
||||||
|
listener: watchListener,
|
||||||
|
): FSWatcher;
|
||||||
|
export function watch(
|
||||||
|
filename: string | URL,
|
||||||
|
listener: watchListener,
|
||||||
|
): FSWatcher;
|
||||||
|
export function watch(
|
||||||
|
filename: string | URL,
|
||||||
|
options: watchOptions,
|
||||||
|
): FSWatcher;
|
||||||
|
export function watch(filename: string | URL): FSWatcher;
|
||||||
|
export function watch(
|
||||||
|
filename: string | URL,
|
||||||
|
optionsOrListener?: watchOptions | watchListener,
|
||||||
|
optionsOrListener2?: watchOptions | watchListener,
|
||||||
|
) {
|
||||||
|
const listener = typeof optionsOrListener === "function"
|
||||||
|
? optionsOrListener
|
||||||
|
: typeof optionsOrListener2 === "function"
|
||||||
|
? optionsOrListener2
|
||||||
|
: undefined;
|
||||||
|
const options = typeof optionsOrListener === "object"
|
||||||
|
? optionsOrListener
|
||||||
|
: typeof optionsOrListener2 === "object"
|
||||||
|
? optionsOrListener2
|
||||||
|
: undefined;
|
||||||
|
filename = filename instanceof URL ? fromFileUrl(filename) : filename;
|
||||||
|
|
||||||
|
const iterator = Deno.watchFs(filename, {
|
||||||
|
recursive: options?.recursive || false,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!listener) throw new Error("No callback function supplied");
|
||||||
|
|
||||||
|
const fsWatcher = new FSWatcher(() => {
|
||||||
|
if (iterator.return) iterator.return();
|
||||||
|
});
|
||||||
|
|
||||||
|
fsWatcher.on("change", listener);
|
||||||
|
|
||||||
|
asyncIterableIteratorToCallback<Deno.FsEvent>(iterator, (val, done) => {
|
||||||
|
if (done) return;
|
||||||
|
fsWatcher.emit("change", val.kind, val.paths[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
return fsWatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
class FSWatcher extends EventEmitter {
|
||||||
|
close: () => void;
|
||||||
|
constructor(closer: () => void) {
|
||||||
|
super();
|
||||||
|
this.close = closer;
|
||||||
|
}
|
||||||
|
ref() {
|
||||||
|
notImplemented("FSWatcher.ref() is not implemented");
|
||||||
|
}
|
||||||
|
unref() {
|
||||||
|
notImplemented("FSWatcher.unref() is not implemented");
|
||||||
|
}
|
||||||
|
}
|
32
std/node/_fs/_fs_watch_test.ts
Normal file
32
std/node/_fs/_fs_watch_test.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import { watch } from "./_fs_watch.ts";
|
||||||
|
import { assertEquals, fail } from "../../testing/asserts.ts";
|
||||||
|
|
||||||
|
function wait(time: number) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, time);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "watching a file",
|
||||||
|
async fn() {
|
||||||
|
const file = Deno.makeTempFileSync();
|
||||||
|
const result: Array<[string, string]> = [];
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
const watcher = watch(
|
||||||
|
file,
|
||||||
|
(eventType, filename) => result.push([eventType, filename]),
|
||||||
|
);
|
||||||
|
wait(100)
|
||||||
|
.then(() => Deno.writeTextFileSync(file, "something"))
|
||||||
|
.then(() => wait(100))
|
||||||
|
.then(() => watcher.close())
|
||||||
|
.then(() => wait(100))
|
||||||
|
.then(resolve);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
assertEquals(result.length >= 1, true);
|
||||||
|
})
|
||||||
|
.catch(() => fail());
|
||||||
|
},
|
||||||
|
});
|
|
@ -11,6 +11,15 @@ import { exists, existsSync } from "./_fs/_fs_exists.ts";
|
||||||
import { mkdir, mkdirSync } from "./_fs/_fs_mkdir.ts";
|
import { mkdir, mkdirSync } from "./_fs/_fs_mkdir.ts";
|
||||||
import { copyFile, copyFileSync } from "./_fs/_fs_copy.ts";
|
import { copyFile, copyFileSync } from "./_fs/_fs_copy.ts";
|
||||||
import { writeFile, writeFileSync } from "./_fs/_fs_writeFile.ts";
|
import { writeFile, writeFileSync } from "./_fs/_fs_writeFile.ts";
|
||||||
|
import { readdir, readdirSync } from "./_fs/_fs_readdir.ts";
|
||||||
|
import { rename, renameSync } from "./_fs/_fs_rename.ts";
|
||||||
|
import { rmdir, rmdirSync } from "./_fs/_fs_rmdir.ts";
|
||||||
|
import { unlink, unlinkSync } from "./_fs/_fs_unlink.ts";
|
||||||
|
import { watch } from "./_fs/_fs_watch.ts";
|
||||||
|
import { open, openSync } from "./_fs/_fs_open.ts";
|
||||||
|
import { stat, statSync } from "./_fs/_fs_stat.ts";
|
||||||
|
import { lstat, lstatSync } from "./_fs/_fs_lstat.ts";
|
||||||
|
|
||||||
import * as promises from "./_fs/promises/mod.ts";
|
import * as promises from "./_fs/promises/mod.ts";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
@ -29,13 +38,28 @@ export {
|
||||||
copyFileSync,
|
copyFileSync,
|
||||||
exists,
|
exists,
|
||||||
existsSync,
|
existsSync,
|
||||||
|
lstat,
|
||||||
|
lstatSync,
|
||||||
mkdir,
|
mkdir,
|
||||||
mkdirSync,
|
mkdirSync,
|
||||||
|
open,
|
||||||
|
openSync,
|
||||||
promises,
|
promises,
|
||||||
|
readdir,
|
||||||
|
readdirSync,
|
||||||
readFile,
|
readFile,
|
||||||
readFileSync,
|
readFileSync,
|
||||||
readlink,
|
readlink,
|
||||||
readlinkSync,
|
readlinkSync,
|
||||||
|
rename,
|
||||||
|
renameSync,
|
||||||
|
rmdir,
|
||||||
|
rmdirSync,
|
||||||
|
stat,
|
||||||
|
statSync,
|
||||||
|
unlink,
|
||||||
|
unlinkSync,
|
||||||
|
watch,
|
||||||
writeFile,
|
writeFile,
|
||||||
writeFileSync,
|
writeFileSync,
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue