├── .gitignore ├── .travis.yml ├── fixtures └── test.txt ├── index.js ├── package.json ├── readme.md └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.12" 4 | - "4.0.0" 5 | 6 | script: "npm test" 7 | -------------------------------------------------------------------------------- /fixtures/test.txt: -------------------------------------------------------------------------------- 1 | hello 2 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var from = require('from2') 2 | var path = require('path') 3 | 4 | module.exports = walker 5 | 6 | function walker (dirs, opts) { 7 | var fs = opts && opts.fs || require('fs') 8 | var filter = opts && opts.filter || function (filename) { return true } 9 | if (!Array.isArray(dirs)) dirs = [dirs] 10 | var maxDepth = opts && opts.maxDepth || Infinity 11 | 12 | dirs = dirs.filter(filter) 13 | 14 | var pending = [] 15 | var root = dirs.shift() 16 | if (root) pending.push(root) 17 | 18 | return from.obj(read) 19 | 20 | function read (size, cb) { 21 | if (!pending.length) { 22 | if (dirs.length) { 23 | root = dirs.shift() 24 | pending.push(root) 25 | return read(size, cb) 26 | } 27 | return cb(null, null) 28 | } 29 | kick(cb) 30 | } 31 | 32 | function kick (cb) { 33 | var name = pending.shift() 34 | if (typeof name === 'undefined') return cb(null, null) 35 | fs.lstat(name, function (err, st) { 36 | if (err) return done(err) 37 | if (!st.isDirectory() || depthLimiter(name, root, maxDepth)) return done(null) 38 | 39 | fs.readdir(name, function (err, files) { 40 | if (err) return done(err) 41 | files.sort() 42 | for (var i = 0; i < files.length; i++) { 43 | var next = path.join(name, files[i]) 44 | if (filter(next)) pending.unshift(next) 45 | } 46 | if (name === root) kick(cb) 47 | else done(null) 48 | }) 49 | 50 | function done (err) { 51 | if (err) return cb(err) 52 | var item = { 53 | root: root, 54 | filepath: name, 55 | stat: st, 56 | relname: root === name ? path.basename(name) : path.relative(root, name), 57 | basename: path.basename(name) 58 | } 59 | var isFile = st.isFile() 60 | if (isFile) { 61 | item.type = 'file' 62 | } 63 | var isDir = st.isDirectory() 64 | if (isDir) item.type = 'directory' 65 | cb(null, item) 66 | } 67 | }) 68 | } 69 | } 70 | 71 | function depthLimiter (filePath, relativeTo, maxDepth) { 72 | if (maxDepth === Infinity) return false 73 | const rootDepth = relativeTo.split(path.sep).length 74 | const fileDepth = filePath.split(path.sep).length 75 | return fileDepth - rootDepth > maxDepth 76 | } 77 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "folder-walker", 3 | "version": "3.2.0", 4 | "description": "A recursive stream of the files and directories in a given folder", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node test.js | tap-spec" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/karissa/folder-walker.git" 12 | }, 13 | "keywords": [ 14 | "folder", 15 | "dir", 16 | "walk", 17 | "stream" 18 | ], 19 | "author": "Karissa McKelvey (http://karissamck.com/)", 20 | "license": "BSD-2-Clause", 21 | "bugs": { 22 | "url": "https://github.com/karissa/folder-walker/issues" 23 | }, 24 | "homepage": "https://github.com/karissa/folder-walker#readme", 25 | "dependencies": { 26 | "from2": "^2.1.0" 27 | }, 28 | "devDependencies": { 29 | "tap-spec": "^4.1.1", 30 | "tape": "^4.4.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # folder-walker 2 | 3 | A recursive stream of the files and directories in a given folder. Can take multiple folders. 4 | 5 | [![build status](http://img.shields.io/travis/karissa/folder-walker.svg?style=flat)](http://travis-ci.org/karissa/folder-walker) 6 | ![dat](http://img.shields.io/badge/Development%20sponsored%20by-dat-green.svg?style=flat) 7 | 8 | ## Install 9 | 10 | ```console 11 | npm install folder-walker 12 | ``` 13 | 14 | ## Example 15 | 16 | ```js 17 | var walker = require('folder-walker') 18 | var stream = walker(['/path/to/folder', '/another/folder/here']) 19 | stream.on('data', function (data) { 20 | console.log(data) 21 | }) 22 | ``` 23 | 24 | Example item in the stream: 25 | 26 | ```js 27 | { 28 | basename: 'index.js', 29 | relname: 'test/index.js', 30 | root: '/Users/karissa/dev/node_modules/folder-walker', 31 | filepath: '/Users/karissa/dev/node_modules/folder-walker/test/index.js', 32 | stat: [fs.Stat Object], 33 | type: 'file' // or 'directory' 34 | } 35 | ``` 36 | 37 | ## API 38 | 39 | #### `stream = walker(dirs, [opts])` 40 | 41 | Create a readable object stream of all files and folders inside of `dirs`. 42 | 43 | `dirs` can be a path to a directory or an array of paths to directories. 44 | 45 | `opts` includes: 46 | 47 | ```js 48 | { 49 | fs: require('fs'), // the fs interface to use 50 | maxDepth: Infinity // maximum folder depth to walk. Minimum depth is 1. 51 | filter: function (filename) { return true } // a function that lets you filter out files by returning false 52 | // filter is applied to the `dirs` argument, and every file that folder-walker finds 53 | } 54 | ``` 55 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var path = require('path') 3 | var fs = require('fs') 4 | var walker = require('./') 5 | 6 | test('test multiple folders', function (t) { 7 | var stream = generateWalker(t, {path: [process.cwd(), path.join(__dirname, 'fixtures')]}) 8 | 9 | stream.on('data', function (data) { 10 | t.ok(data.filepath) 11 | }) 12 | }) 13 | 14 | test('test data stream with only a file', function (t) { 15 | var stream = generateWalker(t, {path: __filename}) 16 | t.plan(1) 17 | 18 | stream.on('data', function (data) { 19 | t.same(data.filepath, __filename) 20 | }) 21 | }) 22 | 23 | test('test data stream with only no files', function (t) { 24 | var folderPath = path.join(__dirname, 'fixtures', 'emptyfolder') 25 | generateEmptyFolder(folderPath) 26 | var stream = generateWalker(t, {path: folderPath}) 27 | t.plan(0) 28 | 29 | stream.on('data', function (data) { 30 | t.fail('should not hit') 31 | }) 32 | }) 33 | 34 | test('test data stream with filter', function (t) { 35 | function filter (filepath) { 36 | return false 37 | } 38 | 39 | var stream = generateWalker(t, {filter: filter}) 40 | 41 | var times = 0 42 | stream.on('data', function (data) { 43 | times += 1 44 | }) 45 | 46 | stream.on('end', function () { 47 | t.same(times, 0) 48 | }) 49 | }) 50 | 51 | test('test data stream filtering out .git', function (t) { 52 | var stream = generateWalker(t) 53 | 54 | stream.on('data', function (data) { 55 | t.equal(data.filepath.indexOf('.git'), -1) 56 | t.ok(data.stat, 'has stat') 57 | t.ok(data.root, 'has root') 58 | t.ok(data.relname) 59 | t.ok(data.filepath) 60 | t.ok(data.basename) 61 | }) 62 | }) 63 | 64 | test('dont include root directory in response', function (t) { 65 | var stream = generateWalker(t) 66 | stream.on('data', function (data) { 67 | if (data.filepath === process.cwd()) t.ok(false) 68 | }) 69 | }) 70 | 71 | test('dont walk past the maxDepth', function (t) { 72 | var stream = walker(['.git', 'node_modules'], { maxDepth: 3 }) 73 | stream.on('data', function (data) { 74 | t.true( 75 | data.filepath.split(path.sep).length - process.cwd().split(path.sep).length <= 3) 76 | }) 77 | 78 | stream.on('error', function (err) { 79 | t.ifError(err) 80 | }) 81 | 82 | stream.on('end', function () { 83 | t.end() 84 | }) 85 | }) 86 | 87 | function generateWalker (t, opts) { 88 | if (!opts) opts = {} 89 | function filter (filepath) { 90 | return filepath.indexOf('.git') === -1 91 | } 92 | var stream = walker(opts.path || process.cwd(), {filter: opts.filter || filter}) 93 | 94 | stream.on('error', function (err) { 95 | t.ifError(err) 96 | }) 97 | 98 | stream.on('end', function () { 99 | t.end() 100 | }) 101 | return stream 102 | } 103 | 104 | function generateEmptyFolder (folderPath) { 105 | try { 106 | fs.mkdirSync(folderPath) 107 | } catch (e) {} 108 | } 109 | --------------------------------------------------------------------------------