1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-23 15:49:44 -05:00

feat(std/fs/node): adding some functions (#7921)

This commit is contained in:
ali ahmed 2020-10-14 17:59:28 +02:00 committed by GitHub
parent f75bd89aff
commit 5bed06fb94
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 1403 additions and 0 deletions

59
std/node/_fs/_fs_lstat.ts Normal file
View 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);
}

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

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

View 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"],
);
},
});

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

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

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

View 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));
},
});

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

View 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
View 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");
}
}

View 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());
},
});

View file

@ -11,6 +11,15 @@ import { exists, existsSync } from "./_fs/_fs_exists.ts";
import { mkdir, mkdirSync } from "./_fs/_fs_mkdir.ts";
import { copyFile, copyFileSync } from "./_fs/_fs_copy.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";
export {
@ -29,13 +38,28 @@ export {
copyFileSync,
exists,
existsSync,
lstat,
lstatSync,
mkdir,
mkdirSync,
open,
openSync,
promises,
readdir,
readdirSync,
readFile,
readFileSync,
readlink,
readlinkSync,
rename,
renameSync,
rmdir,
rmdirSync,
stat,
statSync,
unlink,
unlinkSync,
watch,
writeFile,
writeFileSync,
};