├── .npmignore ├── .eslintignore ├── test ├── fixtures │ ├── foo.md │ └── an (odd) filename.js └── mocha.js ├── .gitignore ├── .babelrc ├── .travis.yml ├── .eslintrc ├── Makefile ├── package.json ├── LICENSE-MIT ├── CHANGELOG.md ├── README.md └── lib └── fileset.js /.npmignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | src 2 | -------------------------------------------------------------------------------- /test/fixtures/foo.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /test/fixtures/an (odd) filename.js: -------------------------------------------------------------------------------- 1 | var odd = true; -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"], 3 | 4 | "plugins": [ 5 | "add-module-exports" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - '6' 5 | - '5' 6 | - '4' 7 | - '0.12' 8 | - '0.10' 9 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "mocha": true 5 | }, 6 | 7 | "parserOptions": { 8 | "sourceType": "module" 9 | }, 10 | 11 | "extends": "standard", 12 | 13 | "rules": { 14 | "semi": ["error", "always"], 15 | "no-multi-spaces": ["error", { "exceptions": { "VariableDeclarator": true } }], 16 | "promise/param-names": 0 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | all: help 3 | 4 | help: 5 | bake -h 6 | 7 | test: 8 | cd test && ../node_modules/.bin/mocha -R spec . 9 | 10 | eslint: 11 | eslint . 12 | 13 | fix: 14 | eslint . --fix 15 | 16 | release: version push publish 17 | 18 | version: 19 | standard-version -m '%s' 20 | 21 | push: 22 | git push origin master --tags 23 | 24 | publish: 25 | npm publish 26 | 27 | .PHONY: test 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fileset", 3 | "version": "2.0.3", 4 | "description": "Wrapper around miniglob / minimatch combo to allow multiple patterns matching and include-exclude ability", 5 | "main": "./lib/fileset", 6 | "scripts": { 7 | "test": "make test" 8 | }, 9 | "dependencies": { 10 | "glob": "^7.0.3", 11 | "minimatch": "^3.0.3" 12 | }, 13 | "devDependencies": { 14 | "babel-cli": "^6.9.0", 15 | "babel-plugin-add-module-exports": "^0.2.1", 16 | "babel-plugin-transform-regenerator": "^6.9.0", 17 | "babel-preset-es2015": "^6.9.0", 18 | "bake-cli": "^0.6.0", 19 | "eslint": "^3.7.1", 20 | "eslint-config-standard": "^6.2.0", 21 | "eslint-plugin-promise": "^3.0.0", 22 | "eslint-plugin-standard": "^2.0.1", 23 | "mocha": "^3.1.2", 24 | "standard-version": "^3.0.0", 25 | "watchd": "^0.1.1" 26 | }, 27 | "homepage": "https://github.com/mklabs/node-fileset", 28 | "repository": { 29 | "type": "git", 30 | "url": "git://github.com/mklabs/node-fileset.git" 31 | }, 32 | "license": "MIT", 33 | "author": "mklabs" 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2016 Mickael Daniel 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | 6 | ## [2.0.2](https://github.com/mklabs/node-fileset/compare/v1.0.1...v2.0.2) (2016-06-27) 7 | 8 | 9 | 10 | 11 | ## [2.0.0](https://github.com/mklabs/node-fileset/compare/v1.0.1...v2.0.0) (2016-06-26) 12 | 13 | 14 | ## [1.0.1](https://github.com/mklabs/node-fileset/compare/v1.0.0...v1.0.1) (2016-06-01) 15 | 16 | 17 | 18 | 19 | # [1.0.0](https://github.com/mklabs/node-fileset/compare/v0.2.1...v1.0.0) (2016-06-01) 20 | 21 | 22 | ### Bug Fixes 23 | 24 | * update old tests to use mocha instead ([f4e0d8e](https://github.com/mklabs/node-fileset/commit/f4e0d8e)) 25 | 26 | 27 | ### Features 28 | 29 | * **sync:** always ignore node_modules in sync mode ([c6593c0](https://github.com/mklabs/node-fileset/commit/c6593c0)) 30 | * update glob 7 ([954bab5](https://github.com/mklabs/node-fileset/commit/954bab5)) 31 | 32 | 33 | ### BREAKING CHANGES 34 | 35 | * Includes an update to glob / minimatch. As described in 36 | 378de99522caf7b665c53472a34a41a0b295b489 37 | 38 | > Since glob 6 removes support for comment and negation patterns, this may 39 | > or may not be a breaking change from fileset's pov. 40 | 41 | ## Changelog 42 | 43 | - Releases: https://github.com/mklabs/node-fileset/releases 44 | 45 | ### 0.2.1 46 | 47 | - Sync API 48 | 49 | ### 0.2.0 50 | 51 | - Drop support for 0.8 52 | - PR mklabs/node-fileset#14 reapplied 53 | - [Minor code style changes](bf8afae22a49cf64720177d6036090db2852d744) 54 | 55 | ### 0.1.8 56 | 57 | - PR mklabs/node-fileset#17 - Reverts PR#14 58 | 59 | ### 0.1.6 / 0.1.7 60 | 61 | - PR mklabs/node-fileset#14 62 | 63 | ### 0.1.x 64 | 65 | - Initial commit 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-fileset [![Build Status](https://secure.travis-ci.org/mklabs/node-fileset.png)](http://travis-ci.org/mklabs/node-fileset) 2 | 3 | Exposes a basic wrapper on top of 4 | [Glob](https://github.com/isaacs/node-glob) / 5 | [minimatch](https://github.com/isaacs/minimatch) combo both written by 6 | @isaacs. Glob now uses JavaScript instead of C++ bindings which makes it 7 | usable in Node.js 0.6.x and Windows platforms. 8 | 9 | [![NPM](https://nodei.co/npm/fileset.png?downloads=true&stars=true)](https://nodei.co/npm/fileset/) 10 | 11 | Adds multiples patterns matching and exlude ability. This is 12 | basically just a sugar API syntax where you can specify a list of includes 13 | and optional exclude patterns. It works by setting up the necessary 14 | miniglob "fileset" and filtering out the results using minimatch. 15 | 16 | *[Changelog](https://github.com/mklabs/node-fileset/blob/master/CHANGELOG.md#changelog)* 17 | 18 | ## Install 19 | 20 | npm install fileset 21 | 22 | ## Usage 23 | 24 | Can be used with callback or emitter style. 25 | 26 | * **include**: list of glob patterns `foo/**/*.js *.md src/lib/**/*` 27 | * **exclude**: *optional* list of glob patterns to filter include 28 | results `foo/**/*.js *.md` 29 | * **callback**: *optional* function that gets called with an error if 30 | something wrong happend, otherwise null with an array of results 31 | 32 | The callback is optional since the fileset method return an instance of 33 | EventEmitter which emit different events you might use: 34 | 35 | * *match*: Every time a match is found, miniglob emits this event with 36 | the pattern. 37 | * *include*: Emitted each time an include match is found. 38 | * *exclude*: Emitted each time an exclude match is found and filtered 39 | out from the fileset. 40 | * *end*: Emitted when the matching is finished with all the matches 41 | found, optionally filtered by the exclude patterns. 42 | 43 | #### Callback 44 | 45 | ```js 46 | var fileset = require('fileset'); 47 | 48 | fileset('**/*.js', '**.min.js', function(err, files) { 49 | if (err) return console.error(err); 50 | 51 | console.log('Files: ', files.length); 52 | console.log(files); 53 | }); 54 | ``` 55 | 56 | #### Event emitter 57 | 58 | ```js 59 | var fileset = require('fileset'); 60 | 61 | fileset('**.coffee README.md *.json Cakefile **.js', 'node_modules/**') 62 | .on('match', console.log.bind(console, 'error')) 63 | .on('include', console.log.bind(console, 'includes')) 64 | .on('exclude', console.log.bind(console, 'excludes')) 65 | .on('end', console.log.bind(console, 'end')); 66 | ``` 67 | 68 | `fileset` returns an instance of EventEmitter, with an `includes` property 69 | which is the array of Fileset objects (inheriting from 70 | `miniglob.Miniglob`) that were used during the mathing process, should 71 | you want to use them individually. 72 | 73 | Check out the 74 | [tests](https://github.com/mklabs/node-fileset/tree/master/tests) for 75 | more examples. 76 | 77 | ## Sync usage 78 | 79 | ```js 80 | var results = fileset.sync('*.md *.js', 'CHANGELOG.md node_modules/**/*.md node_modules/**/*.js'); 81 | ``` 82 | 83 | The behavior should remain the same, although it lacks the last `options` arguments to pass to internal `glob` and `minimatch` dependencies. 84 | 85 | ## Tests 86 | 87 | Run `npm test` 88 | 89 | ## Why 90 | 91 | Mainly for a build tool with cake files, to provide me an easy way to get 92 | a list of files by either using glob or path patterns, optionally 93 | allowing exclude patterns to filter out the results. 94 | 95 | All the magic is happening in 96 | [Glob](https://github.com/isaacs/node-glob) and 97 | [minimatch](https://github.com/isaacs/minimatch). Check them out! 98 | -------------------------------------------------------------------------------- /lib/fileset.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | var minimatch = require('minimatch'); 3 | var glob = require('glob'); 4 | var Glob = glob.Glob; 5 | var EventEmitter = require('events').EventEmitter; 6 | 7 | module.exports = fileset; 8 | 9 | // Async API 10 | function fileset(include, exclude, options, cb) { 11 | if (typeof exclude === 'function') cb = exclude, exclude = ''; 12 | else if (typeof options === 'function') cb = options, options = {}; 13 | 14 | var includes = (typeof include === 'string') ? include.split(' ') : include; 15 | var excludes = (typeof exclude === 'string') ? exclude.split(' ') : exclude; 16 | 17 | var em = new EventEmitter; 18 | var remaining = includes.length; 19 | var results = []; 20 | 21 | if (!includes.length) return cb(new Error('Must provide an include pattern')); 22 | 23 | em.includes = includes.map(function(pattern) { 24 | return new fileset.Fileset(pattern, options) 25 | .on('error', cb ? cb : em.emit.bind(em, 'error')) 26 | .on('match', em.emit.bind(em, 'match')) 27 | .on('match', em.emit.bind(em, 'include')) 28 | .on('end', next.bind({}, pattern)) 29 | }); 30 | 31 | function next(pattern, matches) { 32 | results = results.concat(matches); 33 | 34 | if (!(--remaining)) { 35 | results = results.filter(function(file) { 36 | return !excludes.filter(function(glob) { 37 | var match = minimatch(file, glob, { matchBase: true }); 38 | if(match) em.emit('exclude', file); 39 | return match; 40 | }).length; 41 | }); 42 | 43 | if(cb) cb(null, results); 44 | em.emit('end', results); 45 | } 46 | } 47 | 48 | return em; 49 | } 50 | 51 | // Sync API 52 | fileset.sync = function filesetSync(include, exclude) { 53 | if (!exclude) exclude = ''; 54 | 55 | // includes / excludes, either an array or string separated by comma or whitespace 56 | var includes = (typeof include === 'string') ? include.split(/[\s,]/g) : include; 57 | var excludes = (typeof exclude === 'string') ? exclude.split(/[\s,]/g) : exclude; 58 | 59 | // Filter out any false positive '' empty strings 60 | includes = includes.filter(function(pattern) { return pattern; }); 61 | excludes = excludes.filter(function(pattern) { return pattern; }); 62 | 63 | // - todo: pass in glob options as last param 64 | var options = { matchBase: true }; 65 | 66 | // always ignore node_modules for sync api 67 | options.ignore = ['node_modules/**/*']; 68 | 69 | // First, glob match on all include patters into a single array 70 | var results = includes.map(function(include) { 71 | return glob.sync(include, options); 72 | }).reduce(function(a, b) { 73 | return a.concat(b); 74 | }, []); 75 | 76 | // Then filters out on any exclude match 77 | var ignored = excludes.map(function(exclude) { 78 | return glob.sync(exclude, options); 79 | }).reduce(function(a, b) { 80 | return a.concat(b); 81 | }, []); 82 | 83 | // And filter any exclude match 84 | results = results.filter(function(file) { 85 | return !ignored.filter(function(glob) { 86 | return minimatch(file, glob, { matchBase: true }); 87 | }).length; 88 | }); 89 | 90 | return results; 91 | }; 92 | 93 | fileset.Fileset = function Fileset(pattern, options, cb) { 94 | if (typeof options === 'function') cb = options, options = {}; 95 | if (!options) options = {}; 96 | 97 | // ignore node_modules by default, unless specified 98 | options.ignore = options.ignore || ['node_modules/**/*']; 99 | 100 | Glob.call(this, pattern, options); 101 | 102 | if (typeof cb === 'function') { 103 | this.on('error', cb); 104 | this.on('end', function(matches) { cb(null, matches); }); 105 | } 106 | }; 107 | 108 | util.inherits(fileset.Fileset, Glob); 109 | -------------------------------------------------------------------------------- /test/mocha.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var fileset = require('..'); 3 | var EventEmitter = require('events').EventEmitter; 4 | 5 | describe('Sync API - Given a **.md pattern', function() { 6 | it('returns the list of matching file in this repo', function() { 7 | var results = fileset.sync('*.md', 'test/fixtures/**/*.md'); 8 | assert.ok(Array.isArray(results), 'should be an array'); 9 | assert.ok(results.length, 'should return at least one element'); 10 | assert.equal(results.length, 1, 'actually, should return only one'); 11 | }); 12 | }); 13 | 14 | describe('Sync API - Given a *.md and **.js pattern, and two exclude', function() { 15 | it('returns the list of matching file in this repo', function() { 16 | var results = fileset.sync('*.md *.js', 'CHANGELOG.md test/fixtures/**/*.md test/fixtures/**/*.js'); 17 | 18 | assert.ok(Array.isArray(results), 'should be an array'); 19 | assert.ok(results.length, 'should return at least one element'); 20 | assert.equal(results.length, 3, 'actually, should return only 3'); 21 | }); 22 | }); 23 | 24 | // Given a **.md pattern 25 | describe('Given a **.md pattern', function() { 26 | it('returns the list of matching file in this repo', function(done) { 27 | fileset('*.js', function(err, results) { 28 | if(err) return done(err); 29 | assert.ok(Array.isArray(results), 'should be an array'); 30 | assert.ok(results.length, 'should return at least one element'); 31 | assert.equal(results.length, 1, 'actually, should return only two'); 32 | done(); 33 | }); 34 | }); 35 | }); 36 | 37 | describe('Say we want the **.js files, but not those in node_modules', function() { 38 | it('recursively walks the dir and returns the matching list', function(done) { 39 | fileset('**/*.js', '', function(err, results) { 40 | if(err) return done(err); 41 | assert.ok(Array.isArray(results), 'should be an array'); 42 | assert.equal(results.length, 2); 43 | done(); 44 | }); 45 | }); 46 | 47 | it('recursively walks the dir and returns the matching list', function(done) { 48 | fileset('**/*.js', function(err, results) { 49 | if(err) return done(err); 50 | assert.ok(Array.isArray(results), 'should be an array'); 51 | assert.equal(results.length, 2); 52 | done(); 53 | }); 54 | }); 55 | 56 | it('supports multiple paths at once', function(done) { 57 | fileset('**/*.js *.md', 'node_modules/**', function(err, results) { 58 | if(err) return done(err); 59 | 60 | assert.ok(Array.isArray(results), 'should be an array'); 61 | assert.equal(results.length, 2); 62 | 63 | assert.deepEqual(results, [ 64 | 'fixtures/an (odd) filename.js', 65 | 'mocha.js' 66 | ]); 67 | 68 | done(); 69 | }); 70 | }); 71 | 72 | it('Should support multiple paths for excludes as well', function(done) { 73 | fileset('**/*.js *.md', 'node_modules/** **.md tests/*.js', function(err, results) { 74 | if(err) return done(err); 75 | assert.ok(Array.isArray(results), 'should be an array'); 76 | assert.equal(results.length, 2); 77 | 78 | assert.deepEqual(results, [ 79 | 'fixtures/an (odd) filename.js', 80 | 'mocha.js' 81 | ]); 82 | 83 | done(); 84 | }); 85 | }); 86 | }); 87 | 88 | describe('Testing out emmited events', function() { 89 | it('recursively walk the dir and return the matching list', function(done) { 90 | fileset('**/*.js', 'node_modules/**') 91 | .on('error', done) 92 | .on('end', function(results) { 93 | assert.ok(Array.isArray(results), 'should be an array'); 94 | assert.equal(results.length, 2); 95 | done(); 96 | }); 97 | }); 98 | 99 | it('support multiple paths at once', function(done) { 100 | fileset('**/*.js *.md', 'node_modules/**') 101 | .on('error', done) 102 | .on('end', function(results) { 103 | assert.ok(Array.isArray(results), 'should be an array'); 104 | assert.equal(results.length, 2); 105 | 106 | assert.deepEqual(results, [ 107 | 'fixtures/an (odd) filename.js', 108 | 'mocha.js' 109 | ]); 110 | 111 | done(); 112 | }); 113 | }); 114 | }); 115 | 116 | describe('Testing patterns passed as arrays', function() { 117 | it('match files passed as an array with odd filenames', function(done) { 118 | fileset(['fixtures/*.md', 'fixtures/an (odd) filename.js'], ['*.md']) 119 | .on('error', done) 120 | .on('end', function(results) { 121 | assert.ok(Array.isArray(results), 'should be an array'); 122 | assert.equal(results.length, 1); 123 | assert.deepEqual(results, [ 124 | 'fixtures/an (odd) filename.js', 125 | ]); 126 | 127 | done(); 128 | }); 129 | }); 130 | }); 131 | --------------------------------------------------------------------------------