std/fs/copy.ts

279 lines
8.1 KiB
TypeScript
Raw Normal View History

2020-01-02 20:13:47 +00:00
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import * as path from "../path/mod.ts";
2019-05-16 16:19:17 +00:00
import { ensureDir, ensureDirSync } from "./ensure_dir.ts";
import { isSubdir, getFileInfoType } from "./_util.ts";
import { assert } from "../testing/asserts.ts";
2019-05-16 16:19:17 +00:00
export interface CopyOptions {
/**
* overwrite existing file or directory. Default is `false`
*/
overwrite?: boolean;
/**
2019-06-19 04:22:01 +00:00
* When `true`, will set last modification and access times to the ones of the
* original source files.
2019-05-16 16:19:17 +00:00
* When `false`, timestamp behavior is OS-dependent.
* Default is `false`.
*/
preserveTimestamps?: boolean;
}
async function ensureValidCopy(
src: string,
dest: string,
options: CopyOptions,
isCopyFolder = false
): Promise<Deno.FileInfo | undefined> {
let destStat: Deno.FileInfo;
try {
destStat = await Deno.lstat(dest);
} catch (err) {
if (err instanceof Deno.errors.NotFound) {
return;
2019-05-16 16:19:17 +00:00
}
throw err;
2019-05-16 16:19:17 +00:00
}
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;
2019-05-16 16:19:17 +00:00
}
function ensureValidCopySync(
src: string,
dest: string,
options: CopyOptions,
isCopyFolder = false
): Deno.FileInfo | undefined {
let destStat: Deno.FileInfo;
2019-05-16 16:19:17 +00:00
try {
destStat = Deno.lstatSync(dest);
} catch (err) {
if (err instanceof Deno.errors.NotFound) {
return;
}
throw err;
2019-05-16 16:19:17 +00:00
}
if (isCopyFolder && !destStat.isDirectory) {
throw new Error(
`Cannot overwrite non-directory '${dest}' with directory '${src}'.`
);
}
if (!options.overwrite) {
throw new Error(`'${dest}' already exists.`);
2019-05-16 16:19:17 +00:00
}
return destStat;
2019-05-16 16:19:17 +00:00
}
/* 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);
assert(statInfo.atime instanceof Date, `statInfo.atime is unavailable`);
assert(statInfo.mtime instanceof Date, `statInfo.mtime is unavailable`);
await Deno.utime(dest, statInfo.atime, statInfo.mtime);
2019-05-16 16:19:17 +00:00
}
}
/* 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);
assert(statInfo.atime instanceof Date, `statInfo.atime is unavailable`);
assert(statInfo.mtime instanceof Date, `statInfo.mtime is unavailable`);
Deno.utimeSync(dest, statInfo.atime, statInfo.mtime);
2019-05-16 16:19:17 +00:00
}
}
/* 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);
2019-05-16 16:19:17 +00:00
const type = getFileInfoType(await Deno.lstat(src));
await Deno.symlink(originSrcFilePath, dest, type);
if (options.preserveTimestamps) {
const statInfo = await Deno.lstat(src);
assert(statInfo.atime instanceof Date, `statInfo.atime is unavailable`);
assert(statInfo.mtime instanceof Date, `statInfo.mtime is unavailable`);
await Deno.utime(dest, statInfo.atime, statInfo.mtime);
2019-05-16 16:19:17 +00:00
}
}
/* copy symlink to dest synchronously */
function copySymlinkSync(
src: string,
dest: string,
options: CopyOptions
): void {
ensureValidCopySync(src, dest, options);
const originSrcFilePath = Deno.readLinkSync(src);
2019-05-16 16:19:17 +00:00
const type = getFileInfoType(Deno.lstatSync(src));
Deno.symlinkSync(originSrcFilePath, dest, type);
if (options.preserveTimestamps) {
const statInfo = Deno.lstatSync(src);
assert(statInfo.atime instanceof Date, `statInfo.atime is unavailable`);
assert(statInfo.mtime instanceof Date, `statInfo.mtime is unavailable`);
Deno.utimeSync(dest, statInfo.atime, statInfo.mtime);
2019-05-16 16:19:17 +00:00
}
}
/* 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);
assert(srcStatInfo.atime instanceof Date, `statInfo.atime is unavailable`);
assert(srcStatInfo.mtime instanceof Date, `statInfo.mtime is unavailable`);
await Deno.utime(dest, srcStatInfo.atime, srcStatInfo.mtime);
2019-05-16 16:19:17 +00:00
}
for await (const entry of Deno.readDir(src)) {
const srcPath = path.join(src, entry.name);
2019-05-16 16:19:17 +00:00
const destPath = path.join(dest, path.basename(srcPath as string));
if (entry.isSymlink) {
await copySymLink(srcPath, destPath, options);
} else if (entry.isDirectory) {
2019-05-16 16:19:17 +00:00
await copyDir(srcPath, destPath, options);
} else if (entry.isFile) {
2019-05-16 16:19:17 +00:00
await copyFile(srcPath, destPath, options);
}
}
}
/* copy folder from src to dest synchronously */
function copyDirSync(src: string, dest: string, options: CopyOptions): void {
const destStat = ensureValidCopySync(src, dest, options, true);
2019-05-16 16:19:17 +00:00
if (!destStat) {
ensureDirSync(dest);
}
if (options.preserveTimestamps) {
const srcStatInfo = Deno.statSync(src);
assert(srcStatInfo.atime instanceof Date, `statInfo.atime is unavailable`);
assert(srcStatInfo.mtime instanceof Date, `statInfo.mtime is unavailable`);
Deno.utimeSync(dest, srcStatInfo.atime, srcStatInfo.mtime);
2019-05-16 16:19:17 +00:00
}
for (const entry of Deno.readDirSync(src)) {
assert(entry.name != null, "file.name must be set");
const srcPath = path.join(src, entry.name);
2019-05-16 16:19:17 +00:00
const destPath = path.join(dest, path.basename(srcPath as string));
if (entry.isSymlink) {
copySymlinkSync(srcPath, destPath, options);
} else if (entry.isDirectory) {
2019-05-16 16:19:17 +00:00
copyDirSync(srcPath, destPath, options);
} else if (entry.isFile) {
2019-05-16 16:19:17 +00:00
copyFileSync(srcPath, destPath, options);
}
}
}
/**
* Copy a file or directory. The directory can have contents. Like `cp -r`.
* Requires the `--allow-read` and `--alow-write` flag.
2019-05-16 16:19:17 +00:00
* @param src the file/directory path.
2019-06-19 04:22:01 +00:00
* 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
2019-05-16 16:19:17 +00:00
* @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)) {
2019-05-16 16:19:17 +00:00
throw new Error(
`Cannot copy '${src}' to a subdirectory of itself, '${dest}'.`
);
}
if (srcStat.isSymlink) {
await copySymLink(src, dest, options);
} else if (srcStat.isDirectory) {
2019-05-16 16:19:17 +00:00
await copyDir(src, dest, options);
} else if (srcStat.isFile) {
2019-05-16 16:19:17 +00:00
await copyFile(src, dest, options);
}
}
/**
* Copy a file or directory. The directory can have contents. Like `cp -r`.
* Requires the `--allow-read` and `--alow-write` flag.
2019-05-16 16:19:17 +00:00
* @param src the file/directory path.
2019-06-19 04:22:01 +00:00
* 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
2019-05-16 16:19:17 +00:00
* @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)) {
2019-05-16 16:19:17 +00:00
throw new Error(
`Cannot copy '${src}' to a subdirectory of itself, '${dest}'.`
);
}
if (srcStat.isSymlink) {
copySymlinkSync(src, dest, options);
} else if (srcStat.isDirectory) {
2019-05-16 16:19:17 +00:00
copyDirSync(src, dest, options);
} else if (srcStat.isFile) {
2019-05-16 16:19:17 +00:00
copyFileSync(src, dest, options);
}
}