shelljs/src/cp.js

227 lines
6.8 KiB
JavaScript

var fs = require('fs');
var path = require('path');
var common = require('./common');
var os = require('os');
// Buffered file copy, synchronous
// (Using readFileSync() + writeFileSync() could easily cause a memory overflow
// with large files)
function copyFileSync(srcFile, destFile) {
if (!fs.existsSync(srcFile))
common.error('copyFileSync: no such file or directory: ' + srcFile);
var BUF_LENGTH = 64*1024,
buf = new Buffer(BUF_LENGTH),
bytesRead = BUF_LENGTH,
pos = 0,
fdr = null,
fdw = null;
try {
fdr = fs.openSync(srcFile, 'r');
} catch(e) {
common.error('copyFileSync: could not read src file ('+srcFile+')');
}
try {
fdw = fs.openSync(destFile, 'w');
} catch(e) {
common.error('copyFileSync: could not write to dest file (code='+e.code+'):'+destFile);
}
while (bytesRead === BUF_LENGTH) {
bytesRead = fs.readSync(fdr, buf, 0, BUF_LENGTH, pos);
fs.writeSync(fdw, buf, 0, bytesRead);
pos += bytesRead;
}
fs.closeSync(fdr);
fs.closeSync(fdw);
fs.chmodSync(destFile, fs.statSync(srcFile).mode);
}
// Recursively copies 'sourceDir' into 'destDir'
// Adapted from https://github.com/ryanmcgrath/wrench-js
//
// Copyright (c) 2010 Ryan McGrath
// Copyright (c) 2012 Artur Adib
//
// Licensed under the MIT License
// http://www.opensource.org/licenses/mit-license.php
function cpdirSyncRecursive(sourceDir, destDir, opts) {
if (!opts) opts = {};
/* Ensure there is not a run away recursive copy. */
if (typeof opts.depth === 'undefined') {
opts.depth = 0;
}
if (opts.depth >= common.config.maxdepth) {
// Max depth has been reached, end copy.
return;
} else {
opts.depth++;
}
/* Create the directory where all our junk is moving to; read the mode of the source directory and mirror it */
try {
var checkDir = fs.statSync(sourceDir);
fs.mkdirSync(destDir, checkDir.mode);
} catch (e) {
//if the directory already exists, that's okay
if (e.code !== 'EEXIST') throw e;
}
var files = fs.readdirSync(sourceDir);
for (var i = 0; i < files.length; i++) {
var srcFile = sourceDir + "/" + files[i];
var destFile = destDir + "/" + files[i];
var srcFileStat = fs.lstatSync(srcFile);
var symlinkFull;
if (opts.followsymlink) {
if (cpcheckcycle(sourceDir, srcFile)) {
// Cycle link found.
console.log('Cycle link found.');
symlinkFull = fs.readlinkSync(srcFile);
fs.symlinkSync(symlinkFull, destFile, os.platform() === "win32" ? "junction" : null);
continue;
}
}
if (srcFileStat.isDirectory()) {
/* recursion this thing right on back. */
cpdirSyncRecursive(srcFile, destFile, opts);
} else if (srcFileStat.isSymbolicLink() && !opts.followsymlink) {
symlinkFull = fs.readlinkSync(srcFile);
fs.symlinkSync(symlinkFull, destFile, os.platform() === "win32" ? "junction" : null);
} else if (srcFileStat.isSymbolicLink() && opts.followsymlink) {
srcFileStat = fs.statSync(srcFile);
if (srcFileStat.isDirectory()) {
cpdirSyncRecursive(srcFile, destFile, opts);
} else {
copyFileSync(srcFile, destFile);
}
} else {
/* At this point, we've hit a file actually worth copying... so copy it on over. */
if (fs.existsSync(destFile) && opts.no_force) {
common.log('skipping existing file: ' + files[i]);
} else {
copyFileSync(srcFile, destFile);
}
}
} // for files
} // cpdirSyncRecursive
function cpcheckcycle(sourceDir, srcFile) {
var srcFileStat = fs.lstatSync(srcFile);
if (srcFileStat.isSymbolicLink()) {
// Do cycle check. For example mkdir -p 1/2/3/4 ; cd 1/2/3/4; ln -s ../../3 link ; cd ../../../.. ; cp -RL 1 copy
var cyclecheck = fs.statSync(srcFile);
if (cyclecheck.isDirectory()) {
var sourcerealpath = fs.realpathSync(sourceDir);
var symlinkrealpath = fs.realpathSync(srcFile);
var re = new RegExp(symlinkrealpath);
if (re.test(sourcerealpath)) {
return true;
}
}
}
return false;
}
//@
//@ ### cp([options,] source [, source ...], dest)
//@ ### cp([options,] source_array, dest)
//@ Available options:
//@
//@ + `-f`: force (default behavior)
//@ + `-n`: no-clobber
//@ + `-r`, `-R`: recursive
//@ + `-L`: followsymlink
//@
//@ Examples:
//@
//@ ```javascript
//@ cp('file1', 'dir1');
//@ cp('-R', 'path/to/dir/', '~/newCopy/');
//@ cp('-Rf', '/tmp/*', '/usr/local/*', '/home/tmp');
//@ cp('-Rf', ['/tmp/*', '/usr/local/*'], '/home/tmp'); // same as above
//@ ```
//@
//@ Copies files.
function _cp(options, sources, dest) {
options = common.parseOptions(options, {
'f': '!no_force',
'n': 'no_force',
'R': 'recursive',
'r': 'recursive',
'L': 'followsymlink',
});
// Get sources, dest
if (arguments.length < 3) {
common.error('missing <source> and/or <dest>');
} else {
sources = [].slice.call(arguments, 1, arguments.length - 1);
dest = arguments[arguments.length - 1];
}
var destExists = fs.existsSync(dest),
destStat = destExists && fs.statSync(dest);
// Dest is not existing dir, but multiple sources given
if ((!destExists || !destStat.isDirectory()) && sources.length > 1)
common.error('dest is not a directory (too many sources)');
// Dest is an existing file, but -n is given
if (destExists && destStat.isFile() && options.no_force)
common.error('dest file already exists: ' + dest);
sources.forEach(function(src) {
if (!fs.existsSync(src)) {
common.error('no such file or directory: '+src, true);
return; // skip file
}
var srcStat = fs.statSync(src);
if (srcStat.isDirectory()) {
if (!options.recursive) {
// Non-Recursive
common.error("omitting directory '" + src + "'", true);
} else {
// Recursive
// 'cp /a/source dest' should create 'source' in 'dest'
var newDest = (destStat && destStat.isDirectory()) ?
path.join(dest, path.basename(src)) :
dest;
try {
fs.statSync(path.dirname(dest));
cpdirSyncRecursive(src, newDest, {no_force: options.no_force, followsymlink: options.followsymlink});
} catch(e) {
common.error("cannot create directory '" + dest + "': No such file or directory");
}
}
return; // done with dir
} else {
// If here, src is a file
// When copying to '/path/dir':
// thisDest = '/path/dir/file1'
var thisDest = dest;
if (destStat && destStat.isDirectory())
thisDest = path.normalize(dest + '/' + path.basename(src));
if (fs.existsSync(thisDest) && options.no_force) {
common.error('dest file already exists: ' + thisDest, true);
return; // skip file
}
copyFileSync(src, thisDest);
}
}); // forEach(src)
return new common.ShellString('', common.state.error, common.state.errorCode);
}
module.exports = _cp;