feat: URL support in Deno filesystem methods (#5990)

This commit is contained in:
River 2020-06-12 02:36:20 +10:00 committed by GitHub
parent 813210d433
commit 818a801092
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 741 additions and 66 deletions

View file

@ -21,7 +21,7 @@ import {
export { OpenOptions } from "./ops/fs/open.ts";
export function openSync(
path: string,
path: string | URL,
options: OpenOptions = { read: true }
): File {
checkOpenOptions(options);
@ -30,7 +30,7 @@ export function openSync(
}
export async function open(
path: string,
path: string | URL,
options: OpenOptions = { read: true }
): Promise<File> {
checkOpenOptions(options);
@ -38,7 +38,7 @@ export async function open(
return new File(rid);
}
export function createSync(path: string): File {
export function createSync(path: string | URL): File {
return openSync(path, {
read: true,
write: true,
@ -47,7 +47,7 @@ export function createSync(path: string): File {
});
}
export function create(path: string): Promise<File> {
export function create(path: string | URL): Promise<File> {
return open(path, {
read: true,
write: true,

View file

@ -419,7 +419,7 @@ declare namespace Deno {
*
* Requires `allow-read` and/or `allow-write` permissions depending on options.
*/
export function openSync(path: string, options?: OpenOptions): File;
export function openSync(path: string | URL, options?: OpenOptions): File;
/** Open a file and resolve to an instance of `Deno.File`. The
* file does not need to previously exist if using the `create` or `createNew`
@ -434,7 +434,10 @@ declare namespace Deno {
*
* Requires `allow-read` and/or `allow-write` permissions depending on options.
*/
export function open(path: string, options?: OpenOptions): Promise<File>;
export function open(
path: string | URL,
options?: OpenOptions
): Promise<File>;
/** Creates a file if none exists or truncates an existing file and returns
* an instance of `Deno.File`.
@ -445,7 +448,7 @@ declare namespace Deno {
*
* Requires `allow-read` and `allow-write` permissions.
*/
export function createSync(path: string): File;
export function createSync(path: string | URL): File;
/** Creates a file if none exists or truncates an existing file and resolves to
* an instance of `Deno.File`.
@ -456,7 +459,7 @@ declare namespace Deno {
*
* Requires `allow-read` and `allow-write` permissions.
*/
export function create(path: string): Promise<File>;
export function create(path: string | URL): Promise<File>;
/** Synchronously read from a resource ID (`rid`) into an array buffer (`buffer`).
*
@ -890,7 +893,7 @@ declare namespace Deno {
* Defaults to throwing error if the directory already exists.
*
* Requires `allow-write` permission. */
export function mkdirSync(path: string, options?: MkdirOptions): void;
export function mkdirSync(path: string | URL, options?: MkdirOptions): void;
/** Creates a new directory with the specified path.
*
@ -903,7 +906,10 @@ declare namespace Deno {
* Defaults to throwing error if the directory already exists.
*
* Requires `allow-write` permission. */
export function mkdir(path: string, options?: MkdirOptions): Promise<void>;
export function mkdir(
path: string | URL,
options?: MkdirOptions
): Promise<void>;
export interface MakeTempOptions {
/** Directory where the temporary directory should be created (defaults to
@ -1011,7 +1017,7 @@ declare namespace Deno {
* NOTE: This API currently throws on Windows
*
* Requires `allow-write` permission. */
export function chmodSync(path: string, mode: number): void;
export function chmodSync(path: string | URL, mode: number): void;
/** Changes the permission of a specific file/directory of specified path.
* Ignores the process's umask.
@ -1041,7 +1047,7 @@ declare namespace Deno {
* NOTE: This API currently throws on Windows
*
* Requires `allow-write` permission. */
export function chmod(path: string, mode: number): Promise<void>;
export function chmod(path: string | URL, mode: number): Promise<void>;
/** Synchronously change owner of a regular file or directory. This functionality
* is not available on Windows.
@ -1058,7 +1064,7 @@ declare namespace Deno {
* @param uid user id (UID) of the new owner
* @param gid group id (GID) of the new owner
*/
export function chownSync(path: string, uid: number, gid: number): void;
export function chownSync(path: string | URL, uid: number, gid: number): void;
/** Change owner of a regular file or directory. This functionality
* is not available on Windows.
@ -1075,7 +1081,11 @@ declare namespace Deno {
* @param uid user id (UID) of the new owner
* @param gid group id (GID) of the new owner
*/
export function chown(path: string, uid: number, gid: number): Promise<void>;
export function chown(
path: string | URL,
uid: number,
gid: number
): Promise<void>;
export interface RemoveOptions {
/** Defaults to `false`. If set to `true`, path will be removed even if
@ -1094,7 +1104,7 @@ declare namespace Deno {
* directory and the `recursive` option isn't set to `true`.
*
* Requires `allow-write` permission. */
export function removeSync(path: string, options?: RemoveOptions): void;
export function removeSync(path: string | URL, options?: RemoveOptions): void;
/** Removes the named file or directory.
*
@ -1107,7 +1117,10 @@ declare namespace Deno {
* directory and the `recursive` option isn't set to `true`.
*
* Requires `allow-write` permission. */
export function remove(path: string, options?: RemoveOptions): Promise<void>;
export function remove(
path: string | URL,
options?: RemoveOptions
): Promise<void>;
/** Synchronously renames (moves) `oldpath` to `newpath`. Paths may be files or
* directories. If `newpath` already exists and is not a directory,
@ -1152,7 +1165,7 @@ declare namespace Deno {
* ```
*
* Requires `allow-read` permission. */
export function readTextFileSync(path: string): string;
export function readTextFileSync(path: string | URL): string;
/** Asynchronously reads and returns the entire contents of a file as a utf8
* encoded string. Reading a directory returns an empty data array.
@ -1163,7 +1176,7 @@ declare namespace Deno {
* ```
*
* Requires `allow-read` permission. */
export function readTextFile(path: string): Promise<string>;
export function readTextFile(path: string | URL): Promise<string>;
/** Synchronously reads and returns the entire contents of a file as an array
* of bytes. `TextDecoder` can be used to transform the bytes to string if
@ -1176,7 +1189,7 @@ declare namespace Deno {
* ```
*
* Requires `allow-read` permission. */
export function readFileSync(path: string): Uint8Array;
export function readFileSync(path: string | URL): Uint8Array;
/** Reads and resolves to the entire contents of a file as an array of bytes.
* `TextDecoder` can be used to transform the bytes to string if required.
@ -1189,7 +1202,7 @@ declare namespace Deno {
* ```
*
* Requires `allow-read` permission. */
export function readFile(path: string): Promise<Uint8Array>;
export function readFile(path: string | URL): Promise<Uint8Array>;
/** A FileInfo describes a file and is returned by `stat`, `lstat`,
* `statSync`, `lstatSync`. */
@ -1307,7 +1320,7 @@ declare namespace Deno {
* Throws error if `path` is not a directory.
*
* Requires `allow-read` permission. */
export function readDirSync(path: string): Iterable<DirEntry>;
export function readDirSync(path: string | URL): Iterable<DirEntry>;
/** Reads the directory given by `path` and returns an async iterable of
* `Deno.DirEntry`.
@ -1321,7 +1334,7 @@ declare namespace Deno {
* Throws error if `path` is not a directory.
*
* Requires `allow-read` permission. */
export function readDir(path: string): AsyncIterable<DirEntry>;
export function readDir(path: string | URL): AsyncIterable<DirEntry>;
/** Synchronously copies the contents and permissions of one file to another
* specified path, by default creating a new file if needed, else overwriting.
@ -1333,7 +1346,10 @@ declare namespace Deno {
*
* Requires `allow-read` permission on fromPath.
* Requires `allow-write` permission on toPath. */
export function copyFileSync(fromPath: string, toPath: string): void;
export function copyFileSync(
fromPath: string | URL,
toPath: string | URL
): void;
/** Copies the contents and permissions of one file to another specified path,
* by default creating a new file if needed, else overwriting. Fails if target
@ -1345,7 +1361,10 @@ declare namespace Deno {
*
* Requires `allow-read` permission on fromPath.
* Requires `allow-write` permission on toPath. */
export function copyFile(fromPath: string, toPath: string): Promise<void>;
export function copyFile(
fromPath: string | URL,
toPath: string | URL
): Promise<void>;
/** Returns the full path destination of the named symbolic link.
*
@ -1382,7 +1401,7 @@ declare namespace Deno {
* ```
*
* Requires `allow-read` permission. */
export function lstat(path: string): Promise<FileInfo>;
export function lstat(path: string | URL): Promise<FileInfo>;
/** Synchronously returns a `Deno.FileInfo` for the specified `path`. If
* `path` is a symlink, information for the symlink will be returned instead of
@ -1394,7 +1413,7 @@ declare namespace Deno {
* ```
*
* Requires `allow-read` permission. */
export function lstatSync(path: string): FileInfo;
export function lstatSync(path: string | URL): FileInfo;
/** Resolves to a `Deno.FileInfo` for the specified `path`. Will always
* follow symlinks.
@ -1406,7 +1425,7 @@ declare namespace Deno {
* ```
*
* Requires `allow-read` permission. */
export function stat(path: string): Promise<FileInfo>;
export function stat(path: string | URL): Promise<FileInfo>;
/** Synchronously returns a `Deno.FileInfo` for the specified `path`. Will
* always follow symlinks.
@ -1418,7 +1437,7 @@ declare namespace Deno {
* ```
*
* Requires `allow-read` permission. */
export function statSync(path: string): FileInfo;
export function statSync(path: string | URL): FileInfo;
/** Options for writing to a file. */
export interface WriteFileOptions {
@ -1448,7 +1467,7 @@ declare namespace Deno {
* `false`.
*/
export function writeFileSync(
path: string,
path: string | URL,
data: Uint8Array,
options?: WriteFileOptions
): void;
@ -1468,7 +1487,7 @@ declare namespace Deno {
* Requires `allow-write` permission, and `allow-read` if `options.create` is `false`.
*/
export function writeFile(
path: string,
path: string | URL,
data: Uint8Array,
options?: WriteFileOptions
): Promise<void>;
@ -1482,7 +1501,7 @@ declare namespace Deno {
*
* Requires `allow-write` permission, and `allow-read` if `options.create` is `false`.
*/
export function writeTextFileSync(path: string, data: string): void;
export function writeTextFileSync(path: string | URL, data: string): void;
/** Asynchronously write string `data` to the given `path`, by default creating a new file if needed,
* else overwriting.
@ -1493,7 +1512,10 @@ declare namespace Deno {
*
* Requires `allow-write` permission, and `allow-read` if `options.create` is `false`.
*/
export function writeTextFile(path: string, data: string): Promise<void>;
export function writeTextFile(
path: string | URL,
data: string
): Promise<void>;
/** Synchronously truncates or extends the specified file, to reach the
* specified `len`. If `len` is not specified then the entire file contents

View file

@ -1,10 +1,13 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import { sendSync, sendAsync } from "../dispatch_json.ts";
import { pathFromURL } from "../../util.ts";
export function chmodSync(path: string, mode: number): void {
export function chmodSync(path: string | URL, mode: number): void {
path = pathFromURL(path);
sendSync("op_chmod", { path, mode });
}
export async function chmod(path: string, mode: number): Promise<void> {
export async function chmod(path: string | URL, mode: number): Promise<void> {
path = pathFromURL(path);
await sendAsync("op_chmod", { path, mode });
}

View file

@ -1,14 +1,17 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import { sendSync, sendAsync } from "../dispatch_json.ts";
import { pathFromURL } from "../../util.ts";
export function chownSync(path: string, uid: number, gid: number): void {
export function chownSync(path: string | URL, uid: number, gid: number): void {
path = pathFromURL(path);
sendSync("op_chown", { path, uid, gid });
}
export async function chown(
path: string,
path: string | URL,
uid: number,
gid: number
): Promise<void> {
path = pathFromURL(path);
await sendAsync("op_chown", { path, uid, gid });
}

View file

@ -1,13 +1,23 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import { sendSync, sendAsync } from "../dispatch_json.ts";
import { pathFromURL } from "../../util.ts";
export function copyFileSync(
fromPath: string | URL,
toPath: string | URL
): void {
fromPath = pathFromURL(fromPath);
toPath = pathFromURL(toPath);
export function copyFileSync(fromPath: string, toPath: string): void {
sendSync("op_copy_file", { from: fromPath, to: toPath });
}
export async function copyFile(
fromPath: string,
toPath: string
fromPath: string | URL,
toPath: string | URL
): Promise<void> {
fromPath = pathFromURL(fromPath);
toPath = pathFromURL(toPath);
await sendAsync("op_copy_file", { from: fromPath, to: toPath });
}

View file

@ -1,5 +1,6 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import { sendSync, sendAsync } from "../dispatch_json.ts";
import { pathFromURL } from "../../util.ts";
export interface OpenOptions {
read?: boolean;
@ -15,13 +16,18 @@ export interface OpenOptions {
mode?: number;
}
export function openSync(path: string, options: OpenOptions): number {
export function openSync(path: string | URL, options: OpenOptions): number {
const mode: number | undefined = options?.mode;
path = pathFromURL(path);
return sendSync("op_open", { path, options, mode });
}
export function open(path: string, options: OpenOptions): Promise<number> {
export function open(
path: string | URL,
options: OpenOptions
): Promise<number> {
const mode: number | undefined = options?.mode;
path = pathFromURL(path);
return sendAsync("op_open", {
path,
options,

View file

@ -1,5 +1,6 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import { sendSync, sendAsync } from "../dispatch_json.ts";
import { pathFromURL } from "../../util.ts";
export interface DirEntry {
name: string;
@ -16,11 +17,13 @@ function res(response: ReadDirResponse): DirEntry[] {
return response.entries;
}
export function readDirSync(path: string): Iterable<DirEntry> {
export function readDirSync(path: string | URL): Iterable<DirEntry> {
path = pathFromURL(path);
return res(sendSync("op_read_dir", { path }))[Symbol.iterator]();
}
export function readDir(path: string): AsyncIterable<DirEntry> {
export function readDir(path: string | URL): AsyncIterable<DirEntry> {
path = pathFromURL(path);
const array = sendAsync("op_read_dir", { path }).then(res);
return {
async *[Symbol.asyncIterator](): AsyncIterableIterator<DirEntry> {

View file

@ -1,17 +1,23 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import { sendSync, sendAsync } from "../dispatch_json.ts";
import { pathFromURL } from "../../util.ts";
export interface RemoveOptions {
recursive?: boolean;
}
export function removeSync(path: string, options: RemoveOptions = {}): void {
export function removeSync(
path: string | URL,
options: RemoveOptions = {}
): void {
path = pathFromURL(path);
sendSync("op_remove", { path, recursive: !!options.recursive });
}
export async function remove(
path: string,
path: string | URL,
options: RemoveOptions = {}
): Promise<void> {
path = pathFromURL(path);
await sendAsync("op_remove", { path, recursive: !!options.recursive });
}

View file

@ -1,6 +1,7 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import { sendSync, sendAsync } from "../dispatch_json.ts";
import { build } from "../../build.ts";
import { pathFromURL } from "../../util.ts";
export interface FileInfo {
size: number;
@ -65,7 +66,8 @@ export function parseFileInfo(response: StatResponse): FileInfo {
};
}
export async function lstat(path: string): Promise<FileInfo> {
export async function lstat(path: string | URL): Promise<FileInfo> {
path = pathFromURL(path);
const res = (await sendAsync("op_stat", {
path,
lstat: true,
@ -73,7 +75,8 @@ export async function lstat(path: string): Promise<FileInfo> {
return parseFileInfo(res);
}
export function lstatSync(path: string): FileInfo {
export function lstatSync(path: string | URL): FileInfo {
path = pathFromURL(path);
const res = sendSync("op_stat", {
path,
lstat: true,
@ -81,7 +84,8 @@ export function lstatSync(path: string): FileInfo {
return parseFileInfo(res);
}
export async function stat(path: string): Promise<FileInfo> {
export async function stat(path: string | URL): Promise<FileInfo> {
path = pathFromURL(path);
const res = (await sendAsync("op_stat", {
path,
lstat: false,
@ -89,7 +93,8 @@ export async function stat(path: string): Promise<FileInfo> {
return parseFileInfo(res);
}
export function statSync(path: string): FileInfo {
export function statSync(path: string | URL): FileInfo {
path = pathFromURL(path);
const res = sendSync("op_stat", {
path,
lstat: false,

View file

@ -2,14 +2,14 @@
import { open, openSync } from "./files.ts";
import { readAll, readAllSync } from "./buffer.ts";
export function readFileSync(path: string): Uint8Array {
export function readFileSync(path: string | URL): Uint8Array {
const file = openSync(path);
const contents = readAllSync(file);
file.close();
return contents;
}
export async function readFile(path: string): Promise<Uint8Array> {
export async function readFile(path: string | URL): Promise<Uint8Array> {
const file = await open(path);
const contents = await readAll(file);
file.close();

View file

@ -1,7 +1,7 @@
import { open, openSync } from "./files.ts";
import { readAll, readAllSync } from "./buffer.ts";
export function readTextFileSync(path: string): string {
export function readTextFileSync(path: string | URL): string {
const decoder = new TextDecoder();
const file = openSync(path);
const content = readAllSync(file);
@ -9,7 +9,7 @@ export function readTextFileSync(path: string): string {
return decoder.decode(content);
}
export async function readTextFile(path: string): Promise<string> {
export async function readTextFile(path: string | URL): Promise<string> {
const decoder = new TextDecoder();
const file = await open(path);
const content = await readAll(file);

View file

@ -1,4 +1,6 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import { build } from "./build.ts";
import { exposeForTest } from "./internals.ts";
let logDebug = false;
let logSource = "JS";
@ -78,3 +80,49 @@ export function immutableDefine(
writable: false,
});
}
function pathFromURLWin32(url: URL): string {
if (url.hostname !== "") {
//TODO(actual-size) Node adds a punycode decoding step, we should consider adding this
return `\\\\${url.hostname}${url.pathname}`;
}
const validPath = /^\/(?<driveLetter>[A-Za-z]):\//;
const matches = validPath.exec(url.pathname);
if (!matches?.groups?.driveLetter) {
throw new TypeError("A URL with the file schema must be absolute.");
}
const pathname = url.pathname.replace(/\//g, "\\");
// we don't want a leading slash on an absolute path in Windows
return pathname.slice(1);
}
function pathFromURLPosix(url: URL): string {
if (url.hostname !== "") {
throw new TypeError(`Host must be empty.`);
}
return decodeURIComponent(url.pathname);
}
export function pathFromURL(pathOrUrl: string | URL): string {
if (typeof pathOrUrl == "string") {
try {
pathOrUrl = new URL(pathOrUrl);
} catch {}
}
if (pathOrUrl instanceof URL) {
if (pathOrUrl.protocol != "file:") {
throw new TypeError("Must be a path string or file URL.");
}
return build.os == "windows"
? pathFromURLWin32(pathOrUrl)
: pathFromURLPosix(pathOrUrl);
}
return pathOrUrl;
}
exposeForTest("pathFromURL", pathFromURL);

View file

@ -12,7 +12,7 @@ export interface WriteFileOptions {
}
export function writeFileSync(
path: string,
path: string | URL,
data: Uint8Array,
options: WriteFileOptions = {}
): void {
@ -42,7 +42,7 @@ export function writeFileSync(
}
export async function writeFile(
path: string,
path: string | URL,
data: Uint8Array,
options: WriteFileOptions = {}
): Promise<void> {

View file

@ -1,7 +1,7 @@
import { open, openSync } from "./files.ts";
import { writeAll, writeAllSync } from "./buffer.ts";
export function writeTextFileSync(path: string, data: string): void {
export function writeTextFileSync(path: string | URL, data: string): void {
const file = openSync(path, { write: true, create: true, truncate: true });
const enc = new TextEncoder();
const contents = enc.encode(data);
@ -9,7 +9,10 @@ export function writeTextFileSync(path: string, data: string): void {
file.close();
}
export async function writeTextFile(path: string, data: string): Promise<void> {
export async function writeTextFile(
path: string | URL,
data: string
): Promise<void> {
const file = await open(path, { write: true, create: true, truncate: true });
const enc = new TextEncoder();
const contents = enc.encode(data);

View file

@ -18,6 +18,25 @@ unitTest(
}
);
unitTest(
{ ignore: Deno.build.os === "windows", perms: { read: true, write: true } },
function chmodSyncUrl(): void {
const enc = new TextEncoder();
const data = enc.encode("Hello");
const tempDir = Deno.makeTempDirSync();
const fileUrl = new URL(`file://${tempDir}/test.txt`);
Deno.writeFileSync(fileUrl, data, { mode: 0o666 });
Deno.chmodSync(fileUrl, 0o777);
const fileInfo = Deno.statSync(fileUrl);
assert(fileInfo.mode);
assertEquals(fileInfo.mode & 0o777, 0o777);
Deno.removeSync(tempDir, { recursive: true });
}
);
// Check symlink when not on windows
unitTest(
{
@ -89,6 +108,25 @@ unitTest(
}
);
unitTest(
{ ignore: Deno.build.os === "windows", perms: { read: true, write: true } },
async function chmodUrl(): Promise<void> {
const enc = new TextEncoder();
const data = enc.encode("Hello");
const tempDir = Deno.makeTempDirSync();
const fileUrl = new URL(`file://${tempDir}/test.txt`);
Deno.writeFileSync(fileUrl, data, { mode: 0o666 });
await Deno.chmod(fileUrl, 0o777);
const fileInfo = Deno.statSync(fileUrl);
assert(fileInfo.mode);
assertEquals(fileInfo.mode & 0o777, 0o777);
Deno.removeSync(tempDir, { recursive: true });
}
);
// Check symlink when not on windows
unitTest(

View file

@ -125,6 +125,26 @@ if (Deno.build.os !== "windows") {
}
);
unitTest(
{ perms: { run: true, write: true } },
async function chownSyncWithUrl(): Promise<void> {
// TODO: same as chownSyncSucceed
const { uid, gid } = await getUidAndGid();
const enc = new TextEncoder();
const dirPath = Deno.makeTempDirSync();
const fileUrl = new URL(`file://${dirPath}/chown_test_file.txt`);
const fileData = enc.encode("Hello");
Deno.writeFileSync(fileUrl, fileData);
// the test script creates this file with the same uid and gid,
// here chown is a noop so it succeeds under non-priviledged user
Deno.chownSync(fileUrl, uid, gid);
Deno.removeSync(dirPath, { recursive: true });
}
);
unitTest(
{ perms: { run: true, write: true } },
async function chownSucceed(): Promise<void> {
@ -144,4 +164,24 @@ if (Deno.build.os !== "windows") {
Deno.removeSync(dirPath, { recursive: true });
}
);
unitTest(
{ perms: { run: true, write: true } },
async function chownWithUrl(): Promise<void> {
// TODO: same as chownSyncSucceed
const { uid, gid } = await getUidAndGid();
const enc = new TextEncoder();
const dirPath = await Deno.makeTempDir();
const fileUrl = new URL(`file://${dirPath}/chown_test_file.txt`);
const fileData = enc.encode("Hello");
await Deno.writeFile(fileUrl, fileData);
// the test script creates this file with the same uid and gid,
// here chown is a noop so it succeeds under non-priviledged user
await Deno.chown(fileUrl, uid, gid);
Deno.removeSync(dirPath, { recursive: true });
}
);
}

View file

@ -1,19 +1,22 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import { unitTest, assert, assertEquals } from "./test_util.ts";
function readFileString(filename: string): string {
function readFileString(filename: string | URL): string {
const dataRead = Deno.readFileSync(filename);
const dec = new TextDecoder("utf-8");
return dec.decode(dataRead);
}
function writeFileString(filename: string, s: string): void {
function writeFileString(filename: string | URL, s: string): void {
const enc = new TextEncoder();
const data = enc.encode(s);
Deno.writeFileSync(filename, data, { mode: 0o666 });
}
function assertSameContent(filename1: string, filename2: string): void {
function assertSameContent(
filename1: string | URL,
filename2: string | URL
): void {
const data1 = Deno.readFileSync(filename1);
const data2 = Deno.readFileSync(filename2);
assertEquals(data1, data2);
@ -31,6 +34,29 @@ unitTest(
assertEquals(readFileString(fromFilename), "Hello world!");
// Original == Dest
assertSameContent(fromFilename, toFilename);
Deno.removeSync(tempDir, { recursive: true });
}
);
unitTest(
{ perms: { read: true, write: true } },
function copyFileSyncByUrl(): void {
const tempDir = Deno.makeTempDirSync();
const fromUrl = new URL(
`file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/from.txt`
);
const toUrl = new URL(
`file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/to.txt`
);
writeFileString(fromUrl, "Hello world!");
Deno.copyFileSync(fromUrl, toUrl);
// No change to original file
assertEquals(readFileString(fromUrl), "Hello world!");
// Original == Dest
assertSameContent(fromUrl, toUrl);
Deno.removeSync(tempDir, { recursive: true });
}
);
@ -49,6 +75,8 @@ unitTest(
}
assert(!!err);
assert(err instanceof Deno.errors.NotFound);
Deno.removeSync(tempDir, { recursive: true });
}
);
@ -94,6 +122,8 @@ unitTest(
assertEquals(readFileString(fromFilename), "Hello world!");
// Original == Dest
assertSameContent(fromFilename, toFilename);
Deno.removeSync(tempDir, { recursive: true });
}
);
@ -109,6 +139,29 @@ unitTest(
assertEquals(readFileString(fromFilename), "Hello world!");
// Original == Dest
assertSameContent(fromFilename, toFilename);
Deno.removeSync(tempDir, { recursive: true });
}
);
unitTest(
{ perms: { read: true, write: true } },
async function copyFileByUrl(): Promise<void> {
const tempDir = Deno.makeTempDirSync();
const fromUrl = new URL(
`file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/from.txt`
);
const toUrl = new URL(
`file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/to.txt`
);
writeFileString(fromUrl, "Hello world!");
await Deno.copyFile(fromUrl, toUrl);
// No change to original file
assertEquals(readFileString(fromUrl), "Hello world!");
// Original == Dest
assertSameContent(fromUrl, toUrl);
Deno.removeSync(tempDir, { recursive: true });
}
);
@ -127,6 +180,8 @@ unitTest(
}
assert(!!err);
assert(err instanceof Deno.errors.NotFound);
Deno.removeSync(tempDir, { recursive: true });
}
);
@ -144,6 +199,8 @@ unitTest(
assertEquals(readFileString(fromFilename), "Hello world!");
// Original == Dest
assertSameContent(fromFilename, toFilename);
Deno.removeSync(tempDir, { recursive: true });
}
);

View file

@ -197,6 +197,54 @@ unitTest(
}
);
unitTest(
{
perms: { read: true, write: true },
},
function openSyncUrl(): void {
const tempDir = Deno.makeTempDirSync();
const fileUrl = new URL(
`file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/test_open.txt`
);
const file = Deno.openSync(fileUrl, {
write: true,
createNew: true,
mode: 0o626,
});
file.close();
const pathInfo = Deno.statSync(fileUrl);
if (Deno.build.os !== "windows") {
assertEquals(pathInfo.mode! & 0o777, 0o626 & ~Deno.umask());
}
Deno.removeSync(tempDir, { recursive: true });
}
);
unitTest(
{
perms: { read: true, write: true },
},
async function openUrl(): Promise<void> {
const tempDir = await Deno.makeTempDir();
const fileUrl = new URL(
`file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/test_open.txt`
);
const file = await Deno.open(fileUrl, {
write: true,
createNew: true,
mode: 0o626,
});
file.close();
const pathInfo = Deno.statSync(fileUrl);
if (Deno.build.os !== "windows") {
assertEquals(pathInfo.mode! & 0o777, 0o626 & ~Deno.umask());
}
Deno.removeSync(tempDir, { recursive: true });
}
);
unitTest(
{ perms: { write: false } },
async function writePermFailure(): Promise<void> {
@ -375,6 +423,71 @@ unitTest(
}
);
unitTest(
{ perms: { read: true, write: true } },
async function createFileWithUrl(): Promise<void> {
const tempDir = await Deno.makeTempDir();
const fileUrl = new URL(
`file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/test.txt`
);
const f = await Deno.create(fileUrl);
let fileInfo = Deno.statSync(fileUrl);
assert(fileInfo.isFile);
assert(fileInfo.size === 0);
const enc = new TextEncoder();
const data = enc.encode("Hello");
await f.write(data);
fileInfo = Deno.statSync(fileUrl);
assert(fileInfo.size === 5);
f.close();
await Deno.remove(tempDir, { recursive: true });
}
);
unitTest(
{ perms: { read: true, write: true } },
async function createSyncFile(): Promise<void> {
const tempDir = await Deno.makeTempDir();
const filename = tempDir + "/test.txt";
const f = Deno.createSync(filename);
let fileInfo = Deno.statSync(filename);
assert(fileInfo.isFile);
assert(fileInfo.size === 0);
const enc = new TextEncoder();
const data = enc.encode("Hello");
await f.write(data);
fileInfo = Deno.statSync(filename);
assert(fileInfo.size === 5);
f.close();
// TODO: test different modes
await Deno.remove(tempDir, { recursive: true });
}
);
unitTest(
{ perms: { read: true, write: true } },
async function createSyncFileWithUrl(): Promise<void> {
const tempDir = await Deno.makeTempDir();
const fileUrl = new URL(
`file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/test.txt`
);
const f = Deno.createSync(fileUrl);
let fileInfo = Deno.statSync(fileUrl);
assert(fileInfo.isFile);
assert(fileInfo.size === 0);
const enc = new TextEncoder();
const data = enc.encode("Hello");
await f.write(data);
fileInfo = Deno.statSync(fileUrl);
assert(fileInfo.size === 5);
f.close();
await Deno.remove(tempDir, { recursive: true });
}
);
unitTest(
{ perms: { read: true, write: true } },
async function openModeWrite(): Promise<void> {

View file

@ -0,0 +1,31 @@
import { assertThrows, assertEquals, unitTest } from "./test_util.ts";
// @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol
const { pathFromURL } = Deno[Deno.internal];
unitTest(
{ ignore: Deno.build.os === "windows" },
function pathFromURLPosix(): void {
assertEquals(pathFromURL("file:///test/directory"), "/test/directory");
assertThrows(() => pathFromURL("file://host/test/directory"));
assertThrows(() => pathFromURL("https://deno.land/welcome.ts"));
}
);
unitTest(
{ ignore: Deno.build.os !== "windows" },
function pathFromURLWin32(): void {
assertEquals(pathFromURL("file:///c:/windows/test"), "c:\\windows\\test");
assertThrows(() => pathFromURL("file:///thing/test"));
assertThrows(() => pathFromURL("https://deno.land/welcome.ts"));
/* TODO(ry) Add tests for these situations
* ampersand_&.tx file:///D:/weird_names/ampersand_&.txt
* at_@.txt file:///D:/weird_names/at_@.txt
* emoji_🙃.txt file:///D:/weird_names/emoji_%F0%9F%99%83.txt
* percent_%.txt file:///D:/weird_names/percent_%25.txt
* pound_#.txt file:///D:/weird_names/pound_%23.txt
* space_ .txt file:///D:/weird_names/space_%20.txt
* swapped_surrogate_pair_<EFBFBD><EFBFBD>.txt file:///D:/weird_names/swapped_surrogate_pair_%EF%BF%BD%EF%BF%BD.txt
*/
}
);

View file

@ -1,5 +1,10 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import { unitTest, assert, assertEquals } from "./test_util.ts";
import {
unitTest,
assert,
assertEquals,
pathToAbsoluteFileUrl,
} from "./test_util.ts";
function assertSameContent(files: Deno.DirEntry[]): void {
let counter = 0;
@ -19,6 +24,11 @@ unitTest({ perms: { read: true } }, function readDirSyncSuccess(): void {
assertSameContent(files);
});
unitTest({ perms: { read: true } }, function readDirSyncWithUrl(): void {
const files = [...Deno.readDirSync(pathToAbsoluteFileUrl("cli/tests"))];
assertSameContent(files);
});
unitTest({ perms: { read: false } }, function readDirSyncPerm(): void {
let caughtError = false;
try {
@ -68,6 +78,18 @@ unitTest({ perms: { read: true } }, async function readDirSuccess(): Promise<
assertSameContent(files);
});
unitTest({ perms: { read: true } }, async function readDirWithUrl(): Promise<
void
> {
const files = [];
for await (const dirEntry of Deno.readDir(
pathToAbsoluteFileUrl("cli/tests")
)) {
files.push(dirEntry);
}
assertSameContent(files);
});
unitTest({ perms: { read: false } }, async function readDirPerm(): Promise<
void
> {

View file

@ -1,5 +1,10 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import { unitTest, assert, assertEquals } from "./test_util.ts";
import {
unitTest,
assert,
assertEquals,
pathToAbsoluteFileUrl,
} from "./test_util.ts";
unitTest({ perms: { read: true } }, function readFileSyncSuccess(): void {
const data = Deno.readFileSync("cli/tests/fixture.json");
@ -10,6 +15,17 @@ unitTest({ perms: { read: true } }, function readFileSyncSuccess(): void {
assertEquals(pkg.name, "deno");
});
unitTest({ perms: { read: true } }, function readFileSyncUrl(): void {
const data = Deno.readFileSync(
pathToAbsoluteFileUrl("cli/tests/fixture.json")
);
assert(data.byteLength > 0);
const decoder = new TextDecoder("utf-8");
const json = decoder.decode(data);
const pkg = JSON.parse(json);
assertEquals(pkg.name, "deno");
});
unitTest({ perms: { read: false } }, function readFileSyncPerm(): void {
let caughtError = false;
try {
@ -34,6 +50,19 @@ unitTest({ perms: { read: true } }, function readFileSyncNotFound(): void {
assert(data === undefined);
});
unitTest({ perms: { read: true } }, async function readFileUrl(): Promise<
void
> {
const data = await Deno.readFile(
pathToAbsoluteFileUrl("cli/tests/fixture.json")
);
assert(data.byteLength > 0);
const decoder = new TextDecoder("utf-8");
const json = decoder.decode(data);
const pkg = JSON.parse(json);
assertEquals(pkg.name, "deno");
});
unitTest({ perms: { read: true } }, async function readFileSuccess(): Promise<
void
> {

View file

@ -1,4 +1,9 @@
import { unitTest, assert, assertEquals } from "./test_util.ts";
import {
unitTest,
assert,
assertEquals,
pathToAbsoluteFileUrl,
} from "./test_util.ts";
unitTest({ perms: { read: true } }, function readTextFileSyncSuccess(): void {
const data = Deno.readTextFileSync("cli/tests/fixture.json");
@ -7,6 +12,15 @@ unitTest({ perms: { read: true } }, function readTextFileSyncSuccess(): void {
assertEquals(pkg.name, "deno");
});
unitTest({ perms: { read: true } }, function readTextFileSyncByUrl(): void {
const data = Deno.readTextFileSync(
pathToAbsoluteFileUrl("cli/tests/fixture.json")
);
assert(data.length > 0);
const pkg = JSON.parse(data);
assertEquals(pkg.name, "deno");
});
unitTest({ perms: { read: false } }, function readTextFileSyncPerm(): void {
let caughtError = false;
try {
@ -41,6 +55,17 @@ unitTest(
}
);
unitTest({ perms: { read: true } }, async function readTextFileByUrl(): Promise<
void
> {
const data = await Deno.readTextFile(
pathToAbsoluteFileUrl("cli/tests/fixture.json")
);
assert(data.length > 0);
const pkg = JSON.parse(data);
assertEquals(pkg.name, "deno");
});
unitTest({ perms: { read: false } }, async function readTextFilePerm(): Promise<
void
> {

View file

@ -51,6 +51,36 @@ unitTest(
}
);
unitTest(
{ perms: { write: true, read: true } },
async function removeFileByUrl(): Promise<void> {
for (const method of REMOVE_METHODS) {
// REMOVE FILE
const enc = new TextEncoder();
const data = enc.encode("Hello");
const tempDir = Deno.makeTempDirSync();
const fileUrl = new URL(
`file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/test.txt`
);
Deno.writeFileSync(fileUrl, data, { mode: 0o666 });
const fileInfo = Deno.statSync(fileUrl);
assert(fileInfo.isFile); // check exist first
await Deno[method](fileUrl); // remove
// We then check again after remove
let err;
try {
Deno.statSync(fileUrl);
} catch (e) {
err = e;
}
// File is gone
assert(err instanceof Deno.errors.NotFound);
}
}
);
unitTest(
{ perms: { write: true, read: true } },
async function removeFail(): Promise<void> {

View file

@ -1,5 +1,10 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import { unitTest, assert, assertEquals } from "./test_util.ts";
import {
unitTest,
assert,
assertEquals,
pathToAbsoluteFileUrl,
} from "./test_util.ts";
unitTest(
{ perms: { read: true, write: true } },
@ -18,12 +23,47 @@ unitTest(
const tempFile = Deno.makeTempFileSync();
const tempInfo = Deno.statSync(tempFile);
const now = Date.now();
let now = Date.now();
assert(tempInfo.atime !== null && now - tempInfo.atime.valueOf() < 1000);
assert(tempInfo.mtime !== null && now - tempInfo.mtime.valueOf() < 1000);
assert(
tempInfo.birthtime === null || now - tempInfo.birthtime.valueOf() < 1000
);
const packageInfoByUrl = Deno.statSync(pathToAbsoluteFileUrl("README.md"));
assert(packageInfoByUrl.isFile);
assert(!packageInfoByUrl.isSymlink);
const modulesInfoByUrl = Deno.statSync(
pathToAbsoluteFileUrl("cli/tests/symlink_to_subdir")
);
assert(modulesInfoByUrl.isDirectory);
assert(!modulesInfoByUrl.isSymlink);
const testsInfoByUrl = Deno.statSync(pathToAbsoluteFileUrl("cli/tests"));
assert(testsInfoByUrl.isDirectory);
assert(!testsInfoByUrl.isSymlink);
const tempFileForUrl = Deno.makeTempFileSync();
const tempInfoByUrl = Deno.statSync(
new URL(
`file://${Deno.build.os === "windows" ? "/" : ""}${tempFileForUrl}`
)
);
now = Date.now();
assert(
tempInfoByUrl.atime !== null && now - tempInfoByUrl.atime.valueOf() < 1000
);
assert(
tempInfoByUrl.mtime !== null && now - tempInfoByUrl.mtime.valueOf() < 1000
);
assert(
tempInfoByUrl.birthtime === null ||
now - tempInfoByUrl.birthtime.valueOf() < 1000
);
Deno.removeSync(tempFile, { recursive: true });
Deno.removeSync(tempFileForUrl, { recursive: true });
}
);
@ -58,13 +98,27 @@ unitTest({ perms: { read: true } }, function lstatSyncSuccess(): void {
assert(packageInfo.isFile);
assert(!packageInfo.isSymlink);
const packageInfoByUrl = Deno.lstatSync(pathToAbsoluteFileUrl("README.md"));
assert(packageInfoByUrl.isFile);
assert(!packageInfoByUrl.isSymlink);
const modulesInfo = Deno.lstatSync("cli/tests/symlink_to_subdir");
assert(!modulesInfo.isDirectory);
assert(modulesInfo.isSymlink);
const modulesInfoByUrl = Deno.lstatSync(
pathToAbsoluteFileUrl("cli/tests/symlink_to_subdir")
);
assert(!modulesInfoByUrl.isDirectory);
assert(modulesInfoByUrl.isSymlink);
const coreInfo = Deno.lstatSync("core");
assert(coreInfo.isDirectory);
assert(!coreInfo.isSymlink);
const coreInfoByUrl = Deno.lstatSync(pathToAbsoluteFileUrl("core"));
assert(coreInfoByUrl.isDirectory);
assert(!coreInfoByUrl.isSymlink);
});
unitTest({ perms: { read: false } }, function lstatSyncPerm(): void {
@ -100,23 +154,60 @@ unitTest(
assert(packageInfo.isFile);
assert(!packageInfo.isSymlink);
const packageInfoByUrl = await Deno.stat(
pathToAbsoluteFileUrl("README.md")
);
assert(packageInfoByUrl.isFile);
assert(!packageInfoByUrl.isSymlink);
const modulesInfo = await Deno.stat("cli/tests/symlink_to_subdir");
assert(modulesInfo.isDirectory);
assert(!modulesInfo.isSymlink);
const modulesInfoByUrl = await Deno.stat(
pathToAbsoluteFileUrl("cli/tests/symlink_to_subdir")
);
assert(modulesInfoByUrl.isDirectory);
assert(!modulesInfoByUrl.isSymlink);
const testsInfo = await Deno.stat("cli/tests");
assert(testsInfo.isDirectory);
assert(!testsInfo.isSymlink);
const testsInfoByUrl = await Deno.stat(pathToAbsoluteFileUrl("cli/tests"));
assert(testsInfoByUrl.isDirectory);
assert(!testsInfoByUrl.isSymlink);
const tempFile = await Deno.makeTempFile();
const tempInfo = await Deno.stat(tempFile);
const now = Date.now();
let now = Date.now();
assert(tempInfo.atime !== null && now - tempInfo.atime.valueOf() < 1000);
assert(tempInfo.mtime !== null && now - tempInfo.mtime.valueOf() < 1000);
assert(
tempInfo.birthtime === null || now - tempInfo.birthtime.valueOf() < 1000
);
const tempFileForUrl = await Deno.makeTempFile();
const tempInfoByUrl = await Deno.stat(
new URL(
`file://${Deno.build.os === "windows" ? "/" : ""}${tempFileForUrl}`
)
);
now = Date.now();
assert(
tempInfoByUrl.atime !== null && now - tempInfoByUrl.atime.valueOf() < 1000
);
assert(
tempInfoByUrl.mtime !== null && now - tempInfoByUrl.mtime.valueOf() < 1000
);
assert(
tempInfoByUrl.birthtime === null ||
now - tempInfoByUrl.birthtime.valueOf() < 1000
);
Deno.removeSync(tempFile, { recursive: true });
Deno.removeSync(tempFileForUrl, { recursive: true });
}
);
@ -155,13 +246,27 @@ unitTest({ perms: { read: true } }, async function lstatSuccess(): Promise<
assert(packageInfo.isFile);
assert(!packageInfo.isSymlink);
const packageInfoByUrl = await Deno.lstat(pathToAbsoluteFileUrl("README.md"));
assert(packageInfoByUrl.isFile);
assert(!packageInfoByUrl.isSymlink);
const modulesInfo = await Deno.lstat("cli/tests/symlink_to_subdir");
assert(!modulesInfo.isDirectory);
assert(modulesInfo.isSymlink);
const modulesInfoByUrl = await Deno.lstat(
pathToAbsoluteFileUrl("cli/tests/symlink_to_subdir")
);
assert(!modulesInfoByUrl.isDirectory);
assert(modulesInfoByUrl.isSymlink);
const coreInfo = await Deno.lstat("core");
assert(coreInfo.isDirectory);
assert(!coreInfo.isSymlink);
const coreInfoByUrl = await Deno.lstat(pathToAbsoluteFileUrl("core"));
assert(coreInfoByUrl.isDirectory);
assert(!coreInfoByUrl.isSymlink);
});
unitTest({ perms: { read: false } }, async function lstatPerm(): Promise<void> {

View file

@ -1,6 +1,7 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import { assert, assertEquals } from "../../../std/testing/asserts.ts";
import { resolve } from "../../../std/path/mod.ts";
export {
assert,
assertThrows,
@ -363,3 +364,9 @@ unitTest(
});
}
);
export function pathToAbsoluteFileUrl(path: string): URL {
path = resolve(path);
return new URL(`file://${Deno.build.os === "windows" ? "/" : ""}${path}`);
}

View file

@ -40,6 +40,7 @@ import "./mkdir_test.ts";
import "./net_test.ts";
import "./os_test.ts";
import "./permissions_test.ts";
import "./path_from_url_test.ts";
import "./process_test.ts";
import "./real_path_test.ts";
import "./read_dir_test.ts";

View file

@ -15,6 +15,25 @@ unitTest(
}
);
unitTest(
{ perms: { read: true, write: true } },
function writeFileSyncUrl(): void {
const enc = new TextEncoder();
const data = enc.encode("Hello");
const tempDir = Deno.makeTempDirSync();
const fileUrl = new URL(
`file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/test.txt`
);
Deno.writeFileSync(fileUrl, data);
const dataRead = Deno.readFileSync(fileUrl);
const dec = new TextDecoder("utf-8");
const actual = dec.decode(dataRead);
assertEquals("Hello", actual);
Deno.removeSync(tempDir, { recursive: true });
}
);
unitTest({ perms: { write: true } }, function writeFileSyncFail(): void {
const enc = new TextEncoder();
const data = enc.encode("Hello");
@ -125,6 +144,25 @@ unitTest(
}
);
unitTest(
{ perms: { read: true, write: true } },
async function writeFileUrl(): Promise<void> {
const enc = new TextEncoder();
const data = enc.encode("Hello");
const tempDir = await Deno.makeTempDir();
const fileUrl = new URL(
`file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/test.txt`
);
await Deno.writeFile(fileUrl, data);
const dataRead = Deno.readFileSync(fileUrl);
const dec = new TextDecoder("utf-8");
const actual = dec.decode(dataRead);
assertEquals("Hello", actual);
Deno.removeSync(tempDir, { recursive: true });
}
);
unitTest(
{ perms: { read: true, write: true } },
async function writeFileNotFound(): Promise<void> {

View file

@ -10,6 +10,21 @@ unitTest(
}
);
unitTest(
{ perms: { read: true, write: true } },
function writeTextFileSyncByUrl(): void {
const tempDir = Deno.makeTempDirSync();
const fileUrl = new URL(
`file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/test.txt`
);
Deno.writeTextFileSync(fileUrl, "Hello");
const dataRead = Deno.readTextFileSync(fileUrl);
assertEquals("Hello", dataRead);
Deno.removeSync(fileUrl, { recursive: true });
}
);
unitTest({ perms: { write: true } }, function writeTextFileSyncFail(): void {
const filename = "/baddir/test.txt";
// The following should fail because /baddir doesn't exist (hopefully).
@ -46,6 +61,21 @@ unitTest(
}
);
unitTest(
{ perms: { read: true, write: true } },
async function writeTextFileByUrl(): Promise<void> {
const tempDir = Deno.makeTempDirSync();
const fileUrl = new URL(
`file://${Deno.build.os === "windows" ? "/" : ""}${tempDir}/test.txt`
);
await Deno.writeTextFile(fileUrl, "Hello");
const dataRead = Deno.readTextFileSync(fileUrl);
assertEquals("Hello", dataRead);
Deno.removeSync(fileUrl, { recursive: true });
}
);
unitTest(
{ perms: { read: true, write: true } },
async function writeTextFileNotFound(): Promise<void> {