diff --git a/README.md b/README.md index 092b9d9..a21a2ff 100644 --- a/README.md +++ b/README.md @@ -38,5 +38,24 @@ recursive('some/path', ['foo.cs', '*.html'], function (err, files) { }); ``` +You can also pass functions which are called to determine whether or not to +ignore a file: + +```javascript +var recursive = require('recursive-readdir'); + +function ignoreFunc(file, stats) { + // `file` is the absolute path to the file, and `stats` is an `fs.Stats` + // object returned from `fs.lstat()`. + return stats.isDirectory() && path.basename(file) == "test"; +} + +// Ignore files named 'foo.cs' and descendants of directories named test +recursive('some/path', ['foo.cs', ignoreFunc], function (err, files) { + // Files is an array of filename + console.log(files); +}); +``` + The ignore strings support Glob syntax via [minimatch](https://github.com/isaacs/minimatch). diff --git a/index.js b/index.js index c1c409b..4cc24b2 100644 --- a/index.js +++ b/index.js @@ -2,12 +2,27 @@ var fs = require('fs') var p = require('path') var minimatch = require('minimatch') -// how to know when you are done? +function patternMatcher(pattern) { + return function(path, stats) { + return stats.isFile() && minimatch(path, pattern, {matchBase: true}) + } +} + +function toMatcherFunction(ignoreEntry) { + if (typeof ignoreEntry == 'function') { + return ignoreEntry + } else { + return patternMatcher(ignoreEntry) + } +} + function readdir(path, ignores, callback) { if (typeof ignores == 'function') { callback = ignores ignores = [] } + ignores = ignores.map(toMatcherFunction) + var list = [] fs.readdir(path, function(err, files) { @@ -21,7 +36,6 @@ function readdir(path, ignores, callback) { return callback(null, list) } - var ignoreOpts = {matchBase: true} files.forEach(function(file) { fs.lstat(p.join(path, file), function(_err, stats) { if (_err) { @@ -29,8 +43,16 @@ function readdir(path, ignores, callback) { } file = p.join(path, file) + if (ignores.some(function(matcher) { return matcher(file, stats) })) { + pending -= 1 + if (!pending) { + return callback(null, list) + } + return null + } + if (stats.isDirectory()) { - files = readdir(file, ignores, function(__err, res) { + readdir(file, ignores, function(__err, res) { if (__err) { return callback(__err) } @@ -42,21 +64,13 @@ function readdir(path, ignores, callback) { } }) } else { - for (var i = 0; i < ignores.length; i++) { - if (minimatch(file, ignores[i], ignoreOpts)) { - pending -= 1 - if (pending <= 0) { - return callback(null, list) - } - return null - } - } list.push(file) pending -= 1 if (!pending) { return callback(null, list) } } + }) }) }) diff --git a/test/recursive-readdir-test.js b/test/recursive-readdir-test.js index 2281a9e..3a524f5 100644 --- a/test/recursive-readdir-test.js +++ b/test/recursive-readdir-test.js @@ -3,10 +3,12 @@ var assert = require('assert') var p = require('path') var readdir = require('../index') +function getAbsolutePath(file) { + return p.join(__dirname, file) +} + function getAbsolutePaths(files) { - return files.map(function(file) { - return p.join(__dirname, file) - }) + return files.map(getAbsolutePath) } describe('readdir', function() { @@ -71,6 +73,146 @@ describe('readdir', function() { }) }) + context('when there is a function in the ignores array', function() { + it('passes each file and directory path to the function', function(done) { + var expectedPaths = getAbsolutePaths([ + '/testdir/a', + '/testdir/a/a', + '/testdir/a/beans', + '/testdir/b', + '/testdir/b/123', + '/testdir/b/b', + '/testdir/b/b/hurp-durp', + '/testdir/c.txt', + '/testdir/d.txt' + ]) + var paths = [] + function ignoreFunction(path) { + paths.push(path) + return false + } + readdir(p.join(__dirname, 'testdir'), [ignoreFunction], function(err, list) { + assert.ifError(err) + assert.deepEqual(paths.sort(), expectedPaths.sort()) + done() + }) + }) + + it('passes the lstat object of each file to the function as its second argument', function(done) { + var paths = {} + function ignoreFunction(path, stats) { + paths[path] = stats + return false + } + readdir(p.join(__dirname, 'testdir'), [ignoreFunction], function(err, list) { + assert.ifError(err) + assert(paths[getAbsolutePath('/testdir/a')].isDirectory()) + assert(paths[getAbsolutePath('/testdir/c.txt')].isFile()) + done() + }) + }) + + it('ignores files that the function returns true for', function(done) { + var ignoredFiles = getAbsolutePaths([ + '/testdir/d.txt', + '/testdir/a/beans' + ]) + function ignoreFunction(path) { + return ignoredFiles.indexOf(path) != -1 + } + + readdir(p.join(__dirname, 'testdir'), [ignoreFunction], function(err, list) { + assert.ifError(err) + list.forEach(function(file) { + assert.equal(ignoredFiles.indexOf(file), -1, + 'Failed to ignore file "' + file + '".') + }) + done() + }) + }) + + it('does not ignore files that the function returns false for', function(done) { + var notIgnoredFiles = getAbsolutePaths([ + '/testdir/d.txt', + '/testdir/a/beans' + ]) + function ignoreFunction(path) { + return notIgnoredFiles.indexOf(path) == -1 + } + + readdir(p.join(__dirname, 'testdir'), [ignoreFunction], function(err, list) { + assert.ifError(err) + notIgnoredFiles.forEach(function(file) { + assert.notEqual(notIgnoredFiles.indexOf(file), -1, + 'Incorrectly ignored file "' + file + '".') + }) + done() + }) + }) + + it('ignores directories that the function returns true for', function(done) { + var ignoredDirectory = getAbsolutePath('/testdir/a') + var ignoredFiles = getAbsolutePaths([ + '/testdir/a/a', + '/testdir/a/beans' + ]) + function ignoreFunction(path) { + return ignoredDirectory == path + } + + readdir(p.join(__dirname, 'testdir'), [ignoreFunction], function(err, list) { + assert.ifError(err) + list.forEach(function(file) { + assert.equal(ignoredFiles.indexOf(file), -1, + 'Failed to ignore file "' + file + '".') + }) + done() + }) + }) + + it('does not ignore directories that the function returns false for', function(done) { + var ignoredDirectory = getAbsolutePath('/testdir/a') + var notIgnoredFiles = getAbsolutePaths([ + '/testdir/b/123', + '/testdir/b/b/hurp-durp' + ]) + function ignoreFunction(path) { + return ignoredDirectory == path + } + + readdir(p.join(__dirname, 'testdir'), [ignoreFunction], function(err, list) { + assert.ifError(err) + notIgnoredFiles.forEach(function(file) { + assert.notEqual(notIgnoredFiles.indexOf(file), -1, + 'Incorrectly ignored file "' + file + '".') + }) + done() + }) + }) + + it('does not descend into directories that the function returns true for', function(done) { + var ignoredDirectory = getAbsolutePath('/testdir/a') + var ignoredFiles = getAbsolutePaths([ + '/testdir/a/a', + '/testdir/a/beans' + ]) + var paths = [] + function ignoreFunction(path) { + paths.push(path) + return ignoredDirectory == path + } + + readdir(p.join(__dirname, 'testdir'), [ignoreFunction], function(err, list) { + assert.ifError(err) + paths.forEach(function(file) { + assert.equal(ignoredFiles.indexOf(file), -1, + 'Transversed file in ignored directory "' + file + '".') + }) + done() + }) + }) + }) + it('works when there are no files to report except ignored files', function(done) { readdir(p.join(__dirname, 'testdirBeta'), ['ignore.txt'], function(err, list) { assert.ifError(err)