summaryrefslogtreecommitdiff
path: root/std/fs/copy.ts
diff options
context:
space:
mode:
Diffstat (limited to 'std/fs/copy.ts')
-rw-r--r--std/fs/copy.ts261
1 files changed, 261 insertions, 0 deletions
diff --git a/std/fs/copy.ts b/std/fs/copy.ts
new file mode 100644
index 000000000..616fba975
--- /dev/null
+++ b/std/fs/copy.ts
@@ -0,0 +1,261 @@
+// 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 = false
+): Promise<Deno.FileInfo> {
+ const destStat: Deno.FileInfo | null = 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 = false
+): Deno.FileInfo {
+ let destStat: Deno.FileInfo | null;
+
+ 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 = path.join(src, file.name!);
+ 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 = path.join(src, file.name!);
+ 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);
+ }
+}