Skip to content

Commit

Permalink
chore: Update FileSystemHost to have move and copy methods.
Browse files Browse the repository at this point in the history
BREAKING CHANGE: The FileSystemHost interface now has move and copy methods.
  • Loading branch information
dsherret committed Apr 28, 2018
1 parent 7be9dba commit 4401755
Show file tree
Hide file tree
Showing 8 changed files with 273 additions and 7 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,9 @@
},
"dependencies": {
"@dsherret/to-absolute-glob": "^2.0.2",
"@types/fs-extra": "^5.0.2",
"code-block-writer": "6.13.0",
"fs-extra": "^5.0.0",
"glob-parent": "^3.1.0",
"globby": "^6.1.0",
"is-negated-glob": "^1.0.0",
Expand Down
9 changes: 9 additions & 0 deletions src/errors/PathNotFoundError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { BaseError } from "./BaseError";

export class PathNotFoundError extends BaseError {
constructor(public readonly path: string) {
super(`Path not found: ${path}`, PathNotFoundError.prototype);
}

readonly code = "ENOENT";
}
1 change: 1 addition & 0 deletions src/errors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export * from "./helpers";
export * from "./InvalidOperationError";
export * from "./NotImplementedError";
export * from "./NotSupportedError";
export * from "./PathNotFoundError";
18 changes: 17 additions & 1 deletion src/fileSystem/DefaultFileSystemHost.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as fs from "fs";
import * as fs from "fs-extra";
import * as nodePath from "path";
import * as globby from "globby";
import * as errors from "../errors";
Expand Down Expand Up @@ -82,6 +82,22 @@ export class DefaultFileSystemHost implements FileSystemHost {
fs.mkdirSync(dirPath);
}

move(srcPath: string, destPath: string) {
return fs.move(srcPath, destPath, { overwrite: true });
}

moveSync(srcPath: string, destPath: string) {
fs.moveSync(srcPath, destPath, { overwrite: true });
}

copy(srcPath: string, destPath: string) {
return fs.copy(srcPath, destPath, { overwrite: true });
}

copySync(srcPath: string, destPath: string) {
fs.copySync(srcPath, destPath, { overwrite: true });
}

fileExists(filePath: string) {
return new Promise<boolean>((resolve, reject) => {
fs.stat(filePath, (err, stat) => {
Expand Down
4 changes: 4 additions & 0 deletions src/fileSystem/FileSystemHost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
writeFileSync(filePath: string, fileText: string): void;
mkdir(dirPath: string): Promise<void>;
mkdirSync(dirPath: string): void;
move(srcPath: string, destPath: string): Promise<void>;
moveSync(srcPath: string, destPath: string): void;
copy(srcPath: string, destPath: string): Promise<void>;
copySync(srcPath: string, destPath: string): void;
fileExists(filePath: string): Promise<boolean>;
fileExistsSync(filePath: string): boolean;
directoryExists(dirPath: string): Promise<boolean>;
Expand Down
78 changes: 73 additions & 5 deletions src/fileSystem/VirtualFileSystemHost.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as errors from "../errors";
import { KeyValueCache, FileUtils, StringUtils, ArrayUtils, matchGlobs } from "../utils";
import { KeyValueCache, FileUtils, ArrayUtils, matchGlobs } from "../utils";
import { FileSystemHost } from "./FileSystemHost";

interface VirtualDirectory {
Expand Down Expand Up @@ -27,10 +27,8 @@ export class VirtualFileSystemHost implements FileSystemHost {
path = FileUtils.getStandardizedAbsolutePath(this, path);
if (this.directories.has(path)) {
// remove descendant dirs
for (const directoryPath of ArrayUtils.from(this.directories.getKeys())) {
if (StringUtils.startsWith(directoryPath, path))
this.directories.removeByKey(directoryPath);
}
for (const descendantDirPath of getDescendantDirectories(this.directories.getKeys(), path))
this.directories.removeByKey(descendantDirPath);
// remove this dir
this.directories.removeByKey(path);
return;
Expand Down Expand Up @@ -100,6 +98,69 @@ export class VirtualFileSystemHost implements FileSystemHost {
this.getOrCreateDir(dirPath);
}

move(srcPath: string, destPath: string) {
this.moveSync(srcPath, destPath);
return Promise.resolve();
}

moveSync(srcPath: string, destPath: string) {
srcPath = FileUtils.getStandardizedAbsolutePath(this, srcPath);
destPath = FileUtils.getStandardizedAbsolutePath(this, destPath);

if (this.fileExistsSync(srcPath)) {
const fileText = this.readFileSync(srcPath);
this.deleteSync(srcPath);
this.writeFileSync(destPath, fileText);
}
else if (this.directories.has(srcPath)) {
const moveDirectory = (from: string, to: string) => {
this._copyDirInternal(from, to);
this.directories.removeByKey(from);
};
moveDirectory(srcPath, destPath);

// move descendant dirs
for (const descendantDirPath of getDescendantDirectories(this.directories.getKeys(), srcPath)) {
const relativePath = FileUtils.getRelativePathTo(srcPath, descendantDirPath);
moveDirectory(descendantDirPath, FileUtils.pathJoin(destPath, relativePath));
}
}
else
throw new errors.PathNotFoundError(srcPath);
}

copy(srcPath: string, destPath: string) {
this.copySync(srcPath, destPath);
return Promise.resolve();
}

copySync(srcPath: string, destPath: string) {
srcPath = FileUtils.getStandardizedAbsolutePath(this, srcPath);
destPath = FileUtils.getStandardizedAbsolutePath(this, destPath);

if (this.fileExistsSync(srcPath))
this.writeFileSync(destPath, this.readFileSync(srcPath));
else if (this.directories.has(srcPath)) {
this._copyDirInternal(srcPath, destPath);

// copy descendant dirs
for (const descendantDirPath of getDescendantDirectories(this.directories.getKeys(), srcPath)) {
const relativePath = FileUtils.getRelativePathTo(srcPath, descendantDirPath);
this._copyDirInternal(descendantDirPath, FileUtils.pathJoin(destPath, relativePath));
}
}
else
throw new errors.PathNotFoundError(srcPath);
}

private _copyDirInternal(from: string, to: string) {
const dir = this.directories.get(from)!;
const newDir = this.getOrCreateDir(to);

for (const fileEntry of dir.files.getEntries())
newDir.files.set(FileUtils.pathJoin(to, FileUtils.getBaseName(fileEntry[0])), fileEntry[1]);
}

fileExists(filePath: string) {
return Promise.resolve<boolean>(this.fileExistsSync(filePath));
}
Expand Down Expand Up @@ -154,3 +215,10 @@ export class VirtualFileSystemHost implements FileSystemHost {
return dir;
}
}

function* getDescendantDirectories(directoryPaths: IterableIterator<string>, dirPath: string) {
for (const path of directoryPaths) {
if (FileUtils.pathStartsWith(path, dirPath))
yield path;
}
}
138 changes: 138 additions & 0 deletions src/tests/fileSystem/virtualFileSystemHostTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,144 @@ describe(nameof(VirtualFileSystemHost), () => {
});
});

describe(nameof<VirtualFileSystemHost>(h => h.copy), () => {
// this will test copySync because this calls copySync

it("should copy a directory and all its sub directories", async () => {
const fs = new VirtualFileSystemHost();
fs.writeFileSync("/dir/subdir/file.ts", "text");
fs.writeFileSync("/dir/subdir/nextSubDir/test.ts", "text2");
await fs.copy("/dir/subdir", "/newDir/newSubDir");

expect(fs.directoryExistsSync("/dir")).to.be.true;
expect(fs.directoryExistsSync("/dir/subdir")).to.be.true;
expect(fs.readFileSync("/dir/subdir/file.ts")).to.equal("text");
expect(fs.readFileSync("/dir/subdir/nextSubDir/test.ts")).to.equal("text2");
expect(fs.directoryExistsSync("/newDir")).to.be.true;
expect(fs.directoryExistsSync("/newDir/newSubDir")).to.be.true;
expect(fs.directoryExistsSync("/newDir/newSubDir/nextSubDir")).to.be.true;
expect(fs.readFileSync("/newDir/newSubDir/file.ts")).to.equal("text");
expect(fs.readFileSync("/newDir/newSubDir/nextSubDir/test.ts")).to.equal("text2");
});

it("should merge when copying into another directory", async () => {
const fs = new VirtualFileSystemHost();
fs.writeFileSync("/from/file.ts", "text");
fs.writeFileSync("/from/sub/subFile.ts", "text");
fs.writeFileSync("/to/file.ts", "original");
fs.writeFileSync("/to/test.ts", "text2");
fs.writeFileSync("/to/sub/subFile2.ts", "text3");
await fs.copy("/from", "/to");

expect(fs.directoryExistsSync("/from")).to.be.true;
expect(fs.directoryExistsSync("/from/sub")).to.be.true;
expect(fs.directoryExistsSync("/to/sub")).to.be.true;
expect(fs.readFileSync("/to/file.ts")).to.equal("text");
expect(fs.readFileSync("/to/test.ts")).to.equal("text2");
expect(fs.readFileSync("/to/sub/subFile2.ts")).to.equal("text3");
});

it("should copy a file and create the directory it's being copied to", async () => {
const fs = new VirtualFileSystemHost();
fs.writeFileSync("/from/file.ts", "text");
await fs.copy("/from/file.ts", "/to/to.ts");

expect(fs.directoryExistsSync("/from")).to.be.true;
expect(fs.directoryExistsSync("/to")).to.be.true;
expect(fs.readFileSync("/to/to.ts")).to.equal("text");
});

it("should copy a file and overwrite the file it's being copied to", async () => {
const fs = new VirtualFileSystemHost();
fs.writeFileSync("/from.ts", "text");
fs.writeFileSync("/to.ts", "text2");
await fs.copy("/from.ts", "/to.ts");

expect(fs.readFileSync("/from.ts")).to.equal("text");
expect(fs.readFileSync("/to.ts")).to.equal("text");
});

it("should throw when copying a path that doesn't exist", async () => {
const fs = new VirtualFileSystemHost();
let thrownErr: any;
try {
await fs.copy("/from.ts", "/to.ts");
} catch (err) {
thrownErr = err;
}

expect(thrownErr).to.be.instanceof(errors.PathNotFoundError);
});
});

describe(nameof<VirtualFileSystemHost>(h => h.move), () => {
// this will test moveSync because this calls moveSync

it("should move a directory and all its sub directories", async () => {
const fs = new VirtualFileSystemHost();
fs.writeFileSync("/dir/subdir/file.ts", "text");
fs.writeFileSync("/dir/subdir/nextSubDir/test.ts", "text2");
await fs.move("/dir/subdir", "/newDir/newSubDir");

expect(fs.directoryExistsSync("/dir")).to.be.true;
expect(fs.directoryExistsSync("/dir/subdir")).to.be.false;
expect(fs.directoryExistsSync("/newDir")).to.be.true;
expect(fs.directoryExistsSync("/newDir/newSubDir")).to.be.true;
expect(fs.readFileSync("/newDir/newSubDir/file.ts")).to.equal("text");
expect(fs.directoryExistsSync("/newDir/newSubDir/nextSubDir")).to.be.true;
expect(fs.readFileSync("/newDir/newSubDir/nextSubDir/test.ts")).to.equal("text2");
});

it("should merge two directories", async () => {
const fs = new VirtualFileSystemHost();
fs.writeFileSync("/from/file.ts", "text");
fs.writeFileSync("/from/sub/subFile.ts", "text");
fs.writeFileSync("/to/file.ts", "original");
fs.writeFileSync("/to/test.ts", "text2");
fs.writeFileSync("/to/sub/subFile2.ts", "text3");
await fs.move("/from", "/to");

expect(fs.directoryExistsSync("/from")).to.be.false;
expect(fs.directoryExistsSync("/from/sub")).to.be.false;
expect(fs.directoryExistsSync("/to/sub")).to.be.true;
expect(fs.readFileSync("/to/file.ts")).to.equal("text");
expect(fs.readFileSync("/to/test.ts")).to.equal("text2");
expect(fs.readFileSync("/to/sub/subFile2.ts")).to.equal("text3");
});

it("should move a file and create the directory it's being moved to", async () => {
const fs = new VirtualFileSystemHost();
fs.writeFileSync("/from/file.ts", "text");
await fs.move("/from/file.ts", "/to/to.ts");

expect(fs.directoryExistsSync("/from")).to.be.true;
expect(fs.directoryExistsSync("/to")).to.be.true;
expect(fs.readFileSync("/to/to.ts")).to.equal("text");
});

it("should move a file and overwrite the file it's being moved to", async () => {
const fs = new VirtualFileSystemHost();
fs.writeFileSync("/from.ts", "text");
fs.writeFileSync("/to.ts", "text2");
await fs.move("/from.ts", "/to.ts");

expect(fs.fileExistsSync("/from.ts")).to.be.false;
expect(fs.readFileSync("/to.ts")).to.equal("text");
});

it("should throw when moving a path that doesn't exist", async () => {
const fs = new VirtualFileSystemHost();
let thrownErr: any;
try {
await fs.move("/from.ts", "/to.ts");
} catch (err) {
thrownErr = err;
}

expect(thrownErr).to.be.instanceof(errors.PathNotFoundError);
});
});

describe(nameof<VirtualFileSystemHost>(h => h.fileExists), () => {
const fs = new VirtualFileSystemHost();
fs.writeFileSync("/file.ts", "");
Expand Down
30 changes: 29 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@
version "1.3.0"
resolved "https://registry.yarnpkg.com/@types/fancy-log/-/fancy-log-1.3.0.tgz#a61ab476e5e628cd07a846330df53b85e05c8ce0"

"@types/fs-extra@^5.0.2":
version "5.0.2"
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-5.0.2.tgz#235a7e2b56452cc0a6a4809b53e1d1eaffff9c96"
dependencies:
"@types/node" "*"

"@types/glob-parent@^3.1.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@types/glob-parent/-/glob-parent-3.1.0.tgz#64f42b63eb7858f6d80bd58eda1138e5495e8a38"
Expand All @@ -43,6 +49,10 @@
version "2.1.2"
resolved "https://registry.yarnpkg.com/@types/multimatch/-/multimatch-2.1.2.tgz#7d705c35b6ec6ae94f2dbfc384adb05f0974e6d7"

"@types/node@*":
version "9.6.6"
resolved "https://registry.yarnpkg.com/@types/node/-/node-9.6.6.tgz#439b91f9caf3983cad2eef1e11f6bedcbf9431d2"

"@types/node@^6.0.48":
version "6.0.102"
resolved "http://registry.npmjs.org/@types/node/-/node-6.0.102.tgz#a6cf3b9843286b63eb362a8522bc382d96fe68d1"
Expand Down Expand Up @@ -840,6 +850,14 @@ fragment-cache@^0.2.1:
dependencies:
map-cache "^0.2.2"

fs-extra@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-5.0.0.tgz#414d0110cdd06705734d055652c5411260c31abd"
dependencies:
graceful-fs "^4.1.2"
jsonfile "^4.0.0"
universalify "^0.1.0"

fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
Expand Down Expand Up @@ -1034,7 +1052,7 @@ graceful-fs@^3.0.0:
dependencies:
natives "^1.1.0"

graceful-fs@^4.0.0, graceful-fs@^4.1.2:
graceful-fs@^4.0.0, graceful-fs@^4.1.2, graceful-fs@^4.1.6:
version "4.1.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"

Expand Down Expand Up @@ -1586,6 +1604,12 @@ json3@3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1"

jsonfile@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
optionalDependencies:
graceful-fs "^4.1.6"

jsonify@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
Expand Down Expand Up @@ -2817,6 +2841,10 @@ unique-stream@^2.0.2:
json-stable-stringify "^1.0.0"
through2-filter "^2.0.0"

universalify@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.1.tgz#fa71badd4437af4c148841e3b3b165f9e9e590b7"

unset-value@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559"
Expand Down

0 comments on commit 4401755

Please sign in to comment.