var fs = require('fs'); | |
var path = require('path'); | |
var common = require('./common'); | |
// 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 = {}; | |
/* Create the directory where all our junk is moving to; read the mode of the source directory and mirror it */ | |
var checkDir = fs.statSync(sourceDir); | |
try { | |
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); | |
if (srcFileStat.isDirectory()) { | |
/* recursion this thing right on back. */ | |
cpdirSyncRecursive(srcFile, destFile, opts); | |
} else if (srcFileStat.isSymbolicLink()) { | |
var symlinkFull = fs.readlinkSync(srcFile); | |
fs.symlinkSync(symlinkFull, destFile); | |
} else { | |
/* At this point, we've hit a file actually worth copying... so copy it on over. */ | |
if (fs.existsSync(destFile) && !opts.force) { | |
common.log('skipping existing file: ' + files[i]); | |
} else { | |
copyFileSync(srcFile, destFile); | |
} | |
} | |
} // for files | |
} // cpdirSyncRecursive | |
//@ | |
//@ ### cp([options ,] source [,source ...], dest) | |
//@ ### cp([options ,] source_array, dest) | |
//@ Available options: | |
//@ | |
//@ + `-f`: force | |
//@ + `-r, -R`: recursive | |
//@ | |
//@ Examples: | |
//@ | |
//@ ```javascript | |
//@ cp('file1', 'dir1'); | |
//@ cp('-Rf', '/tmp/*', '/usr/local/*', '/home/tmp'); | |
//@ cp('-Rf', ['/tmp/*', '/usr/local/*'], '/home/tmp'); // same as above | |
//@ ``` | |
//@ | |
//@ Copies files. The wildcard `*` is accepted. | |
function _cp(options, sources, dest) { | |
options = common.parseOptions(options, { | |
'f': 'force', | |
'R': 'recursive', | |
'r': 'recursive' | |
}); | |
// Get sources, dest | |
if (arguments.length < 3) { | |
common.error('missing <source> and/or <dest>'); | |
} else if (arguments.length > 3) { | |
sources = [].slice.call(arguments, 1, arguments.length - 1); | |
dest = arguments[arguments.length - 1]; | |
} else if (typeof sources === 'string') { | |
sources = [sources]; | |
} else if ('length' in sources) { | |
sources = sources; // no-op for array | |
} else { | |
common.error('invalid arguments'); | |
} | |
var exists = fs.existsSync(dest), | |
stats = exists && fs.statSync(dest); | |
// Dest is not existing dir, but multiple sources given | |
if ((!exists || !stats.isDirectory()) && sources.length > 1) | |
common.error('dest is not a directory (too many sources)'); | |
// Dest is an existing file, but no -f given | |
if (exists && stats.isFile() && !options.force) | |
common.error('dest file already exists: ' + dest); | |
if (options.recursive) { | |
// Recursive allows the shortcut syntax "sourcedir/" for "sourcedir/*" | |
// (see Github issue #15) | |
sources.forEach(function(src, i) { | |
if (src[src.length - 1] === '/') | |
sources[i] += '*'; | |
}); | |
// Create dest | |
try { | |
fs.mkdirSync(dest, parseInt('0777', 8)); | |
} catch (e) { | |
// like Unix's cp, keep going even if we can't create dest dir | |
} | |
} | |
sources = common.expand(sources); | |
sources.forEach(function(src) { | |
if (!fs.existsSync(src)) { | |
common.error('no such file or directory: '+src, true); | |
return; // skip file | |
} | |
// If here, src exists | |
if (fs.statSync(src).isDirectory()) { | |
if (!options.recursive) { | |
// Non-Recursive | |
common.log(src + ' is a directory (not copied)'); | |
} else { | |
// Recursive | |
// 'cp /a/source dest' should create 'source' in 'dest' | |
var newDest = path.join(dest, path.basename(src)), | |
checkDir = fs.statSync(src); | |
try { | |
fs.mkdirSync(newDest, checkDir.mode); | |
} catch (e) { | |
//if the directory already exists, that's okay | |
if (e.code !== 'EEXIST') throw e; | |
} | |
cpdirSyncRecursive(src, newDest, {force: options.force}); | |
} | |
return; // done with dir | |
} | |
// If here, src is a file | |
// When copying to '/path/dir': | |
// thisDest = '/path/dir/file1' | |
var thisDest = dest; | |
if (fs.existsSync(dest) && fs.statSync(dest).isDirectory()) | |
thisDest = path.normalize(dest + '/' + path.basename(src)); | |
if (fs.existsSync(thisDest) && !options.force) { | |
common.error('dest file already exists: ' + thisDest, true); | |
return; // skip file | |
} | |
copyFileSync(src, thisDest); | |
}); // forEach(src) | |
} | |
module.exports = _cp; |