Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@ Think `cp -r`, but pure node, and asynchronous. `ncp` can be used both as a CLI

## Command Line usage

Usage is simple: `ncp [source] [dest] [--limit=concurrency limit]
[--filter=filter] --stopOnErr`
Usage is simple: `ncp [source] [dest] [--limit=concurrency limit] [--flat]
[--filter="filter"] [--find="regex"] [--findtext="text"] [--replace="text"] --stopOnErr`

The 'filter' is a Regular Expression - matched files will be copied.
The 'filter' is a Regular Expression - matched files will be copied. It should be quoted in double qoutes eg: `--filter="\.(html|css)"`

The 'flat' is a boolean flag that will copy files at flat hierarchy, IE:
- [source] = test
- [dest] = testCopy

File which is found at "test/testSubdirectory/config.json" will be copied at "testCopy/config.json".

The 'concurrency limit' is an integer that represents how many pending file system requests `ncp` has at a time.

Expand Down Expand Up @@ -57,6 +63,14 @@ You can also call ncp like `ncp(source, destination, options, callback)`.
* `options.stopOnErr` - boolean=false. If set to true, `ncp` will behave like `cp -r`,
and stop on the first error it encounters. By default, `ncp` continues copying, logging all
errors and returning an array.

* `options.find` - a `RegExp` instance, against which each file name is used to find text in file using regex expression.

* `options.findtext` - a `text` which is used in each file to find text.

* `options.replace` - a `text` which used to replace text in file.

* `options.flat` - a `boolean` which used to copy files from directory tree to single folder.

* `options.errs` - stream. If `options.stopOnErr` is `false`, a stream can be provided, and errors will be written to this stream.

Expand Down
45 changes: 39 additions & 6 deletions bin/ncp
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
#!/usr/bin/env node




var ncp = require('../lib/ncp'),
args = process.argv.slice(2),
fs = require('fs'),
source, dest;

if (args.length < 2) {
console.error('Usage: ncp [source] [destination] [--filter=filter] [--limit=concurrency limit]');
console.error('Usage: ncp [source] [destination] [--filter=filter] [--limit=concurrency limit] [--flat] [--clobber]');
process.exit(1);
}

Expand All @@ -23,11 +22,47 @@ args.forEach(function (arg) {
options.limit = parseInt(arg.split('=', 2)[1], 10);
}
if (startsWith(arg, "--filter=")) {
options.filter = new RegExp(arg.split('=', 2)[1]);
var reg = arg.split('=', 2)[1].replace(/\"/g,"");
options.filter = new RegExp(reg);
}
if (startsWith(arg, "--stoponerr")) {
options.stopOnErr = true;
}
if (startsWith(arg, "--flat")) {
options.flatCopy = true;
}
if (startsWith(arg, "--clobber")) {
options.clobber = true;
}
if (startsWith(arg, "--find=")) {
var reg = arg.replace("--find=","");
options.find = new RegExp(reg, 'g');
}
if (startsWith(arg, "--findtext=")) {
var reg = arg.replace("--findtext=","");
options.find = reg;
}
if (startsWith(arg, "--replace=")) {
var reg = arg.replace("--replace=","");
options.replace = reg;

options.transform = function(readStream, file, target) {
const fileData = [];
var readStream = fs.createReadStream(file.name);

readStream.on("data", function (chunk) {
fileData.push(chunk);
});

readStream.on("end", function () {
var fileStr = Buffer.concat(fileData).toString().replace(options.find, options.replace);
writeStream = fs.createWriteStream(target, { mode: file.mode });
writeStream.write(fileStr, function() {
fs.utimesSync(target, file.atime, file.mtime);
});
});
};
}
});

ncp.ncp(args[0], args[1], options, function (err) {
Expand All @@ -44,5 +79,3 @@ ncp.ncp(args[0], args[1], options, function (err) {
process.exit(1);
}
});


105 changes: 65 additions & 40 deletions lib/ncp.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ function ncp (source, dest, options, callback) {
filter = options.filter,
rename = options.rename,
transform = options.transform,
clobber = options.clobber !== false,
clobber = options.clobber ? options.clobber : false,
modified = options.modified,
dereference = options.dereference,
flatCopy = options.flatCopy,
errs = null,
started = 0,
finished = 0,
Expand All @@ -31,20 +32,8 @@ function ncp (source, dest, options, callback) {

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);
}
}
}
function startCopy(source) {
started++;
return getStats(source);
}

Expand Down Expand Up @@ -72,7 +61,25 @@ function ncp (source, dest, options, callback) {
return onDir(item);
}
else if (stats.isFile()) {
return onFile(item);

if (filter) {
if (filter instanceof RegExp) {
if (!filter.test(source)) {
return cb();
}
else {
return onFile(item);
}
}
else if (typeof filter === 'function') {
if (!filter(source)) {
return cb();
}
else {
return onFile(item);
}
}
}
}
else if (stats.isSymbolicLink()) {
// Symlinks don't really need to know about the mode.
Expand All @@ -82,11 +89,17 @@ function ncp (source, dest, options, callback) {
}

function onFile(file) {
var target = file.name.replace(currentPath, targetPath);
var target = null;
if (flatCopy) {
var fileName = file.name.split('\\').pop().split('/').pop();
target = targetPath + "/" + fileName;
} else {
target = file.name.replace(currentPath, targetPath);
}
if(rename) {
target = rename(target);
}
isWritable(target, function (writable) {
isFileWritable(target, function (writable) {
if (writable) {
return copyFile(file, target);
}
Expand All @@ -111,27 +124,31 @@ function ncp (source, dest, options, callback) {
}

function copyFile(file, target) {
var readStream = fs.createReadStream(file.name),
writeStream = fs.createWriteStream(target, { mode: file.mode });

readStream.on('error', onError);
writeStream.on('error', onError);
var readStream = fs.createReadStream(file.name);

readStream.on('error', onError);

if(transform) {
transform(readStream, writeStream, file);
if(transform) {
transform(readStream, file, target);
cb();
} else {
writeStream.on('open', function() {
readStream.pipe(writeStream);
var writeStream = fs.createWriteStream(target, { mode: file.mode });

writeStream.on('error', onError);

writeStream.on('open', function() {
readStream.pipe(writeStream);
});

writeStream.once('finish', function() {
if (modified) {
//target file modified date sync.
fs.utimesSync(target, file.atime, file.mtime);
cb();
}
else cb();
});
}
writeStream.once('finish', function() {
if (modified) {
//target file modified date sync.
fs.utimesSync(target, file.atime, file.mtime);
cb();
}
else cb();
});
}

function rmFile(file, done) {
Expand All @@ -146,7 +163,7 @@ function ncp (source, dest, options, callback) {
function onDir(dir) {
var target = dir.name.replace(currentPath, targetPath);
isWritable(target, function (writable) {
if (writable) {
if (writable && !flatCopy) {
return mkDir(dir, target);
}
copyDir(dir.name);
Expand All @@ -167,8 +184,8 @@ function ncp (source, dest, options, callback) {
if (err) {
return onError(err);
}
items.forEach(function (item) {
startCopy(path.join(dir, item));
items.forEach(function (item) {
startCopy(path.join(dir, item), false);
});
return cb();
});
Expand Down Expand Up @@ -228,6 +245,16 @@ function ncp (source, dest, options, callback) {
});
}

function isFileWritable(path, done) {
fs.access(path, fs.W_OK, function (err, stats) {
if (err && err.code !== 'ENOENT') {
return done(false);
} else {
return done(true);
}
});
}

function onError(err) {
if (options.stopOnError) {
return cback(err);
Expand Down Expand Up @@ -257,5 +284,3 @@ function ncp (source, dest, options, callback) {
}
}
}