Skip to content

Commit

Permalink
Added a new option to skip folders
Browse files Browse the repository at this point in the history
  • Loading branch information
Yqnn committed Sep 21, 2020
1 parent 07c96d1 commit 2a97b77
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 23 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,9 @@ Returns a EventEmitter reading given root recursively.

### Options

* `pattern`: Glob pattern or Array of Glob patterns to match the found files with.
* `ignore`: Glob pattern or Array of Glob patterns to exclude matches. Note: `ignore` patterns are *always* in `dot:true` mode.
* `pattern`: Glob pattern or Array of Glob patterns to match the found files with. A file has to match at least one of the provided patterns to be returned.
* `ignore`: Glob pattern or Array of Glob patterns to exclude matches. If a file or a folder matches at least one of the provided patterns, it's not returned. It doesn't prevent files from folder content to be returned. Note: `ignore` patterns are *always* in `dot:true` mode.
* `skip`: Glob pattern or Array of Glob patterns to exclude folders. If a folder matches one of the provided patterns, it's not returned, and it's not explored: this prevents any of its children to be returned. Note: `skip` patterns are *always* in `dot:true` mode.
* `mark`: Add a `/` character to directory matches.
* `stat`: Set to true to stat *all* results. This reduces performance.
* `silent`: When an unusual error is encountered when attempting to read a directory, a warning will be printed to stderr. Set the `silent` option to true to suppress these warnings.
Expand Down
35 changes: 26 additions & 9 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ function stat(file, followSyslinks) {
});
}

async function* exploreWalkAsync(dir, path, followSyslinks, useStat, strict) {
async function* exploreWalkAsync(dir, path, followSyslinks, useStat, shouldSkip, strict) {
let files = await readdir(path + dir, strict);
for(const file of files) {
const filename = dir + '/' + file.name;
Expand All @@ -71,15 +71,18 @@ async function* exploreWalkAsync(dir, path, followSyslinks, useStat, strict) {
}
stats = stats || file;

yield {relative, absolute, stats};

if(stats.isDirectory()) {
yield* exploreWalkAsync(filename, path, followSyslinks, useStat, false);
if(!shouldSkip(relative)) {
yield {relative, absolute, stats};
yield* exploreWalkAsync(filename, path, followSyslinks, useStat, shouldSkip, false);
}
} else {
yield {relative, absolute, stats};
}
}
}
async function* explore(path, followSyslinks, useStat) {
yield* exploreWalkAsync('', path, followSyslinks, useStat, true);
async function* explore(path, followSyslinks, useStat, shouldSkip) {
yield* exploreWalkAsync('', path, followSyslinks, useStat, shouldSkip, true);
}


Expand All @@ -91,6 +94,7 @@ function readOptions(options) {
matchBase: !!options.matchBase,
nocase: !!options.nocase,
ignore: options.ignore,
skip: options.skip,

follow: !!options.follow,
stat: !!options.stat,
Expand All @@ -114,7 +118,7 @@ class ReaddirGlob extends EventEmitter {
if(this.options.pattern) {
const matchers = Array.isArray(this.options.pattern) ? this.options.pattern : [this.options.pattern];
this.matchers = matchers.map( m =>
new Minimatch(this.options.pattern, {
new Minimatch(m, {
dot: this.options.dot,
noglobstar:this.options.noglobstar,
matchBase:this.options.matchBase,
Expand All @@ -130,8 +134,16 @@ class ReaddirGlob extends EventEmitter {
new Minimatch(ignore, {dot: true})
);
}

this.skipMatchers = [];
if(this.options.skip) {
const skipPatterns = Array.isArray(this.options.skip) ? this.options.skip : [this.options.skip];
this.skipMatchers = skipPatterns.map( skip =>
new Minimatch(skip, {dot: true})
);
}

this.iterator = explore(resolve(cwd || '.'), this.options.follow, this.options.stat);
this.iterator = explore(resolve(cwd || '.'), this.options.follow, this.options.stat, this._shouldSkipDirectory.bind(this));
this.paused = false;
this.inactive = false;
this.aborted = false;
Expand All @@ -146,9 +158,14 @@ class ReaddirGlob extends EventEmitter {
setTimeout( () => this._next(), 0);
}

_shouldSkipDirectory(relative) {
//console.log(relative, this.skipMatchers.some(m => m.match(relative)));
return this.skipMatchers.some(m => m.match(relative));
}

_fileMatches(relative, isDirectory) {
const file = relative + (isDirectory ? '/' : '');
return this.matchers.every(m => m.match(file))
return (this.matchers.length === 0 || this.matchers.some(m => m.match(file)))
&& !this.ignoreMatchers.some(m => m.match(file))
&& (!this.options.nodir || !isDirectory);
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"author": "Yann Armelin",
"name": "readdir-glob",
"description": "Recursive fs.readdir with streaming API and glob filtering.",
"version": "1.0.0",
"version": "1.1.0",
"homepage": "https://github.com/Yqnn/node-readdir-glob",
"repository": {
"type": "git",
Expand Down
12 changes: 6 additions & 6 deletions scripts/benchmark.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ fi

echo

echo Node statSync and readdirSync timing:
echo Node statSync and readdirSync:
time node -e '
var fs=require("fs");
var count = 0;
Expand All @@ -42,39 +42,39 @@ time node -e '
console.log(count)'
echo

echo Node glob timing:
echo readdir-glob:
time node -e '
var glob=require(process.argv[1]);
glob(".", {nodir: true} ,function (er, files) {
console.log(files.length)
})' "$wd"
echo

echo Node glob with pattern timing:
echo readdir-glob with pattern:
time node -e '
var glob=require(process.argv[1]);
glob(".", {pattern:"**/*.txt"}, function (er, files) {
console.log(files.length)
})' "$wd"
echo

echo Node glob with pattern and stat timing:
echo readdir-glob with pattern and stat:
time node -e '
var glob=require(process.argv[1]);
glob(".", {stat:true, pattern:"**/*.txt"}, function (er, files) {
console.log(files.length)
})' "$wd"
echo

echo Node glob with pattern and follow timing:
echo readdir-glob with pattern and follow:
time node -e '
var glob=require(process.argv[1]);
glob(".", {follow:true, pattern:"**/*.txt"}, function (er, files) {
console.log(files.length)
})' "$wd"
echo

echo Node glob with --prof
echo readdir-glob with --prof
cd $wd
bash ./scripts/profile.sh

Expand Down
2 changes: 1 addition & 1 deletion test/00-env.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const origCwd = process.cwd();
afterAll(() => {
process.chdir(origCwd); // not sure it's needed
process.chdir(origCwd);
});
9 changes: 5 additions & 4 deletions test/new-glob-optional-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
const { ReaddirGlob } = require('..');
const path = require('path');


let f = 'test/'+path.basename(__filename);
beforeAll(() => {
process.chdir(__dirname + '/fixtures');
});

test('new glob, with cb, and no options', done => {
new ReaddirGlob('.', {pattern:f}, function(er, results) {
new ReaddirGlob('./a/bc/e/', function(er, results) {
expect(er).toBeFalsy();
expect(results).toEqual([f]);
expect(results).toEqual(['f']);
done();
});
});
29 changes: 29 additions & 0 deletions test/pattern-list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const glob = require('../');

beforeAll(() => {
process.chdir(__dirname + '/fixtures');
});


// [cwd, options, expected]
const cases = [
[ 'a', { pattern:['abcdef/*', 'z'], mark: true}, [
"abcdef/g/",
"z/",
]
]
];

cases.forEach(c => {
const cwd = c[0];
const options = c[1];
const expected = c[2].sort();
test(cwd + ' ' + JSON.stringify(options), done => {
glob(cwd, options, (er, res) => {
expect(er).toBeFalsy();
res.sort();
expect(res).toEqual(expected);
done();
})
})
})
49 changes: 49 additions & 0 deletions test/skip.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
const glob = require('../');

beforeAll(() => {
process.chdir(__dirname + '/fixtures');
});


// [cwd, options, expected]
const cases = [
[ 'a', { pattern:'**/*', mark: true, skip: ['*/g', 'cb'] }, [
"abcdef/",
"abcfed/",
"b/",
"b/c/",
"b/c/d",
"bc/",
"bc/e/",
"bc/e/f",
"c/",
"c/d/",
"c/d/c/",
"c/d/c/b",
"symlink/",
"symlink/a/",
"symlink/a/b/",
"symlink/a/b/c",
"x/",
"z/",
]
],
[ 'a/c', { mark: true, skip: '**/c' }, [
'd/',
]
]
];

cases.forEach(c => {
const cwd = c[0];
const options = c[1];
const expected = c[2].sort();
test(cwd + ' ' + JSON.stringify(options), done => {
glob(cwd, options, (er, res) => {
expect(er).toBeFalsy();
res.sort();
expect(res).toEqual(expected);
done();
})
})
})

0 comments on commit 2a97b77

Please sign in to comment.