Skip to content

Commit

Permalink
feat(fs): add copy/copySync (#278)
Browse files Browse the repository at this point in the history
  • Loading branch information
axetroy authored and ry committed May 16, 2019
1 parent 98257ef commit ab5b088
Show file tree
Hide file tree
Showing 9 changed files with 837 additions and 0 deletions.
13 changes: 13 additions & 0 deletions fs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,19 @@ moveSync("./foo", "./existingFolder", { overwrite: true });
// Will overwrite existingFolder
```

### copy

copy a file or directory. Overwrites it if option provided

```ts
import { copy, copySync } from "https://deno.land/std/fs/mod.ts";

copy("./foo", "./bar"); // returns a promise
copySync("./foo", "./bar"); // void
copySync("./foo", "./existingFolder", { overwrite: true });
// Will overwrite existingFolder
```

### readJson

Reads a JSON file and then parses it into an object
Expand Down
260 changes: 260 additions & 0 deletions fs/copy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
// 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 { isSubdir, getFileInfoType } from "./utils.ts";

export interface CopyOptions {
/**
* overwrite existing file or directory. Default is `false`
*/
overwrite?: boolean;
/**
* When `true`, will set last modification and access times to the ones of the original source files.
* When `false`, timestamp behavior is OS-dependent.
* Default is `false`.
*/
preserveTimestamps?: boolean;
}

async function ensureValidCopy(
src: string,
dest: string,
options: CopyOptions,
isCopyFolder: boolean = false
): Promise<Deno.FileInfo> {
let destStat: Deno.FileInfo;

destStat = await Deno.lstat(dest).catch(
(): Promise<null> => Promise.resolve(null)
);

if (destStat) {
if (isCopyFolder && !destStat.isDirectory()) {
throw new Error(
`Cannot overwrite non-directory '${dest}' with directory '${src}'.`
);
}
if (!options.overwrite) {
throw new Error(`'${dest}' already exists.`);
}
}

return destStat;
}

function ensureValidCopySync(
src: string,
dest: string,
options: CopyOptions,
isCopyFolder: boolean = false
): Deno.FileInfo {
let destStat: Deno.FileInfo;

try {
destStat = Deno.lstatSync(dest);
} catch {
// ignore error
}

if (destStat) {
if (isCopyFolder && !destStat.isDirectory()) {
throw new Error(
`Cannot overwrite non-directory '${dest}' with directory '${src}'.`
);
}
if (!options.overwrite) {
throw new Error(`'${dest}' already exists.`);
}
}

return destStat;
}

/* copy file to dest */
async function copyFile(
src: string,
dest: string,
options: CopyOptions
): Promise<void> {
await ensureValidCopy(src, dest, options);
await Deno.copyFile(src, dest);
if (options.preserveTimestamps) {
const statInfo = await Deno.stat(src);
await Deno.utime(dest, statInfo.accessed, statInfo.modified);
}
}
/* copy file to dest synchronously */
function copyFileSync(src: string, dest: string, options: CopyOptions): void {
ensureValidCopySync(src, dest, options);
Deno.copyFileSync(src, dest);
if (options.preserveTimestamps) {
const statInfo = Deno.statSync(src);
Deno.utimeSync(dest, statInfo.accessed, statInfo.modified);
}
}

/* copy symlink to dest */
async function copySymLink(
src: string,
dest: string,
options: CopyOptions
): Promise<void> {
await ensureValidCopy(src, dest, options);
const originSrcFilePath = await Deno.readlink(src);
const type = getFileInfoType(await Deno.lstat(src));
await Deno.symlink(originSrcFilePath, dest, type);
if (options.preserveTimestamps) {
const statInfo = await Deno.lstat(src);
await Deno.utime(dest, statInfo.accessed, statInfo.modified);
}
}

/* copy symlink to dest synchronously */
function copySymlinkSync(
src: string,
dest: string,
options: CopyOptions
): void {
ensureValidCopySync(src, dest, options);
const originSrcFilePath = Deno.readlinkSync(src);
const type = getFileInfoType(Deno.lstatSync(src));
Deno.symlinkSync(originSrcFilePath, dest, type);
if (options.preserveTimestamps) {
const statInfo = Deno.lstatSync(src);
Deno.utimeSync(dest, statInfo.accessed, statInfo.modified);
}
}

/* copy folder from src to dest. */
async function copyDir(
src: string,
dest: string,
options: CopyOptions
): Promise<void> {
const destStat = await ensureValidCopy(src, dest, options, true);

if (!destStat) {
await ensureDir(dest);
}

if (options.preserveTimestamps) {
const srcStatInfo = await Deno.stat(src);
await Deno.utime(dest, srcStatInfo.accessed, srcStatInfo.modified);
}

const files = await Deno.readDir(src);

for (const file of files) {
const srcPath = file.path as string;
const destPath = path.join(dest, path.basename(srcPath as string));
if (file.isDirectory()) {
await copyDir(srcPath, destPath, options);
} else if (file.isFile()) {
await copyFile(srcPath, destPath, options);
} else if (file.isSymlink()) {
await copySymLink(srcPath, destPath, options);
}
}
}

/* copy folder from src to dest synchronously */
function copyDirSync(src: string, dest: string, options: CopyOptions): void {
const destStat: Deno.FileInfo = ensureValidCopySync(src, dest, options, true);

if (!destStat) {
ensureDirSync(dest);
}

if (options.preserveTimestamps) {
const srcStatInfo = Deno.statSync(src);
Deno.utimeSync(dest, srcStatInfo.accessed, srcStatInfo.modified);
}

const files = Deno.readDirSync(src);

for (const file of files) {
const srcPath = file.path as string;
const destPath = path.join(dest, path.basename(srcPath as string));
if (file.isDirectory()) {
copyDirSync(srcPath, destPath, options);
} else if (file.isFile()) {
copyFileSync(srcPath, destPath, options);
} else if (file.isSymlink()) {
copySymlinkSync(srcPath, destPath, options);
}
}
}

/**
* Copy a file or directory. The directory can have contents. Like `cp -r`.
* @param src the file/directory path.
* Note that if `src` is a directory it will copy everything inside of this directory,
* not the entire directory itself
* @param dest the destination path. Note that if `src` is a file, `dest` cannot be a directory
* @param options
*/
export async function copy(
src: string,
dest: string,
options: CopyOptions = {}
): Promise<void> {
src = path.resolve(src);
dest = path.resolve(dest);

if (src === dest) {
throw new Error("Source and destination cannot be the same.");
}

const srcStat = await Deno.lstat(src);

if (srcStat.isDirectory() && isSubdir(src, dest)) {
throw new Error(
`Cannot copy '${src}' to a subdirectory of itself, '${dest}'.`
);
}

if (srcStat.isDirectory()) {
await copyDir(src, dest, options);
} else if (srcStat.isFile()) {
await copyFile(src, dest, options);
} else if (srcStat.isSymlink()) {
await copySymLink(src, dest, options);
}
}

/**
* Copy a file or directory. The directory can have contents. Like `cp -r`.
* @param src the file/directory path.
* Note that if `src` is a directory it will copy everything inside of this directory,
* not the entire directory itself
* @param dest the destination path. Note that if `src` is a file, `dest` cannot be a directory
* @param options
*/
export function copySync(
src: string,
dest: string,
options: CopyOptions = {}
): void {
src = path.resolve(src);
dest = path.resolve(dest);

if (src === dest) {
throw new Error("Source and destination cannot be the same.");
}

const srcStat = Deno.lstatSync(src);

if (srcStat.isDirectory() && isSubdir(src, dest)) {
throw new Error(
`Cannot copy '${src}' to a subdirectory of itself, '${dest}'.`
);
}

if (srcStat.isDirectory()) {
copyDirSync(src, dest, options);
} else if (srcStat.isFile()) {
copyFileSync(src, dest, options);
} else if (srcStat.isSymlink()) {
copySymlinkSync(src, dest, options);
}
}
Loading

0 comments on commit ab5b088

Please sign in to comment.