Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add ensureLink/ensureLinkSync for fs #353

Merged
merged 4 commits into from
Apr 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions fs/ensure_link.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
import * as path from "./path/mod.ts";
import { ensureDir, ensureDirSync } from "./ensure_dir.ts";
import { exists, existsSync } from "./exists.ts";
import { PathType, getFileInfoType } from "./utils.ts";

/**
* Ensures that the hard link exists.
* If the directory structure does not exist, it is created.
*
* @param src the source file path. Directory hard links are not allowed.
* @param dest the destination link path
*/
export async function ensureLink(src: string, dest: string): Promise<void> {
if (await exists(dest)) {
const destStatInfo = await Deno.lstat(dest);
const destFilePathType = getFileInfoType(destStatInfo);
if (destFilePathType !== PathType.file) {
throw new Error(
`Ensure path exists, expected 'file', got '${destFilePathType}'`
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard links don't have a specific type, so we can only use it as file.

);
}
return;
}

await ensureDir(path.dirname(dest));

await Deno.link(src, dest);
}

/**
* Ensures that the hard link exists.
* If the directory structure does not exist, it is created.
*
* @param src the source file path. Directory hard links are not allowed.
* @param dest the destination link path
*/
export function ensureLinkSync(src: string, dest: string): void {
if (existsSync(dest)) {
const destStatInfo = Deno.lstatSync(dest);
const destFilePathType = getFileInfoType(destStatInfo);
if (destFilePathType !== PathType.file) {
throw new Error(
`Ensure path exists, expected 'file', got '${destFilePathType}'`
);
}
return;
}

ensureDirSync(path.dirname(dest));

Deno.linkSync(src, dest);
}
170 changes: 170 additions & 0 deletions fs/ensure_link_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
// TODO(axetroy): Add test for Windows once symlink is implemented for Windows.
import { test } from "../testing/mod.ts";
import {
assertEquals,
assertThrows,
assertThrowsAsync
} from "../testing/asserts.ts";
import { ensureLink, ensureLinkSync } from "./ensure_link.ts";
import * as path from "./path/mod.ts";

const testdataDir = path.resolve("fs", "testdata");

test(async function ensureLinkIfItNotExist() {
const srcDir = path.join(testdataDir, "ensure_link_1");
const destDir = path.join(testdataDir, "ensure_link_1_2");
const testFile = path.join(srcDir, "test.txt");
const linkFile = path.join(destDir, "link.txt");

await assertThrowsAsync(async () => {
await ensureLink(testFile, linkFile);
});

await Deno.remove(destDir, { recursive: true });
});

test(function ensureLinkSyncIfItNotExist() {
const testDir = path.join(testdataDir, "ensure_link_2");
const testFile = path.join(testDir, "test.txt");
const linkFile = path.join(testDir, "link.txt");

assertThrows(() => {
ensureLinkSync(testFile, linkFile);
});

Deno.removeSync(testDir, { recursive: true });
});

test(async function ensureLinkIfItExist() {
const testDir = path.join(testdataDir, "ensure_link_3");
const testFile = path.join(testDir, "test.txt");
const linkFile = path.join(testDir, "link.txt");

await Deno.mkdir(testDir, true);
await Deno.writeFile(testFile, new Uint8Array());

await ensureLink(testFile, linkFile);

const srcStat = await Deno.lstat(testFile);
const linkStat = await Deno.lstat(linkFile);

assertEquals(srcStat.isFile(), true);
assertEquals(linkStat.isFile(), true);

// har link success. try to change one of them. they should be change both.

// let's change origin file.
await Deno.writeFile(testFile, new TextEncoder().encode("123"));

const testFileContent1 = new TextDecoder().decode(
await Deno.readFile(testFile)
);
const linkFileContent1 = new TextDecoder().decode(
await Deno.readFile(testFile)
);

assertEquals(testFileContent1, "123");
assertEquals(testFileContent1, linkFileContent1);

// let's change link file.
await Deno.writeFile(testFile, new TextEncoder().encode("abc"));

const testFileContent2 = new TextDecoder().decode(
await Deno.readFile(testFile)
);
const linkFileContent2 = new TextDecoder().decode(
await Deno.readFile(testFile)
);

assertEquals(testFileContent2, "abc");
assertEquals(testFileContent2, linkFileContent2);

await Deno.remove(testDir, { recursive: true });
});

test(function ensureLinkSyncIfItExist() {
const testDir = path.join(testdataDir, "ensure_link_4");
const testFile = path.join(testDir, "test.txt");
const linkFile = path.join(testDir, "link.txt");

Deno.mkdirSync(testDir, true);
Deno.writeFileSync(testFile, new Uint8Array());

ensureLinkSync(testFile, linkFile);

const srcStat = Deno.lstatSync(testFile);

const linkStat = Deno.lstatSync(linkFile);

assertEquals(srcStat.isFile(), true);
assertEquals(linkStat.isFile(), true);

// har link success. try to change one of them. they should be change both.

// let's change origin file.
Deno.writeFileSync(testFile, new TextEncoder().encode("123"));

const testFileContent1 = new TextDecoder().decode(
Deno.readFileSync(testFile)
);
const linkFileContent1 = new TextDecoder().decode(
Deno.readFileSync(testFile)
);

assertEquals(testFileContent1, "123");
assertEquals(testFileContent1, linkFileContent1);

// let's change link file.
Deno.writeFileSync(testFile, new TextEncoder().encode("abc"));

const testFileContent2 = new TextDecoder().decode(
Deno.readFileSync(testFile)
);
const linkFileContent2 = new TextDecoder().decode(
Deno.readFileSync(testFile)
);

assertEquals(testFileContent2, "abc");
assertEquals(testFileContent2, linkFileContent2);

Deno.removeSync(testDir, { recursive: true });
});

test(async function ensureLinkDirectoryIfItExist() {
const testDir = path.join(testdataDir, "ensure_link_origin_3");
const linkDir = path.join(testdataDir, "ensure_link_link_3");
const testFile = path.join(testDir, "test.txt");

await Deno.mkdir(testDir, true);
await Deno.writeFile(testFile, new Uint8Array());

await assertThrowsAsync(
async () => {
await ensureLink(testDir, linkDir);
},
Deno.DenoError,
"Operation not permitted (os error 1)"
);

Deno.removeSync(testDir, { recursive: true });
});

test(function ensureLinkSyncDirectoryIfItExist() {
const testDir = path.join(testdataDir, "ensure_link_origin_3");
const linkDir = path.join(testdataDir, "ensure_link_link_3");
const testFile = path.join(testDir, "test.txt");

Deno.mkdirSync(testDir, true);
Deno.writeFileSync(testFile, new Uint8Array());

assertThrows(
() => {
ensureLinkSync(testDir, linkDir);
},
Deno.DenoError,
"Operation not permitted (os error 1)"
);

Deno.removeSync(testDir, { recursive: true });
});
1 change: 1 addition & 0 deletions fs/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
export * from "./empty_dir.ts";
export * from "./ensure_dir.ts";
export * from "./ensure_file.ts";
export * from "./ensure_link.ts";
export * from "./ensure_symlink.ts";
export * from "./exists.ts";
export * from "./glob.ts";
Expand Down