diff --git a/fs/README.md b/fs/README.md index 93c95de03e..3acf757a88 100644 --- a/fs/README.md +++ b/fs/README.md @@ -45,6 +45,29 @@ ensureFile("./folder/targetFile.dat"); // returns promise ensureFileSync("./folder/targetFile.dat"); // void ``` +### ensureSymlink + +Ensures that the link exists. +If the directory structure does not exist, it is created. + +```ts +import { + ensureSymlink, + ensureSymlinkSync +} from "https://deno.land/std/fs/mod.ts"; + +ensureSymlink( + "./folder/targetFile.dat", + "./folder/targetFile.link.dat", + "file" +); // returns promise +ensureSymlinkSync( + "./folder/targetFile.dat", + "./folder/targetFile.link.dat", + "file" +); // void +``` + ### eol Detects and format the passed string for the targeted End Of Line character. diff --git a/fs/ensure_symlink.ts b/fs/ensure_symlink.ts new file mode 100644 index 0000000000..fbb89948e4 --- /dev/null +++ b/fs/ensure_symlink.ts @@ -0,0 +1,71 @@ +// 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"; + +const isWindows = Deno.platform.os === "win"; + +/** + * Ensures that the link exists. + * If the directory structure does not exist, it is created. + * + * @param src the source file path + * @param dest the destination link path + */ +export async function ensureSymlink(src: string, dest: string): Promise { + const srcStatInfo = await Deno.lstat(src); + const srcFilePathType = getFileInfoType(srcStatInfo); + + if (await exists(dest)) { + const destStatInfo = await Deno.lstat(dest); + const destFilePathType = getFileInfoType(destStatInfo); + if (destFilePathType !== PathType.symlink) { + throw new Error( + `Ensure path exists, expected 'symlink', got '${destFilePathType}'` + ); + } + return; + } + + await ensureDir(path.dirname(dest)); + + // TODO(axetroy): remove this if condition. refs: https://github.com/denoland/deno/issues/2169 + if (isWindows) { + await Deno.symlink(src, dest, srcFilePathType || undefined); + } else { + await Deno.symlink(src, dest); + } +} + +/** + * Ensures that the link exists. + * If the directory structure does not exist, it is created. + * + * @param src the source file path + * @param dest the destination link path + */ +export function ensureSymlinkSync(src: string, dest: string): void { + const srcStatInfo = Deno.lstatSync(src); + const srcFilePathType = getFileInfoType(srcStatInfo); + + if (existsSync(dest)) { + const destStatInfo = Deno.lstatSync(dest); + const destFilePathType = getFileInfoType(destStatInfo); + if (destFilePathType !== PathType.symlink) { + throw new Error( + `Ensure path exists, expected 'symlink', got '${destFilePathType}'` + ); + } + return; + } + + ensureDirSync(path.dirname(dest)); + + // TODO(axetroy): remove this if condition. refs: https://github.com/denoland/deno/issues/2169 + if (isWindows) { + Deno.symlinkSync(src, dest, srcFilePathType || undefined); + } else { + Deno.symlinkSync(src, dest); + } +} diff --git a/fs/ensure_symlink_test.ts b/fs/ensure_symlink_test.ts new file mode 100644 index 0000000000..22be30b889 --- /dev/null +++ b/fs/ensure_symlink_test.ts @@ -0,0 +1,165 @@ +// 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 { ensureSymlink, ensureSymlinkSync } from "./ensure_symlink.ts"; +import * as path from "./path/mod.ts"; + +const testdataDir = path.resolve("fs", "testdata"); +const isWindows = Deno.platform.os === "win"; + +test(async function ensureSymlinkIfItNotExist() { + const testDir = path.join(testdataDir, "link_file_1"); + const testFile = path.join(testDir, "test.txt"); + + assertThrowsAsync(async () => { + await ensureSymlink(testFile, path.join(testDir, "test1.txt")); + }); + + assertThrowsAsync(async () => { + await Deno.stat(testFile).then(() => { + throw new Error("test file should exists."); + }); + }); +}); + +test(function ensureSymlinkSyncIfItNotExist() { + const testDir = path.join(testdataDir, "link_file_2"); + const testFile = path.join(testDir, "test.txt"); + + assertThrows(() => { + ensureSymlinkSync(testFile, path.join(testDir, "test1.txt")); + }); + + assertThrows(() => { + Deno.statSync(testFile); + throw new Error("test file should exists."); + }); +}); + +test(async function ensureSymlinkIfItExist() { + const testDir = path.join(testdataDir, "link_file_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()); + + if (isWindows) { + await assertThrowsAsync( + () => ensureSymlink(testFile, linkFile), + Error, + "Not implemented" + ); + await Deno.remove(testDir, { recursive: true }); + return; + } else { + await ensureSymlink(testFile, linkFile); + } + + const srcStat = await Deno.lstat(testFile); + const linkStat = await Deno.lstat(linkFile); + + assertEquals(srcStat.isFile(), true); + assertEquals(linkStat.isSymlink(), true); + + await Deno.remove(testDir, { recursive: true }); +}); + +test(function ensureSymlinkSyncIfItExist() { + const testDir = path.join(testdataDir, "link_file_4"); + const testFile = path.join(testDir, "test.txt"); + const linkFile = path.join(testDir, "link.txt"); + + Deno.mkdirSync(testDir, true); + Deno.writeFileSync(testFile, new Uint8Array()); + + if (isWindows) { + assertThrows( + () => ensureSymlinkSync(testFile, linkFile), + Error, + "Not implemented" + ); + Deno.removeSync(testDir, { recursive: true }); + return; + } else { + ensureSymlinkSync(testFile, linkFile); + } + + const srcStat = Deno.lstatSync(testFile); + + const linkStat = Deno.lstatSync(linkFile); + + assertEquals(srcStat.isFile(), true); + assertEquals(linkStat.isSymlink(), true); + + Deno.removeSync(testDir, { recursive: true }); +}); + +test(async function ensureSymlinkDirectoryIfItExist() { + const testDir = path.join(testdataDir, "link_file_origin_3"); + const linkDir = path.join(testdataDir, "link_file_link_3"); + const testFile = path.join(testDir, "test.txt"); + + await Deno.mkdir(testDir, true); + await Deno.writeFile(testFile, new Uint8Array()); + + if (isWindows) { + await assertThrowsAsync( + () => ensureSymlink(testDir, linkDir), + Error, + "Not implemented" + ); + await Deno.remove(testDir, { recursive: true }); + return; + } else { + await ensureSymlink(testDir, linkDir); + } + + const testDirStat = await Deno.lstat(testDir); + const linkDirStat = await Deno.lstat(linkDir); + const testFileStat = await Deno.lstat(testFile); + + assertEquals(testFileStat.isFile(), true); + assertEquals(testDirStat.isDirectory(), true); + assertEquals(linkDirStat.isSymlink(), true); + + await Deno.remove(linkDir, { recursive: true }); + await Deno.remove(testDir, { recursive: true }); +}); + +test(function ensureSymlinkSyncDirectoryIfItExist() { + const testDir = path.join(testdataDir, "link_file_origin_3"); + const linkDir = path.join(testdataDir, "link_file_link_3"); + const testFile = path.join(testDir, "test.txt"); + + Deno.mkdirSync(testDir, true); + Deno.writeFileSync(testFile, new Uint8Array()); + + if (isWindows) { + assertThrows( + () => ensureSymlinkSync(testDir, linkDir), + Error, + "Not implemented" + ); + Deno.removeSync(testDir, { recursive: true }); + return; + } else { + ensureSymlinkSync(testDir, linkDir); + } + + const testDirStat = Deno.lstatSync(testDir); + const linkDirStat = Deno.lstatSync(linkDir); + const testFileStat = Deno.lstatSync(testFile); + + assertEquals(testFileStat.isFile(), true); + assertEquals(testDirStat.isDirectory(), true); + assertEquals(linkDirStat.isSymlink(), true); + + Deno.removeSync(linkDir, { recursive: true }); + Deno.removeSync(testDir, { recursive: true }); +}); diff --git a/fs/test.ts b/fs/test.ts index 08c6f2cf88..9f6b4827b9 100644 --- a/fs/test.ts +++ b/fs/test.ts @@ -8,6 +8,7 @@ import "./eol_test.ts"; import "./empty_dir_test.ts"; import "./ensure_dir_test.ts"; import "./ensure_file_test.ts"; +import "./ensure_symlink_test.ts"; import "./move_test.ts"; import "./read_json_test.ts"; import "./write_json_test.ts";