| var fs = require('fs'), |
| path = require('path'); |
| |
| module.exports = ncp |
| ncp.ncp = ncp |
| |
| function ncp (source, dest, options, callback) { |
| if (!callback) { |
| callback = options; |
| options = {}; |
| } |
| |
| var basePath = process.cwd(), |
| currentPath = path.resolve(basePath, source), |
| targetPath = path.resolve(basePath, dest), |
| filter = options.filter, |
| transform = options.transform, |
| clobber = options.clobber !== false, |
| errs = null, |
| started = 0, |
| finished = 0, |
| running = 0, |
| limit = options.limit || ncp.limit || 16; |
| |
| limit = (limit < 1) ? 1 : (limit > 512) ? 512 : limit; |
| |
| startCopy(currentPath); |
| |
| function startCopy(source) { |
| started++; |
| if (filter) { |
| if (filter instanceof RegExp) { |
| if (!filter.test(source)) { |
| return cb(true); |
| } |
| } |
| else if (typeof filter === 'function') { |
| if (!filter(source)) { |
| return cb(true); |
| } |
| } |
| } |
| return getStats(source); |
| } |
| |
| function defer(fn) { |
| if (typeof(setImmediate) === 'function') |
| return setImmediate(fn); |
| return process.nextTick(fn); |
| } |
| |
| function getStats(source) { |
| if (running >= limit) { |
| return defer(function () { |
| getStats(source); |
| }); |
| } |
| running++; |
| fs.lstat(source, function (err, stats) { |
| var item = {}; |
| if (err) { |
| return onError(err); |
| } |
| |
| // We need to get the mode from the stats object and preserve it. |
| item.name = source; |
| item.mode = stats.mode; |
| |
| if (stats.isDirectory()) { |
| return onDir(item); |
| } |
| else if (stats.isFile()) { |
| return onFile(item); |
| } |
| else if (stats.isSymbolicLink()) { |
| // Symlinks don't really need to know about the mode. |
| return onLink(source); |
| } |
| }); |
| } |
| |
| function onFile(file) { |
| var target = file.name.replace(currentPath, targetPath); |
| isWritable(target, function (writable) { |
| if (writable) { |
| return copyFile(file, target); |
| } |
| if(clobber) |
| rmFile(target, function () { |
| copyFile(file, target); |
| }); |
| }); |
| } |
| |
| function copyFile(file, target) { |
| var readStream = fs.createReadStream(file.name), |
| writeStream = fs.createWriteStream(target, { mode: file.mode }); |
| if(transform) { |
| transform(readStream, writeStream,file); |
| } else { |
| readStream.pipe(writeStream); |
| } |
| readStream.once('end', cb); |
| } |
| |
| function rmFile(file, done) { |
| fs.unlink(file, function (err) { |
| if (err) { |
| return onError(err); |
| } |
| return done(); |
| }); |
| } |
| |
| function onDir(dir) { |
| var target = dir.name.replace(currentPath, targetPath); |
| isWritable(target, function (writable) { |
| if (writable) { |
| return mkDir(dir, target); |
| } |
| copyDir(dir.name); |
| }); |
| } |
| |
| function mkDir(dir, target) { |
| fs.mkdir(target, dir.mode, function (err) { |
| if (err) { |
| return onError(err); |
| } |
| copyDir(dir.name); |
| }); |
| } |
| |
| function copyDir(dir) { |
| fs.readdir(dir, function (err, items) { |
| if (err) { |
| return onError(err); |
| } |
| items.forEach(function (item) { |
| startCopy(dir + '/' + item); |
| }); |
| return cb(); |
| }); |
| } |
| |
| function onLink(link) { |
| var target = link.replace(currentPath, targetPath); |
| fs.readlink(link, function (err, resolvedPath) { |
| if (err) { |
| return onError(err); |
| } |
| checkLink(resolvedPath, target); |
| }); |
| } |
| |
| function checkLink(resolvedPath, target) { |
| isWritable(target, function (writable) { |
| if (writable) { |
| return makeLink(resolvedPath, target); |
| } |
| fs.readlink(target, function (err, targetDest) { |
| if (err) { |
| return onError(err); |
| } |
| if (targetDest === resolvedPath) { |
| return cb(); |
| } |
| return rmFile(target, function () { |
| makeLink(resolvedPath, target); |
| }); |
| }); |
| }); |
| } |
| |
| function makeLink(linkPath, target) { |
| fs.symlink(linkPath, target, function (err) { |
| if (err) { |
| return onError(err); |
| } |
| return cb(); |
| }); |
| } |
| |
| function isWritable(path, done) { |
| fs.lstat(path, function (err, stats) { |
| if (err) { |
| if (err.code === 'ENOENT') return done(true); |
| return done(false); |
| } |
| return done(false); |
| }); |
| } |
| |
| function onError(err) { |
| if (options.stopOnError) { |
| return callback(err); |
| } |
| else if (!errs && options.errs) { |
| errs = fs.createWriteStream(options.errs); |
| } |
| else if (!errs) { |
| errs = []; |
| } |
| if (typeof errs.write === 'undefined') { |
| errs.push(err); |
| } |
| else { |
| errs.write(err.stack + '\n\n'); |
| } |
| return cb(); |
| } |
| |
| function cb(skipped) { |
| if (!skipped) running--; |
| finished++; |
| if ((started === finished) && (running === 0)) { |
| return errs ? callback(errs) : callback(null); |
| } |
| } |
| }; |
| |
| |